Skip to content

Feat: adds audit logging and multi-currency support to BankAtm #533

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.codedifferently.lesson17.bank;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

public class AuditLog {

private final List<String> entries = new ArrayList<>();

public void record(String message) {
entries.add(LocalDateTime.now() + " - " + message);
}

public List<String> getEntries() {
return entries;
}

public void printLog() {
entries.forEach(System.out::println);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ public class BankAtm {

private final Map<UUID, Customer> customerById = new HashMap<>();
private final Map<String, CheckingAccount> accountByNumber = new HashMap<>();
private final AuditLog auditLog;

public BankAtm(AuditLog auditLog) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A constructor doesn't exist without a class.


this.auditLog = auditLog;
}

/**
* Adds a checking account to the bank.
Expand Down Expand Up @@ -40,14 +46,35 @@ public Set<CheckingAccount> findAccountsByCustomerId(UUID customerId) {
}

/**
* Deposits funds into an account.
* Deposits funds into the specified account. This method supports both cash (Double) and
* MoneyOrder objects as deposit sources.
*
* @param accountNumber The account number.
* @param amount The amount to deposit.
* @param accountNumber the account to deposit into
* @param source the deposit source; must be a Double (cash) or MoneyOrder
*/
public void depositFunds(String accountNumber, double amount) {
CheckingAccount account = getAccountOrThrow(accountNumber);
account.deposit(amount);
public void depositFunds(String accountNumber, Object source) {
// Retrieve the target account using the provided account number
CheckingAccount targetAccount = (CheckingAccount) accountByNumber.get(accountNumber);

// If the deposit source is a MoneyOrder, extract the amount and deposit it
if (source instanceof MoneyOrder newMoneyOrder) {
if (targetAccount != null) {
double amount = newMoneyOrder.getAmount();
targetAccount.deposit(amount);
auditLog.record("Deposited $" + amount + " via MoneyOrder to account " + accountNumber);
}
}
// If the deposit source is cash (Double), proceed with a standard deposit
else if (source instanceof Double amount) {
if (targetAccount != null) {
targetAccount.deposit(amount);
auditLog.record("Deposited $" + amount + " in cash to account " + accountNumber);
}
}
// If the source type is unsupported, throw an exception
else {
throw new IllegalArgumentException("Unsupported deposit type");
}
}

/**
Expand All @@ -59,6 +86,11 @@ public void depositFunds(String accountNumber, double amount) {
public void depositFunds(String accountNumber, Check check) {
CheckingAccount account = getAccountOrThrow(accountNumber);
check.depositFunds(account);
auditLog.record("Deposited check" + check + "into account" + accountNumber);
}

public void printAuditLog() {
auditLog.printLog();
}

/**
Expand All @@ -70,6 +102,7 @@ public void depositFunds(String accountNumber, Check check) {
public void withdrawFunds(String accountNumber, double amount) {
CheckingAccount account = getAccountOrThrow(accountNumber);
account.withdraw(amount);
auditLog.record("Withdrew $" + amount + "from account" + accountNumber);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.codedifferently.lesson17.bank;

public interface CurrencyConverter {
double convert(double amount, String fromCurrency, String toCurrency);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.codedifferently.lesson17.bank;

public class CurrencyConverterException extends RuntimeException {
public CurrencyConverterException(String fromCurrency, String toCurrency) {
super("Unsupported conversion from " + fromCurrency + " to " + toCurrency);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.codedifferently.lesson17.bank;

public class MoneyOrder {
private final double amount;
private final String sourceAccountNumber;

public MoneyOrder(CheckingAccount sourceAccount, double amount) {
this.amount = amount;
this.sourceAccountNumber = sourceAccount.getAccountNumber();
sourceAccount.withdraw(amount); // Withdraw immediately
}

public double getAmount() {
return amount;
}

public String getSourceAccountNumber() {
return sourceAccountNumber;
}

public void depositTo(CheckingAccount destinationAccount) {
destinationAccount.deposit(amount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.codedifferently.lesson17.bank;

import java.util.Map;

public class SimpleCurrencyConverter implements CurrencyConverter {
private final Map<String, Double> exchangeRates;

public SimpleCurrencyConverter(Map<String, Double> exchangeRates) {
this.exchangeRates = exchangeRates;
}

@Override
public double convert(double amount, String fromCurrency, String toCurrency) {
if (fromCurrency.equals(toCurrency)) return amount;

String key = fromCurrency + "_TO_" + toCurrency;
Double rate = exchangeRates.get(key);

if (rate == null) {
throw new CurrencyConverterException(fromCurrency, toCurrency);
}

return amount * rate;
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package com.codedifferently.lesson17.bank;

import java.util.Map;
import java.util.Set;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import com.codedifferently.lesson17.bank.exceptions.AccountNotFoundException;
import com.codedifferently.lesson17.bank.exceptions.CheckVoidedException;
import java.util.Set;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class BankAtmTest {

Expand All @@ -18,18 +23,29 @@ class BankAtmTest {
private Customer customer1;
private Customer customer2;

Map<String, Double> rates =
Map.of(
"USD_TO_EUR", 0.85,
"EUR_TO_USD", 1.18);
SimpleCurrencyConverter converter = new SimpleCurrencyConverter(rates);

@BeforeEach
void setUp() {
classUnderTest = new BankAtm();
AuditLog auditLog = new AuditLog();
classUnderTest = new BankAtm(auditLog);
customer1 = new Customer(UUID.randomUUID(), "John Doe");
customer2 = new Customer(UUID.randomUUID(), "Jane Smith");

account1 = new CheckingAccount("123456789", Set.of(customer1), 100.0);
account2 = new CheckingAccount("987654321", Set.of(customer1, customer2), 200.0);

customer1.addAccount(account1);
customer1.addAccount(account2);
customer2.addAccount(account2);

classUnderTest.addAccount(account1);
classUnderTest.addAccount(account2);

}

@Test
Expand Down Expand Up @@ -107,4 +123,34 @@ void testWithdrawFunds_AccountNotFound() {
.isThrownBy(() -> classUnderTest.withdrawFunds(nonExistingAccountNumber, 50.0))
.withMessage("Account not found");
}

@Test
void testDeposit_LogsToConsole() {
classUnderTest.depositFunds(account1.getAccountNumber(), 50.0);

assertThat(account1.getBalance()).isEqualTo(150.0);

System.out.println("🧾 Audit Log:");
classUnderTest.printAuditLog();
}

@Test
public void testConvert_USDToEUR() {
double result = converter.convert(100.0, "USD", "EUR");
assertEquals(85.0, result, 0.001);
}

@Test
public void testConvert_SameCurrency() {
double result = converter.convert(100.0, "USD", "USD");
assertEquals(100.0, result, 0.001);
}

@Test
public void testUnsupportedConversion_ThrowsException() {
Exception exception =
assertThrows(
CurrencyConverterException.class, () -> converter.convert(100.0, "USD", "JPY"));
assertTrue(exception.getMessage().contains("Unsupported conversion"));
}
}
Loading