Skip to content

feat: implement bank account system with audit logging to BankATM #526

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 10 commits into from
Closed
1 change: 1 addition & 0 deletions lesson_17/bank/bank_app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dependencies {
implementation("com.google.code.gson:gson:2.11.0")
implementation("org.projectlombok:lombok:1.18.30")
implementation("org.springframework.boot:spring-boot-starter")

}

application {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.codedifferently.lesson17.bank;

import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
* The {@code AuditLog} class is responsible for logging important events in the banking system. It
* uses the Log4j logging framework to log messages at different levels.
*/
public class AuditLog {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not a bad implementation, good work. You might be interested in a library called Log4j that could be useful to you as well, but this is good enough for your homework.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Refactored AuditLog class to Implement Log4j logic instead of custom implementation.


private static final Logger logger = LogManager.getLogger(AuditLog.class.getName());
private final List<String> logEntries = new ArrayList<>();

/**
* Logs a message at the INFO level and adds it to the internal list of log entries.
*
* @param message
*/
public void log(String message) {
logger.info(message);
logEntries.add(message);
}

/**
* Logs an error message at the ERROR level and adds it to the internal list of log entries.
*
* @param message
*/
public void logError(String message) {
logger.error(message);
logEntries.add(message);
}

/**
* Returns the list of log entries.
*
* @return A list of log entries
*/
public List<String> getLogEntries() {
return logEntries;
}

public void clear() {
logEntries.clear();
logger.debug("Audit log cleared");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class BankAtm {

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

Choose a reason for hiding this comment

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

Violates the D in SOLID, no?


/**
* Adds a checking account to the bank.
Expand All @@ -25,6 +26,7 @@ public void addAccount(CheckingAccount account) {
owner -> {
customerById.put(owner.getId(), owner);
});
auditLog.log("Added account " + account.getAccountNumber() + " to the bank");
}

/**
Expand All @@ -34,6 +36,7 @@ public void addAccount(CheckingAccount account) {
* @return The unique set of accounts owned by the customer.
*/
public Set<CheckingAccount> findAccountsByCustomerId(UUID customerId) {
auditLog.log("Finding accounts for customer " + customerId);
return customerById.containsKey(customerId)
? customerById.get(customerId).getAccounts()
: Set.of();
Expand All @@ -48,6 +51,7 @@ public Set<CheckingAccount> findAccountsByCustomerId(UUID customerId) {
public void depositFunds(String accountNumber, double amount) {
CheckingAccount account = getAccountOrThrow(accountNumber);
account.deposit(amount);
auditLog.log("Deposited " + amount + " into account " + accountNumber);
}

/**
Expand All @@ -58,7 +62,9 @@ public void depositFunds(String accountNumber, double amount) {
*/
public void depositFunds(String accountNumber, Check check) {
CheckingAccount account = getAccountOrThrow(accountNumber);

check.depositFunds(account);
auditLog.log("Deposited check into account " + accountNumber);
}

/**
Expand All @@ -67,9 +73,11 @@ public void depositFunds(String accountNumber, Check check) {
* @param accountNumber
* @param amount
*/
public void withdrawFunds(String accountNumber, double amount) {
public void withdrawFunds(String accountNumber, double amount, Check check) {
CheckingAccount account = getAccountOrThrow(accountNumber);
account.withdraw(amount);

auditLog.log("Withdrew " + amount + " from account " + accountNumber);
}

/**
Expand All @@ -83,6 +91,7 @@ private CheckingAccount getAccountOrThrow(String accountNumber) {
if (account == null || account.isClosed()) {
throw new AccountNotFoundException("Account not found");
}
auditLog.log("Retrieved account " + accountNumber);
return account;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,11 @@ public void withdraw(double amount) throws InsufficientFundsException {
if (isClosed()) {
throw new IllegalStateException("Cannot withdraw from a closed account");
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Why?

if (amount <= 0) {
throw new IllegalStateException("Withdrawal amount must be positive");
}

if (balance < amount) {
throw new InsufficientFundsException("Account does not have enough funds for withdrawal");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.codedifferently.lesson17.bank;

import com.codedifferently.lesson17.bank.exceptions.InsufficientFundsException;
import java.util.Set;

/**
* The {@code SavingsAccount} class represents a savings account in a banking system. It extends the
* {@code CheckingAccount} class and provides additional functionality specific to savings accounts.
*/
public class SavingsAccount extends CheckingAccount {
Copy link
Contributor

Choose a reason for hiding this comment

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

A savings accounts is not a checking account.


/**
* Constructs a new SavingsAccount with the specified account number, owners, and balance.
*
* @param accountNumber
* @param owners
* @param balance
*/
public SavingsAccount(String accountNumber, Set<Customer> owners, double balance) {
super(accountNumber, owners, balance);
}

/**
* Gets the HashCode of the account number.
*
* @return The hash code of the account number.
*/
@Override
public int hashCode() {
return getAccountNumber().hashCode();
}

/**
* Checks if this SavingsAccount is equal to another object.
*
* @param obj The object to compare with.
* @return true if the objects are equal, false otherwise.
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof SavingsAccount other) {
return getAccountNumber().equals(other.getAccountNumber())
&& getOwners().equals(other.getOwners())
&& getBalance() == other.getBalance()
&& isClosed() == other.isClosed();
}
return false;
}

/**
* Will throw and exception if the user attempt to make a withdrawl while under the instance of
* SavingsAccount.
*
* <p>And the makes a second withdrawl that will check if there is a check and throw an error if
* one comes in.
*
* @param amount The amount to deposit.
*/
@Override
public void withdraw(double amount) throws InsufficientFundsException {
throw new IllegalStateException("you cannot write a check from the savings account");
}

public void withdraw(double amount, Check check) {
if (check != null) {
throw new IllegalStateException("Cannot withdraw from a savings account using a check");
}
withdraw(amount); // Call the original withdraw method
}

/**
* Returns a string representation of the SavingsAccount.
*
* @return A string representation of the SavingsAccount.
*/
@Override
public String toString() {
return "SavingsAccount"
+ "accountNumber='"
+ getAccountNumber()
+ '\''
+ ", owners="
+ getOwners()
+ ", balance="
+ getBalance()
+ ", isActive="
+ isClosed()
+ '}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.codedifferently.lesson17.bank;

import org.junit.jupiter.api.Test;

public class AuditLogTest {
@Test
public void testLog() {
AuditLog auditLog = new AuditLog();

String message = "Test log message";
auditLog.log(message);
}

@Test
public void testGetLogEntries() {
AuditLog auditLog = new AuditLog();

String message1 = "Test log message 1";
String message2 = "Test log message 2";
auditLog.log(message1);
auditLog.log(message2);

assert auditLog.getLogEntries().size() == 2;
assert auditLog.getLogEntries().get(0).equals(message1);
assert auditLog.getLogEntries().get(1).equals(message2);
}

@Test
public void testClear() {
AuditLog auditLog = new AuditLog();

String message = "Test log message";
auditLog.log(message);
auditLog.clear();

assert auditLog.getLogEntries().isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ void testDepositFunds_DoesntDepositCheckTwice() {
@Test
void testWithdrawFunds() {
// Act
classUnderTest.withdrawFunds(account2.getAccountNumber(), 50.0);
classUnderTest.withdrawFunds(account2.getAccountNumber(), 50.0, null);

// Assert
assertThat(account2.getBalance()).isEqualTo(150.0);
Expand All @@ -104,7 +104,7 @@ void testWithdrawFunds_AccountNotFound() {

// Act & Assert
assertThatExceptionOfType(AccountNotFoundException.class)
.isThrownBy(() -> classUnderTest.withdrawFunds(nonExistingAccountNumber, 50.0))
.isThrownBy(() -> classUnderTest.withdrawFunds(nonExistingAccountNumber, 50.0, null))
.withMessage("Account not found");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ void deposit_withNegativeAmount() {
}

@Test
void withdraw() {
void withdraw() { // Assuming Check has a constructor with id and amount
// Assuming Check has a constructor with id and amount
classUnderTest.withdraw(50.0);
assertEquals(50.0, classUnderTest.getBalance());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.codedifferently.lesson17.bank;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.util.HashSet;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class SavingsAccountTest {

private SavingsAccount classUnderTest;
private HashSet<Customer> owners;

@BeforeEach
void setUp() {
owners = new HashSet<>();
owners.add(new Customer(UUID.randomUUID(), "John Doe"));
owners.add(new Customer(UUID.randomUUID(), "Jane Smith"));
classUnderTest = new SavingsAccount("123456789", owners, 100.0);
}

@Test
void testHashCode() {
int expected = classUnderTest.getAccountNumber().hashCode();
int actual = classUnderTest.hashCode();
assertEquals(expected, actual);
}

@Test
void testEquals() {
SavingsAccount other = new SavingsAccount("123456789", owners, 100.0);
boolean expected = true;
boolean actual = classUnderTest.equals(other);
assertEquals(expected, actual);
}

@Test
void testCheckWithdrawl() {
Check check = new Check("123456789", 1000.0, classUnderTest);

IllegalStateException exception =
assertThrows(
IllegalStateException.class,
() -> {
classUnderTest.withdraw(1000, check);
});

assertEquals("Cannot withdraw from a savings account using a check", exception.getMessage());
}
}