Created
June 15, 2016 19:35
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.bank.atm; | |
/** | |
* | |
* @author Kannan Peroumal | |
* 14/06/2016 | |
* for Societe Generale | |
* | |
*/ | |
public interface AccountService { | |
CustomerAccount createNewCustomer(CustomerAccount customerAccount) throws RuntimeException; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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())); | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.bank.atm; | |
/** | |
* | |
* @author Kannan Peroumal | |
* 14/06/2016 | |
* for Societe Generale | |
* | |
*/ | |
public enum CustomerOperationType { | |
WITHDRAW, | |
DEPOSIT | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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