diff --git a/.github/workflows/check_lesson_17_java_pr.yaml b/.github/workflows/check_lesson_17_java_pr.yaml index 14cdac96c..cce35e6d9 100644 --- a/.github/workflows/check_lesson_17_java_pr.yaml +++ b/.github/workflows/check_lesson_17_java_pr.yaml @@ -4,7 +4,7 @@ on: pull_request: branches: [ "main" ] paths: - - "lesson_17/oop/**" + - "lesson_17/bank/**" jobs: build: @@ -24,5 +24,5 @@ jobs: distribution: 'temurin' - name: Build Lesson 17 with Java - working-directory: ./lesson_17/oop + working-directory: ./lesson_17/bank run: ./gradlew check \ No newline at end of file diff --git a/lesson_16/objects/gradle/wrapper/gradle-wrapper.properties b/lesson_16/objects/gradle/wrapper/gradle-wrapper.properties index bdc9a83b1..5c40527d4 100644 --- a/lesson_16/objects/gradle/wrapper/gradle-wrapper.properties +++ b/lesson_16/objects/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/lesson_16/objects/objects_app/build.gradle.kts b/lesson_16/objects/objects_app/build.gradle.kts index 0ce9bd266..c49fe31ff 100644 --- a/lesson_16/objects/objects_app/build.gradle.kts +++ b/lesson_16/objects/objects_app/build.gradle.kts @@ -2,6 +2,7 @@ plugins { // Apply the application plugin to add support for building a CLI application in Java. application eclipse + jacoco id("com.diffplug.spotless") version "6.25.0" id("org.springframework.boot") version "3.2.2" id("com.adarshr.test-logger") version "4.0.0" @@ -38,8 +39,29 @@ application { tasks.named("test") { // Use JUnit Platform for unit tests. useJUnitPlatform() + finalizedBy(tasks.jacocoTestReport) } +tasks.jacocoTestReport { + dependsOn(tasks.test) + reports { + xml.required = true + } +} + +tasks.jacocoTestCoverageVerification { + violationRules { + rule { + limit { + minimum = "0.8".toBigDecimal() + } + } + } +} + +tasks.check { + dependsOn(tasks.jacocoTestCoverageVerification) +} configure { diff --git a/lesson_17/README.md b/lesson_17/README.md index dd2c37b01..98096a83d 100644 --- a/lesson_17/README.md +++ b/lesson_17/README.md @@ -16,34 +16,26 @@ Please review the following resources before lecture: ## Homework -- [ ] Complete [Library Management System](#library-management-system) assignment. +- [ ] Complete [Applying SOLID principles](#applying-solid-principles-bank-atm) exercise. -## Library Management System +## Applying SOLID Principles (Bank ATM) -For this assignment, you will build a library management system using object oriented principles. Pay attention to the requirements careful in order to implement your system successfully. +Your task for this assignment is add enhancements to an ATM simulator. The [BankAtm][bankatm-file] is at the center of the model, allowing us to add one or more `CheckingAccount` instances and make withdrawals or deposits via cash or check. You will need to implement at least two of the following functional enhancements to the `BankAtm` class WITHOUT adding a new method. Note that you can update existing methods, however. ### Functional Requirements -* We want to define a book and keep track of its title, isbn, author(s), number of pages, and whether it is checked out or not. -* We want define a patron with properties including their name and the books they have checked out. -* We want to define a library that: - * Allows us to add/remove a book to and from its collection of books. - * Allows us to register new patrons - * Allows us to check out books to patrons. - * Allows us to return books from patrons. +* We want to support a `SavingsAccount` that works just like the `CheckingAccount`, but doesn't allow you to write checks against the account. +* We want the `BankAtm` class to support the concept of a `BusinessCheckingAccount`. A business account requires that at least one of the owning accounts is a business. +* In addition to supporting checks and cash, we also want to support the concept of another monetary instrument called a `MoneyOrder`. Unlike a `Check`, a `MoneyOrder` withdraws funds from a source account immediately on creation for the purposes of this simulation.. +* For traceability, all of the transactions in the `BankAtm` class should logged. Create an `AuditLog` class that keeps a record of all debits and credits to any account and integrate it with the `BankAtm` class. +* For the `depositFunds` method that accepts a cash amount, we'd like the ability to deposit funds in a variety of currencies. Add a parameter that accepts a currency type and a new object that encapsulates the currency converter logic for converting a cash amount to the account currency type. ### Technical Requirements -* All of your types must be created in a unique sub-package under the `com.codedifferently.lesson17` namespace. -* Include tests for all of your classes and public methods. -* Include appropriate `javadoc` for your classes and methods. In VS Code, you can usually start a comment with `/**` and press `Enter` to let the IDE automatically create some starter text for you. +* You must integrate new features into the `BankAtm` without adding a new public method. Existing public methods may be modified without breaking existing functionality. +* You must update the `BankAtm` tests and may modify or add other applicable tests. +* Feel free to add the minimal number of classes, interfaces, or abstract classes needed to fulfill each requirement. +* You must update existing javadocs and may add new documentation for new types and methods you introduce. -### Tips - -* Read the functional requirements carefully, and take special care to identify *objects*, their *data*, and their *actions*. -* It is highly recommended that you write your tests firsts, and then build what you need to make the tests work. This is essential to [Test-Driven Development][tdd-article]. -* How will you ensure that books are properly checked out using the library? What if someone tries to check out a book the library doesn't own (or vice-versa with returns)? -* What are the public methods that you need to have to get the system working? Don't make anything public that you don't need to fulfill the requirements of the assignment. For instance, adding a getter method for a book's ISBN makes sense, but adding a method for returning the library's hours of operation is beyond the scope of this assignment. -* How can you use collections to manage books and patrons? For instance, it would be wise to ensure that patrons can only be registered once based on some unique identifier. - -[tdd-article]: https://semaphoreci.com/blog/test-driven-development +[bank-folder]: ./bank/ +[bankatm-file]: ./bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/BankAtm.java \ No newline at end of file diff --git a/lesson_17/oop/.gitattributes b/lesson_17/bank/.gitattributes similarity index 100% rename from lesson_17/oop/.gitattributes rename to lesson_17/bank/.gitattributes diff --git a/lesson_17/oop/.gitignore b/lesson_17/bank/.gitignore similarity index 100% rename from lesson_17/oop/.gitignore rename to lesson_17/bank/.gitignore diff --git a/lesson_17/oop/oop_app/build.gradle.kts b/lesson_17/bank/bank_app/build.gradle.kts similarity index 84% rename from lesson_17/oop/oop_app/build.gradle.kts rename to lesson_17/bank/bank_app/build.gradle.kts index ab18680b7..3f029f942 100644 --- a/lesson_17/oop/oop_app/build.gradle.kts +++ b/lesson_17/bank/bank_app/build.gradle.kts @@ -2,6 +2,7 @@ plugins { // Apply the application plugin to add support for building a CLI application in Java. application eclipse + jacoco id("com.diffplug.spotless") version "6.25.0" id("org.springframework.boot") version "3.2.2" id("com.adarshr.test-logger") version "4.0.0" @@ -38,8 +39,29 @@ application { tasks.named("test") { // Use JUnit Platform for unit tests. useJUnitPlatform() + finalizedBy(tasks.jacocoTestReport) } +tasks.jacocoTestReport { + dependsOn(tasks.test) + reports { + xml.required = true + } +} + +tasks.jacocoTestCoverageVerification { + violationRules { + rule { + limit { + minimum = "0.8".toBigDecimal() + } + } + } +} + +tasks.check { + dependsOn(tasks.jacocoTestCoverageVerification) +} configure { diff --git a/lesson_17/oop/oop_app/src/main/java/com/codedifferently/lesson17/Lesson17.java b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/Lesson17.java similarity index 100% rename from lesson_17/oop/oop_app/src/main/java/com/codedifferently/lesson17/Lesson17.java rename to lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/Lesson17.java diff --git a/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/BankAtm.java b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/BankAtm.java new file mode 100644 index 000000000..8cbcd3cc0 --- /dev/null +++ b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/BankAtm.java @@ -0,0 +1,88 @@ +package com.codedifferently.lesson17.bank; + +import com.codedifferently.lesson17.bank.exceptions.AccountNotFoundException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** Represents a bank ATM. */ +public class BankAtm { + + private final Map customerById = new HashMap<>(); + private final Map accountByNumber = new HashMap<>(); + + /** + * Adds a checking account to the bank. + * + * @param account The account to add. + */ + public void addAccount(CheckingAccount account) { + accountByNumber.put(account.getAccountNumber(), account); + account + .getOwners() + .forEach( + owner -> { + customerById.put(owner.getId(), owner); + }); + } + + /** + * Finds all accounts owned by a customer. + * + * @param customerId The ID of the customer. + * @return The unique set of accounts owned by the customer. + */ + public Set findAccountsByCustomerId(UUID customerId) { + return customerById.containsKey(customerId) + ? customerById.get(customerId).getAccounts() + : Set.of(); + } + + /** + * Deposits funds into an account. + * + * @param accountNumber The account number. + * @param amount The amount to deposit. + */ + public void depositFunds(String accountNumber, double amount) { + CheckingAccount account = getAccountOrThrow(accountNumber); + account.deposit(amount); + } + + /** + * Deposits funds into an account using a check. + * + * @param accountNumber The account number. + * @param check The check to deposit. + */ + public void depositFunds(String accountNumber, Check check) { + CheckingAccount account = getAccountOrThrow(accountNumber); + check.depositFunds(account); + } + + /** + * Withdraws funds from an account. + * + * @param accountNumber + * @param amount + */ + public void withdrawFunds(String accountNumber, double amount) { + CheckingAccount account = getAccountOrThrow(accountNumber); + account.withdraw(amount); + } + + /** + * Gets an account by its number or throws an exception if not found. + * + * @param accountNumber The account number. + * @return The account. + */ + private CheckingAccount getAccountOrThrow(String accountNumber) { + CheckingAccount account = accountByNumber.get(accountNumber); + if (account == null || account.isClosed()) { + throw new AccountNotFoundException("Account not found"); + } + return account; + } +} diff --git a/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/Check.java b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/Check.java new file mode 100644 index 000000000..061fa4a5c --- /dev/null +++ b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/Check.java @@ -0,0 +1,82 @@ +package com.codedifferently.lesson17.bank; + +import com.codedifferently.lesson17.bank.exceptions.CheckVoidedException; + +/** Represents a check. */ +public class Check { + + private final String checkNumber; + private final double amount; + private final CheckingAccount account; + private boolean isVoided = false; + + /** + * Creates a new check. + * + * @param checkNumber The check number. + * @param amount The amount of the check. + * @param account The account the check is drawn on. + */ + public Check(String checkNumber, double amount, CheckingAccount account) { + if (amount < 0) { + throw new IllegalArgumentException("Check amount must be positive"); + } + this.checkNumber = checkNumber; + this.amount = amount; + this.account = account; + } + + /** + * Gets the voided status of the check. + * + * @return True if the check is voided, and false otherwise. + */ + public boolean getIsVoided() { + return isVoided; + } + + /** Voids the check. */ + public void voidCheck() { + isVoided = true; + } + + /** + * Deposits the check into an account. + * + * @param toAccount The account to deposit the check into. + */ + public void depositFunds(CheckingAccount toAccount) { + if (isVoided) { + throw new CheckVoidedException("Check is voided"); + } + account.withdraw(amount); + toAccount.deposit(amount); + voidCheck(); + } + + @Override + public int hashCode() { + return checkNumber.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Check other) { + return checkNumber.equals(other.checkNumber); + } + return false; + } + + @Override + public String toString() { + return "Check{" + + "checkNumber='" + + checkNumber + + '\'' + + ", amount=" + + amount + + ", account=" + + account.getAccountNumber() + + '}'; + } +} diff --git a/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/CheckingAccount.java b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/CheckingAccount.java new file mode 100644 index 000000000..5d8aeb74d --- /dev/null +++ b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/CheckingAccount.java @@ -0,0 +1,131 @@ +package com.codedifferently.lesson17.bank; + +import com.codedifferently.lesson17.bank.exceptions.InsufficientFundsException; +import java.util.Set; + +/** Represents a checking account. */ +public class CheckingAccount { + + private final Set owners; + private final String accountNumber; + private double balance; + private boolean isActive; + + /** + * Creates a new checking account. + * + * @param accountNumber The account number. + * @param owners The owners of the account. + * @param initialBalance The initial balance of the account. + */ + public CheckingAccount(String accountNumber, Set owners, double initialBalance) { + this.accountNumber = accountNumber; + this.owners = owners; + this.balance = initialBalance; + isActive = true; + } + + /** + * Gets the account number. + * + * @return The account number. + */ + public String getAccountNumber() { + return accountNumber; + } + + /** + * Gets the owners of the account. + * + * @return The owners of the account. + */ + public Set getOwners() { + return owners; + } + + /** + * Deposits funds into the account. + * + * @param amount The amount to deposit. + */ + public void deposit(double amount) throws IllegalStateException { + if (isClosed()) { + throw new IllegalStateException("Cannot deposit to a closed account"); + } + if (amount <= 0) { + throw new IllegalArgumentException("Deposit amount must be positive"); + } + balance += amount; + } + + /** + * Withdraws funds from the account. + * + * @param amount + * @throws InsufficientFundsException + */ + public void withdraw(double amount) throws InsufficientFundsException { + if (isClosed()) { + throw new IllegalStateException("Cannot withdraw from a closed account"); + } + 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"); + } + balance -= amount; + } + + /** + * Gets the balance of the account. + * + * @return The balance of the account. + */ + public double getBalance() { + return balance; + } + + /** Closes the account. */ + public void closeAccount() throws IllegalStateException { + if (balance > 0) { + throw new IllegalStateException("Cannot close account with a positive balance"); + } + isActive = false; + } + + /** + * Checks if the account is closed. + * + * @return True if the account is closed, otherwise false. + */ + public boolean isClosed() { + return !isActive; + } + + @Override + public int hashCode() { + return accountNumber.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof CheckingAccount other) { + return accountNumber.equals(other.accountNumber); + } + return false; + } + + @Override + public String toString() { + return "CheckingAccount{" + + "accountNumber='" + + accountNumber + + '\'' + + ", balance=" + + balance + + ", isActive=" + + isActive + + '}'; + } +} diff --git a/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/Customer.java b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/Customer.java new file mode 100644 index 000000000..af0847134 --- /dev/null +++ b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/Customer.java @@ -0,0 +1,78 @@ +package com.codedifferently.lesson17.bank; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** Represents a customer of the bank. */ +public class Customer { + + private final UUID id; + private final String name; + private final Set accounts = new HashSet<>(); + + /** + * Creates a new customer. + * + * @param id The ID of the customer. + * @param name The name of the customer. + */ + public Customer(UUID id, String name) { + this.id = id; + this.name = name; + } + + /** + * Gets the ID of the customer. + * + * @return The ID of the customer. + */ + public UUID getId() { + return id; + } + + /** + * Gets the name of the customer. + * + * @return The name of the customer. + */ + public String getName() { + return name; + } + + /** + * Adds a checking account to the customer. + * + * @param account The account to add. + */ + public void addAccount(CheckingAccount account) { + accounts.add(account); + } + + /** + * Gets the accounts owned by the customer. + * + * @return The unique set of accounts owned by the customer. + */ + public Set getAccounts() { + return accounts; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Customer other) { + return id.equals(other.id); + } + return false; + } + + @Override + public String toString() { + return "Customer{" + "id=" + id + ", name='" + name + '\'' + '}'; + } +} diff --git a/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/exceptions/AccountNotFoundException.java b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/exceptions/AccountNotFoundException.java new file mode 100644 index 000000000..b03386e17 --- /dev/null +++ b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/exceptions/AccountNotFoundException.java @@ -0,0 +1,10 @@ +package com.codedifferently.lesson17.bank.exceptions; + +public class AccountNotFoundException extends RuntimeException { + + public AccountNotFoundException() {} + + public AccountNotFoundException(String message) { + super(message); + } +} diff --git a/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/exceptions/CheckVoidedException.java b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/exceptions/CheckVoidedException.java new file mode 100644 index 000000000..d51443d4a --- /dev/null +++ b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/exceptions/CheckVoidedException.java @@ -0,0 +1,10 @@ +package com.codedifferently.lesson17.bank.exceptions; + +public class CheckVoidedException extends RuntimeException { + + public CheckVoidedException() {} + + public CheckVoidedException(String message) { + super(message); + } +} diff --git a/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/exceptions/InsufficientFundsException.java b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/exceptions/InsufficientFundsException.java new file mode 100644 index 000000000..75de59329 --- /dev/null +++ b/lesson_17/bank/bank_app/src/main/java/com/codedifferently/lesson17/bank/exceptions/InsufficientFundsException.java @@ -0,0 +1,10 @@ +package com.codedifferently.lesson17.bank.exceptions; + +public class InsufficientFundsException extends RuntimeException { + + public InsufficientFundsException() {} + + public InsufficientFundsException(String message) { + super(message); + } +} diff --git a/lesson_17/oop/oop_app/src/test/java/com/codedifferently/lesson17/Lesson17Test.java b/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/Lesson17Test.java similarity index 87% rename from lesson_17/oop/oop_app/src/test/java/com/codedifferently/lesson17/Lesson17Test.java rename to lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/Lesson17Test.java index 9f1e93bb0..29ccdc4f5 100644 --- a/lesson_17/oop/oop_app/src/test/java/com/codedifferently/lesson17/Lesson17Test.java +++ b/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/Lesson17Test.java @@ -7,7 +7,7 @@ class Lesson17Test { @Test - void testLesson17_create() { + void testInstantiate() { assertThat(new Lesson17()).isNotNull(); } } diff --git a/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/BankAtmTest.java b/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/BankAtmTest.java new file mode 100644 index 000000000..fa4a913a2 --- /dev/null +++ b/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/BankAtmTest.java @@ -0,0 +1,110 @@ +package com.codedifferently.lesson17.bank; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +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 { + + private BankAtm classUnderTest; + private CheckingAccount account1; + private CheckingAccount account2; + private Customer customer1; + private Customer customer2; + + @BeforeEach + void setUp() { + classUnderTest = new BankAtm(); + 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 + void testAddAccount() { + // Arrange + Customer customer3 = new Customer(UUID.randomUUID(), "Alice Johnson"); + CheckingAccount account3 = new CheckingAccount("555555555", Set.of(customer3), 300.0); + customer3.addAccount(account3); + + // Act + classUnderTest.addAccount(account3); + + // Assert + Set accounts = classUnderTest.findAccountsByCustomerId(customer3.getId()); + assertThat(accounts).containsOnly(account3); + } + + @Test + void testFindAccountsByCustomerId() { + // Act + Set accounts = classUnderTest.findAccountsByCustomerId(customer1.getId()); + + // Assert + assertThat(accounts).containsOnly(account1, account2); + } + + @Test + void testDepositFunds() { + // Act + classUnderTest.depositFunds(account1.getAccountNumber(), 50.0); + + // Assert + assertThat(account1.getBalance()).isEqualTo(150.0); + } + + @Test + void testDepositFunds_Check() { + // Arrange + Check check = new Check("987654321", 100.0, account1); + + // Act + classUnderTest.depositFunds("987654321", check); + + // Assert + assertThat(account1.getBalance()).isEqualTo(0); + assertThat(account2.getBalance()).isEqualTo(300.0); + } + + @Test + void testDepositFunds_DoesntDepositCheckTwice() { + Check check = new Check("987654321", 100.0, account1); + + classUnderTest.depositFunds("987654321", check); + + assertThatExceptionOfType(CheckVoidedException.class) + .isThrownBy(() -> classUnderTest.depositFunds("987654321", check)) + .withMessage("Check is voided"); + } + + @Test + void testWithdrawFunds() { + // Act + classUnderTest.withdrawFunds(account2.getAccountNumber(), 50.0); + + // Assert + assertThat(account2.getBalance()).isEqualTo(150.0); + } + + @Test + void testWithdrawFunds_AccountNotFound() { + String nonExistingAccountNumber = "999999999"; + + // Act & Assert + assertThatExceptionOfType(AccountNotFoundException.class) + .isThrownBy(() -> classUnderTest.withdrawFunds(nonExistingAccountNumber, 50.0)) + .withMessage("Account not found"); + } +} diff --git a/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/CheckTest.java b/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/CheckTest.java new file mode 100644 index 000000000..6b62d39ba --- /dev/null +++ b/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/CheckTest.java @@ -0,0 +1,78 @@ +package com.codedifferently.lesson17.bank; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import com.codedifferently.lesson17.bank.exceptions.CheckVoidedException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class CheckTest { + + private CheckingAccount account1; + private CheckingAccount account2; + private Check classUnderTest; + + @BeforeEach + void setUp() { + account1 = new CheckingAccount("123456789", null, 100.0); + account2 = new CheckingAccount("987654321", null, 200.0); + classUnderTest = new Check("123456789", 50.0, account1); + } + + @Test + void testDepositFunds() { + // Act + classUnderTest.depositFunds(account2); + + // Assert + assertThat(account1.getBalance()).isEqualTo(50.0); + assertThat(account2.getBalance()).isEqualTo(250.0); + } + + @Test + void testDepositFunds_CheckVoided() { + // Arrange + classUnderTest.voidCheck(); + + // Act & Assert + assertThatExceptionOfType(CheckVoidedException.class) + .isThrownBy(() -> classUnderTest.depositFunds(account2)) + .withMessage("Check is voided"); + } + + @Test + void testConstructor_CantCreateCheckWithNegativeAmount() { + // Act & Assert + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> new Check("123456789", -50.0, account1)) + .withMessage("Check amount must be positive"); + } + + @Test + void testHashCode() { + // Arrange + Check otherCheck = new Check("123456789", 100.0, account1); + + // Assert + assertThat(classUnderTest.hashCode()).isEqualTo(otherCheck.hashCode()); + } + + @Test + void testEquals() { + // Arrange + Check otherCheck = new Check("123456789", 100.0, account1); + Check differentCheck = new Check("987654321", 100.0, account1); + + // Assert + assertThat(classUnderTest.equals(otherCheck)).isTrue(); + assertThat(classUnderTest.equals(differentCheck)).isFalse(); + } + + @Test + void testToString() { + // Assert + assertThat(classUnderTest.toString()) + .isEqualTo("Check{checkNumber='123456789', amount=50.0, account=123456789}"); + } +} diff --git a/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/CheckingAccountTest.java b/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/CheckingAccountTest.java new file mode 100644 index 000000000..f155d8e5b --- /dev/null +++ b/lesson_17/bank/bank_app/src/test/java/com/codedifferently/lesson17/bank/CheckingAccountTest.java @@ -0,0 +1,106 @@ +package com.codedifferently.lesson17.bank; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.codedifferently.lesson17.bank.exceptions.InsufficientFundsException; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class CheckingAccountTest { + + private CheckingAccount classUnderTest; + private Set 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 CheckingAccount("123456789", owners, 100.0); + } + + @Test + void getAccountNumber() { + assertEquals("123456789", classUnderTest.getAccountNumber()); + } + + @Test + void getOwners() { + assertEquals(owners, classUnderTest.getOwners()); + } + + @Test + void deposit() { + classUnderTest.deposit(50.0); + assertEquals(150.0, classUnderTest.getBalance()); + } + + @Test + void deposit_withNegativeAmount() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> classUnderTest.deposit(-50.0)); + } + + @Test + void withdraw() { + classUnderTest.withdraw(50.0); + assertEquals(50.0, classUnderTest.getBalance()); + } + + @Test + void withdraw_withNegativeAmount() { + assertThatExceptionOfType(IllegalStateException.class) + .isThrownBy(() -> classUnderTest.withdraw(-50.0)) + .withMessage("Withdrawal amount must be positive"); + } + + @Test + void withdraw_withInsufficientBalance() { + assertThatExceptionOfType(InsufficientFundsException.class) + .isThrownBy(() -> classUnderTest.withdraw(150.0)) + .withMessage("Account does not have enough funds for withdrawal"); + } + + @Test + void getBalance() { + assertEquals(100.0, classUnderTest.getBalance()); + } + + @Test + void closeAccount_withPositiveBalance() { + assertThatExceptionOfType(IllegalStateException.class) + .isThrownBy(() -> classUnderTest.closeAccount()); + } + + @Test + void isClosed() { + assertFalse(classUnderTest.isClosed()); + classUnderTest.withdraw(100); + classUnderTest.closeAccount(); + assertTrue(classUnderTest.isClosed()); + } + + @Test + void equals() { + CheckingAccount otherAccount = new CheckingAccount("123456789", owners, 200.0); + assertEquals(classUnderTest, otherAccount); + } + + @Test + void hashCodeTest() { + CheckingAccount otherAccount = new CheckingAccount("123456789", owners, 200.0); + assertEquals(classUnderTest.hashCode(), otherAccount.hashCode()); + } + + @Test + void toStringTest() { + String expected = "CheckingAccount{accountNumber='123456789', balance=100.0, isActive=true}"; + assertEquals(expected, classUnderTest.toString()); + } +} diff --git a/lesson_17/oop/gradle/wrapper/gradle-wrapper.jar b/lesson_17/bank/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from lesson_17/oop/gradle/wrapper/gradle-wrapper.jar rename to lesson_17/bank/gradle/wrapper/gradle-wrapper.jar diff --git a/lesson_17/oop/gradle/wrapper/gradle-wrapper.properties b/lesson_17/bank/gradle/wrapper/gradle-wrapper.properties similarity index 92% rename from lesson_17/oop/gradle/wrapper/gradle-wrapper.properties rename to lesson_17/bank/gradle/wrapper/gradle-wrapper.properties index bdc9a83b1..5c40527d4 100644 --- a/lesson_17/oop/gradle/wrapper/gradle-wrapper.properties +++ b/lesson_17/bank/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/lesson_17/oop/gradlew b/lesson_17/bank/gradlew similarity index 100% rename from lesson_17/oop/gradlew rename to lesson_17/bank/gradlew diff --git a/lesson_17/oop/gradlew.bat b/lesson_17/bank/gradlew.bat similarity index 100% rename from lesson_17/oop/gradlew.bat rename to lesson_17/bank/gradlew.bat diff --git a/lesson_17/oop/settings.gradle.kts b/lesson_17/bank/settings.gradle.kts similarity index 88% rename from lesson_17/oop/settings.gradle.kts rename to lesson_17/bank/settings.gradle.kts index e5004de63..444376e6a 100644 --- a/lesson_17/oop/settings.gradle.kts +++ b/lesson_17/bank/settings.gradle.kts @@ -9,5 +9,5 @@ includeBuild("../../lib/java/codedifferently-instructional") -rootProject.name = "lesson_17" -include("oop_app") +rootProject.name = "lesson_13" +include("bank_app") diff --git a/lesson_17/oop/oop_app/src/main/java/com/codedifferently/lesson17/library/Book.java b/lesson_17/oop/oop_app/src/main/java/com/codedifferently/lesson17/library/Book.java deleted file mode 100644 index 2e4d93037..000000000 --- a/lesson_17/oop/oop_app/src/main/java/com/codedifferently/lesson17/library/Book.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.codedifferently.lesson17.library; - -import java.util.List; - -/** Represents a book in the library. */ -public class Book { - private String title; - private String isbn; - private List authors; - private int numPages; - private boolean checkedOut; - - public Book(String title, String isbn, List authors, int numPages) { - this.title = title; - this.isbn = isbn; - this.authors = authors; - this.numPages = numPages; - this.checkedOut = false; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getIsbn() { - return isbn; - } - - public void setIsbn(String isbn) { - this.isbn = isbn; - } - - public List getAuthors() { - return authors; - } - - public void setAuthors(List authors) { - this.authors = authors; - } - - public int getNumPages() { - return numPages; - } - - public void setNumPages(int numPages) { - this.numPages = numPages; - } - - public boolean isCheckedOut() { - return checkedOut; - } - - public void setCheckedOut(boolean checkedOut) { - this.checkedOut = checkedOut; - } -} diff --git a/lesson_17/oop/oop_app/src/main/java/com/codedifferently/lesson17/library/Library.java b/lesson_17/oop/oop_app/src/main/java/com/codedifferently/lesson17/library/Library.java deleted file mode 100644 index 8f56dbd2f..000000000 --- a/lesson_17/oop/oop_app/src/main/java/com/codedifferently/lesson17/library/Library.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.codedifferently.lesson17.library; - -import java.util.ArrayList; -import java.util.List; - -/** Represents a library with books and patrons. */ -public class Library { - private List marvelComics; - private List patrons; - - public Library() { - this.marvelComics = new ArrayList<>(); - this.patrons = new ArrayList<>(); - } - - public void addComic(Book comic) { - marvelComics.add(comic); - } - - public void removeComic(Book comic) { - marvelComics.remove(comic); - } - - public void registerPatron(Patron patron) { - patrons.add(patron); - } - - public void checkOutComic(Book comic, Patron patron) { - if (marvelComics.contains(comic) && patrons.contains(patron)) { - patron.checkOutBook(comic); - } else { - System.out.println("Marvel comic or patron not found in the library."); - } - } - - public void returnComic(Book comic, Patron patron) { - if (marvelComics.contains(comic) && patrons.contains(patron)) { - patron.returnBook(comic); - } else { - System.out.println("Marvel comic or patron not found in the library."); - } - } - - public List getMarvelComics() { - return marvelComics; - } - - public void setMarvelComics(List marvelComics) { - this.marvelComics = marvelComics; - } - - public List getPatrons() { - return patrons; - } - - public void setPatrons(List patrons) { - this.patrons = patrons; - } -} diff --git a/lesson_17/oop/oop_app/src/main/java/com/codedifferently/lesson17/library/Patron.java b/lesson_17/oop/oop_app/src/main/java/com/codedifferently/lesson17/library/Patron.java deleted file mode 100644 index 427a02f6a..000000000 --- a/lesson_17/oop/oop_app/src/main/java/com/codedifferently/lesson17/library/Patron.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.codedifferently.lesson17.library; - -import java.util.ArrayList; -import java.util.List; - -/** Represents a patron of the library. */ -public class Patron { - private String name; - private List checkedOutBooks; - - public Patron(String name) { - this.name = name; - this.checkedOutBooks = new ArrayList<>(); - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public List getCheckedOutBooks() { - return checkedOutBooks; - } - - public void setCheckedOutBooks(List checkedOutBooks) { - this.checkedOutBooks = checkedOutBooks; - } - - public void checkOutBook(Book book) { - if (!book.isCheckedOut()) { - book.setCheckedOut(true); - checkedOutBooks.add(book); - } else { - System.out.println("Marvel comic is already checked out."); - } - } - - public void returnBook(Book book) { - if (checkedOutBooks.contains(book)) { - book.setCheckedOut(false); - checkedOutBooks.remove(book); - } else { - System.out.println("Marvel comic is not checked out by this patron."); - } - } -}