From 692665e4dadd245df0278a6ba1814815c25dca84 Mon Sep 17 00:00:00 2001 From: Ihor Yanchak Date: Sat, 3 Jun 2017 09:52:52 -0400 Subject: [PATCH 1/7] Commit refactored code with following changes: 1. Restructured code to use namespaces better 2. Fixed ignored Test cases and renamed other according to Java spec 3. Improved code coverage 4. Fixed code (missing synchronization on date provider, incorrect get first customer name function and etc) 5. Introduced enum for account type 6. Introduced BusinessException hierarchy and added boundary scenario test cases for them. 7. Added new feature and test coverage to transfer between two accounts for customer. --- src/main/java/com/abc/Account.java | 73 --------- src/main/java/com/abc/DateProvider.java | 18 --- src/main/java/com/abc/Transaction.java | 16 -- .../java/com/abc/domain/objects/Account.java | 89 +++++++++++ .../com/abc/{ => domain/objects}/Bank.java | 27 ++-- .../abc/{ => domain/objects}/Customer.java | 45 ++++-- .../com/abc/domain/objects/Transaction.java | 25 ++++ .../com/abc/util/impl/DateProviderImpl.java | 12 ++ src/test/java/com/abc/BankTest.java | 54 ------- src/test/java/com/abc/CustomerTest.java | 57 -------- src/test/java/com/abc/TransactionTest.java | 13 -- .../java/com/abc/domain/objects/BankTest.java | 99 +++++++++++++ .../com/abc/domain/objects/CustomerTest.java | 138 ++++++++++++++++++ .../abc/domain/objects/TransactionTest.java | 26 ++++ 14 files changed, 440 insertions(+), 252 deletions(-) delete mode 100644 src/main/java/com/abc/Account.java delete mode 100644 src/main/java/com/abc/DateProvider.java delete mode 100644 src/main/java/com/abc/Transaction.java create mode 100644 src/main/java/com/abc/domain/objects/Account.java rename src/main/java/com/abc/{ => domain/objects}/Bank.java (57%) rename src/main/java/com/abc/{ => domain/objects}/Customer.java (56%) create mode 100644 src/main/java/com/abc/domain/objects/Transaction.java create mode 100644 src/main/java/com/abc/util/impl/DateProviderImpl.java delete mode 100644 src/test/java/com/abc/BankTest.java delete mode 100644 src/test/java/com/abc/CustomerTest.java delete mode 100644 src/test/java/com/abc/TransactionTest.java create mode 100644 src/test/java/com/abc/domain/objects/BankTest.java create mode 100644 src/test/java/com/abc/domain/objects/CustomerTest.java create mode 100644 src/test/java/com/abc/domain/objects/TransactionTest.java 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/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/objects/Account.java b/src/main/java/com/abc/domain/objects/Account.java new file mode 100644 index 00000000..748cca63 --- /dev/null +++ b/src/main/java/com/abc/domain/objects/Account.java @@ -0,0 +1,89 @@ +package com.abc.domain.objects; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import com.abc.domain.constants.AccountType; +import com.abc.domain.exceptions.BusinessException; +import com.abc.domain.exceptions.InvalidTransactionException; +import com.abc.util.DateProvider; +import com.abc.util.impl.DateProviderImpl; + +public class Account { + + private static final int DAY = 24*3600*1000; + private final AccountType accountType; + private final List transactions; + private final DateProvider dateProvider; + + public Account(AccountType accountType,DateProvider dateProvider) { + this.accountType = accountType; + this.transactions = new ArrayList(); + this.dateProvider=dateProvider; + } + + 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, dateProvider)); + } + } + + 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, dateProvider)); + } + } + public boolean lastTransactionIn(int days) { + long startOfDay = (dateProvider.now().getTime()/DAY)*DAY; + Date daysAgo = new Date(startOfDay-days*DAY); + for (Transaction t:transactions){ + if (t.getTransactionDate().after(daysAgo)){ + return true; + } + } + return false; + } + 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 MAXI_SAVINGS: + if (amount <= 1000){ + return amount * 0.02; + } + if (amount <= 2000){ + return 20 + (amount - 1000) * 0.05; + } + return 70 + (amount - 2000) * 0.1; + case CHECKING: + default: + return amount * 0.001; + } + } + + public double sumTransactions() { + double amount = 0.0; + for (Transaction t : transactions) + amount += t.getAmount(); + return amount; + } + + public AccountType getAccountType() { + return accountType; + } + + public List getTransactions() { + return transactions; + } +} diff --git a/src/main/java/com/abc/Bank.java b/src/main/java/com/abc/domain/objects/Bank.java similarity index 57% rename from src/main/java/com/abc/Bank.java rename to src/main/java/com/abc/domain/objects/Bank.java index 5dd535bd..2917b91c 100644 --- a/src/main/java/com/abc/Bank.java +++ b/src/main/java/com/abc/domain/objects/Bank.java @@ -1,16 +1,22 @@ -package com.abc; +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; + public class Bank { - private List customers; + private final List customers; public Bank() { customers = new ArrayList(); } - public void addCustomer(Customer customer) { + public void addCustomer(Customer customer) throws BusinessException { + if (customer==null){ + throw new InvalidCustomerException("customer is null"); + } customers.add(customer); } @@ -27,20 +33,17 @@ private String format(int number, String word) { return number + " " + (number == 1 ? word : word + "s"); } - public double totalInterestPaid() { + public double totalInterestPaid() throws BusinessException { 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"; - } + public String getFirstCustomerName() throws InvalidCustomerException { + if (customers.size()>0){ + return customers.get(0).getName(); + } + throw new InvalidCustomerException("No customers yet"); } } diff --git a/src/main/java/com/abc/Customer.java b/src/main/java/com/abc/domain/objects/Customer.java similarity index 56% rename from src/main/java/com/abc/Customer.java rename to src/main/java/com/abc/domain/objects/Customer.java index 31571685..25ba95fd 100644 --- a/src/main/java/com/abc/Customer.java +++ b/src/main/java/com/abc/domain/objects/Customer.java @@ -1,9 +1,13 @@ -package com.abc; +package com.abc.domain.objects; + +import static java.lang.Math.abs; import java.util.ArrayList; import java.util.List; -import static java.lang.Math.abs; +import com.abc.domain.constants.TransferState; +import com.abc.domain.exceptions.BusinessException; +import com.abc.domain.exceptions.InvalidTransactionException; public class Customer { private String name; @@ -27,7 +31,7 @@ public int getNumberOfAccounts() { return accounts.size(); } - public double totalInterestEarned() { + public double totalInterestEarned() throws BusinessException { double total = 0; for (Account a : accounts) total += a.interestEarned(); @@ -51,22 +55,22 @@ private String statementForAccount(Account a) { //Translate to pretty account type switch(a.getAccountType()){ - case Account.CHECKING: + case CHECKING: s += "Checking Account\n"; break; - case Account.SAVINGS: + case SAVINGS: s += "Savings Account\n"; break; - case Account.MAXI_SAVINGS: + case 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; + for (Transaction t : a.getTransactions()) { + s += " " + (t.getAmount() < 0 ? "withdrawal" : "deposit") + " " + toDollars(t.getAmount()) + "\n"; + total += t.getAmount(); } s += "Total " + toDollars(total); return s; @@ -75,4 +79,27 @@ private String statementForAccount(Account a) { private String toDollars(double d){ return String.format("$%,.2f", abs(d)); } + + public void transfer(Account accountFrom, Account accountTo, double amount) throws BusinessException { + if (accountFrom.equals(accountTo)){ + throw new InvalidTransactionException("Cannot transfer to itself"); + } + if (accountFrom.sumTransactions() Date: Sat, 3 Jun 2017 09:59:33 -0400 Subject: [PATCH 2/7] Follow-up commit with new files and some cleanup. --- .../com/abc/domain/constants/AccountType.java | 7 + .../abc/domain/constants/TransferState.java | 7 + .../domain/exceptions/BusinessException.java | 8 ++ .../exceptions/InvalidCustomerException.java | 8 ++ .../InvalidTransactionException.java | 9 ++ .../java/com/abc/domain/objects/Account.java | 12 -- src/main/java/com/abc/util/DateProvider.java | 7 + .../com/abc/domain/constants/Precision.java | 5 + .../com/abc/domain/objects/AccountTest.java | 132 ++++++++++++++++++ 9 files changed, 183 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/abc/domain/constants/AccountType.java create mode 100644 src/main/java/com/abc/domain/constants/TransferState.java create mode 100644 src/main/java/com/abc/domain/exceptions/BusinessException.java create mode 100644 src/main/java/com/abc/domain/exceptions/InvalidCustomerException.java create mode 100644 src/main/java/com/abc/domain/exceptions/InvalidTransactionException.java create mode 100644 src/main/java/com/abc/util/DateProvider.java create mode 100644 src/test/java/com/abc/domain/constants/Precision.java create mode 100644 src/test/java/com/abc/domain/objects/AccountTest.java 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..20650ea7 --- /dev/null +++ b/src/main/java/com/abc/domain/constants/AccountType.java @@ -0,0 +1,7 @@ +package com.abc.domain.constants; + +public enum AccountType { + CHECKING, + SAVINGS, + MAXI_SAVINGS +} 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..98d86df1 --- /dev/null +++ b/src/main/java/com/abc/domain/constants/TransferState.java @@ -0,0 +1,7 @@ +package com.abc.domain.constants; + +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..85130e6d --- /dev/null +++ b/src/main/java/com/abc/domain/exceptions/BusinessException.java @@ -0,0 +1,8 @@ +package com.abc.domain.exceptions; + +public class BusinessException extends Exception { + private static final long serialVersionUID = 7156699628264008860L; + 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..e32334be --- /dev/null +++ b/src/main/java/com/abc/domain/exceptions/InvalidCustomerException.java @@ -0,0 +1,8 @@ +package com.abc.domain.exceptions; + +public class InvalidCustomerException extends BusinessException { + private static final long serialVersionUID = 1827978046616513675L; + public InvalidCustomerException(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..cc46c985 --- /dev/null +++ b/src/main/java/com/abc/domain/exceptions/InvalidTransactionException.java @@ -0,0 +1,9 @@ +package com.abc.domain.exceptions; + +public class InvalidTransactionException extends BusinessException { + private static final long serialVersionUID = 3796741547535023801L; + + 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 index 748cca63..8f60d1ba 100644 --- a/src/main/java/com/abc/domain/objects/Account.java +++ b/src/main/java/com/abc/domain/objects/Account.java @@ -8,11 +8,9 @@ import com.abc.domain.exceptions.BusinessException; import com.abc.domain.exceptions.InvalidTransactionException; import com.abc.util.DateProvider; -import com.abc.util.impl.DateProviderImpl; public class Account { - private static final int DAY = 24*3600*1000; private final AccountType accountType; private final List transactions; private final DateProvider dateProvider; @@ -38,16 +36,6 @@ public void withdraw(double amount) throws BusinessException { transactions.add(new Transaction(-amount, dateProvider)); } } - public boolean lastTransactionIn(int days) { - long startOfDay = (dateProvider.now().getTime()/DAY)*DAY; - Date daysAgo = new Date(startOfDay-days*DAY); - for (Transaction t:transactions){ - if (t.getTransactionDate().after(daysAgo)){ - return true; - } - } - return false; - } public double interestEarned() { double amount = sumTransactions(); switch (accountType) { diff --git a/src/main/java/com/abc/util/DateProvider.java b/src/main/java/com/abc/util/DateProvider.java new file mode 100644 index 00000000..96217ec9 --- /dev/null +++ b/src/main/java/com/abc/util/DateProvider.java @@ -0,0 +1,7 @@ +package com.abc.util; + +import java.util.Date; + +public interface DateProvider { + Date now(); +} 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..91b26174 --- /dev/null +++ b/src/test/java/com/abc/domain/constants/Precision.java @@ -0,0 +1,5 @@ +package com.abc.domain.constants; + +public class Precision { + public static final double DOUBLE_PRECISION=1e-15; +} 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..2f0b5090 --- /dev/null +++ b/src/test/java/com/abc/domain/objects/AccountTest.java @@ -0,0 +1,132 @@ +package com.abc.domain.objects; + +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.objects.Account; +import com.abc.util.impl.DateProviderImpl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.List; + +public class AccountTest { + + @Test // Test Account Positive Withdraw + public void testAccountPositiveWithdraw() throws BusinessException { + + Account account = new Account(AccountType.CHECKING,DateProviderImpl.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,DateProviderImpl.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,DateProviderImpl.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,DateProviderImpl.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,DateProviderImpl.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,DateProviderImpl.INSTANCE); + + try { + account.deposit(-1000.0); + fail(); + } catch (InvalidTransactionException e) { + // expected + } + } + + @Test //Test earned checking interest rate + public void testCheckingEarnedInterestRate() throws BusinessException { + Account account = new Account(AccountType.CHECKING,DateProviderImpl.INSTANCE); + account.deposit(1500.0); + assertEquals(1.5,account.interestEarned(),Precision.DOUBLE_PRECISION); + } + + @Test //Test earned savings interest rate under 1000 + public void testSavingsEarnedInterestRateUnder1000() throws BusinessException { + Account savingAccount = new Account(AccountType.SAVINGS,DateProviderImpl.INSTANCE); + savingAccount.deposit(750.0); + assertEquals(0.75,savingAccount.interestEarned(),Precision.DOUBLE_PRECISION); + } + + @Test //Test earned savings interest rate over 1000 + public void testSavingsEarnedInterestRateOver1000() throws BusinessException { + Account savingAccount = new Account(AccountType.SAVINGS,DateProviderImpl.INSTANCE); + savingAccount.deposit(1250.0); + assertEquals(1.5,savingAccount.interestEarned(),Precision.DOUBLE_PRECISION); + } + + + @Test //Test earned maxi savings interest rate under 1000 + public void testMaxiSavingsEarnedInterestRateUnder1000() throws BusinessException { + Account maxiSavingAccount = new Account(AccountType.MAXI_SAVINGS,DateProviderImpl.INSTANCE); + maxiSavingAccount.deposit(750.0); + assertEquals(15.0,maxiSavingAccount.interestEarned(),Precision.DOUBLE_PRECISION); + } + + @Test //Test earned savings interest rate under 2000 + public void testMaxiSavingsEarnedInterestRateUnder2000() throws BusinessException { + Account savingAccount = new Account(AccountType.MAXI_SAVINGS,DateProviderImpl.INSTANCE); + savingAccount.deposit(1700.0); + assertEquals(55.0,savingAccount .interestEarned(),Precision.DOUBLE_PRECISION); + } + + @Test //Test earned savings interest rate over 2000 + public void testMaxiSavingsEarnedInterestRateOver2000() throws BusinessException { + Account savingAccount = new Account(AccountType.MAXI_SAVINGS,DateProviderImpl.INSTANCE); + savingAccount.deposit(2500.0); + assertEquals(120.0,savingAccount .interestEarned(),Precision.DOUBLE_PRECISION); + } + +} From 4cf4ed740bceedf5d69a5eb2cd4f4a2cc6732ef8 Mon Sep 17 00:00:00 2001 From: Ihor Yanchak Date: Sat, 3 Jun 2017 19:34:10 -0400 Subject: [PATCH 3/7] Following changes were done: 1. Added feature to accrue daily based on compound rate 2. Added unit tests to cover different scenarios for above feature 3. Some additional clean up and comments indication to accrue on 365 days basis --- .../domain/constants/DailyCompoundRate.java | 27 +++ .../java/com/abc/domain/objects/Account.java | 96 ++++++-- .../abc/domain/objects/AmountPerPeriod.java | 21 ++ .../java/com/abc/domain/objects/Bank.java | 5 + .../java/com/abc/domain/objects/Customer.java | 16 +- .../com/abc/domain/objects/Transaction.java | 13 +- src/main/java/com/abc/util/DateProvider.java | 7 - .../java/com/abc/util/DateTimeProvider.java | 7 + .../com/abc/util/impl/DateProviderImpl.java | 12 - .../abc/util/impl/DateTimeProviderImpl.java | 12 + .../com/abc/domain/constants/Precision.java | 4 +- .../com/abc/domain/objects/AccountTest.java | 205 ++++++++++++++---- .../java/com/abc/domain/objects/BankTest.java | 43 ++-- .../com/abc/domain/objects/CustomerTest.java | 110 ++++++++-- .../abc/domain/objects/TransactionTest.java | 17 +- .../domain/util/impl/DateTestHelperImpl.java | 22 ++ 16 files changed, 483 insertions(+), 134 deletions(-) create mode 100644 src/main/java/com/abc/domain/constants/DailyCompoundRate.java create mode 100644 src/main/java/com/abc/domain/objects/AmountPerPeriod.java delete mode 100644 src/main/java/com/abc/util/DateProvider.java create mode 100644 src/main/java/com/abc/util/DateTimeProvider.java delete mode 100644 src/main/java/com/abc/util/impl/DateProviderImpl.java create mode 100644 src/main/java/com/abc/util/impl/DateTimeProviderImpl.java create mode 100644 src/test/java/com/abc/domain/util/impl/DateTestHelperImpl.java 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..0a88b042 --- /dev/null +++ b/src/main/java/com/abc/domain/constants/DailyCompoundRate.java @@ -0,0 +1,27 @@ +package com.abc.domain.constants; +/** + * @author Ihor + * 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 + */ +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/objects/Account.java b/src/main/java/com/abc/domain/objects/Account.java index 8f60d1ba..7a9cd26e 100644 --- a/src/main/java/com/abc/domain/objects/Account.java +++ b/src/main/java/com/abc/domain/objects/Account.java @@ -1,31 +1,34 @@ package com.abc.domain.objects; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; -import java.util.Date; 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.DateProvider; +import com.abc.util.DateTimeProvider; +import com.abc.util.impl.DateTimeProviderImpl; public class Account { private final AccountType accountType; private final List transactions; - private final DateProvider dateProvider; + private final DateTimeProvider dateTimeProvider; - public Account(AccountType accountType,DateProvider dateProvider) { + public Account(AccountType accountType, DateTimeProvider dateTimeProvider) { this.accountType = accountType; this.transactions = new ArrayList(); - this.dateProvider=dateProvider; + this.dateTimeProvider = dateTimeProvider; } 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, dateProvider)); + transactions.add(new Transaction(amount, dateTimeProvider)); } } @@ -33,30 +36,75 @@ 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, dateProvider)); + transactions.add(new Transaction(-amount, dateTimeProvider)); } } - public double interestEarned() { - double amount = sumTransactions(); + private double calculateInterestFactor(double rate, long days){ + return Math.pow(rate, days)-1; + } + + + private List getAmountsPerPeriod() { + 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; + } + + public double calculateInterestEarned() { + 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; + } + //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 * 0.001; - } - else{ - return 1 + (amount - 1000) * 0.002; + 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 * 0.02; + if (amount <= 1000) { + return amount * calculateInterestFactor(DailyCompoundRate.MaxiSavings.UNDER_1000, days); } - if (amount <= 2000){ - return 20 + (amount - 1000) * 0.05; + if (amount <= 2000) { + return 1000 * calculateInterestFactor(DailyCompoundRate.MaxiSavings.UNDER_1000, days) + + (amount - 1000) * calculateInterestFactor(DailyCompoundRate.MaxiSavings.UNDER_2000, days); } - return 70 + (amount - 2000) * 0.1; + 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 * 0.001; + default: + return amount * calculateInterestFactor(DailyCompoundRate.Checking.ANY, days); } } @@ -74,4 +122,10 @@ public AccountType getAccountType() { public List getTransactions() { return transactions; } + + @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..3c7c2592 --- /dev/null +++ b/src/main/java/com/abc/domain/objects/AmountPerPeriod.java @@ -0,0 +1,21 @@ +package com.abc.domain.objects; + +public class AmountPerPeriod { + private final double amount; + private final long days; + public AmountPerPeriod(double amount, long day) { + this.amount = amount; + this.days = day; + } + public double getAmount() { + return amount; + } + public long getDays() { + return days; + } + @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 index 2917b91c..8352dbcf 100644 --- a/src/main/java/com/abc/domain/objects/Bank.java +++ b/src/main/java/com/abc/domain/objects/Bank.java @@ -46,4 +46,9 @@ public String getFirstCustomerName() throws InvalidCustomerException { } throw new InvalidCustomerException("No customers yet"); } + + @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 index 25ba95fd..dbd7f0c4 100644 --- a/src/main/java/com/abc/domain/objects/Customer.java +++ b/src/main/java/com/abc/domain/objects/Customer.java @@ -34,7 +34,7 @@ public int getNumberOfAccounts() { public double totalInterestEarned() throws BusinessException { double total = 0; for (Account a : accounts) - total += a.interestEarned(); + total += a.calculateInterestEarned(); return total; } @@ -79,13 +79,23 @@ private String statementForAccount(Account a) { private String toDollars(double d){ return String.format("$%,.2f", abs(d)); } + + //Check whether account belongs to customer + private void checkAccountOwnership (Account account) throws InvalidTransactionException{ + if (!accounts.contains(account)){ + throw new InvalidTransactionException("account is not owned by customer: "+account); + } + } 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 itself"); + throw new InvalidTransactionException("Cannot transfer to the same account"); } if (accountFrom.sumTransactions() transactions = account.getTransactions(); assertEquals(1, transactions.size()); @@ -30,7 +87,7 @@ public void testAccountPositiveWithdraw() throws BusinessException { @Test // Test Zero Account Withdraw public void testAccountZeroWithdraw() throws BusinessException { - Account account = new Account(AccountType.CHECKING,DateProviderImpl.INSTANCE); + Account account = new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE); try { account.withdraw(0); fail(); @@ -42,7 +99,7 @@ public void testAccountZeroWithdraw() throws BusinessException { @Test // Test Negative Account Withdraw public void testAccountNegativeWithdraw() throws BusinessException { - Account account = new Account(AccountType.CHECKING,DateProviderImpl.INSTANCE); + Account account = new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE); try { account.withdraw(-100.0); fail(); @@ -54,7 +111,7 @@ public void testAccountNegativeWithdraw() throws BusinessException { @Test // Test Account Positive Deposit public void testAccountPositiveDeposit() throws BusinessException { - Account account = new Account(AccountType.CHECKING,DateProviderImpl.INSTANCE); + Account account = new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE); account.deposit(100.0); List transactions = account.getTransactions(); assertEquals(1, transactions.size()); @@ -64,7 +121,7 @@ public void testAccountPositiveDeposit() throws BusinessException { @Test // Test Account Zero Deposit public void testAccountZeroDeposit() throws BusinessException { - Account account = new Account(AccountType.CHECKING,DateProviderImpl.INSTANCE); + Account account = new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE); try { account.deposit(0.0); @@ -76,7 +133,7 @@ public void testAccountZeroDeposit() throws BusinessException { @Test // Test Account Negative Deposit public void testAccountNegativeDeposit() throws BusinessException { - Account account = new Account(AccountType.CHECKING,DateProviderImpl.INSTANCE); + Account account = new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE); try { account.deposit(-1000.0); @@ -86,47 +143,119 @@ public void testAccountNegativeDeposit() throws BusinessException { } } - @Test //Test earned checking interest rate - public void testCheckingEarnedInterestRate() throws BusinessException { - Account account = new Account(AccountType.CHECKING,DateProviderImpl.INSTANCE); + @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.interestEarned(),Precision.DOUBLE_PRECISION); + assertEquals(1.5,account.calculateInterestEarned(),Precision.DOUBLE_PRECISION); } - @Test //Test earned savings interest rate under 1000 - public void testSavingsEarnedInterestRateUnder1000() throws BusinessException { - Account savingAccount = new Account(AccountType.SAVINGS,DateProviderImpl.INSTANCE); + @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.interestEarned(),Precision.DOUBLE_PRECISION); + assertEquals(0.75,savingAccount.calculateInterestEarned(),Precision.DOUBLE_PRECISION); } - @Test //Test earned savings interest rate over 1000 - public void testSavingsEarnedInterestRateOver1000() throws BusinessException { - Account savingAccount = new Account(AccountType.SAVINGS,DateProviderImpl.INSTANCE); + @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.interestEarned(),Precision.DOUBLE_PRECISION); + assertEquals(1.5,savingAccount.calculateInterestEarned(),Precision.DOUBLE_PRECISION); } - @Test //Test earned maxi savings interest rate under 1000 - public void testMaxiSavingsEarnedInterestRateUnder1000() throws BusinessException { - Account maxiSavingAccount = new Account(AccountType.MAXI_SAVINGS,DateProviderImpl.INSTANCE); + @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.interestEarned(),Precision.DOUBLE_PRECISION); + assertEquals(15.0,maxiSavingAccount.calculateInterestEarned(),Precision.DOUBLE_PRECISION); } - @Test //Test earned savings interest rate under 2000 - public void testMaxiSavingsEarnedInterestRateUnder2000() throws BusinessException { - Account savingAccount = new Account(AccountType.MAXI_SAVINGS,DateProviderImpl.INSTANCE); + @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 .interestEarned(),Precision.DOUBLE_PRECISION); + assertEquals(55.0,savingAccount .calculateInterestEarned(),Precision.DOUBLE_PRECISION); } - @Test //Test earned savings interest rate over 2000 - public void testMaxiSavingsEarnedInterestRateOver2000() throws BusinessException { - Account savingAccount = new Account(AccountType.MAXI_SAVINGS,DateProviderImpl.INSTANCE); + @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 .interestEarned(),Precision.DOUBLE_PRECISION); + 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/BankTest.java b/src/test/java/com/abc/domain/objects/BankTest.java index 997f379f..374152ae 100644 --- a/src/test/java/com/abc/domain/objects/BankTest.java +++ b/src/test/java/com/abc/domain/objects/BankTest.java @@ -1,25 +1,40 @@ 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.objects.Account; -import com.abc.domain.objects.Bank; -import com.abc.domain.objects.Customer; -import com.abc.util.impl.DateProviderImpl; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import com.abc.domain.util.impl.DateTestHelperImpl; +import com.abc.util.DateTimeProvider; +import com.abc.util.impl.DateTimeProviderImpl; 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 public void testCustomerSummary() throws BusinessException { Bank bank = new Bank(); Customer john = new Customer("John"); - john.openAccount(new Account(AccountType.CHECKING,DateProviderImpl.INSTANCE)); + john.openAccount(new Account(AccountType.CHECKING,dateTimeProvider365DaysAgo)); bank.addCustomer(john); assertEquals("Customer Summary\n - John (1 account)", bank.customerSummary()); @@ -28,7 +43,7 @@ public void testCustomerSummary() throws BusinessException { @Test public void testCheckingAccount() throws BusinessException { Bank bank = new Bank(); - Account checkingAccount = new Account(AccountType.CHECKING,DateProviderImpl.INSTANCE); + Account checkingAccount = new Account(AccountType.CHECKING,dateTimeProvider365DaysAgo); Customer bill = new Customer("Bill").openAccount(checkingAccount); bank.addCustomer(bill); @@ -40,7 +55,7 @@ public void testCheckingAccount() throws BusinessException { @Test public void testSavingsAccount() throws BusinessException { Bank bank = new Bank(); - Account savingsAccount = new Account(AccountType.SAVINGS,DateProviderImpl.INSTANCE); + Account savingsAccount = new Account(AccountType.SAVINGS,dateTimeProvider365DaysAgo); bank.addCustomer(new Customer("Bill").openAccount(savingsAccount)); savingsAccount.deposit(1500.0); @@ -51,7 +66,7 @@ public void testSavingsAccount() throws BusinessException { @Test public void testMaxiSavingsAccount() throws BusinessException { Bank bank = new Bank(); - Account maxiSavingsAccount = new Account(AccountType.MAXI_SAVINGS,DateProviderImpl.INSTANCE); + Account maxiSavingsAccount = new Account(AccountType.MAXI_SAVINGS,dateTimeProvider365DaysAgo); bank.addCustomer(new Customer("Bill").openAccount(maxiSavingsAccount)); maxiSavingsAccount.deposit(3000.0); @@ -75,7 +90,7 @@ public void testGetFirstCustomerNameEmpty(){ //Test that retrieval of first customer name succeeds if there is one customer public void testGetFirstCustomerNameOneCustomer() throws BusinessException{ Bank bank = new Bank(); - Account checkingAccount = new Account(AccountType.CHECKING,DateProviderImpl.INSTANCE); + Account checkingAccount = new Account(AccountType.CHECKING,dateTimeProvider365DaysAgo); Customer george = new Customer("George").openAccount(checkingAccount); bank.addCustomer(george); assertEquals("George",bank.getFirstCustomerName()); @@ -85,11 +100,11 @@ public void testGetFirstCustomerNameOneCustomer() throws BusinessException{ //Test that first customer remains the same when second customer is added public void testGetFirstCustomerNameRemainsTheSameWhenSecond() throws BusinessException{ Bank bank = new Bank(); - Account georgeCheckingAccount = new Account(AccountType.CHECKING,DateProviderImpl.INSTANCE); + Account georgeCheckingAccount = new Account(AccountType.CHECKING,dateTimeProvider365DaysAgo); Customer george = new Customer("George").openAccount(georgeCheckingAccount); bank.addCustomer(george); - Account karenSavingsAccount = new Account(AccountType.SAVINGS,DateProviderImpl.INSTANCE); + Account karenSavingsAccount = new Account(AccountType.SAVINGS,dateTimeProvider365DaysAgo); Customer karen = new Customer("Karen").openAccount(karenSavingsAccount); bank.addCustomer(karen); diff --git a/src/test/java/com/abc/domain/objects/CustomerTest.java b/src/test/java/com/abc/domain/objects/CustomerTest.java index c673bcda..ac63fb5b 100644 --- a/src/test/java/com/abc/domain/objects/CustomerTest.java +++ b/src/test/java/com/abc/domain/objects/CustomerTest.java @@ -1,25 +1,26 @@ 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.domain.objects.Account; -import com.abc.domain.objects.Customer; -import com.abc.util.impl.DateProviderImpl; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import com.abc.util.DateTimeProvider; +import com.abc.util.impl.DateTimeProviderImpl; public class CustomerTest { @Test //Test customer statement generation public void testStatementGeneration() throws BusinessException{ - Account checkingAccount = new Account(AccountType.CHECKING,DateProviderImpl.INSTANCE); - Account savingsAccount = new Account(AccountType.SAVINGS,DateProviderImpl.INSTANCE); + 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); @@ -43,32 +44,32 @@ public void testStatementGeneration() throws BusinessException{ @Test public void testOneAccount(){ - Customer oscar = new Customer("Oscar").openAccount(new Account(AccountType.SAVINGS,DateProviderImpl.INSTANCE)); + Customer oscar = new Customer("Oscar").openAccount(new Account(AccountType.SAVINGS,DateTimeProviderImpl.INSTANCE)); assertEquals(1, oscar.getNumberOfAccounts()); } @Test public void testTwoAccount(){ Customer oscar = new Customer("Oscar") - .openAccount(new Account(AccountType.SAVINGS,DateProviderImpl.INSTANCE)); - oscar.openAccount(new Account(AccountType.CHECKING,DateProviderImpl.INSTANCE)); + .openAccount(new Account(AccountType.SAVINGS,DateTimeProviderImpl.INSTANCE)); + oscar.openAccount(new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE)); assertEquals(2, oscar.getNumberOfAccounts()); } @Test public void testThreeAcounts() { Customer oscar = new Customer("Oscar") - .openAccount(new Account(AccountType.SAVINGS,DateProviderImpl.INSTANCE)); - oscar.openAccount(new Account(AccountType.CHECKING,DateProviderImpl.INSTANCE)); - oscar.openAccount(new Account(AccountType.MAXI_SAVINGS,DateProviderImpl.INSTANCE)); + .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 checking account to savings public void testAccountTransferCompleteAmount() throws BusinessException{ - Account checkingAccount = new Account(AccountType.CHECKING,DateProviderImpl.INSTANCE); - Account savingsAccount = new Account(AccountType.SAVINGS,DateProviderImpl.INSTANCE); + 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); @@ -83,8 +84,8 @@ public void testAccountTransferCompleteAmount() throws BusinessException{ @Test //Test attempt to transfer over amount in checking to savings public void testAccountTransferOverAmountInAccount() throws BusinessException{ - Account checkingAccount = new Account(AccountType.CHECKING,DateProviderImpl.INSTANCE); - Account savingsAccount = new Account(AccountType.SAVINGS,DateProviderImpl.INSTANCE); + 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); @@ -102,8 +103,8 @@ public void testAccountTransferOverAmountInAccount() throws BusinessException{ @Test //Test attempt to transfer negative in checking to savings public void testAccountTransferNegativeAmountAccount() throws BusinessException{ - Account checkingAccount = new Account(AccountType.CHECKING,DateProviderImpl.INSTANCE); - Account savingsAccount = new Account(AccountType.SAVINGS,DateProviderImpl.INSTANCE); + 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); @@ -120,7 +121,7 @@ public void testAccountTransferNegativeAmountAccount() throws BusinessException{ @Test //Test attempt to transfer from maxiSaving to itself public void testAccountTransferAccountToItself() throws BusinessException{ - Account maxiSavingAccount = new Account(AccountType.MAXI_SAVINGS,DateProviderImpl.INSTANCE); + Account maxiSavingAccount = new Account(AccountType.MAXI_SAVINGS,DateTimeProviderImpl.INSTANCE); Customer william = new Customer("William").openAccount(maxiSavingAccount); @@ -133,6 +134,71 @@ public void testAccountTransferAccountToItself() throws BusinessException{ //Expected } } - + @Test + //Test attempt to transfer from savings to checking when deposit fails + 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 + //Test attempt to transfer from account not owned by customer + 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 + //Test attempt to transfer to account not owned by customer + 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 index 8a6c23dc..66e84bb4 100644 --- a/src/test/java/com/abc/domain/objects/TransactionTest.java +++ b/src/test/java/com/abc/domain/objects/TransactionTest.java @@ -1,21 +1,20 @@ package com.abc.domain.objects; -import org.junit.Test; +import static org.junit.Assert.assertEquals; -import com.abc.domain.constants.Precision; -import com.abc.domain.objects.Transaction; -import com.abc.util.DateProvider; +import java.time.LocalDateTime; -import static org.junit.Assert.assertEquals; +import org.junit.Test; -import java.util.Date; +import com.abc.domain.constants.Precision; +import com.abc.util.DateTimeProvider; public class TransactionTest { @Test public void testTransaction() { - final Date date=new Date(); - DateProvider dt=new DateProvider(){ - public Date now() { + final LocalDateTime date=LocalDateTime.now(); + DateTimeProvider dt=new DateTimeProvider(){ + public LocalDateTime now() { return 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 Date: Sat, 3 Jun 2017 22:25:12 -0400 Subject: [PATCH 4/7] Summary: 1. Added Java doc for all public metho 2. Added missing test cases --- .../com/abc/domain/constants/AccountType.java | 9 +- .../domain/constants/DailyCompoundRate.java | 36 +-- .../abc/domain/constants/TransferState.java | 8 +- .../domain/exceptions/BusinessException.java | 14 +- .../exceptions/InvalidCustomerException.java | 13 + .../exceptions/InvalidPeriodException.java | 24 ++ .../InvalidTransactionException.java | 12 + .../java/com/abc/domain/objects/Account.java | 141 +++++++--- .../abc/domain/objects/AmountPerPeriod.java | 44 ++- .../java/com/abc/domain/objects/Bank.java | 129 ++++++--- .../java/com/abc/domain/objects/Customer.java | 252 ++++++++++++------ .../com/abc/domain/objects/Transaction.java | 48 +++- .../java/com/abc/util/DateTimeProvider.java | 13 +- .../abc/util/impl/DateTimeProviderImpl.java | 20 +- .../com/abc/domain/constants/Precision.java | 7 +- .../com/abc/domain/objects/AccountTest.java | 249 ++++++++--------- .../domain/objects/AmountPerPeriodTest.java | 59 ++++ .../java/com/abc/domain/objects/BankTest.java | 21 +- .../com/abc/domain/objects/CustomerTest.java | 31 ++- .../abc/domain/objects/TransactionTest.java | 41 ++- .../util/impl/DateTimeProviderImplTest.java | 25 ++ 21 files changed, 860 insertions(+), 336 deletions(-) create mode 100644 src/main/java/com/abc/domain/exceptions/InvalidPeriodException.java create mode 100644 src/test/java/com/abc/domain/objects/AmountPerPeriodTest.java create mode 100644 src/test/java/com/abc/domain/util/impl/DateTimeProviderImplTest.java diff --git a/src/main/java/com/abc/domain/constants/AccountType.java b/src/main/java/com/abc/domain/constants/AccountType.java index 20650ea7..3c0772c7 100644 --- a/src/main/java/com/abc/domain/constants/AccountType.java +++ b/src/main/java/com/abc/domain/constants/AccountType.java @@ -1,7 +1,10 @@ package com.abc.domain.constants; +/** + * The enumeration for Account Type + * + * @author Ihor + */ public enum AccountType { - CHECKING, - SAVINGS, - MAXI_SAVINGS + 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 index 0a88b042..76432d67 100644 --- a/src/main/java/com/abc/domain/constants/DailyCompoundRate.java +++ b/src/main/java/com/abc/domain/constants/DailyCompoundRate.java @@ -1,27 +1,29 @@ package com.abc.domain.constants; + /** - * @author Ihor * 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 + * 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 Checking { + static final double ANY = 1.0000027383608262443494848243471; } - interface Savings { - static final double UNDER_1000=1.0000027383608262443494848243471; - static final double OVER_1000=1.0000054739948799896979591374816; + + 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; + + 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 index 98d86df1..bf734794 100644 --- a/src/main/java/com/abc/domain/constants/TransferState.java +++ b/src/main/java/com/abc/domain/constants/TransferState.java @@ -1,7 +1,11 @@ package com.abc.domain.constants; +/** + * The states to transfer between accounts + * + * @author Ihor + */ public enum TransferState { - INITIAL, - WITHDRAWN_COMPLETE + 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 index 85130e6d..380405a7 100644 --- a/src/main/java/com/abc/domain/exceptions/BusinessException.java +++ b/src/main/java/com/abc/domain/exceptions/BusinessException.java @@ -1,7 +1,19 @@ 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; + 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 index e32334be..81a71f60 100644 --- a/src/main/java/com/abc/domain/exceptions/InvalidCustomerException.java +++ b/src/main/java/com/abc/domain/exceptions/InvalidCustomerException.java @@ -1,7 +1,20 @@ 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 index cc46c985..3625a272 100644 --- a/src/main/java/com/abc/domain/exceptions/InvalidTransactionException.java +++ b/src/main/java/com/abc/domain/exceptions/InvalidTransactionException.java @@ -1,8 +1,20 @@ 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 index 7a9cd26e..75ffd2d5 100644 --- a/src/main/java/com/abc/domain/objects/Account.java +++ b/src/main/java/com/abc/domain/objects/Account.java @@ -10,20 +10,41 @@ import com.abc.domain.exceptions.BusinessException; import com.abc.domain.exceptions.InvalidTransactionException; import com.abc.util.DateTimeProvider; -import com.abc.util.impl.DateTimeProviderImpl; +/** + * 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"); @@ -32,6 +53,13 @@ public void deposit(double amount) throws BusinessException { } } + /** + * 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"); @@ -39,49 +67,80 @@ public void withdraw(double amount) throws BusinessException { transactions.add(new Transaction(-amount, dateTimeProvider)); } } - private double calculateInterestFactor(double rate, long days){ - return Math.pow(rate, days)-1; - } + /** + * 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; + } - private List getAmountsPerPeriod() { - List amountsPerPeriod=new ArrayList(); - LocalDateTime previousDateTime=null; - double previousAmount=0.0; - for (Transaction transaction:transactions){ + /** + * 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){ + if (previousDateTime != null) { long days = ChronoUnit.DAYS.between(previousDateTime.toLocalDate(), currentDateTime.toLocalDate()); - AmountPerPeriod amountPerPeriod = new AmountPerPeriod(previousAmount,days); + AmountPerPeriod amountPerPeriod = new AmountPerPeriod(previousAmount, days); amountsPerPeriod.add(amountPerPeriod); } - previousDateTime=currentDateTime; - previousAmount=previousAmount+transaction.getAmount(); + 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); + if (previousDateTime != null) { + long days = ChronoUnit.DAYS.between(previousDateTime.toLocalDate(), dateTimeProvider.now().toLocalDate()); + AmountPerPeriod amountPerPeriod = new AmountPerPeriod(previousAmount, days); amountsPerPeriod.add(amountPerPeriod); } return amountsPerPeriod; } - - public double calculateInterestEarned() { - 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; + + /** + * 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; } - //Please note for leap year (365 days) the annual interest will be higher then non-leap year (365) + + /** + * 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: @@ -100,14 +159,19 @@ private double calculateInterestEarned(double amount, long 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); + + 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) @@ -115,14 +179,29 @@ public double sumTransactions() { 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=" diff --git a/src/main/java/com/abc/domain/objects/AmountPerPeriod.java b/src/main/java/com/abc/domain/objects/AmountPerPeriod.java index 3c7c2592..a5a4203a 100644 --- a/src/main/java/com/abc/domain/objects/AmountPerPeriod.java +++ b/src/main/java/com/abc/domain/objects/AmountPerPeriod.java @@ -1,18 +1,58 @@ 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; - public AmountPerPeriod(double amount, long day) { + + /** + * 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 = day; + 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 index 8352dbcf..4864282d 100644 --- a/src/main/java/com/abc/domain/objects/Bank.java +++ b/src/main/java/com/abc/domain/objects/Bank.java @@ -6,47 +6,98 @@ 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; - - public Bank() { - customers = new ArrayList(); - } - - public void addCustomer(Customer customer) throws BusinessException { - if (customer==null){ - throw new InvalidCustomerException("customer is null"); - } - 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() throws BusinessException { - double total = 0; - for(Customer c: customers) - total += c.totalInterestEarned(); - return total; - } - - public String getFirstCustomerName() throws InvalidCustomerException { - if (customers.size()>0){ - return customers.get(0).getName(); - } - throw new InvalidCustomerException("No customers yet"); - } + 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 index dbd7f0c4..7487d0d9 100644 --- a/src/main/java/com/abc/domain/objects/Customer.java +++ b/src/main/java/com/abc/domain/objects/Customer.java @@ -9,107 +9,187 @@ 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 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() throws BusinessException { - double total = 0; - for (Account a : accounts) - total += a.calculateInterestEarned(); - 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 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 : a.getTransactions()) { - s += " " + (t.getAmount() < 0 ? "withdrawal" : "deposit") + " " + toDollars(t.getAmount()) + "\n"; - total += t.getAmount(); - } - s += "Total " + toDollars(total); - return s; - } - - private String toDollars(double d){ - return String.format("$%,.2f", abs(d)); - } - - //Check whether account belongs to customer - private void checkAccountOwnership (Account account) throws InvalidTransactionException{ - if (!accounts.contains(account)){ - throw new InvalidTransactionException("account is not owned by customer: "+account); - } - } + 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() { + String statement = "Statement for " + name + "\n"; + double total = 0.0; + for (Account a : accounts) { + statement += "\n" + getStatementForAccount(a) + "\n"; + total += a.sumTransactions(); + } + statement += "\nTotal In All Accounts " + formatToDollars(total); + return statement; + } + + /** + * 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)){ + + if (accountFrom.equals(accountTo)) { throw new InvalidTransactionException("Cannot transfer to the same account"); } - if (accountFrom.sumTransactions() transactions = account.getTransactions(); assertEquals(1, transactions.size()); @@ -87,7 +93,7 @@ public void testAccountPositiveWithdraw() throws BusinessException { @Test // Test Zero Account Withdraw public void testAccountZeroWithdraw() throws BusinessException { - Account account = new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE); + Account account = new Account(AccountType.CHECKING, DateTimeProviderImpl.INSTANCE); try { account.withdraw(0); fail(); @@ -99,7 +105,7 @@ public void testAccountZeroWithdraw() throws BusinessException { @Test // Test Negative Account Withdraw public void testAccountNegativeWithdraw() throws BusinessException { - Account account = new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE); + Account account = new Account(AccountType.CHECKING, DateTimeProviderImpl.INSTANCE); try { account.withdraw(-100.0); fail(); @@ -111,7 +117,7 @@ public void testAccountNegativeWithdraw() throws BusinessException { @Test // Test Account Positive Deposit public void testAccountPositiveDeposit() throws BusinessException { - Account account = new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE); + Account account = new Account(AccountType.CHECKING, DateTimeProviderImpl.INSTANCE); account.deposit(100.0); List transactions = account.getTransactions(); assertEquals(1, transactions.size()); @@ -121,7 +127,7 @@ public void testAccountPositiveDeposit() throws BusinessException { @Test // Test Account Zero Deposit public void testAccountZeroDeposit() throws BusinessException { - Account account = new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE); + Account account = new Account(AccountType.CHECKING, DateTimeProviderImpl.INSTANCE); try { account.deposit(0.0); @@ -133,7 +139,7 @@ public void testAccountZeroDeposit() throws BusinessException { @Test // Test Account Negative Deposit public void testAccountNegativeDeposit() throws BusinessException { - Account account = new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE); + Account account = new Account(AccountType.CHECKING, DateTimeProviderImpl.INSTANCE); try { account.deposit(-1000.0); @@ -143,119 +149,124 @@ public void testAccountNegativeDeposit() throws BusinessException { } } - @Test //Test earned checking interest rate after 365 Days - public void testCheckingEarnedInterestRateAfter365Days() throws BusinessException { - - Account account = new Account(AccountType.CHECKING,dateTimeProvider365DaysAgo); + @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); + 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); - } + 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); + @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); - } - + 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); + @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); - } + 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); + @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); - } + 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); + @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); - } + assertEquals(120.0, savingAccount.calculateInterestEarned(), Precision.DOUBLE_PRECISION); + } + + @Test // Test earned checking interest rate after 182 Days + public void testCheckingEarnedInterestRateAfter182Days() throws BusinessException { - @Test //Test earned checking interest rate after 182 Days - public void testCheckingEarnedInterestRateAfter182Days() throws BusinessException { - - Account account = new Account(AccountType.CHECKING,dateTimeProvider182DaysAgo); + 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); + 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); - } + 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); + @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); - } - + 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); + @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); - } + 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); + @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); - } + 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); + @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); - } + 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); + @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); - } + 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); + @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); - } + 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); + @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); + 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 index 374152ae..fd030467 100644 --- a/src/test/java/com/abc/domain/objects/BankTest.java +++ b/src/test/java/com/abc/domain/objects/BankTest.java @@ -14,8 +14,12 @@ import com.abc.domain.exceptions.InvalidCustomerException; import com.abc.domain.util.impl.DateTestHelperImpl; import com.abc.util.DateTimeProvider; -import com.abc.util.impl.DateTimeProviderImpl; +/** + * Class to test bank domain object. + * @author Ihor + * + */ public class BankTest { private DateTimeProvider dateTimeProvider365DaysAgo; @@ -30,6 +34,7 @@ public LocalDateTime now() { } }; } + //Test that customer summaery is generate correctly @Test public void testCustomerSummary() throws BusinessException { Bank bank = new Bank(); @@ -37,9 +42,10 @@ public void testCustomerSummary() throws BusinessException { john.openAccount(new Account(AccountType.CHECKING,dateTimeProvider365DaysAgo)); bank.addCustomer(john); - assertEquals("Customer Summary\n - John (1 account)", bank.customerSummary()); + 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(); @@ -51,7 +57,8 @@ public void testCheckingAccount() throws BusinessException { 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(); @@ -63,6 +70,7 @@ public void testSavingsAccount() throws BusinessException { 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(); @@ -73,6 +81,7 @@ public void testMaxiSavingsAccount() throws BusinessException { 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(){ @@ -86,8 +95,8 @@ public void testGetFirstCustomerNameEmpty(){ } } - @Test //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); @@ -95,9 +104,9 @@ public void testGetFirstCustomerNameOneCustomer() throws BusinessException{ bank.addCustomer(george); assertEquals("George",bank.getFirstCustomerName()); } - + + //Test that first customer remains the same when second customer is added @Test - //Test that first customer remains the same when second customer is added public void testGetFirstCustomerNameRemainsTheSameWhenSecond() throws BusinessException{ Bank bank = new Bank(); Account georgeCheckingAccount = new Account(AccountType.CHECKING,dateTimeProvider365DaysAgo); diff --git a/src/test/java/com/abc/domain/objects/CustomerTest.java b/src/test/java/com/abc/domain/objects/CustomerTest.java index ac63fb5b..23916e15 100644 --- a/src/test/java/com/abc/domain/objects/CustomerTest.java +++ b/src/test/java/com/abc/domain/objects/CustomerTest.java @@ -13,10 +13,15 @@ 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 //Test customer statement generation + //Test customer statement generation + @Test public void testStatementGeneration() throws BusinessException{ Account checkingAccount = new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE); @@ -42,12 +47,14 @@ public void testStatementGeneration() throws BusinessException{ "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") @@ -56,6 +63,7 @@ public void testTwoAccount(){ assertEquals(2, oscar.getNumberOfAccounts()); } + //Test opening of savings, checking and maxi savings accounts @Test public void testThreeAcounts() { Customer oscar = new Customer("Oscar") @@ -66,7 +74,7 @@ public void testThreeAcounts() { } @Test - //Test transfer complete checking account to savings + //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); @@ -80,9 +88,9 @@ public void testAccountTransferCompleteAmount() throws BusinessException{ 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 - //Test attempt to transfer over amount in checking to savings public void testAccountTransferOverAmountInAccount() throws BusinessException{ Account checkingAccount = new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE); Account savingsAccount = new Account(AccountType.SAVINGS,DateTimeProviderImpl.INSTANCE); @@ -100,8 +108,8 @@ public void testAccountTransferOverAmountInAccount() throws BusinessException{ } } + //Test attempt to transfer negative in checking to savings @Test - //Test attempt to transfer negative in checking to savings public void testAccountTransferNegativeAmountAccount() throws BusinessException{ Account checkingAccount = new Account(AccountType.CHECKING,DateTimeProviderImpl.INSTANCE); Account savingsAccount = new Account(AccountType.SAVINGS,DateTimeProviderImpl.INSTANCE); @@ -118,8 +126,9 @@ public void testAccountTransferNegativeAmountAccount() throws BusinessException{ //Expected } } + + //Test attempt to transfer from maxi savings to itself @Test - //Test attempt to transfer from maxiSaving to itself public void testAccountTransferAccountToItself() throws BusinessException{ Account maxiSavingAccount = new Account(AccountType.MAXI_SAVINGS,DateTimeProviderImpl.INSTANCE); @@ -134,8 +143,9 @@ public void testAccountTransferAccountToItself() throws BusinessException{ //Expected } } + + //Test attempt to transfer from savings to checking when deposit fails @Test - //Test attempt to transfer from savings to checking when deposit fails public void testAccountTransferWhenDepositFails() throws BusinessException{ Account savingsAccount = new Account(AccountType.SAVINGS,DateTimeProviderImpl.INSTANCE); DateTimeProvider dt=new DateTimeProvider(){ @@ -160,8 +170,8 @@ public LocalDateTime now() { } + //Test attempt to transfer from account not owned by customer @Test - //Test attempt to transfer from account not owned by customer public void testAccountTransferWhenFromAccountIsNotOwnedByCustomer() throws BusinessException{ Account bobSavingsAccount = new Account(AccountType.SAVINGS,DateTimeProviderImpl.INSTANCE); Account bobMaxiSavingsAccount = new Account(AccountType.MAXI_SAVINGS,DateTimeProviderImpl.INSTANCE); @@ -180,8 +190,9 @@ public void testAccountTransferWhenFromAccountIsNotOwnedByCustomer() throws Busi 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 - //Test attempt to transfer to account not owned by customer public void testAccountTransferWhenToAccountIsNotOwnedByCustomer() throws BusinessException{ Account bobSavingsAccount = new Account(AccountType.SAVINGS,DateTimeProviderImpl.INSTANCE); Account bobMaxiSavingsAccount = new Account(AccountType.MAXI_SAVINGS,DateTimeProviderImpl.INSTANCE); diff --git a/src/test/java/com/abc/domain/objects/TransactionTest.java b/src/test/java/com/abc/domain/objects/TransactionTest.java index 66e84bb4..2e2135d2 100644 --- a/src/test/java/com/abc/domain/objects/TransactionTest.java +++ b/src/test/java/com/abc/domain/objects/TransactionTest.java @@ -9,17 +9,40 @@ import com.abc.domain.constants.Precision; import com.abc.util.DateTimeProvider; +/** + * Class to test transaction domain object + * + * @author Ihor + * + */ public class TransactionTest { - @Test - public void testTransaction() { - final LocalDateTime date=LocalDateTime.now(); - DateTimeProvider dt=new DateTimeProvider(){ + + // 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(5,dt); - assertEquals(t.getAmount(),5.0,Precision.DOUBLE_PRECISION); - assertEquals(t.getTransactionDate(),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/DateTimeProviderImplTest.java b/src/test/java/com/abc/domain/util/impl/DateTimeProviderImplTest.java new file mode 100644 index 00000000..c876245b --- /dev/null +++ b/src/test/java/com/abc/domain/util/impl/DateTimeProviderImplTest.java @@ -0,0 +1,25 @@ +/** + * + */ +package com.abc.domain.util.impl; + +import static org.junit.Assert.assertFalse; + +import java.time.LocalDateTime; + +import org.junit.Test; + +import com.abc.util.impl.DateTimeProviderImpl; + +/** + * Class to test date time provider + * @author Ihor + */ +public class DateTimeProviderImplTest { + //Test that now from date time provider is not before current time + @Test + public void testNow(){ + LocalDateTime now = DateTimeProviderImpl.INSTANCE.now(); + assertFalse(LocalDateTime.now().isBefore(now)); + } +} From dd3ff9d1eec5879892f8880a32b93f7100c2e482 Mon Sep 17 00:00:00 2001 From: Ihor Yanchak Date: Sat, 3 Jun 2017 22:26:49 -0400 Subject: [PATCH 5/7] Formatting change --- .../com/abc/domain/util/impl/DateTimeProviderImplTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/abc/domain/util/impl/DateTimeProviderImplTest.java b/src/test/java/com/abc/domain/util/impl/DateTimeProviderImplTest.java index c876245b..8a4bb6fa 100644 --- a/src/test/java/com/abc/domain/util/impl/DateTimeProviderImplTest.java +++ b/src/test/java/com/abc/domain/util/impl/DateTimeProviderImplTest.java @@ -13,13 +13,14 @@ /** * Class to test date time provider + * * @author Ihor */ public class DateTimeProviderImplTest { - //Test that now from date time provider is not before current time + // Test that now from date time provider is not before current time @Test - public void testNow(){ - LocalDateTime now = DateTimeProviderImpl.INSTANCE.now(); + public void testNow() { + LocalDateTime now = DateTimeProviderImpl.INSTANCE.now(); assertFalse(LocalDateTime.now().isBefore(now)); } } From 39ce4aee9dc1e7cfae2e2c41cbda40bd543778b1 Mon Sep 17 00:00:00 2001 From: Ihor Yanchak Date: Sat, 3 Jun 2017 22:42:13 -0400 Subject: [PATCH 6/7] Small performance improvement. --- src/main/java/com/abc/domain/objects/Customer.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/abc/domain/objects/Customer.java b/src/main/java/com/abc/domain/objects/Customer.java index 7487d0d9..c2f14630 100644 --- a/src/main/java/com/abc/domain/objects/Customer.java +++ b/src/main/java/com/abc/domain/objects/Customer.java @@ -78,14 +78,15 @@ public double totalInterestEarned() throws BusinessException { * @return Statement */ public String getStatement() { - String statement = "Statement for " + name + "\n"; + StringBuilder statement = new StringBuilder(); + statement.append("Statement for " + name + "\n"); double total = 0.0; for (Account a : accounts) { - statement += "\n" + getStatementForAccount(a) + "\n"; + statement.append("\n" + getStatementForAccount(a) + "\n"); total += a.sumTransactions(); } - statement += "\nTotal In All Accounts " + formatToDollars(total); - return statement; + statement.append("\nTotal In All Accounts " + formatToDollars(total)); + return statement.toString(); } /** From f5e9b3970c113f6dda02e243e6a1a93692cb6e83 Mon Sep 17 00:00:00 2001 From: Ihor Yanchak Date: Sat, 3 Jun 2017 22:44:20 -0400 Subject: [PATCH 7/7] Changed README.md to include added features --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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