diff --git a/src/main/java/com/abc/Account.java b/src/main/java/com/abc/Account.java index 099691e0..56008d27 100644 --- a/src/main/java/com/abc/Account.java +++ b/src/main/java/com/abc/Account.java @@ -1,73 +1,103 @@ package com.abc; +import static java.lang.Math.abs; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.ListIterator; -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 abstract class Account { + protected static final double DEFAULT_RATE = 0.001; + protected static final int DAYS_IN_YEAR = 365; + + private List transactions = Collections.synchronizedList( new ArrayList() ); - 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 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 void withdraw(double amount) { + if (amount <= 0) { + throw new IllegalArgumentException("amount must be greater than zero"); + } + synchronized(this) { + if (amount > sumTransactions()) { + throw new IllegalArgumentException("insufficient funds"); + } else { + transactions.add(new Transaction(-amount)); + } + } + } + + public synchronized double interestEarned() { + double principal = 0.0; + double interest = 0.0; + Instant latestWidtdrawalOn = Instant.MIN; + ListIterator i = transactions.listIterator(); + while(i.hasNext()) { + Transaction t = i.next(); + Instant interestStartingOn = t.getTransactionDate().toInstant().truncatedTo(ChronoUnit.DAYS); + principal += t.getAmount(); + + if(t.getAmount() < 0) { latestWidtdrawalOn = interestStartingOn; } + + Instant interstUntil = DateProvider.getInstance().now().toInstant().truncatedTo(ChronoUnit.DAYS); //default: interest until today + + //continue over all transaction on the same day + while(i.hasNext()) { + Transaction t1 = i.next(); + Instant d1 = t1.getTransactionDate().toInstant().truncatedTo(ChronoUnit.DAYS); + if(d1.isAfter(interestStartingOn)) { + interstUntil = d1; + i.previous(); + break; + } else { + if(t1.getAmount() < 0) { latestWidtdrawalOn = d1; } + principal += t1.getAmount(); + } + } + + interest += interestEarnedInPeriod(principal, interestStartingOn, interstUntil, latestWidtdrawalOn); + } + return interest; + } + + public double interestEarnedInPeriod(double onAmount, Instant start, Instant end, Instant latestWidtdrawalOn) { + return interestEarnedIn(ChronoUnit.DAYS.between(start, end), onAmount); + } + + public abstract double interestEarnedIn(long days, double onAmount); + + public synchronized String statementForAccount() { + String s = statementHeading() + "\n"; - 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; + //Now total up all the transactions + double total = 0.0; + for (Transaction t : transactions) { + s += " " + (t.getAmount() < 0 ? "withdrawal" : "deposit") + " " + toDollars(t.getAmount()) + "\n"; + total += t.getAmount(); } + s += "Total " + toDollars(total); + return s; } - public double sumTransactions() { - return checkIfTransactionsExist(true); + private String toDollars(double d){ + return String.format("$%,.2f", abs(d)); } - private double checkIfTransactionsExist(boolean checkAll) { - double amount = 0.0; - for (Transaction t: transactions) - amount += t.amount; - return amount; - } - - public int getAccountType() { - return accountType; - } + public synchronized double sumTransactions() { + double amount = 0.0; + for (Transaction t : transactions) + amount += t.getAmount(); + return amount; + } + public abstract String statementHeading(); } diff --git a/src/main/java/com/abc/AccountChecking.java b/src/main/java/com/abc/AccountChecking.java new file mode 100644 index 00000000..316bdbc4 --- /dev/null +++ b/src/main/java/com/abc/AccountChecking.java @@ -0,0 +1,11 @@ +package com.abc; + +public class AccountChecking extends Account { + public double interestEarnedIn(long days, double onAmount) { + return onAmount * days / DAYS_IN_YEAR * DEFAULT_RATE; + } + + public String statementHeading() { + return "Checking Account"; + } +} diff --git a/src/main/java/com/abc/AccountMaxiSavings.java b/src/main/java/com/abc/AccountMaxiSavings.java new file mode 100644 index 00000000..18e63e7e --- /dev/null +++ b/src/main/java/com/abc/AccountMaxiSavings.java @@ -0,0 +1,21 @@ +package com.abc; + +public class AccountMaxiSavings extends Account { + private static final double RATE1 = 0.02; + private static final double RATE2 = 0.05; + private static final double RATE3 = 0.10; + + public double interestEarnedIn(long days, double onAmount) { + double daysFrac = (double) days / DAYS_IN_YEAR; + if (onAmount <= 1000) + return (onAmount * daysFrac) * RATE1; + else if (onAmount <= 2000) + return (1000.0d * daysFrac) * RATE1 + ((onAmount - 1000) * daysFrac) * RATE2; + else + return (1000.0d * daysFrac) * RATE1 + (1000.0d * daysFrac) * RATE2 + ((onAmount - 2000) * daysFrac) * RATE3; + } + + public String statementHeading() { + return "Maxi Savings Account"; + } +} diff --git a/src/main/java/com/abc/AccountSavings.java b/src/main/java/com/abc/AccountSavings.java new file mode 100644 index 00000000..5e42b9ca --- /dev/null +++ b/src/main/java/com/abc/AccountSavings.java @@ -0,0 +1,17 @@ +package com.abc; + +public class AccountSavings extends Account { + private static final double RATE = 0.002; + + public double interestEarnedIn(long days, double onAmount) { + double daysFrac = (double) days / DAYS_IN_YEAR; + if (onAmount <= 1000) + return (onAmount * daysFrac) * DEFAULT_RATE; + else + return (1000.0d * daysFrac) * DEFAULT_RATE + ((onAmount - 1000) * daysFrac) * RATE; + } + + public String statementHeading() { + return "Savings Account"; + } +} diff --git a/src/main/java/com/abc/AccountSuperSavings.java b/src/main/java/com/abc/AccountSuperSavings.java new file mode 100644 index 00000000..f3e27151 --- /dev/null +++ b/src/main/java/com/abc/AccountSuperSavings.java @@ -0,0 +1,43 @@ +package com.abc; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +public class AccountSuperSavings extends Account { + private static final double RATE = 0.05; + private static final int noWithdrawalPeriod = 10; //No withdrawals means extra savings + + public double interestEarnedIn(long days, double onAmount) { + return onAmount * days / DAYS_IN_YEAR * DEFAULT_RATE; + } + + public double interestEarnedIn(long days, double onAmount, boolean didWithdrawInBlackoutPeriod) { + return onAmount * days / DAYS_IN_YEAR * (didWithdrawInBlackoutPeriod ? DEFAULT_RATE : RATE); + } + + public String statementHeading() { + return "Super Savings Account"; + } + + public double interestEarnedInPeriod(double onAmount, Instant start, Instant end, Instant latestWidtdrawalOn) { + double interest = 0.0; + Instant widthdrawalPlus10 = latestWidtdrawalOn.plus(noWithdrawalPeriod, ChronoUnit.DAYS); + long daysWorse = 0; //inside the withdrawal window + long daysBetter = 0; //outside of the withdrawal window + if(widthdrawalPlus10.isBefore(start)) { + daysBetter = ChronoUnit.DAYS.between(start, end); + } else if (widthdrawalPlus10.isAfter(end)) { + daysWorse = ChronoUnit.DAYS.between(start, end); + } else { + daysWorse = ChronoUnit.DAYS.between(start, widthdrawalPlus10); + daysBetter = ChronoUnit.DAYS.between(widthdrawalPlus10, end); + } + if(daysWorse > 0) { + interest += interestEarnedIn(daysWorse, onAmount, true); + } + if(daysBetter > 0) { + interest += interestEarnedIn(daysBetter, onAmount, false); + } + return interest; + } +} diff --git a/src/main/java/com/abc/Bank.java b/src/main/java/com/abc/Bank.java index 5dd535bd..56b12115 100644 --- a/src/main/java/com/abc/Bank.java +++ b/src/main/java/com/abc/Bank.java @@ -1,20 +1,21 @@ package com.abc; import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class Bank { private List customers; public Bank() { - customers = new ArrayList(); + customers = Collections.synchronizedList( new ArrayList() ); } public void addCustomer(Customer customer) { customers.add(customer); } - public String customerSummary() { + public synchronized String customerSummary() { String summary = "Customer Summary"; for (Customer c : customers) summary += "\n - " + c.getName() + " (" + format(c.getNumberOfAccounts(), "account") + ")"; @@ -27,20 +28,10 @@ private String format(int number, String word) { return number + " " + (number == 1 ? word : word + "s"); } - public double totalInterestPaid() { + public synchronized double totalInterestPaid() { double total = 0; - for(Customer c: customers) + for(Customer c : customers) total += c.totalInterestEarned(); return total; } - - public String getFirstCustomer() { - try { - customers = null; - return customers.get(0).getName(); - } catch (Exception e){ - e.printStackTrace(); - return "Error"; - } - } } diff --git a/src/main/java/com/abc/Customer.java b/src/main/java/com/abc/Customer.java index 31571685..3c4bcb72 100644 --- a/src/main/java/com/abc/Customer.java +++ b/src/main/java/com/abc/Customer.java @@ -1,17 +1,18 @@ package com.abc; +import static java.lang.Math.abs; + import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import static java.lang.Math.abs; - public class Customer { private String name; private List accounts; public Customer(String name) { this.name = name; - this.accounts = new ArrayList(); + this.accounts = Collections.synchronizedList(new ArrayList()); } public String getName() { @@ -22,57 +23,38 @@ public Customer openAccount(Account account) { accounts.add(account); return this; } + + public void transfer(double amount, Account from, Account to) { + if(accounts.contains(from) && accounts.contains(to) && from != to) { + synchronized(this) { + from.withdraw(amount); + to.deposit(amount); + } + } else { + throw new IllegalArgumentException("Both accounts must belong to the same customer, and cannot be same."); + } + } public int getNumberOfAccounts() { return accounts.size(); } - public double totalInterestEarned() { + public synchronized double totalInterestEarned() { double total = 0; for (Account a : accounts) total += a.interestEarned(); return total; } - public String getStatement() { + public synchronized String getStatement() { String statement = null; statement = "Statement for " + name + "\n"; double total = 0.0; for (Account a : accounts) { - statement += "\n" + statementForAccount(a) + "\n"; + statement += "\n" + a.statementForAccount() + "\n"; total += a.sumTransactions(); } - statement += "\nTotal In All Accounts " + toDollars(total); + statement += "\nTotal In All Accounts " + String.format("$%,.2f", abs(total)); return statement; } - - private String statementForAccount(Account a) { - String s = ""; - - //Translate to pretty account type - switch(a.getAccountType()){ - case Account.CHECKING: - s += "Checking Account\n"; - break; - case Account.SAVINGS: - s += "Savings Account\n"; - break; - case Account.MAXI_SAVINGS: - s += "Maxi Savings Account\n"; - break; - } - - //Now total up all the transactions - double total = 0.0; - for (Transaction t : a.transactions) { - s += " " + (t.amount < 0 ? "withdrawal" : "deposit") + " " + toDollars(t.amount) + "\n"; - total += t.amount; - } - s += "Total " + toDollars(total); - return s; - } - - private String toDollars(double d){ - return String.format("$%,.2f", abs(d)); - } } diff --git a/src/main/java/com/abc/DateProvider.java b/src/main/java/com/abc/DateProvider.java index 035ee90b..b04f297c 100644 --- a/src/main/java/com/abc/DateProvider.java +++ b/src/main/java/com/abc/DateProvider.java @@ -4,15 +4,16 @@ import java.util.Date; public class DateProvider { - private static DateProvider instance = null; + private static DateProvider instance = null; - public static DateProvider getInstance() { - if (instance == null) - instance = new DateProvider(); - return instance; - } + public static DateProvider getInstance() { + if (instance == null) + instance = new DateProvider(); + return instance; + } - public Date now() { - return Calendar.getInstance().getTime(); - } + public Date now() { + Calendar cal = Calendar.getInstance(); + return cal.getTime(); + } } diff --git a/src/main/java/com/abc/Transaction.java b/src/main/java/com/abc/Transaction.java index c1f7c67e..f7734d4e 100644 --- a/src/main/java/com/abc/Transaction.java +++ b/src/main/java/com/abc/Transaction.java @@ -1,16 +1,22 @@ package com.abc; -import java.util.Calendar; import java.util.Date; public class Transaction { - public final double amount; + private final double amount; - private Date transactionDate; + private final Date transactionDate; - public Transaction(double amount) { - this.amount = amount; - this.transactionDate = DateProvider.getInstance().now(); - } + public Transaction(double amount) { + this.amount = amount; + this.transactionDate = DateProvider.getInstance().now(); + } + public Date getTransactionDate() { + return transactionDate; + } + + public double getAmount() { + return amount; + } } diff --git a/src/test/java/com/abc/AccountTest.java b/src/test/java/com/abc/AccountTest.java new file mode 100644 index 00000000..833bac92 --- /dev/null +++ b/src/test/java/com/abc/AccountTest.java @@ -0,0 +1,209 @@ +package com.abc; + +import static java.util.Calendar.DAY_OF_YEAR; +import static org.junit.Assert.assertEquals; + +import java.lang.reflect.Field; +import java.util.Calendar; +import java.util.Date; + +import org.junit.After; +import org.junit.Test; + +public class AccountTest { + private static final double DOUBLE_DELTA = 1e-15; + + private final class DateProvider1 extends DateProvider { + public Date now() { + Calendar cal = Calendar.getInstance(); + cal.set(DAY_OF_YEAR, 1); + return cal.getTime(); + } + } + private final class DateProvider2 extends DateProvider { + public Date now() { + Calendar cal = Calendar.getInstance(); + cal.set(DAY_OF_YEAR, 366); + return cal.getTime(); + } + } + private final class DateProvider3 extends DateProvider { + public Date now() { + Calendar cal = Calendar.getInstance(); + cal.set(DAY_OF_YEAR, 11); + return cal.getTime(); + } + } + private final class DateProvider4 extends DateProvider { + public Date now() { + Calendar cal = Calendar.getInstance(); + cal.set(DAY_OF_YEAR, 21); + return cal.getTime(); + } + } + + @After + public void tearDown() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Field field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, null); + } + + @Test + public void accountInterestChecking() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Field field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider1()); + + Account account = new AccountChecking(); + account.deposit(100.0); + + field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider2()); + + assertEquals(0.1, account.interestEarned(), DOUBLE_DELTA); + } + + @Test + public void accountInterestSavings() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Field field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider1()); + + Account account = new AccountSavings(); + account.deposit(100.0); + + field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider2()); + + assertEquals(0.1, account.interestEarned(), DOUBLE_DELTA); + } + + @Test + public void accountInterestSavingsLargeAmount() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Field field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider1()); + + Account account = new AccountSavings(); + account.deposit(1100.0); + + field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider2()); + + assertEquals(1.2, account.interestEarned(), DOUBLE_DELTA); + } + + @Test + public void accountInterestMaxiSavings() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Field field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider1()); + + Account account = new AccountMaxiSavings(); + account.deposit(100.0); + + field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider2()); + + assertEquals(2.0, account.interestEarned(), DOUBLE_DELTA); + } + + @Test + public void accountInterestMaxiSavingsMidAmount() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Field field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider1()); + + Account account = new AccountMaxiSavings(); + account.deposit(1100.0); + + field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider2()); + + assertEquals(25.0, account.interestEarned(), DOUBLE_DELTA); + } + + @Test + public void accountInterestMaxiSavingsLargeAmount() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Field field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider1()); + + Account account = new AccountMaxiSavings(); + account.deposit(2100.0); + + field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider2()); + + assertEquals(80.0, account.interestEarned(), DOUBLE_DELTA); + } + + @Test + public void accountInterestSuperSavingsNoWithdrawals() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Field field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider1()); + + Account account = new AccountSuperSavings(); + account.deposit(100.0); + + field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider2()); + + assertEquals(5.0, account.interestEarned(), DOUBLE_DELTA); + } + + @Test + public void accountInterestSuperSavingsWithdrawalsWithin10Days() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Field field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider1()); + + Account account = new AccountSuperSavings(); + account.deposit(200.0); + account.withdraw(100.0); + + field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider3()); + + assertEquals(1d/365, account.interestEarned(), DOUBLE_DELTA); + } + + @Test + public void accountInterestSuperSavingsWithdrawalMixed() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Field field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider1()); + + Account account = new AccountSuperSavings(); + account.deposit(200.0); + account.withdraw(100.0); + + field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider4()); + + assertEquals(1d/365 + 1000d * 0.05 / 365, account.interestEarned(), DOUBLE_DELTA); + } + + @Test + public void statementHeading() { + Account account = new AccountChecking(); + assertEquals("Checking Account", account.statementHeading()); + account = new AccountSavings(); + assertEquals("Savings Account", account.statementHeading()); + account = new AccountMaxiSavings(); + assertEquals("Maxi Savings Account", account.statementHeading()); + account = new AccountSuperSavings(); + assertEquals("Super Savings Account", account.statementHeading()); + } +} diff --git a/src/test/java/com/abc/BankTest.java b/src/test/java/com/abc/BankTest.java index f8a82896..c9b7b509 100644 --- a/src/test/java/com/abc/BankTest.java +++ b/src/test/java/com/abc/BankTest.java @@ -1,54 +1,171 @@ package com.abc; -import org.junit.Test; - +import static java.util.Calendar.DAY_OF_YEAR; import static org.junit.Assert.assertEquals; +import java.lang.reflect.Field; +import java.util.Calendar; +import java.util.Date; + +import org.junit.After; +import org.junit.Test; + public class BankTest { private static final double DOUBLE_DELTA = 1e-15; - @Test + private final class DateProvider1 extends DateProvider { + public Date now() { + Calendar cal = Calendar.getInstance(); + cal.set(DAY_OF_YEAR, 1); + return cal.getTime(); + } + } + private final class DateProvider2 extends DateProvider { + public Date now() { + Calendar cal = Calendar.getInstance(); + cal.set(DAY_OF_YEAR, 366); + return cal.getTime(); + } + } + + @After + public void tearDown() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Field field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, null); + } + + @Test public void customerSummary() { Bank bank = new Bank(); Customer john = new Customer("John"); - john.openAccount(new Account(Account.CHECKING)); + john.openAccount(new AccountChecking()); bank.addCustomer(john); assertEquals("Customer Summary\n - John (1 account)", bank.customerSummary()); } + @Test + public void customerSummary2Accounts() { + Bank bank = new Bank(); + Customer john = new Customer("John"); + john.openAccount(new AccountChecking()); + john.openAccount(new AccountSavings()); + bank.addCustomer(john); + + assertEquals("Customer Summary\n - John (2 accounts)", bank.customerSummary()); + } + + @Test + public void customerSummary2Customers2Accounts() { + Bank bank = new Bank(); + Customer john = new Customer("John"); + Customer jack = new Customer("Jack"); + john.openAccount(new AccountChecking()); + jack.openAccount(new AccountSavings()); + bank.addCustomer(john); + bank.addCustomer(jack); + + assertEquals("Customer Summary\n - John (1 account)\n - Jack (1 account)", bank.customerSummary()); + } + @Test - public void checkingAccount() { + public void checkingAccount() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Field field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider1()); + Bank bank = new Bank(); - Account checkingAccount = new Account(Account.CHECKING); + Account checkingAccount = new AccountChecking(); Customer bill = new Customer("Bill").openAccount(checkingAccount); bank.addCustomer(bill); checkingAccount.deposit(100.0); + field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider2()); + assertEquals(0.1, bank.totalInterestPaid(), DOUBLE_DELTA); } @Test - public void savings_account() { + public void savings_account() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { + Field field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider1()); + Bank bank = new Bank(); - Account checkingAccount = new Account(Account.SAVINGS); - bank.addCustomer(new Customer("Bill").openAccount(checkingAccount)); + Account account = new AccountSavings(); + bank.addCustomer(new Customer("Bill").openAccount(account)); + + account.deposit(1500.0); - checkingAccount.deposit(1500.0); + field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider2()); assertEquals(2.0, bank.totalInterestPaid(), DOUBLE_DELTA); } @Test - public void maxi_savings_account() { + public void maxi_savings_account() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Field field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider1()); + Bank bank = new Bank(); - Account checkingAccount = new Account(Account.MAXI_SAVINGS); - bank.addCustomer(new Customer("Bill").openAccount(checkingAccount)); + Account account = new AccountMaxiSavings(); + bank.addCustomer(new Customer("Bill").openAccount(account)); + + account.deposit(3000.0); - checkingAccount.deposit(3000.0); + field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider2()); assertEquals(170.0, bank.totalInterestPaid(), DOUBLE_DELTA); } + @Test + public void super_savings_account() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Field field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider1()); + + Bank bank = new Bank(); + Account account = new AccountSuperSavings(); + bank.addCustomer(new Customer("Bill").openAccount(account)); + + account.deposit(3000.0); + + field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider2()); + + assertEquals(150.0, bank.totalInterestPaid(), DOUBLE_DELTA); + } + + @Test + public void multipleCustomersTotalInterest() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Field field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider1()); + + Bank bank = new Bank(); + Account account1 = new AccountSuperSavings(); + Account account2 = new AccountMaxiSavings(); + bank.addCustomer(new Customer("Bill").openAccount(account1)); + bank.addCustomer(new Customer("Jack").openAccount(account2)); + + account1.deposit(3000.0); + account2.deposit(3000.0); + + field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider2()); + + assertEquals(320.0, bank.totalInterestPaid(), DOUBLE_DELTA); + } + } diff --git a/src/test/java/com/abc/CustomerTest.java b/src/test/java/com/abc/CustomerTest.java index b8df9498..d7fbbee5 100644 --- a/src/test/java/com/abc/CustomerTest.java +++ b/src/test/java/com/abc/CustomerTest.java @@ -1,57 +1,141 @@ package com.abc; -import org.junit.Ignore; -import org.junit.Test; - +import static java.util.Calendar.DAY_OF_YEAR; import static org.junit.Assert.assertEquals; +import java.lang.reflect.Field; +import java.util.Calendar; +import java.util.Date; + +import org.junit.Test; + public class CustomerTest { + private static final double DOUBLE_DELTA = 1e-15; + + @Test + public void testGetStatement() { + Account checkingAccount = new AccountChecking(); + Account savingsAccount = new AccountSavings(); + Account mSavingsAccount = new AccountMaxiSavings(); + Account sSavingsAccount = new AccountSuperSavings(); + + Customer henry = new Customer("Henry") + .openAccount(checkingAccount) + .openAccount(savingsAccount) + .openAccount(mSavingsAccount) + .openAccount(sSavingsAccount); + + checkingAccount.deposit(100.0); + savingsAccount.deposit(4000.0); + savingsAccount.withdraw(200.0); + mSavingsAccount.deposit(5000.0); + mSavingsAccount.withdraw(300.0); + sSavingsAccount.deposit(6000.0); + sSavingsAccount.withdraw(400.0); + + assertEquals("Statement for " + henry.getName() + "\n\n" + "Checking Account\n" + " deposit $100.00\n" + "Total $100.00\n" + + "\n" + "Savings Account\n" + " deposit $4,000.00\n" + " withdrawal $200.00\n" + "Total $3,800.00\n" + + "\n" + "Maxi Savings Account\n" + " deposit $5,000.00\n" + " withdrawal $300.00\n" + "Total $4,700.00\n" + + "\n" + "Super Savings Account\n" + " deposit $6,000.00\n" + " withdrawal $400.00\n" + "Total $5,600.00\n" + + "\n" + "Total In All Accounts $14,200.00", henry.getStatement()); + } + + @Test + public void testNumberOfAccounts() { + Customer oscar = new Customer("Oscar").openAccount(new AccountSavings()); + oscar.openAccount(new AccountChecking()); + oscar.openAccount(new AccountMaxiSavings()); + oscar.openAccount(new AccountSuperSavings()); + assertEquals(4, oscar.getNumberOfAccounts()); + } + + @Test + public void testTotalInterestEarnedIn10Days() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + final class DateProvider1 extends DateProvider { + public Date now() { + Calendar cal = Calendar.getInstance(); + cal.set(DAY_OF_YEAR, 2); + return cal.getTime(); + } + } + final class DateProvider2 extends DateProvider { + public Date now() { + Calendar cal = Calendar.getInstance(); + cal.set(DAY_OF_YEAR, 12); + return cal.getTime(); + } + } + Field field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider1()); + + Account checkingAccount = new AccountChecking(); + Account savingsAccount = new AccountSavings(); + Customer henry = new Customer("Henry").openAccount(checkingAccount).openAccount(savingsAccount); + checkingAccount.deposit(100.0); + savingsAccount.deposit(4000.0); + + field = DateProvider.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, new DateProvider2()); + + assertEquals("Interest calculation error.", 0.19452054794520546, henry.totalInterestEarned(), DOUBLE_DELTA); + field.set(null, null); + } + + @Test + public void testTotalInterestEarnedZeroDays() { + Account checkingAccount = new AccountChecking(); + Account savingsAccount = new AccountSavings(); + Customer henry = new Customer("Henry").openAccount(checkingAccount).openAccount(savingsAccount); + checkingAccount.deposit(100.0); + savingsAccount.deposit(4000.0); + assertEquals("Interest calculation error.", 0.0, henry.totalInterestEarned(), DOUBLE_DELTA); + } + + @Test + public void testTransfer() { + Account checkingAccount = new AccountChecking(); + Account savingsAccount = new AccountSavings(); + Customer henry = new Customer("Henry").openAccount(checkingAccount).openAccount(savingsAccount); + checkingAccount.deposit(100.0); + savingsAccount.deposit(4000.0); + henry.transfer(100.0, savingsAccount, checkingAccount); + assertEquals("Transfer error", 200.0, checkingAccount.sumTransactions(), DOUBLE_DELTA); + } - @Test //Test customer statement generation - public void testApp(){ - - Account checkingAccount = new Account(Account.CHECKING); - Account savingsAccount = new Account(Account.SAVINGS); - - Customer henry = new Customer("Henry").openAccount(checkingAccount).openAccount(savingsAccount); - - checkingAccount.deposit(100.0); - savingsAccount.deposit(4000.0); - savingsAccount.withdraw(200.0); - - assertEquals("Statement for Henry\n" + - "\n" + - "Checking Account\n" + - " deposit $100.00\n" + - "Total $100.00\n" + - "\n" + - "Savings Account\n" + - " deposit $4,000.00\n" + - " withdrawal $200.00\n" + - "Total $3,800.00\n" + - "\n" + - "Total In All Accounts $3,900.00", henry.getStatement()); - } - - @Test - public void testOneAccount(){ - Customer oscar = new Customer("Oscar").openAccount(new Account(Account.SAVINGS)); - assertEquals(1, oscar.getNumberOfAccounts()); - } - - @Test - public void testTwoAccount(){ - Customer oscar = new Customer("Oscar") - .openAccount(new Account(Account.SAVINGS)); - oscar.openAccount(new Account(Account.CHECKING)); - assertEquals(2, oscar.getNumberOfAccounts()); - } - - @Ignore - public void testThreeAcounts() { - Customer oscar = new Customer("Oscar") - .openAccount(new Account(Account.SAVINGS)); - oscar.openAccount(new Account(Account.CHECKING)); - assertEquals(3, oscar.getNumberOfAccounts()); - } + @Test(expected = IllegalArgumentException.class) + public void testTransferSameAccounts() { + Account checkingAccount = new AccountChecking(); + Customer henry = new Customer("Henry").openAccount(checkingAccount); + checkingAccount.deposit(100.0); + henry.transfer(100.0, checkingAccount, checkingAccount); + } + + @Test(expected = IllegalArgumentException.class) + public void testTransferNotMyToAccount() { + Account checkingAccount = new AccountChecking(); + Account savingsAccount = new AccountSavings(); + checkingAccount.deposit(100.0); + Customer henry = new Customer("Henry").openAccount(checkingAccount); + henry.transfer(100.0, checkingAccount, savingsAccount); + } + + @Test(expected = IllegalArgumentException.class) + public void testTransferNotMyFromAccount() { + Account checkingAccount = new AccountChecking(); + Account savingsAccount = new AccountSavings(); + checkingAccount.deposit(100.0); + Customer henry = new Customer("Henry").openAccount(savingsAccount); + henry.transfer(100.0, checkingAccount, savingsAccount); + } + + @Test(expected = IllegalArgumentException.class) + public void testTransferNoneMyAccount() { + Account checkingAccount = new AccountChecking(); + Account savingsAccount = new AccountSavings(); + checkingAccount.deposit(100.0); + Customer henry = new Customer("Henry"); + henry.transfer(100.0, checkingAccount, savingsAccount); + } } diff --git a/src/test/java/com/abc/DateProviderTest.java b/src/test/java/com/abc/DateProviderTest.java new file mode 100644 index 00000000..4001f225 --- /dev/null +++ b/src/test/java/com/abc/DateProviderTest.java @@ -0,0 +1,24 @@ +package com.abc; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Calendar; +import java.util.Date; + +import org.junit.Test; + +public class DateProviderTest { + @Test + public void singleton() { + DateProvider dp1 = DateProvider.getInstance(); + DateProvider dp2 = DateProvider.getInstance(); + assertEquals("Does not return singleton.", dp1, dp2); + } + + @Test + public void properNow() { + Date dpTime = DateProvider.getInstance().now(); + assertTrue("Time is wrong in created object.", Calendar.getInstance().getTime().getTime() - dpTime.getTime() < 100); + } +} diff --git a/src/test/java/com/abc/TransactionTest.java b/src/test/java/com/abc/TransactionTest.java index 28983234..00db5452 100644 --- a/src/test/java/com/abc/TransactionTest.java +++ b/src/test/java/com/abc/TransactionTest.java @@ -1,13 +1,19 @@ package com.abc; -import org.junit.Test; - +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.util.Date; + +import org.junit.Test; + public class TransactionTest { @Test public void transaction() { Transaction t = new Transaction(5); + Date presentTime = DateProvider.getInstance().now(); assertTrue(t instanceof Transaction); + assertEquals("Amount is wrong in created object.", 5, t.getAmount(), 0.0); + assertTrue("Time is wrong in created object.", presentTime.getTime() - t.getTransactionDate().getTime() < 100); } }