diff --git a/README.md b/README.md index fa921a37..6bae1a72 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,9 @@ A dummy application for a bank; should provide various functions of a retail ban * **Maxi-Savings accounts** have a rate of 2% for the first $1,000 then 5% for the next $1,000 then 10% * A bank manager can get a report showing the list of customers and how many accounts they have * A bank manager can get a report showing the total interest paid by the bank on all accounts +* A customer can transfer between their accounts +* Interest rates should accrue daily (incl. weekends), rates above are per-annum ### Additional Features -* A customer can transfer between their accounts * Change **Maxi-Savings accounts** to have an interest rate of 5% assuming no withdrawals in the past 10 days otherwise 0.1% -* Interest rates should accrue daily (incl. weekends), rates above are per-annum diff --git a/src/main/java/com/abc/Account.java b/src/main/java/com/abc/Account.java deleted file mode 100644 index 099691e0..00000000 --- a/src/main/java/com/abc/Account.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.abc; - -import java.util.ArrayList; -import java.util.List; - -public class Account { - - public static final int CHECKING = 0; - public static final int SAVINGS = 1; - public static final int MAXI_SAVINGS = 2; - - private final int accountType; - public List transactions; - - public Account(int accountType) { - this.accountType = accountType; - this.transactions = new ArrayList(); - } - - public void deposit(double amount) { - if (amount <= 0) { - throw new IllegalArgumentException("amount must be greater than zero"); - } else { - transactions.add(new Transaction(amount)); - } - } - -public void withdraw(double amount) { - if (amount <= 0) { - throw new IllegalArgumentException("amount must be greater than zero"); - } else { - transactions.add(new Transaction(-amount)); - } -} - - public double interestEarned() { - double amount = sumTransactions(); - switch(accountType){ - case SAVINGS: - if (amount <= 1000) - return amount * 0.001; - else - return 1 + (amount-1000) * 0.002; -// case SUPER_SAVINGS: -// if (amount <= 4000) -// return 20; - case MAXI_SAVINGS: - if (amount <= 1000) - return amount * 0.02; - if (amount <= 2000) - return 20 + (amount-1000) * 0.05; - return 70 + (amount-2000) * 0.1; - default: - return amount * 0.001; - } - } - - public double sumTransactions() { - return checkIfTransactionsExist(true); - } - - private double checkIfTransactionsExist(boolean checkAll) { - double amount = 0.0; - for (Transaction t: transactions) - amount += t.amount; - return amount; - } - - public int getAccountType() { - return accountType; - } - -} diff --git a/src/main/java/com/abc/Bank.java b/src/main/java/com/abc/Bank.java deleted file mode 100644 index 5dd535bd..00000000 --- a/src/main/java/com/abc/Bank.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.abc; - -import java.util.ArrayList; -import java.util.List; - -public class Bank { - private List customers; - - public Bank() { - customers = new ArrayList(); - } - - public void addCustomer(Customer customer) { - customers.add(customer); - } - - public String customerSummary() { - String summary = "Customer Summary"; - for (Customer c : customers) - summary += "\n - " + c.getName() + " (" + format(c.getNumberOfAccounts(), "account") + ")"; - return summary; - } - - //Make sure correct plural of word is created based on the number passed in: - //If number passed in is 1 just return the word otherwise add an 's' at the end - private String format(int number, String word) { - return number + " " + (number == 1 ? word : word + "s"); - } - - public double totalInterestPaid() { - double total = 0; - for(Customer c: customers) - total += c.totalInterestEarned(); - return total; - } - - public String getFirstCustomer() { - try { - customers = null; - return customers.get(0).getName(); - } catch (Exception e){ - e.printStackTrace(); - return "Error"; - } - } -} diff --git a/src/main/java/com/abc/Customer.java b/src/main/java/com/abc/Customer.java deleted file mode 100644 index 31571685..00000000 --- a/src/main/java/com/abc/Customer.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.abc; - -import java.util.ArrayList; -import java.util.List; - -import static java.lang.Math.abs; - -public class Customer { - private String name; - private List accounts; - - public Customer(String name) { - this.name = name; - this.accounts = new ArrayList(); - } - - public String getName() { - return name; - } - - public Customer openAccount(Account account) { - accounts.add(account); - return this; - } - - public int getNumberOfAccounts() { - return accounts.size(); - } - - public double totalInterestEarned() { - double total = 0; - for (Account a : accounts) - total += a.interestEarned(); - return total; - } - - public String getStatement() { - String statement = null; - statement = "Statement for " + name + "\n"; - double total = 0.0; - for (Account a : accounts) { - statement += "\n" + statementForAccount(a) + "\n"; - total += a.sumTransactions(); - } - statement += "\nTotal In All Accounts " + toDollars(total); - return statement; - } - - private String statementForAccount(Account a) { - String s = ""; - - //Translate to pretty account type - switch(a.getAccountType()){ - case Account.CHECKING: - s += "Checking Account\n"; - break; - case Account.SAVINGS: - s += "Savings Account\n"; - break; - case Account.MAXI_SAVINGS: - s += "Maxi Savings Account\n"; - break; - } - - //Now total up all the transactions - double total = 0.0; - for (Transaction t : a.transactions) { - s += " " + (t.amount < 0 ? "withdrawal" : "deposit") + " " + toDollars(t.amount) + "\n"; - total += t.amount; - } - s += "Total " + toDollars(total); - return s; - } - - private String toDollars(double d){ - return String.format("$%,.2f", abs(d)); - } -} diff --git a/src/main/java/com/abc/DateProvider.java b/src/main/java/com/abc/DateProvider.java deleted file mode 100644 index 035ee90b..00000000 --- a/src/main/java/com/abc/DateProvider.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.abc; - -import java.util.Calendar; -import java.util.Date; - -public class DateProvider { - private static DateProvider instance = null; - - public static DateProvider getInstance() { - if (instance == null) - instance = new DateProvider(); - return instance; - } - - public Date now() { - return Calendar.getInstance().getTime(); - } -} diff --git a/src/main/java/com/abc/Transaction.java b/src/main/java/com/abc/Transaction.java deleted file mode 100644 index c1f7c67e..00000000 --- a/src/main/java/com/abc/Transaction.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.abc; - -import java.util.Calendar; -import java.util.Date; - -public class Transaction { - public final double amount; - - private Date transactionDate; - - public Transaction(double amount) { - this.amount = amount; - this.transactionDate = DateProvider.getInstance().now(); - } - -} diff --git a/src/main/java/com/abc/domain/constants/AccountType.java b/src/main/java/com/abc/domain/constants/AccountType.java new file mode 100644 index 00000000..3c0772c7 --- /dev/null +++ b/src/main/java/com/abc/domain/constants/AccountType.java @@ -0,0 +1,10 @@ +package com.abc.domain.constants; + +/** + * The enumeration for Account Type + * + * @author Ihor + */ +public enum AccountType { + CHECKING, SAVINGS, MAXI_SAVINGS +} diff --git a/src/main/java/com/abc/domain/constants/DailyCompoundRate.java b/src/main/java/com/abc/domain/constants/DailyCompoundRate.java new file mode 100644 index 00000000..76432d67 --- /dev/null +++ b/src/main/java/com/abc/domain/constants/DailyCompoundRate.java @@ -0,0 +1,29 @@ +package com.abc.domain.constants; + +/** + * The daily compound rate+1 based on 365 days + * + * Following matrix was used to convert rates from non-leap year to daily 0.1% + * => 1.0000027383608262443494848243471 0.2% => + * 1.0000054739948799896979591374816 2% => 1.0000542552451767719379729880399 5% + * => 1.0001336806171134403505084797728 10% => 1.0002611578760678121616866817054 + * + * @author Ihor + */ +public interface DailyCompoundRate { + interface Checking { + static final double ANY = 1.0000027383608262443494848243471; + } + + interface Savings { + static final double UNDER_1000 = 1.0000027383608262443494848243471; + static final double OVER_1000 = 1.0000054739948799896979591374816; + } + + interface MaxiSavings { + static final double UNDER_1000 = 1.0000542552451767719379729880399; + static final double UNDER_2000 = 1.0001336806171134403505084797728; + static final double OVER_2000 = 1.0002611578760678121616866817054; + } + +} diff --git a/src/main/java/com/abc/domain/constants/TransferState.java b/src/main/java/com/abc/domain/constants/TransferState.java new file mode 100644 index 00000000..bf734794 --- /dev/null +++ b/src/main/java/com/abc/domain/constants/TransferState.java @@ -0,0 +1,11 @@ +package com.abc.domain.constants; + +/** + * The states to transfer between accounts + * + * @author Ihor + */ +public enum TransferState { + INITIAL, WITHDRAWN_COMPLETE + +} diff --git a/src/main/java/com/abc/domain/exceptions/BusinessException.java b/src/main/java/com/abc/domain/exceptions/BusinessException.java new file mode 100644 index 00000000..380405a7 --- /dev/null +++ b/src/main/java/com/abc/domain/exceptions/BusinessException.java @@ -0,0 +1,20 @@ +package com.abc.domain.exceptions; + +/** + * The superclass for all business exception in Bank + * + * @author Ihor + */ +public class BusinessException extends Exception { + private static final long serialVersionUID = 7156699628264008860L; + + /** + * The constructor for BusinessException + * + * @param message + * Error message + */ + public BusinessException(String message) { + super(message); + } +} diff --git a/src/main/java/com/abc/domain/exceptions/InvalidCustomerException.java b/src/main/java/com/abc/domain/exceptions/InvalidCustomerException.java new file mode 100644 index 00000000..81a71f60 --- /dev/null +++ b/src/main/java/com/abc/domain/exceptions/InvalidCustomerException.java @@ -0,0 +1,21 @@ +package com.abc.domain.exceptions; + +/** + * The exception to indicate that Customer data is invalid. It is Business + * Exception subclass. + * + * @author Ihor + */ +public class InvalidCustomerException extends BusinessException { + private static final long serialVersionUID = 1827978046616513675L; + + /** + * The constructor for InvalidCustomerException + * + * @param message + * Error message + */ + public InvalidCustomerException(String message) { + super(message); + } +} diff --git a/src/main/java/com/abc/domain/exceptions/InvalidPeriodException.java b/src/main/java/com/abc/domain/exceptions/InvalidPeriodException.java new file mode 100644 index 00000000..1f6d0edd --- /dev/null +++ b/src/main/java/com/abc/domain/exceptions/InvalidPeriodException.java @@ -0,0 +1,24 @@ +/** + * + */ +package com.abc.domain.exceptions; + +/** + * @author Ihor + * + */ +public class InvalidPeriodException extends BusinessException { + + private static final long serialVersionUID = 8836080878991008928L; + + /** + * Constructor to create InvalidPeriodException + * + * @param message + * Error message + */ + public InvalidPeriodException(String message) { + super(message); + } + +} diff --git a/src/main/java/com/abc/domain/exceptions/InvalidTransactionException.java b/src/main/java/com/abc/domain/exceptions/InvalidTransactionException.java new file mode 100644 index 00000000..3625a272 --- /dev/null +++ b/src/main/java/com/abc/domain/exceptions/InvalidTransactionException.java @@ -0,0 +1,21 @@ +package com.abc.domain.exceptions; + +/** + * The exception to indicate that transaction is invalid. It is Business + * Exception subclass. + * + * @author Ihor + */ +public class InvalidTransactionException extends BusinessException { + private static final long serialVersionUID = 3796741547535023801L; + + /** + * The constructor for InvalidTransactionException + * + * @param message + * Error message + */ + public InvalidTransactionException(String message) { + super(message); + } +} diff --git a/src/main/java/com/abc/domain/objects/Account.java b/src/main/java/com/abc/domain/objects/Account.java new file mode 100644 index 00000000..75ffd2d5 --- /dev/null +++ b/src/main/java/com/abc/domain/objects/Account.java @@ -0,0 +1,210 @@ +package com.abc.domain.objects; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; + +import com.abc.domain.constants.AccountType; +import com.abc.domain.constants.DailyCompoundRate; +import com.abc.domain.exceptions.BusinessException; +import com.abc.domain.exceptions.InvalidTransactionException; +import com.abc.util.DateTimeProvider; + +/** + * Account domain object contains following information: - type of account - + * list of transaction involving account - date time provider to use for + * transactions + * + * @author Ihor + */ +public class Account { + + private final AccountType accountType; + private final List transactions; + private final DateTimeProvider dateTimeProvider; + + /** + * The constructor for Account + * + * @param accountType + * Account type + * @param dateTimeProvider + * Date time provider for transactions + */ + public Account(AccountType accountType, DateTimeProvider dateTimeProvider) { + this.accountType = accountType; + this.transactions = new ArrayList(); + this.dateTimeProvider = dateTimeProvider; + } + + /** + * Method to deposit amount + * + * @param amount + * Amount to deposit + * @throws BusinessException + */ + public void deposit(double amount) throws BusinessException { + if (amount <= 0) { + throw new InvalidTransactionException("amount must be greater than zero"); + } else { + transactions.add(new Transaction(amount, dateTimeProvider)); + } + } + + /** + * Method to deposit amount + * + * @param amount + * Amount to deposit + * @throws BusinessException + */ + public void withdraw(double amount) throws BusinessException { + if (amount <= 0) { + throw new InvalidTransactionException("amount must be greater than zero"); + } else { + transactions.add(new Transaction(-amount, dateTimeProvider)); + } + } + + /** + * Method to calculate interest factor for compounding + * + * @param rate + * Daily rate + * @param days + * Number of days + * @return interest factor + */ + private double calculateInterestFactor(double rate, long days) { + return Math.pow(rate, days) - 1; + } + + /** + * Method to get amounts that were held and for how many days + * + * @return Amounts and number of days + * @throws BusinessException + */ + private List getAmountsPerPeriod() throws BusinessException { + List amountsPerPeriod = new ArrayList(); + LocalDateTime previousDateTime = null; + double previousAmount = 0.0; + for (Transaction transaction : transactions) { + LocalDateTime currentDateTime = transaction.getTransactionDate(); + if (previousDateTime != null) { + long days = ChronoUnit.DAYS.between(previousDateTime.toLocalDate(), currentDateTime.toLocalDate()); + AmountPerPeriod amountPerPeriod = new AmountPerPeriod(previousAmount, days); + amountsPerPeriod.add(amountPerPeriod); + } + previousDateTime = currentDateTime; + previousAmount = previousAmount + transaction.getAmount(); + } + if (previousDateTime != null) { + long days = ChronoUnit.DAYS.between(previousDateTime.toLocalDate(), dateTimeProvider.now().toLocalDate()); + AmountPerPeriod amountPerPeriod = new AmountPerPeriod(previousAmount, days); + amountsPerPeriod.add(amountPerPeriod); + } + return amountsPerPeriod; + } + + /** + * method to calculate interest earned + * + * @return Interest earned on this account + * @throws BusinessException + */ + public double calculateInterestEarned() throws BusinessException { + List amountsPerPeriod = getAmountsPerPeriod(); + double interest = 0; + double rollingInterest = 0.0; + // compound interest calculated each period and add to amount in each + // period + for (AmountPerPeriod amountPerPeriod : amountsPerPeriod) { + long days = amountPerPeriod.getDays(); + double amount = amountPerPeriod.getAmount() + rollingInterest; + interest = calculateInterestEarned(amount, days); + rollingInterest += interest; + + } + return rollingInterest; + } + + /** + * The method to calculate interest rate earned + * + * @param amount + * amount + * @param days + * number of days held + * @return Interest rate earned Please note for leap year (365 days) the + * annual interest will be higher then non-leap year (365) + */ + private double calculateInterestEarned(double amount, long days) { + switch (accountType) { + case SAVINGS: + if (amount <= 1000) { + return amount * calculateInterestFactor(DailyCompoundRate.Savings.UNDER_1000, days); + } else { + return 1000 * calculateInterestFactor(DailyCompoundRate.Savings.UNDER_1000, days) + + (amount - 1000) * calculateInterestFactor(DailyCompoundRate.Savings.OVER_1000, days); + } + case MAXI_SAVINGS: + if (amount <= 1000) { + return amount * calculateInterestFactor(DailyCompoundRate.MaxiSavings.UNDER_1000, days); + } + if (amount <= 2000) { + return 1000 * calculateInterestFactor(DailyCompoundRate.MaxiSavings.UNDER_1000, days) + + (amount - 1000) * calculateInterestFactor(DailyCompoundRate.MaxiSavings.UNDER_2000, days); + } + return 1000 * calculateInterestFactor(DailyCompoundRate.MaxiSavings.UNDER_1000, days) + + 1000 * calculateInterestFactor(DailyCompoundRate.MaxiSavings.UNDER_2000, days) + + +(amount - 2000) * calculateInterestFactor(DailyCompoundRate.MaxiSavings.OVER_2000, days); + case CHECKING: + default: + return amount * calculateInterestFactor(DailyCompoundRate.Checking.ANY, days); + } + } + + /** + * Method to sum all transactions on account + * + * @return Sum of all transaction amounts + */ + public double sumTransactions() { + double amount = 0.0; + for (Transaction t : transactions) + amount += t.getAmount(); + return amount; + } + + /** + * get account type + * + * @return Account type + */ + public AccountType getAccountType() { + return accountType; + } + + /** + * get transactions + * + * @return Transaction list + */ + public List getTransactions() { + return transactions; + } + + /** + * String Representation for account + * + * @return String for account + */ + @Override + public String toString() { + return "Account [accountType=" + accountType + ", transactions=" + transactions + ", dateTimeProvider=" + + dateTimeProvider + "]"; + } +} diff --git a/src/main/java/com/abc/domain/objects/AmountPerPeriod.java b/src/main/java/com/abc/domain/objects/AmountPerPeriod.java new file mode 100644 index 00000000..a5a4203a --- /dev/null +++ b/src/main/java/com/abc/domain/objects/AmountPerPeriod.java @@ -0,0 +1,61 @@ +package com.abc.domain.objects; + +import com.abc.domain.exceptions.InvalidPeriodException; + +/** + * Data holder for amount and number days amount is held. Currently period can + * be only days + * + * @author Ihor + * + */ +public class AmountPerPeriod { + private final double amount; + private final long days; + + /** + * Constructor for AmountPerPerido + * + * @param amount + * Amount + * @param days + * Number of days held + * @throws InvalidPeriodException + */ + public AmountPerPeriod(double amount, long days) throws InvalidPeriodException { + this.amount = amount; + this.days = days; + if (days<0){ + throw new InvalidPeriodException("Number of days is invalid: "+days); + } + } + + /** + * Get amount + * + * @return Amount + */ + public double getAmount() { + return amount; + } + + /** + * Get number of days + * + * @return Number of days + */ + public long getDays() { + return days; + } + + /** + * String Representation for amount per period + * + * @return String for amount per period + */ + @Override + public String toString() { + return "AmountPerPeriod [amount=" + amount + ", days=" + days + "]"; + } + +} diff --git a/src/main/java/com/abc/domain/objects/Bank.java b/src/main/java/com/abc/domain/objects/Bank.java new file mode 100644 index 00000000..4864282d --- /dev/null +++ b/src/main/java/com/abc/domain/objects/Bank.java @@ -0,0 +1,105 @@ +package com.abc.domain.objects; + +import java.util.ArrayList; +import java.util.List; + +import com.abc.domain.exceptions.BusinessException; +import com.abc.domain.exceptions.InvalidCustomerException; + +/** + * Bank class contains list of customers that have accounts in the bank. + * + * @author Ihor + * + */ +public class Bank { + private final List customers; + + /** + * Constructor for Bank cass + */ + public Bank() { + customers = new ArrayList(); + } + + /** + * method to add customer + * + * @param customer + * Customer with account(s) in Bank + * @throws BusinessException + */ + public void addCustomer(Customer customer) throws BusinessException { + if (customer == null) { + throw new InvalidCustomerException("customer is null"); + } + customers.add(customer); + } + + /** + * Method to generate customer summary + * + * @return Customer summary + */ + public String generateCustomerSummary() { + String summary = "Customer Summary"; + for (Customer c : customers) + summary += "\n - " + c.getName() + " (" + format(c.getNumberOfAccounts(), "account") + ")"; + return summary; + } + + /** + * Method to format word + * + * @param number + * Number to format + * @param word + * Word to format + * + * @return Formatted word + * + * Make sure correct plural of word is created based on the number + * passed in: If number passed in is 1 just return the word + * otherwise add an 's' at the end + * + */ + private String format(int number, String word) { + return number + " " + (number == 1 ? word : word + "s"); + } + + /** + * Total interest paid for all customer + * + * @return Total interest paid + * @throws BusinessException + */ + public double totalInterestPaid() throws BusinessException { + double total = 0; + for (Customer c : customers) + total += c.totalInterestEarned(); + return total; + } + + /** + * Method to get first customer name + * + * @return First customer name + * @throws InvalidCustomerException + */ + public String getFirstCustomerName() throws InvalidCustomerException { + if (customers.size() > 0) { + return customers.get(0).getName(); + } + throw new InvalidCustomerException("No customers yet"); + } + + /** + * String representation for Bank + * + * @return String representation for Bank + */ + @Override + public String toString() { + return "Bank [customers=" + customers + "]"; + } +} diff --git a/src/main/java/com/abc/domain/objects/Customer.java b/src/main/java/com/abc/domain/objects/Customer.java new file mode 100644 index 00000000..c2f14630 --- /dev/null +++ b/src/main/java/com/abc/domain/objects/Customer.java @@ -0,0 +1,196 @@ +package com.abc.domain.objects; + +import static java.lang.Math.abs; + +import java.util.ArrayList; +import java.util.List; + +import com.abc.domain.constants.TransferState; +import com.abc.domain.exceptions.BusinessException; +import com.abc.domain.exceptions.InvalidTransactionException; + +/** + * Class for customer that contains customer name and list of accounts + * + * @author Ihor + */ +public class Customer { + private final String name; + private final List accounts; + + /** + * Constructor to create customer class + * + * @param name + * Name of customer + */ + public Customer(String name) { + this.name = name; + this.accounts = new ArrayList(); + } + + /** + * Method to get name of customer + * + * @return Name of customer + */ + public String getName() { + return name; + } + + /** + * Method to open account + * + * @param account + * Account to open + * @return the class itself + */ + public Customer openAccount(Account account) { + accounts.add(account); + return this; + } + + /** + * Get number of Accouns for the customer + * + * @return Number of accounts for the customer + */ + public int getNumberOfAccounts() { + return accounts.size(); + } + + /** + * Method to get total interest earned + * + * @return Total interest earned + * @throws BusinessException + */ + public double totalInterestEarned() throws BusinessException { + double total = 0; + for (Account a : accounts) + total += a.calculateInterestEarned(); + return total; + } + + /** + * Method to get Statement + * + * @return Statement + */ + public String getStatement() { + StringBuilder statement = new StringBuilder(); + statement.append("Statement for " + name + "\n"); + double total = 0.0; + for (Account a : accounts) { + statement.append("\n" + getStatementForAccount(a) + "\n"); + total += a.sumTransactions(); + } + statement.append("\nTotal In All Accounts " + formatToDollars(total)); + return statement.toString(); + } + + /** + * Method to get statement for account + * + * @param account + * Account + * @return Statement for account + */ + private String getStatementForAccount(Account account) { + String s = ""; + + // Translate to pretty account type + switch (account.getAccountType()) { + case CHECKING: + s += "Checking Account\n"; + break; + case SAVINGS: + s += "Savings Account\n"; + break; + case MAXI_SAVINGS: + s += "Maxi Savings Account\n"; + break; + } + + // Now total up all the transactions + double total = 0.0; + for (Transaction t : account.getTransactions()) { + s += " " + (t.getAmount() < 0 ? "withdrawal" : "deposit") + " " + formatToDollars(t.getAmount()) + "\n"; + total += t.getAmount(); + } + s += "Total " + formatToDollars(total); + return s; + } + + /** + * Format to dollars + * + * @param amount + * Amount + * @return formatted amount + */ + private String formatToDollars(double amount) { + return String.format("$%,.2f", abs(amount)); + } + + /** + * Method to check whether account belongs to customer + * + * @param account + * Account + * @throws InvalidTransactionException + * when account does not belong to customer + */ + private void checkAccountOwnership(Account account) throws InvalidTransactionException { + if (!accounts.contains(account)) { + throw new InvalidTransactionException("account is not owned by customer: " + account); + } + } + + /** + * Method to transfer between accounts + * + * @param accountFrom + * Account from + * @param accountTo + * Account to + * @param amount + * Amount + * @throws BusinessException + */ + public void transfer(Account accountFrom, Account accountTo, double amount) throws BusinessException { + checkAccountOwnership(accountFrom); + checkAccountOwnership(accountTo); + + if (accountFrom.equals(accountTo)) { + throw new InvalidTransactionException("Cannot transfer to the same account"); + } + if (accountFrom.sumTransactions() < amount) { + throw new InvalidTransactionException("Cannot transfer more then current amount" + amount); + } + + TransferState transferState = TransferState.INITIAL; + try { + accountFrom.withdraw(amount); + transferState = TransferState.WITHDRAWN_COMPLETE; + accountTo.deposit(amount); + } + // compensate withdraw if deposit fail + catch (Exception exception) { + if (TransferState.WITHDRAWN_COMPLETE.equals(transferState)) { + accountFrom.deposit(amount); + } + throw exception; + } + } + + /** + * String representation of customer + * + * @return String representation of customer + */ + @Override + public String toString() { + return "Customer [name=" + name + ", accounts=" + accounts + "]"; + } +} diff --git a/src/main/java/com/abc/domain/objects/Transaction.java b/src/main/java/com/abc/domain/objects/Transaction.java new file mode 100644 index 00000000..421793d0 --- /dev/null +++ b/src/main/java/com/abc/domain/objects/Transaction.java @@ -0,0 +1,58 @@ +package com.abc.domain.objects; + +import java.time.LocalDateTime; + +import com.abc.util.DateTimeProvider; + +/** + * Class that represents Transaction and contains amount and transaction date + * + * @author Ihor + * + */ +public class Transaction { + private final double amount; + private final LocalDateTime transactionDate; + + /** + * Constructor for transaction + * + * @param amount + * Amount + * @param dateTimeProvider + * Date time provider for transaction + */ + public Transaction(double amount, DateTimeProvider dateTimeProvider) { + this.amount = amount; + transactionDate = dateTimeProvider.now(); + } + + /** + * Get transaction date + * + * @return Transaction date + */ + public LocalDateTime getTransactionDate() { + return transactionDate; + } + + /** + * Get amount for transaction + * + * @return Amount for transaction + */ + public double getAmount() { + return amount; + } + + /** + * String representation for transaction + * + * @return String representation for transaction + */ + @Override + public String toString() { + return "Transaction [amount=" + amount + ", transactionDate=" + transactionDate + "]"; + } + +} diff --git a/src/main/java/com/abc/util/DateTimeProvider.java b/src/main/java/com/abc/util/DateTimeProvider.java new file mode 100644 index 00000000..8b093423 --- /dev/null +++ b/src/main/java/com/abc/util/DateTimeProvider.java @@ -0,0 +1,18 @@ +package com.abc.util; + +import java.time.LocalDateTime; + +/** + * Interface to provide date time information + * + * @author Ihor + * + */ +public interface DateTimeProvider { + /** + * Method to get date time for current date time + * + * @return current date time + */ + LocalDateTime now(); +} diff --git a/src/main/java/com/abc/util/impl/DateTimeProviderImpl.java b/src/main/java/com/abc/util/impl/DateTimeProviderImpl.java new file mode 100644 index 00000000..74af5bb0 --- /dev/null +++ b/src/main/java/com/abc/util/impl/DateTimeProviderImpl.java @@ -0,0 +1,30 @@ +package com.abc.util.impl; + +import java.time.LocalDateTime; + +import com.abc.util.DateTimeProvider; + +/** + * Singleton class to provide date time + * + * @author Ihor + * + */ +public enum DateTimeProviderImpl implements DateTimeProvider { + /** + * Singleton instance for date time provider + */ + INSTANCE; + /** + * Private constructor + */ + private DateTimeProviderImpl(){ + } + /** + * @see interface + */ + @Override + public LocalDateTime now() { + return LocalDateTime.now(); + } +} diff --git a/src/test/java/com/abc/BankTest.java b/src/test/java/com/abc/BankTest.java deleted file mode 100644 index f8a82896..00000000 --- a/src/test/java/com/abc/BankTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.abc; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class BankTest { - private static final double DOUBLE_DELTA = 1e-15; - - @Test - public void customerSummary() { - Bank bank = new Bank(); - Customer john = new Customer("John"); - john.openAccount(new Account(Account.CHECKING)); - bank.addCustomer(john); - - assertEquals("Customer Summary\n - John (1 account)", bank.customerSummary()); - } - - @Test - public void checkingAccount() { - Bank bank = new Bank(); - Account checkingAccount = new Account(Account.CHECKING); - Customer bill = new Customer("Bill").openAccount(checkingAccount); - bank.addCustomer(bill); - - checkingAccount.deposit(100.0); - - assertEquals(0.1, bank.totalInterestPaid(), DOUBLE_DELTA); - } - - @Test - public void savings_account() { - Bank bank = new Bank(); - Account checkingAccount = new Account(Account.SAVINGS); - bank.addCustomer(new Customer("Bill").openAccount(checkingAccount)); - - checkingAccount.deposit(1500.0); - - assertEquals(2.0, bank.totalInterestPaid(), DOUBLE_DELTA); - } - - @Test - public void maxi_savings_account() { - Bank bank = new Bank(); - Account checkingAccount = new Account(Account.MAXI_SAVINGS); - bank.addCustomer(new Customer("Bill").openAccount(checkingAccount)); - - checkingAccount.deposit(3000.0); - - assertEquals(170.0, bank.totalInterestPaid(), DOUBLE_DELTA); - } - -} diff --git a/src/test/java/com/abc/CustomerTest.java b/src/test/java/com/abc/CustomerTest.java deleted file mode 100644 index b8df9498..00000000 --- a/src/test/java/com/abc/CustomerTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.abc; - -import org.junit.Ignore; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class CustomerTest { - - @Test //Test customer statement generation - public void testApp(){ - - Account checkingAccount = new Account(Account.CHECKING); - Account savingsAccount = new Account(Account.SAVINGS); - - Customer henry = new Customer("Henry").openAccount(checkingAccount).openAccount(savingsAccount); - - checkingAccount.deposit(100.0); - savingsAccount.deposit(4000.0); - savingsAccount.withdraw(200.0); - - assertEquals("Statement for Henry\n" + - "\n" + - "Checking Account\n" + - " deposit $100.00\n" + - "Total $100.00\n" + - "\n" + - "Savings Account\n" + - " deposit $4,000.00\n" + - " withdrawal $200.00\n" + - "Total $3,800.00\n" + - "\n" + - "Total In All Accounts $3,900.00", henry.getStatement()); - } - - @Test - public void testOneAccount(){ - Customer oscar = new Customer("Oscar").openAccount(new Account(Account.SAVINGS)); - assertEquals(1, oscar.getNumberOfAccounts()); - } - - @Test - public void testTwoAccount(){ - Customer oscar = new Customer("Oscar") - .openAccount(new Account(Account.SAVINGS)); - oscar.openAccount(new Account(Account.CHECKING)); - assertEquals(2, oscar.getNumberOfAccounts()); - } - - @Ignore - public void testThreeAcounts() { - Customer oscar = new Customer("Oscar") - .openAccount(new Account(Account.SAVINGS)); - oscar.openAccount(new Account(Account.CHECKING)); - assertEquals(3, oscar.getNumberOfAccounts()); - } -} diff --git a/src/test/java/com/abc/TransactionTest.java b/src/test/java/com/abc/TransactionTest.java deleted file mode 100644 index 28983234..00000000 --- a/src/test/java/com/abc/TransactionTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.abc; - -import org.junit.Test; - -import static org.junit.Assert.assertTrue; - -public class TransactionTest { - @Test - public void transaction() { - Transaction t = new Transaction(5); - assertTrue(t instanceof Transaction); - } -} diff --git a/src/test/java/com/abc/domain/constants/Precision.java b/src/test/java/com/abc/domain/constants/Precision.java new file mode 100644 index 00000000..3e184fa6 --- /dev/null +++ b/src/test/java/com/abc/domain/constants/Precision.java @@ -0,0 +1,10 @@ +package com.abc.domain.constants; + +/** + * Precision for comparing double + * + * @author Ihor + */ +public interface Precision { + static final double DOUBLE_PRECISION = 1e-10; +} diff --git a/src/test/java/com/abc/domain/objects/AccountTest.java b/src/test/java/com/abc/domain/objects/AccountTest.java new file mode 100644 index 00000000..e0f8e76f --- /dev/null +++ b/src/test/java/com/abc/domain/objects/AccountTest.java @@ -0,0 +1,272 @@ +package com.abc.domain.objects; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.time.LocalDateTime; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import com.abc.domain.constants.AccountType; +import com.abc.domain.constants.Precision; +import com.abc.domain.exceptions.BusinessException; +import com.abc.domain.exceptions.InvalidTransactionException; +import com.abc.domain.util.impl.DateTestHelperImpl; +import com.abc.util.DateTimeProvider; +import com.abc.util.impl.DateTimeProviderImpl; + +/** + * Test Account domain object For daily interest rates, the expected rate was + * calculated manually using Windows 10 calculator. E.g. for multiple day + * transaction in maxi savings account done on three days 365 ago, 182 ago, 91 + * ago the interest rate accrued is: (9.9778910204323415116791735909692+ + * 24.763565242231429895420724745376+ 24.472895787683816075965225393111)+ + * (4.9493009078485256048770098422766+ 9.291574211057275642684627949108)+ + * 4.0755277034013996840802408902768 + * + * @author Ihor + */ +public class AccountTest { + private DateTimeProvider dateTimeProvider365DaysAgo; + private DateTimeProvider dateTimeProvider182DaysAgo; + private DateTimeProvider dateTimeProviderTwoDaysAgo; + private DateTimeProvider dateTimeProviderThreeDaysAgo; + + @Before + public void setup() { + dateTimeProvider365DaysAgo = new DateTimeProvider() { + int counter = 0; + + public LocalDateTime now() { + LocalDateTime date = new DateTestHelperImpl().getMultiDaysAgoOrNowBasedOnCounter(counter, + new long[] { 365 }); + counter++; + return date; + } + }; + dateTimeProvider182DaysAgo = new DateTimeProvider() { + int counter = 0; + + public LocalDateTime now() { + LocalDateTime date = new DateTestHelperImpl().getMultiDaysAgoOrNowBasedOnCounter(counter, + new long[] { 182 }); + counter++; + return date; + } + }; + dateTimeProviderTwoDaysAgo = new DateTimeProvider() { + int counter = 0; + + public LocalDateTime now() { + LocalDateTime date = new DateTestHelperImpl().getMultiDaysAgoOrNowBasedOnCounter(counter, + new long[] { 365, 182 }); + counter++; + return date; + } + }; + dateTimeProviderThreeDaysAgo = new DateTimeProvider() { + int counter = 0; + + public LocalDateTime now() { + LocalDateTime date = new DateTestHelperImpl().getMultiDaysAgoOrNowBasedOnCounter(counter, + new long[] { 365, 182, 91 }); + counter++; + return date; + } + }; + + } + + @Test // Test Account Positive Withdraw + public void testAccountPositiveWithdraw() throws BusinessException { + + Account account = new Account(AccountType.CHECKING, DateTimeProviderImpl.INSTANCE); + account.withdraw(200.0); + List transactions = account.getTransactions(); + assertEquals(1, transactions.size()); + Transaction transaction = transactions.get(0); + assertEquals(transaction.getAmount(), -200.0, Precision.DOUBLE_PRECISION); + } + + @Test // Test Zero Account Withdraw + public void testAccountZeroWithdraw() throws BusinessException { + + Account account = new Account(AccountType.CHECKING, DateTimeProviderImpl.INSTANCE); + try { + account.withdraw(0); + fail(); + } catch (InvalidTransactionException e) { + // expected + } + } + + @Test // Test Negative Account Withdraw + public void testAccountNegativeWithdraw() throws BusinessException { + + Account account = new Account(AccountType.CHECKING, DateTimeProviderImpl.INSTANCE); + try { + account.withdraw(-100.0); + fail(); + } catch (InvalidTransactionException e) { + // expected + } + } + + @Test // Test Account Positive Deposit + public void testAccountPositiveDeposit() throws BusinessException { + + Account account = new Account(AccountType.CHECKING, DateTimeProviderImpl.INSTANCE); + account.deposit(100.0); + List transactions = account.getTransactions(); + assertEquals(1, transactions.size()); + Transaction transaction = transactions.get(0); + assertEquals(transaction.getAmount(), 100.0, Precision.DOUBLE_PRECISION); + } + + @Test // Test Account Zero Deposit + public void testAccountZeroDeposit() throws BusinessException { + Account account = new Account(AccountType.CHECKING, DateTimeProviderImpl.INSTANCE); + + try { + account.deposit(0.0); + fail(); + } catch (InvalidTransactionException e) { + // expected + } + } + + @Test // Test Account Negative Deposit + public void testAccountNegativeDeposit() throws BusinessException { + Account account = new Account(AccountType.CHECKING, DateTimeProviderImpl.INSTANCE); + + try { + account.deposit(-1000.0); + fail(); + } catch (InvalidTransactionException e) { + // expected + } + } + + @Test // Test earned checking interest rate after 365 Days + public void testCheckingEarnedInterestRateAfter365Days() throws BusinessException { + + Account account = new Account(AccountType.CHECKING, dateTimeProvider365DaysAgo); + account.deposit(1500.0); + assertEquals(1.5, account.calculateInterestEarned(), Precision.DOUBLE_PRECISION); + } + + @Test // Test earned savings interest rate under 1000 after 365 Days + public void testSavingsEarnedInterestRateUnder1000After365Days() throws BusinessException { + Account savingAccount = new Account(AccountType.SAVINGS, dateTimeProvider365DaysAgo); + savingAccount.deposit(750.0); + assertEquals(0.75, savingAccount.calculateInterestEarned(), Precision.DOUBLE_PRECISION); + } + + @Test // Test earned savings interest rate over 1000 after 365 Days + public void testSavingsEarnedInterestRateOver1000After365Days() throws BusinessException { + Account savingAccount = new Account(AccountType.SAVINGS, dateTimeProvider365DaysAgo); + savingAccount.deposit(1250.0); + assertEquals(1.5, savingAccount.calculateInterestEarned(), Precision.DOUBLE_PRECISION); + } + + @Test // Test earned maxi savings interest rate under 1000 after 365 Days + public void testMaxiSavingsEarnedInterestRateUnder1000After365Days() throws BusinessException { + Account maxiSavingAccount = new Account(AccountType.MAXI_SAVINGS, dateTimeProvider365DaysAgo); + maxiSavingAccount.deposit(750.0); + assertEquals(15.0, maxiSavingAccount.calculateInterestEarned(), Precision.DOUBLE_PRECISION); + } + + @Test // Test earned savings interest rate under 2000 after 365 Days + public void testMaxiSavingsEarnedInterestRateUnder2000After365Days() throws BusinessException { + Account savingAccount = new Account(AccountType.MAXI_SAVINGS, dateTimeProvider365DaysAgo); + savingAccount.deposit(1700.0); + assertEquals(55.0, savingAccount.calculateInterestEarned(), Precision.DOUBLE_PRECISION); + } + + @Test // Test earned savings interest rate over 2000 after 365 Days + public void testMaxiSavingsEarnedInterestRateOver2000After365Days() throws BusinessException { + Account savingAccount = new Account(AccountType.MAXI_SAVINGS, dateTimeProvider365DaysAgo); + savingAccount.deposit(2500.0); + assertEquals(120.0, savingAccount.calculateInterestEarned(), Precision.DOUBLE_PRECISION); + } + + @Test // Test earned checking interest rate after 182 Days + public void testCheckingEarnedInterestRateAfter182Days() throws BusinessException { + + Account account = new Account(AccountType.CHECKING, dateTimeProvider182DaysAgo); + account.deposit(1500.0); + assertEquals(0.74775780066311976621199576727046, account.calculateInterestEarned(), Precision.DOUBLE_PRECISION); + } + + @Test // Test earned savings interest rate under 1000 after 182 Days + public void testSavingsEarnedInterestRateUnder1000After182Days() throws BusinessException { + Account savingAccount = new Account(AccountType.SAVINGS, dateTimeProvider182DaysAgo); + savingAccount.deposit(750.0); + assertEquals(0.37387890033155988310599788363523, savingAccount.calculateInterestEarned(), + Precision.DOUBLE_PRECISION); + } + + @Test // Test earned savings interest rate over 1000 after 182 Days + public void testSavingsEarnedInterestRateOver1000After182Days() throws BusinessException { + Account savingAccount = new Account(AccountType.SAVINGS, dateTimeProvider182DaysAgo); + savingAccount.deposit(1250.0); + assertEquals(0.74769539483045061054276355856288, savingAccount.calculateInterestEarned(), + Precision.DOUBLE_PRECISION); + } + + @Test // Test earned maxi savings interest rate under 1000 after 182 Days + public void testMaxiSavingsEarnedInterestRateUnder1000After182Days() throws BusinessException { + Account maxiSavingAccount = new Account(AccountType.MAXI_SAVINGS, dateTimeProvider182DaysAgo); + maxiSavingAccount.deposit(750.0); + assertEquals(7.4423230463801110871215505228452, maxiSavingAccount.calculateInterestEarned(), + Precision.DOUBLE_PRECISION); + } + + @Test // Test earned savings interest rate under 2000 after 182 Days + public void testMaxiSavingsEarnedInterestRateUnder2000After182Days() throws BusinessException { + Account savingAccount = new Account(AccountType.MAXI_SAVINGS, dateTimeProvider182DaysAgo); + savingAccount.deposit(1700.0); + assertEquals(27.161712164095583757370851084434, savingAccount.calculateInterestEarned(), + Precision.DOUBLE_PRECISION); + } + + @Test // Test earned savings interest rate over 2000 after 182Days + public void testMaxiSavingsEarnedInterestRateOver2000After182Days() throws BusinessException { + Account savingAccount = new Account(AccountType.MAXI_SAVINGS, dateTimeProvider182DaysAgo); + savingAccount.deposit(2500.0); + assertEquals(58.885651243884128036848612945029, savingAccount.calculateInterestEarned(), + Precision.DOUBLE_PRECISION); + } + + @Test // Test earned checking interest rate after multiple Days + public void testCheckingEarnedInterestRateAfterMultiDays() throws BusinessException { + + Account account = new Account(AccountType.CHECKING, dateTimeProviderTwoDaysAgo); + account.deposit(1500.0); + account.deposit(1500.0); + assertEquals(2.2477578006631197662119957411426, account.calculateInterestEarned(), Precision.DOUBLE_PRECISION); + } + + @Test // Test earned savings interest rate after multiple Days + public void testSavingsEarnedInterestRateAfterMultipleDays() throws BusinessException { + Account savingAccount = new Account(AccountType.SAVINGS, dateTimeProviderTwoDaysAgo); + savingAccount.deposit(750.0); + savingAccount.deposit(500.0); + assertEquals(1.1240038055589322706501103217248, savingAccount.calculateInterestEarned(), + Precision.DOUBLE_PRECISION); + } + + @Test // Test earned savings interest rate after multiple Days + public void testMaxiSavingsEarnedInterestRateAlfterMultipleDays() throws BusinessException { + Account savingAccount = new Account(AccountType.MAXI_SAVINGS, dateTimeProviderThreeDaysAgo); + savingAccount.deposit(2500.0); + savingAccount.withdraw(800.0); + savingAccount.withdraw(950.0); + assertEquals(77.530754872654788414707002411118, savingAccount.calculateInterestEarned(), + Precision.DOUBLE_PRECISION); + + } + +} diff --git a/src/test/java/com/abc/domain/objects/AmountPerPeriodTest.java b/src/test/java/com/abc/domain/objects/AmountPerPeriodTest.java new file mode 100644 index 00000000..add2e1ee --- /dev/null +++ b/src/test/java/com/abc/domain/objects/AmountPerPeriodTest.java @@ -0,0 +1,59 @@ +package com.abc.domain.objects; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import org.junit.Test; + +import com.abc.domain.constants.Precision; +import com.abc.domain.exceptions.InvalidPeriodException; + +/** + * Class to test amount per period domain object + * + * @author Ihor + * + */ +public class AmountPerPeriodTest { + + // Test positive amount and period. It should succeed + @Test + public void testPositiveAmountAndPeriod() throws InvalidPeriodException { + AmountPerPeriod amountPerPeriod = new AmountPerPeriod(5.0, 3); + assertEquals(amountPerPeriod.getAmount(), 5.0, Precision.DOUBLE_PRECISION); + assertEquals(amountPerPeriod.getDays(), 3); + } + + // Test negative amount and period. It should fail + @Test + public void testNegativeAmountAndPeriod() throws InvalidPeriodException { + try{ + new AmountPerPeriod(-5.0, -3); + fail(); + } + catch(InvalidPeriodException e){ + //Excepted + } + } + + // Test negative amount and positive period. It should succeed + @Test + public void testNegativeAmountAndPositivePeriod() throws InvalidPeriodException { + AmountPerPeriod amountPerPeriod = new AmountPerPeriod(-5.0, 3); + assertEquals(amountPerPeriod.getAmount(), -5.0, Precision.DOUBLE_PRECISION); + assertEquals(amountPerPeriod.getDays(), 3); + } + + // Test positive amount and negative period. It should fail + @Test + public void testPositiveAmountAndNegativePeriod() throws InvalidPeriodException { + try{ + new AmountPerPeriod(5.0, -3); + fail(); + } + catch(InvalidPeriodException e){ + //Excepted + } + } + +} diff --git a/src/test/java/com/abc/domain/objects/BankTest.java b/src/test/java/com/abc/domain/objects/BankTest.java new file mode 100644 index 00000000..fd030467 --- /dev/null +++ b/src/test/java/com/abc/domain/objects/BankTest.java @@ -0,0 +1,123 @@ +package com.abc.domain.objects; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.time.LocalDateTime; + +import org.junit.Before; +import org.junit.Test; + +import com.abc.domain.constants.AccountType; +import com.abc.domain.constants.Precision; +import com.abc.domain.exceptions.BusinessException; +import com.abc.domain.exceptions.InvalidCustomerException; +import com.abc.domain.util.impl.DateTestHelperImpl; +import com.abc.util.DateTimeProvider; + +/** + * Class to test bank domain object. + * @author Ihor + * + */ +public class BankTest { + private DateTimeProvider dateTimeProvider365DaysAgo; + + @Before + public void setup(){ + dateTimeProvider365DaysAgo=new DateTimeProvider(){ + int counter=0; + public LocalDateTime now() { + LocalDateTime date=new DateTestHelperImpl().getMultiDaysAgoOrNowBasedOnCounter(counter,new long[] {365}); + counter++; + return date; + } + }; + } + //Test that customer summaery is generate correctly + @Test + public void testCustomerSummary() throws BusinessException { + Bank bank = new Bank(); + Customer john = new Customer("John"); + john.openAccount(new Account(AccountType.CHECKING,dateTimeProvider365DaysAgo)); + bank.addCustomer(john); + + assertEquals("Customer Summary\n - John (1 account)", bank.generateCustomerSummary()); + } + + //Test that opening and depositing to checking account works correctly + @Test + public void testCheckingAccount() throws BusinessException { + Bank bank = new Bank(); + Account checkingAccount = new Account(AccountType.CHECKING,dateTimeProvider365DaysAgo); + Customer bill = new Customer("Bill").openAccount(checkingAccount); + bank.addCustomer(bill); + + checkingAccount.deposit(100.0); + + assertEquals(0.1, bank.totalInterestPaid(), Precision.DOUBLE_PRECISION); + } + + //Test that opening and depositing to savings account works correctly + @Test + public void testSavingsAccount() throws BusinessException { + Bank bank = new Bank(); + Account savingsAccount = new Account(AccountType.SAVINGS,dateTimeProvider365DaysAgo); + bank.addCustomer(new Customer("Bill").openAccount(savingsAccount)); + + savingsAccount.deposit(1500.0); + + assertEquals(2.0, bank.totalInterestPaid(), Precision.DOUBLE_PRECISION); + } + + //Test that opening and depositing to maxi savings account works correctly + @Test + public void testMaxiSavingsAccount() throws BusinessException { + Bank bank = new Bank(); + Account maxiSavingsAccount = new Account(AccountType.MAXI_SAVINGS,dateTimeProvider365DaysAgo); + bank.addCustomer(new Customer("Bill").openAccount(maxiSavingsAccount)); + + maxiSavingsAccount.deposit(3000.0); + + assertEquals(170.0, bank.totalInterestPaid(), Precision.DOUBLE_PRECISION); + } + + @Test + //Test that retrieval of first customer name gives error if no customers yet + public void testGetFirstCustomerNameEmpty(){ + Bank bank = new Bank(); + try{ + bank.getFirstCustomerName(); + fail(); + } + catch(InvalidCustomerException e){ + //expected + } + } + + //Test that retrieval of first customer name succeeds if there is one customer + @Test + public void testGetFirstCustomerNameOneCustomer() throws BusinessException{ + Bank bank = new Bank(); + Account checkingAccount = new Account(AccountType.CHECKING,dateTimeProvider365DaysAgo); + Customer george = new Customer("George").openAccount(checkingAccount); + bank.addCustomer(george); + assertEquals("George",bank.getFirstCustomerName()); + } + + //Test that first customer remains the same when second customer is added + @Test + public void testGetFirstCustomerNameRemainsTheSameWhenSecond() throws BusinessException{ + Bank bank = new Bank(); + Account georgeCheckingAccount = new Account(AccountType.CHECKING,dateTimeProvider365DaysAgo); + Customer george = new Customer("George").openAccount(georgeCheckingAccount); + bank.addCustomer(george); + + Account karenSavingsAccount = new Account(AccountType.SAVINGS,dateTimeProvider365DaysAgo); + Customer karen = new Customer("Karen").openAccount(karenSavingsAccount); + bank.addCustomer(karen); + + assertEquals("George",bank.getFirstCustomerName()); + } + +} diff --git a/src/test/java/com/abc/domain/objects/CustomerTest.java b/src/test/java/com/abc/domain/objects/CustomerTest.java new file mode 100644 index 00000000..23916e15 --- /dev/null +++ b/src/test/java/com/abc/domain/objects/CustomerTest.java @@ -0,0 +1,215 @@ +package com.abc.domain.objects; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.time.LocalDateTime; + +import org.junit.Test; + +import com.abc.domain.constants.AccountType; +import com.abc.domain.constants.Precision; +import com.abc.domain.exceptions.BusinessException; +import com.abc.domain.exceptions.InvalidTransactionException; +import com.abc.util.DateTimeProvider; +import com.abc.util.impl.DateTimeProviderImpl; +/** + * Class to test customer domain object + * @author Ihor + * + */ +public class CustomerTest { + + //Test customer statement generation + @Test + public void testStatementGeneration() throws BusinessException{ + + Account checkingAccount = new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE); + Account savingsAccount = new Account(AccountType.SAVINGS,DateTimeProviderImpl.INSTANCE); + + Customer henry = new Customer("Henry").openAccount(checkingAccount).openAccount(savingsAccount); + + checkingAccount.deposit(100.0); + savingsAccount.deposit(4000.0); + savingsAccount.withdraw(200.0); + + assertEquals("Statement for Henry\n" + + "\n" + + "Checking Account\n" + + " deposit $100.00\n" + + "Total $100.00\n" + + "\n" + + "Savings Account\n" + + " deposit $4,000.00\n" + + " withdrawal $200.00\n" + + "Total $3,800.00\n" + + "\n" + + "Total In All Accounts $3,900.00", henry.getStatement()); + } + + //Test opening of one savings account + @Test + public void testOneAccount(){ + Customer oscar = new Customer("Oscar").openAccount(new Account(AccountType.SAVINGS,DateTimeProviderImpl.INSTANCE)); + assertEquals(1, oscar.getNumberOfAccounts()); + } + + //Test opening of savings and checking accounts + @Test + public void testTwoAccount(){ + Customer oscar = new Customer("Oscar") + .openAccount(new Account(AccountType.SAVINGS,DateTimeProviderImpl.INSTANCE)); + oscar.openAccount(new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE)); + assertEquals(2, oscar.getNumberOfAccounts()); + } + + //Test opening of savings, checking and maxi savings accounts + @Test + public void testThreeAcounts() { + Customer oscar = new Customer("Oscar") + .openAccount(new Account(AccountType.SAVINGS,DateTimeProviderImpl.INSTANCE)); + oscar.openAccount(new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE)); + oscar.openAccount(new Account(AccountType.MAXI_SAVINGS,DateTimeProviderImpl.INSTANCE)); + assertEquals(3, oscar.getNumberOfAccounts()); + } + + @Test + //Test transfer complete amount from checking account to savings + public void testAccountTransferCompleteAmount() throws BusinessException{ + Account checkingAccount = new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE); + Account savingsAccount = new Account(AccountType.SAVINGS,DateTimeProviderImpl.INSTANCE); + + Customer william = new Customer("William").openAccount(checkingAccount).openAccount(savingsAccount); + + checkingAccount.deposit(1000.0); + savingsAccount.deposit(2500.0); + + william.transfer(checkingAccount,savingsAccount,1000.0); + assertEquals(checkingAccount.sumTransactions(),0.0,Precision.DOUBLE_PRECISION); + assertEquals(savingsAccount.sumTransactions(),3500.0,Precision.DOUBLE_PRECISION); + } + + //Test attempt to transfer over amount in checking to savings + @Test + public void testAccountTransferOverAmountInAccount() throws BusinessException{ + Account checkingAccount = new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE); + Account savingsAccount = new Account(AccountType.SAVINGS,DateTimeProviderImpl.INSTANCE); + + Customer william = new Customer("William").openAccount(checkingAccount).openAccount(savingsAccount); + + checkingAccount.deposit(250.0); + savingsAccount.deposit(1500.0); + try{ + william.transfer(checkingAccount,savingsAccount,300.0); + fail(); + } + catch (InvalidTransactionException e){ + //Expected + } + } + + //Test attempt to transfer negative in checking to savings + @Test + public void testAccountTransferNegativeAmountAccount() throws BusinessException{ + Account checkingAccount = new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE); + Account savingsAccount = new Account(AccountType.SAVINGS,DateTimeProviderImpl.INSTANCE); + + Customer william = new Customer("William").openAccount(checkingAccount).openAccount(savingsAccount); + + checkingAccount.deposit(250.0); + savingsAccount.deposit(1500.0); + try{ + william.transfer(checkingAccount,savingsAccount,-10.0); + fail(); + } + catch (InvalidTransactionException e){ + //Expected + } + } + + //Test attempt to transfer from maxi savings to itself + @Test + public void testAccountTransferAccountToItself() throws BusinessException{ + Account maxiSavingAccount = new Account(AccountType.MAXI_SAVINGS,DateTimeProviderImpl.INSTANCE); + + Customer william = new Customer("William").openAccount(maxiSavingAccount); + + maxiSavingAccount.deposit(2000.0); + try{ + william.transfer(maxiSavingAccount,maxiSavingAccount,10.0); + fail(); + } + catch (InvalidTransactionException e){ + //Expected + } + } + + //Test attempt to transfer from savings to checking when deposit fails + @Test + public void testAccountTransferWhenDepositFails() throws BusinessException{ + Account savingsAccount = new Account(AccountType.SAVINGS,DateTimeProviderImpl.INSTANCE); + DateTimeProvider dt=new DateTimeProvider(){ + public LocalDateTime now() { + throw new IllegalArgumentException("Test error"); + } + }; + + Account checkingAccount = new Account(AccountType.CHECKING,dt); + Customer mary = new Customer("Mary").openAccount(checkingAccount).openAccount(savingsAccount); + + savingsAccount.deposit(2000.0); + try{ + mary.transfer(savingsAccount,checkingAccount,1.0); + fail(); + } + catch (IllegalArgumentException e){ + //Expected + } + assertEquals(2000.0,savingsAccount.sumTransactions(),Precision.DOUBLE_PRECISION); + assertEquals(0.0,checkingAccount.sumTransactions(),Precision.DOUBLE_PRECISION); + + } + + //Test attempt to transfer from account not owned by customer + @Test + public void testAccountTransferWhenFromAccountIsNotOwnedByCustomer() throws BusinessException{ + Account bobSavingsAccount = new Account(AccountType.SAVINGS,DateTimeProviderImpl.INSTANCE); + Account bobMaxiSavingsAccount = new Account(AccountType.MAXI_SAVINGS,DateTimeProviderImpl.INSTANCE); + Account aliceCheckingAccount = new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE); + Customer bob = new Customer("Bob").openAccount(bobSavingsAccount); + bobSavingsAccount.deposit(100.0); + aliceCheckingAccount.deposit(2000.0); + new Customer("Alice").openAccount(aliceCheckingAccount); + try{ + bob.transfer(aliceCheckingAccount, bobMaxiSavingsAccount, 1000.0); + fail(); + } + catch (InvalidTransactionException e){ + //Expected + } + assertEquals(100.0,bobSavingsAccount.sumTransactions(),Precision.DOUBLE_PRECISION); + assertEquals(2000.0,aliceCheckingAccount.sumTransactions(),Precision.DOUBLE_PRECISION); + } + + //Test attempt to transfer to account not owned by customer + @Test + public void testAccountTransferWhenToAccountIsNotOwnedByCustomer() throws BusinessException{ + Account bobSavingsAccount = new Account(AccountType.SAVINGS,DateTimeProviderImpl.INSTANCE); + Account bobMaxiSavingsAccount = new Account(AccountType.MAXI_SAVINGS,DateTimeProviderImpl.INSTANCE); + Account aliceCheckingAccount = new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE); + Customer bob = new Customer("Bob").openAccount(bobSavingsAccount); + bobSavingsAccount.deposit(100.0); + aliceCheckingAccount.deposit(2000.0); + new Customer("Alice").openAccount(aliceCheckingAccount); + try{ + bob.transfer(bobMaxiSavingsAccount, aliceCheckingAccount, 50.0); + fail(); + } + catch (InvalidTransactionException e){ + //Expected + } + assertEquals(100.0,bobSavingsAccount.sumTransactions(),Precision.DOUBLE_PRECISION); + assertEquals(2000.0,aliceCheckingAccount.sumTransactions(),Precision.DOUBLE_PRECISION); + } + +} diff --git a/src/test/java/com/abc/domain/objects/TransactionTest.java b/src/test/java/com/abc/domain/objects/TransactionTest.java new file mode 100644 index 00000000..2e2135d2 --- /dev/null +++ b/src/test/java/com/abc/domain/objects/TransactionTest.java @@ -0,0 +1,48 @@ +package com.abc.domain.objects; + +import static org.junit.Assert.assertEquals; + +import java.time.LocalDateTime; + +import org.junit.Test; + +import com.abc.domain.constants.Precision; +import com.abc.util.DateTimeProvider; + +/** + * Class to test transaction domain object + * + * @author Ihor + * + */ +public class TransactionTest { + + // Test positive amount transaction + @Test + public void testPositiveAmountTransaction() { + final LocalDateTime date = LocalDateTime.now(); + DateTimeProvider dt = new DateTimeProvider() { + public LocalDateTime now() { + return date; + } + }; + Transaction t = new Transaction(5.0, dt); + assertEquals(t.getAmount(), 5.0, Precision.DOUBLE_PRECISION); + assertEquals(t.getTransactionDate(), date); + } + + // Test negative amount transaction + @Test + public void testNegativeAmountTransaction() { + final LocalDateTime date = LocalDateTime.now(); + DateTimeProvider dt = new DateTimeProvider() { + public LocalDateTime now() { + return date; + } + }; + Transaction t = new Transaction(-3.0, dt); + assertEquals(t.getAmount(), -3.0, Precision.DOUBLE_PRECISION); + assertEquals(t.getTransactionDate(), date); + } + +} diff --git a/src/test/java/com/abc/domain/util/impl/DateTestHelperImpl.java b/src/test/java/com/abc/domain/util/impl/DateTestHelperImpl.java new file mode 100644 index 00000000..67d0961f --- /dev/null +++ b/src/test/java/com/abc/domain/util/impl/DateTestHelperImpl.java @@ -0,0 +1,22 @@ +package com.abc.domain.util.impl; + +import java.time.LocalDateTime; + +public class DateTestHelperImpl { + public LocalDateTime getDaysAgo(long days){ + LocalDateTime localDateTime=LocalDateTime.now(); + localDateTime=localDateTime.minusDays(days); + localDateTime=localDateTime.minusMinutes(1);// to make sure it is days + return localDateTime; + } + //Get days ago for matching counter or now for remaining calls + public LocalDateTime getMultiDaysAgoOrNowBasedOnCounter(int counter,long[] days) { + for (int i=0;i