Skip to content

Instantly share code, notes, and snippets.

@kannanperoumal
Created June 15, 2016 19:35
Show Gist options
  • Save kannanperoumal/4d5722b25e7b77a6e5291c762ed5b7f7 to your computer and use it in GitHub Desktop.
Save kannanperoumal/4d5722b25e7b77a6e5291c762ed5b7f7 to your computer and use it in GitHub Desktop.
Bank ATM service test with create account, deposit, withdraw and statement by dates
package com.bank.atm;
/**
*
* @author Kannan Peroumal
* 14/06/2016
* for Societe Generale
*
*/
public interface AccountService {
CustomerAccount createNewCustomer(CustomerAccount customerAccount) throws RuntimeException;
}
package com.bank.atm;
import java.util.Date;
import java.util.List;
/**
*
* @author Kannan Peroumal
* 14/06/2016
* for Societe Generale
*
*/
public interface ATMService {
void withdraw(CustomerAccount account, double withdrawAmount) throws RuntimeException;
void deposit(CustomerAccount account, double depositAmount) throws RuntimeException;
List<CustomerOperation> getStatement(CustomerAccount account, Date dateStart, Date dateEnd) throws RuntimeException;
}
package com.bank.atm;
import static com.bank.atm.CustomerOperationType.DEPOSIT;
import static com.bank.atm.CustomerOperationType.WITHDRAW;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentSkipListSet;
/**
*
* @author Kannan Peroumal
* 14/06/2016
* for Societe Generale
*
*/
public class ATMServiceImpl implements ATMService, AccountService {
private static final int acNoStartIndex = 1000000;
private static int currentIndex = 1;
private static final Set<String> fastCustomerSearchList = new ConcurrentSkipListSet<String>();
private static final Map<CustomerAccount, ConcurrentLinkedQueue<CustomerOperation>> customerAccounts =
new ConcurrentHashMap<CustomerAccount, ConcurrentLinkedQueue<CustomerOperation>>();
/**
* This allows withdrawing amount from an existing account without overdraft
*/
public void withdraw(CustomerAccount account, double withdrawAmount) throws RuntimeException {
if(account == null){
throw new RuntimeException(ErrMessage.ACCOUNT_EMPTY);
}
if(!customerAccounts.containsKey(account)){
throw new RuntimeException(ErrMessage.ACCOUNT_UNKNOWN);
}
if(withdrawAmount < 1){
throw new RuntimeException(ErrMessage.WITHDRAW_MIN_AMOUNT);
}
if((account.getBalance() - withdrawAmount) < 1){
throw new RuntimeException(ErrMessage.WITHDRAW_EXCEED_BALANCE);
}
ConcurrentLinkedQueue<CustomerOperation> customerOperations = customerAccounts.get(account);
customerOperations.add(new CustomerOperation(account.getAccountNo(), WITHDRAW, Calendar.getInstance(), withdrawAmount));
customerAccounts.put(account, customerOperations);
account.setBalance(account.getBalance()-withdrawAmount);
}
/**
* This allows depositing amount into an existing account
*/
public void deposit(CustomerAccount account, double depositAmount) throws RuntimeException {
if(account == null){
throw new RuntimeException(ErrMessage.ACCOUNT_EMPTY);
}
if(!customerAccounts.containsKey(account)){
throw new RuntimeException(ErrMessage.ACCOUNT_UNKNOWN);
}
if(depositAmount < 1){
throw new RuntimeException(ErrMessage.DEPOSIT_MIN_AMOUNT);
}
ConcurrentLinkedQueue<CustomerOperation> customerOperations = customerAccounts.get(account);
if(customerOperations == null){
customerOperations = new ConcurrentLinkedQueue<CustomerOperation>();
}
customerOperations.add(new CustomerOperation(account.getAccountNo(), DEPOSIT, Calendar.getInstance(), depositAmount));
customerAccounts.put(account, customerOperations);
account.setBalance(account.getBalance()+depositAmount);
}
/**
* This allows getting transaction statement by dates for particular account
*/
public List<CustomerOperation> getStatement(CustomerAccount account, Date dateStart, Date dateEnd) throws RuntimeException {
if(account == null){
throw new RuntimeException(ErrMessage.ACCOUNT_EMPTY);
}
if(!customerAccounts.containsKey(account)){
throw new RuntimeException(ErrMessage.ACCOUNT_UNKNOWN);
}
if(dateStart == null){
throw new RuntimeException(ErrMessage.STATEMENT_INVALID_START_DATE);
}
if(dateEnd == null){
throw new RuntimeException(ErrMessage.STATEMENT_INVALID_END_DATE);
}
ConcurrentLinkedQueue<CustomerOperation> customerOperations = customerAccounts.get(account);
if(customerOperations == null || customerOperations.size()<1){
return null;
}
List<CustomerOperation> result = new ArrayList<CustomerOperation>();
synchronized (result) {
for (CustomerOperation o : customerOperations) {
Date d = o.getOperationDate().getTime();
if(d.compareTo(dateStart)>=0 && d.compareTo(dateEnd)<=0){
result.add(o);
}
}
}
return result;
}
/**
* This service allows creating a new account holder with new account number through synchronization
*/
public CustomerAccount createNewCustomer(CustomerAccount account) throws RuntimeException {
if(account == null){
throw new RuntimeException(ErrMessage.ACCOUNT_EMPTY);
}
if(account.getFullName()==null || account.getFullName().trim().equals("") ){
throw new RuntimeException(ErrMessage.CUSTOMER_NAME_INVALID);
}
if(fastCustomerSearchList.contains(account.getFullName())){
throw new RuntimeException(ErrMessage.CUSTOMER_EXISTS_ALREADY);
}
generateNewAccountNumber(account);
customerAccounts.put(account, new ConcurrentLinkedQueue<CustomerOperation>());
fastCustomerSearchList.add(account.getFullName());
return account;
}
/**
* Allows creating a new customer account
* @return
* @throws RuntimeException
*/
private void generateNewAccountNumber(CustomerAccount account) throws RuntimeException{
account.setAccountNo(acNoStartIndex+(currentIndex++));
if(customerAccounts.containsKey(account)){
throw new RuntimeException(ErrMessage.CUSTOMER_ACCONT_NUM_ERR);
}
}
}
package com.bank.atm;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
/**
*
* @author Kannan Peroumal
* 14/06/2016
* for Societe Generale
*
*/
public class ATMServiceImplTest {
private static ATMService atmService;
private static CustomerAccount customer1; // without opening balance
private static CustomerAccount customer2; // with opening balance
private static CustomerAccount customer3; // with opening balance
private static CustomerAccount customer4; // with opening balance
private static Date startDate;
private static Date endDate;
@Rule
public final ExpectedException exception = ExpectedException.none();
@BeforeClass
public static void setUpBeforeClass() throws Exception {
ATMServiceImpl globalInstance = new ATMServiceImpl();
atmService = globalInstance;
AccountService accountService = globalInstance;
customer1 = accountService.createNewCustomer(new CustomerAccount("Firstname1", "Lastname1", new Date()));
customer2 = accountService.createNewCustomer(new CustomerAccount("Kannan", "Peroumal", new Date()));
customer3 = accountService.createNewCustomer(new CustomerAccount("Firstname3", "Lastname3", new Date()));
customer4 = accountService.createNewCustomer(new CustomerAccount("Firstname4", "Lastname4", new Date()));
atmService.deposit(customer2, 1000);
atmService.deposit(customer2, 50);
atmService.deposit(customer2, 500);
atmService.deposit(customer2, 100);
atmService.deposit(customer2, 1000);
atmService.deposit(customer3, 15000.50);
atmService.deposit(customer4, 25000.78);
atmService.withdraw(customer2, 100.78);
atmService.withdraw(customer2, 100.78);
Calendar cal = Calendar.getInstance();
cal.set(Calendar.DATE, cal.get(Calendar.DATE)-2);
startDate = cal.getTime();
endDate = Calendar.getInstance().getTime();
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
startDate = null;
endDate = null;
customer1 = null;
customer2 = null;
customer3 = null;
customer4 = null;
atmService = null;
}
@Before
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
}
/////////// case withdraw //////////////
@Test
public void test1_WithdrawSuccess() throws Exception{
try{
atmService.withdraw(customer2, 50.78); // no exception occurred
atmService.deposit(customer1, 10);
atmService.withdraw(customer1, 5.78); // no exception occurred
}catch(RuntimeException e){
Assert.assertFalse(true);
throw e;
}
}
@Test
public void test1_WithdrawAccountInfoEmpty() throws Exception{
exception.expect(RuntimeException.class);
exception.expectMessage(ErrMessage.ACCOUNT_EMPTY);
atmService.withdraw(null, 50.78);
}
@Test
public void test1_WithdrawUnknownAccountNumber() throws Exception{
exception.expect(RuntimeException.class);
exception.expectMessage(ErrMessage.ACCOUNT_UNKNOWN);
atmService.withdraw(new CustomerAccount(1234), 50.78);
}
@Test
public void test1_WithdrawAmountNotAllowed() throws Exception{
exception.expect(RuntimeException.class);
exception.expectMessage(ErrMessage.WITHDRAW_MIN_AMOUNT);
atmService.withdraw(customer3, 0);
}
@Test
public void test1_WithdrawOverdraftNotAllowed() throws Exception{
exception.expect(RuntimeException.class);
exception.expectMessage(ErrMessage.WITHDRAW_EXCEED_BALANCE);
atmService.withdraw(customer2, 15230);
}
/////////// case deposit ///////////////
@Test
public void test2_DepositSuccess() throws Exception{
try{
atmService.deposit(customer2, 50.78); // no exception occurred
atmService.deposit(customer1, 5.78); // no exception occurred
}catch(RuntimeException e){
Assert.assertFalse(true);
}
}
@Test
public void test2_DepositAccountInfoEmpty() throws Exception{
exception.expect(RuntimeException.class);
exception.expectMessage(ErrMessage.ACCOUNT_EMPTY);
atmService.deposit(null, 50.78);
}
@Test
public void test2_DepositUnknownAccountNumber() throws Exception{
exception.expect(RuntimeException.class);
exception.expectMessage(ErrMessage.ACCOUNT_UNKNOWN);
atmService.deposit(new CustomerAccount(1234), 50.78);
}
@Test
public void test2_DepositAmountNotAllowed() throws Exception{
exception.expect(RuntimeException.class);
exception.expectMessage(ErrMessage.DEPOSIT_MIN_AMOUNT);
atmService.deposit(customer3, 0);
}
/////////// case statement ///////////////
@Test
public void test3_StatementAccountInfoEmpty() throws Exception{
exception.expect(RuntimeException.class);
exception.expectMessage(ErrMessage.ACCOUNT_EMPTY);
atmService.getStatement(null, startDate, endDate);
}
@Test
public void test3_StatementUnknownAccountNumber() throws Exception{
exception.expect(RuntimeException.class);
exception.expectMessage(ErrMessage.ACCOUNT_UNKNOWN);
atmService.getStatement(new CustomerAccount(1234), startDate, endDate);
}
@Test
public void test3_StatementStartDateInvalid() throws Exception{
exception.expect(RuntimeException.class);
exception.expectMessage(ErrMessage.STATEMENT_INVALID_START_DATE);
atmService.getStatement(customer2, null, endDate);
}
@Test
public void test3_StatementEndDateInvalid() throws Exception{
exception.expect(RuntimeException.class);
exception.expectMessage(ErrMessage.STATEMENT_INVALID_END_DATE);
atmService.getStatement(customer2, startDate, null);
}
@Test
public void test3_StatementSuccess() throws Exception{
List<CustomerOperation> emptyList = new ArrayList<CustomerOperation>();
List<CustomerOperation> result = atmService.getStatement(customer2, startDate, endDate);
Assert.assertNotNull(result);
Assert.assertNotSame(emptyList, result);
}
@Test
public void test3_StatementPrint() throws Exception{
CustomerAccount customer = customer2;
List<CustomerOperation> result = atmService.getStatement(customer, startDate, endDate);
if(result==null || result.size()<1){
return;
}
DateFormat db = DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault());
NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.getDefault());
NumberFormat number = new DecimalFormat("0.00");
System.out.println(customer.getFullName());
System.out.println("Account number: "+customer.getAccountNo());
System.out.println("Account opening date: "+db.format(customer.getAccountOpeningDate().getTime()));
System.out.println("-----------------------------------------------------------");
System.out.println("Date\t\tDetails\t\tDebit\tCredit");
System.out.println("-----------------------------------------------------------");
for (CustomerOperation o : result) {
System.out.print(db.format(o.getOperationDate().getTime()) + "\t");
System.out.print(o.getOperationMode() + "\t");
if(o.getOperationMode().equals(CustomerOperationType.WITHDRAW)){
System.out.print(number.format(o.getOperationAmount()));
}else if(o.getOperationMode().equals(CustomerOperationType.DEPOSIT)){
System.out.print("\t\t"+ number.format(o.getOperationAmount()));
}
System.out.println();
}
System.out.println("-----------------------------------------------------------");
System.out.println("Available balance:"+ currency.format(customer.getBalance()));
}
}
package com.bank.atm;
import java.util.Date;
/**
*
* @author Kannan Peroumal
* 14/06/2016
* for Societe Generale
*
*/
public class CustomerAccount extends CustomerDetails{
private int accountNo;
private double balance;
public CustomerAccount() { }
public CustomerAccount(int accountNo) {
this.accountNo = accountNo;
}
public CustomerAccount(String firstName, String lastName, Date accountOpeningDate) {
super(firstName, lastName, accountOpeningDate);
}
public CustomerAccount(int accountNo, String firstName, String lastName, Date accountOpeningDate) {
super(firstName, lastName, accountOpeningDate);
this.accountNo = accountNo;
}
public int getAccountNo() {
return accountNo;
}
public void setAccountNo(int accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
@Override
public int hashCode() {
return accountNo;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CustomerAccount other = (CustomerAccount) obj;
if (accountNo != other.accountNo)
return false;
return true;
}
}
package com.bank.atm;
import java.util.Date;
/**
*
* @author Kannan Peroumal
* 14/06/2016
* for Societe Generale
*
*/
public abstract class CustomerDetails {
private String firstName;
private String lastName;
private Date accountOpeningDate;
public CustomerDetails() { }
public CustomerDetails(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public CustomerDetails(String firstName, String lastName, Date accountOpeningDate) {
this.firstName = firstName;
this.lastName = lastName;
this.accountOpeningDate = accountOpeningDate;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Date getAccountOpeningDate() {
return accountOpeningDate;
}
public void setAccountOpeningDate(Date accountOpeningDate) {
this.accountOpeningDate = accountOpeningDate;
}
public String getFullName(){
return (firstName + " " + lastName).toUpperCase();
}
}
package com.bank.atm;
import java.util.Calendar;
/**
*
* @author Kannan Peroumal
* 14/06/2016
* for Societe Generale
*
*/
public class CustomerOperation extends CustomerAccount{
private CustomerOperationType operationMode;
private Calendar operationDate;
private double operationAmount;
public CustomerOperation(int accountNo, CustomerOperationType operationMode, Calendar operationDate, double operationAmount) {
super(accountNo);
this.operationMode = operationMode;
this.operationDate = operationDate;
this.operationAmount = operationAmount;
}
public CustomerOperationType getOperationMode() {
return operationMode;
}
public void setOperationMode(CustomerOperationType operationMode) {
this.operationMode = operationMode;
}
public Calendar getOperationDate() {
return operationDate;
}
public void setOperationDate(Calendar operationDate) {
this.operationDate = operationDate;
}
public double getOperationAmount() {
return operationAmount;
}
public void setOperationAmount(double operationAmount) {
this.operationAmount = operationAmount;
}
}
package com.bank.atm;
/**
*
* @author Kannan Peroumal
* 14/06/2016
* for Societe Generale
*
*/
public enum CustomerOperationType {
WITHDRAW,
DEPOSIT
}
package com.bank.atm;
public final class ErrMessage {
public static final String ACCOUNT_EMPTY = "Error: Customer account number is empty";
public static final String ACCOUNT_UNKNOWN = "Error: Unknown customer";
public static final String WITHDRAW_MIN_AMOUNT = "Error: minimum withdraw amount is 1";
public static final String WITHDRAW_EXCEED_BALANCE = "Error: withdraw amount is greater than the available balance";
public static final String DEPOSIT_MIN_AMOUNT = "Error: minimum deposit amount is 1";
public static final String STATEMENT_INVALID_START_DATE = "Error: Enter a valid start date";
public static final String STATEMENT_INVALID_END_DATE = "Error: Enter a valid end date";
public static final String CUSTOMER_NAME_INVALID = "Error: Not a valid customer name";
public static final String CUSTOMER_EXISTS_ALREADY = "Error: This customer exists already";
public static final String CUSTOMER_ACCONT_NUM_ERR = "Error: while generating new account number";
}
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bank.atmservice</groupId>
<artifactId>_test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>_test</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment