From e4b38a57edefd99050b4aaf42e9f68552ef57565 Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Sat, 28 Feb 2026 11:29:39 +0100 Subject: [PATCH 01/17] perf[User]: Improve User validation and update corresponding JUnit tests --- .../edu/group5/app/model/user/UserTest.java | 166 ++++++++++-------- 1 file changed, 92 insertions(+), 74 deletions(-) diff --git a/src/test/java/edu/group5/app/model/user/UserTest.java b/src/test/java/edu/group5/app/model/user/UserTest.java index 1e7277a..324e3a0 100644 --- a/src/test/java/edu/group5/app/model/user/UserTest.java +++ b/src/test/java/edu/group5/app/model/user/UserTest.java @@ -1,136 +1,154 @@ package edu.group5.app.model.user; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +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 org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; public class UserTest { - private static final int TEST_USER_ID = 1; - private static final String TEST_ROLE = "Donor"; - private static final String TEST_FIRST_NAME = "John"; - private static final String TEST_LAST_NAME = "Doe"; - private static final String TEST_EMAIL = "john.doe@example.com"; - private static final String TEST_PASSWORD = "password123"; - private static final String TEST_PASSWORD_HASH = new BCryptPasswordEncoder().encode(TEST_PASSWORD); - private static final int WRONG_USER_ID = -5; - private static final String WRONG_ROLE = ""; - private static final String WRONG_FIRST_NAME = ""; - private static final String WRONG_LAST_NAME = ""; - private static final String WRONG_EMAIL = ""; - private static final String WRONG_PASSWORD_HASH = ""; + private int testUserId; + private String testRole; + private String testFirstName; + private String testLastName; + private String testEmail; + private String testPassword; + private String testPasswordHash; - private void constructorTest(int userId, String role, String firstName, String lastName, String email, String passwordHash, boolean negativeTest) { - boolean exceptionThrown = negativeTest; - try { - new User(userId, role, firstName, lastName, email, passwordHash); - } catch (Exception e) { - exceptionThrown = !negativeTest; - } finally { - assertFalse(exceptionThrown); - } + @BeforeEach + void setUp() { + testUserId = 1; + testRole = "Donor"; + testFirstName = "John"; + testLastName = "Doe"; + testEmail = "john.doe@example.com"; + testPassword = "password123"; + testPasswordHash = new BCryptPasswordEncoder().encode(testPassword); } - @Test - public void constructorThrowsNoException() { - constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, false); - } + private void constructorTest(int userId, String role, String firstName, + String lastName, String email, String passwordHash, + String expectedMessage) { + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> new User(userId, role, firstName, lastName, email, passwordHash) + ); - @Test - public void constructorWithNegativeUserIdThrowsException() { - constructorTest(WRONG_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, true); + assertEquals(expectedMessage, exception.getMessage()); } @Test - public void constructorWithEmptyRoleThrowsException() { - constructorTest(TEST_USER_ID, WRONG_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, true); + void constructorCreatesValidUser() { + User user = new User(testUserId, testRole, testFirstName, + testLastName, testEmail, testPasswordHash); + assertEquals(testUserId, user.getUserId()); + assertEquals(testRole, user.getRole()); + assertEquals(testFirstName, user.getFirstName()); + assertEquals(testLastName, user.getLastName()); + assertEquals(testEmail, user.getEmail()); + assertEquals(testPasswordHash, user.getPasswordHash()); } @Test - public void constructorWithEmptyFirstNameThrowsException() { - constructorTest(TEST_USER_ID, TEST_ROLE, WRONG_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, true); + void constructorWithNegativeUserIdThrowsException() { + constructorTest(-1, testRole, testFirstName, testLastName, + testEmail, testPasswordHash, "User ID must be positive"); } @Test - public void constructorWithEmptyLastNameThrowsException() { - constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, WRONG_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, true); + void constructorWithNullRoleThrowsException() { + constructorTest(testUserId, null, testFirstName, testLastName, + testEmail, testPasswordHash, "Role cannot be null or empty"); } @Test - public void constructorWithEmptyEmailThrowsException() { - constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, WRONG_EMAIL, TEST_PASSWORD_HASH, true); + void constructorWithEmptyRoleThrowsException() { + constructorTest(testUserId, "", testFirstName, testLastName, + testEmail, testPasswordHash, "Role cannot be null or empty"); } @Test - public void constructorWithEmptyPasswordHashThrowsException() { - constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, WRONG_PASSWORD_HASH, true); + void constructorWithNullFirstNameThrowsException() { + constructorTest(testUserId, testRole, null, testLastName, + testEmail, testPasswordHash, "First name cannot be null or empty"); } - private void getMethodComparer(User user, boolean negativeTest) { - if (user.getUserId() == TEST_USER_ID && - user.getRole().equals(TEST_ROLE) && - user.getFirstName().equals(TEST_FIRST_NAME) && - user.getLastName().equals(TEST_LAST_NAME) && - user.getEmail().equals(TEST_EMAIL) && - user.getPasswordHash() != null) { - assertFalse(negativeTest); - } + @Test + void constructorWithEmptyFirstNameThrowsException() { + constructorTest(testUserId, testRole, "", testLastName, + testEmail, testPasswordHash, "First name cannot be null or empty"); } @Test - public void getMethodsReturnCorrectInformation() { - User testUser = new User(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH); - getMethodComparer(testUser, false); + void constructorWithNullLastNameThrowsException() { + constructorTest(testUserId, testRole, testFirstName, null, + testEmail, testPasswordHash, "Last name cannot be null or empty"); } @Test - public void verifyPasswordReturnsTrueForCorrectPassword() { - User testUser = new User(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH); - assertTrue(testUser.verifyPassword(TEST_PASSWORD)); + void constructorWithEmptyLastNameThrowsException() { + constructorTest(testUserId, testRole, testFirstName, "", + testEmail, testPasswordHash, "Last name cannot be null or empty"); } @Test - public void verifyPasswordReturnsFalseForIncorrectPassword() { - User testUser = new User(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH); - assertFalse(testUser.verifyPassword("wrongPassword")); + void constructorWithNullEmailThrowsException() { + constructorTest(testUserId, testRole, testFirstName, testLastName, + null, testPasswordHash, "Email cannot be null or empty"); } @Test - public void verifyPasswordReturnsFalseForNullPassword() { - User testUser = new User(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH); - assertFalse(testUser.verifyPassword(null)); + void constructorWithEmptyEmailThrowsException() { + constructorTest(testUserId, testRole, testFirstName, testLastName, + "", testPasswordHash, "Email cannot be null or empty"); } @Test - public void verifyPasswordReturnsFalseForEmptyPassword() { - User testUser = new User(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH); - assertFalse(testUser.verifyPassword("")); + void constructorWithNullPasswordHashThrowsException() { + constructorTest(testUserId, testRole, testFirstName, testLastName, + testEmail, null, "Password hash cannot be null or empty"); } @Test - public void constructorWithNullRoleThrowsException() { - constructorTest(TEST_USER_ID, null, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, true); + void constructorWithEmptyPasswordHashThrowsException() { + constructorTest(testUserId, testRole, testFirstName, testLastName, + testEmail, "", "Password hash cannot be null or empty"); } + @Test - public void constructorWithNullFirstNameThrowsException() { - constructorTest(TEST_USER_ID, TEST_ROLE, null, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, true); + void verifyPasswordReturnsTrueForCorrectPassword() { + User user = new User(testUserId, testRole, testFirstName, + testLastName, testEmail, testPasswordHash); + + assertTrue(user.verifyPassword(testPassword)); } @Test - public void constructorWithNullLastNameThrowsException() { - constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, null, TEST_EMAIL, TEST_PASSWORD_HASH, true); + void verifyPasswordReturnsFalseForIncorrectPassword() { + User user = new User(testUserId, testRole, testFirstName, + testLastName, testEmail, testPasswordHash); + + assertFalse(user.verifyPassword("wrongPassword")); } @Test - public void constructorWithNullEmailThrowsException() { - constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, null, TEST_PASSWORD_HASH, true); + void verifyPasswordReturnsFalseForNullPassword() { + User user = new User(testUserId, testRole, testFirstName, + testLastName, testEmail, testPasswordHash); + + assertFalse(user.verifyPassword(null)); } @Test - public void constructorWithNullPasswordHashThrowsException() { - constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, null, true); + void verifyPasswordReturnsFalseForEmptyPassword() { + User user = new User(testUserId, testRole, testFirstName, + testLastName, testEmail, testPasswordHash); + + assertFalse(user.verifyPassword("")); } -} +} \ No newline at end of file From 0db6023cb41551536298c4362ea1eb6e492fc27f Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Mon, 2 Mar 2026 15:54:04 +0100 Subject: [PATCH 02/17] Perf: Update model classes and write Customer class --- .../app/model/donation/DonationService.java | 41 +++++++++++++ .../edu/group5/app/model/user/Customer.java | 49 +++++++++++++++ .../java/edu/group5/app/model/user/User.java | 2 +- .../user/{UserTest.java => CustomerTest.java} | 59 ++++++++----------- 4 files changed, 116 insertions(+), 35 deletions(-) create mode 100644 src/main/java/edu/group5/app/model/donation/DonationService.java create mode 100644 src/main/java/edu/group5/app/model/user/Customer.java rename src/test/java/edu/group5/app/model/user/{UserTest.java => CustomerTest.java} (67%) diff --git a/src/main/java/edu/group5/app/model/donation/DonationService.java b/src/main/java/edu/group5/app/model/donation/DonationService.java new file mode 100644 index 0000000..ecd13da --- /dev/null +++ b/src/main/java/edu/group5/app/model/donation/DonationService.java @@ -0,0 +1,41 @@ +package edu.group5.app.service; + +import java.math.BigDecimal; +import java.sql.Timestamp; +import edu.group5.app.model.donation.Donation; +import edu.group5.app.model.organization.Organization; +import edu.group5.app.model.organization.OrganizationRepository; +import edu.group5.app.model.user.Customer; +import edu.group5.app.model.donation.DonationRepository; + +public class DonationService { + + private final DonationRepository donationRepository; + private final OrganizationRepository organizationRepository; + + public DonationService(DonationRepository donationRepository, + OrganizationRepository organizationRepository) { + this.donationRepository = donationRepository; + this.organizationRepository = organizationRepository; + } + + public boolean donate(Customer customer, + int orgNumber, + BigDecimal amount) { + if (customer == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) { + return false; + } + Organization org = organizationRepository.findByOrgNumber(orgNumber); + if (org == null) { + return false; + } + Donation donation = new Donation( + customer.getUserId(), + org.getOrgNumber(), + amount, + new Timestamp(System.currentTimeMillis()) + ); + donationRepository.addDonation(donation); + return true; + } +} \ No newline at end of file diff --git a/src/main/java/edu/group5/app/model/user/Customer.java b/src/main/java/edu/group5/app/model/user/Customer.java new file mode 100644 index 0000000..5d61aab --- /dev/null +++ b/src/main/java/edu/group5/app/model/user/Customer.java @@ -0,0 +1,49 @@ +package edu.group5.app.model.user; + +import java.util.HashMap; +import java.util.Map; + +import edu.group5.app.model.organization.Organization; + +/** + * Customer class represents a customer in the system. + * It extends the User class and includes additional functionality specific to customers, + * such as managing their preferences for organizations. + * Each customer has a map of preferences that associates organization numbers with the corresponding Organization objects. + */ +public class Customer extends User { + private List preferences; + + /** + * Constructs a Customer object. + * Calls the {@link User} superclass constructor to initialize common user fields + * and initializes the preferences list. + * @param userId the unique identifier for the user, must be a positive integer + * @param firstName the first name of the customer + * @param lastName the last name of the customer + * @param email the email address of the customer + * @param passwordHash the hashed password of the customer + * @throws IllegalArgumentException if any required field is invalid + */ + public Customer(int userId, + String firstName, + String lastName, + String email, + String passwordHash) { + + super(userId, "Customer", firstName, lastName, email, passwordHash); + this.preferences = new ArrayList<>(); + } + + public List getPreferences() { + return preferences; + } + + public void addPreference(int orgNumber) { + preferences.add(orgNumber); + } + + public void removePreference(int orgNumber) { + preferences.remove(Integer.valueOf(orgNumber)); + } +} \ No newline at end of file diff --git a/src/main/java/edu/group5/app/model/user/User.java b/src/main/java/edu/group5/app/model/user/User.java index 8dd48cd..e20e4f4 100644 --- a/src/main/java/edu/group5/app/model/user/User.java +++ b/src/main/java/edu/group5/app/model/user/User.java @@ -9,7 +9,7 @@ * by comparing the provided plaintext password with the stored hashed password using BCrypt. * */ -public class User { +public abstract class User { private int userId; private String role; private String firstName; diff --git a/src/test/java/edu/group5/app/model/user/UserTest.java b/src/test/java/edu/group5/app/model/user/CustomerTest.java similarity index 67% rename from src/test/java/edu/group5/app/model/user/UserTest.java rename to src/test/java/edu/group5/app/model/user/CustomerTest.java index 324e3a0..8678fc9 100644 --- a/src/test/java/edu/group5/app/model/user/UserTest.java +++ b/src/test/java/edu/group5/app/model/user/CustomerTest.java @@ -1,5 +1,4 @@ package edu.group5.app.model.user; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -9,10 +8,9 @@ import org.junit.jupiter.api.Test; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -public class UserTest { +public class CustomerTest { private int testUserId; - private String testRole; private String testFirstName; private String testLastName; private String testEmail; @@ -22,7 +20,6 @@ public class UserTest { @BeforeEach void setUp() { testUserId = 1; - testRole = "Donor"; testFirstName = "John"; testLastName = "Doe"; testEmail = "john.doe@example.com"; @@ -30,12 +27,12 @@ void setUp() { testPasswordHash = new BCryptPasswordEncoder().encode(testPassword); } - private void constructorTest(int userId, String role, String firstName, + private void constructorTest(int userId, String firstName, String lastName, String email, String passwordHash, String expectedMessage) { IllegalArgumentException exception = assertThrows( IllegalArgumentException.class, - () -> new User(userId, role, firstName, lastName, email, passwordHash) + () -> new Customer(userId, firstName, lastName, email, passwordHash) ); assertEquals(expectedMessage, exception.getMessage()); @@ -43,10 +40,9 @@ private void constructorTest(int userId, String role, String firstName, @Test void constructorCreatesValidUser() { - User user = new User(testUserId, testRole, testFirstName, + User user = new Customer(testUserId, testFirstName, testLastName, testEmail, testPasswordHash); assertEquals(testUserId, user.getUserId()); - assertEquals(testRole, user.getRole()); assertEquals(testFirstName, user.getFirstName()); assertEquals(testLastName, user.getLastName()); assertEquals(testEmail, user.getEmail()); @@ -54,75 +50,70 @@ void constructorCreatesValidUser() { } @Test - void constructorWithNegativeUserIdThrowsException() { - constructorTest(-1, testRole, testFirstName, testLastName, - testEmail, testPasswordHash, "User ID must be positive"); - } - - @Test - void constructorWithNullRoleThrowsException() { - constructorTest(testUserId, null, testFirstName, testLastName, - testEmail, testPasswordHash, "Role cannot be null or empty"); + void testInstanceOfCustomer() { + User user = new Customer(testUserId, testFirstName, + testLastName, testEmail, testPasswordHash); + assertTrue(user instanceof Customer); } @Test - void constructorWithEmptyRoleThrowsException() { - constructorTest(testUserId, "", testFirstName, testLastName, - testEmail, testPasswordHash, "Role cannot be null or empty"); + void constructorWithNegativeUserIdThrowsException() { + constructorTest(-1, testFirstName, testLastName, + testEmail, testPasswordHash, "User ID must be positive"); } @Test void constructorWithNullFirstNameThrowsException() { - constructorTest(testUserId, testRole, null, testLastName, + constructorTest(testUserId, null, testLastName, testEmail, testPasswordHash, "First name cannot be null or empty"); } @Test void constructorWithEmptyFirstNameThrowsException() { - constructorTest(testUserId, testRole, "", testLastName, + constructorTest(testUserId, "", testLastName, testEmail, testPasswordHash, "First name cannot be null or empty"); } @Test void constructorWithNullLastNameThrowsException() { - constructorTest(testUserId, testRole, testFirstName, null, + constructorTest(testUserId, testFirstName, null, testEmail, testPasswordHash, "Last name cannot be null or empty"); } @Test void constructorWithEmptyLastNameThrowsException() { - constructorTest(testUserId, testRole, testFirstName, "", - testEmail, testPasswordHash, "Last name cannot be null or empty"); + constructorTest(testUserId, testFirstName, + "", testEmail, testPasswordHash, "Last name cannot be null or empty"); } @Test void constructorWithNullEmailThrowsException() { - constructorTest(testUserId, testRole, testFirstName, testLastName, + constructorTest(testUserId, testFirstName, testLastName, null, testPasswordHash, "Email cannot be null or empty"); } @Test void constructorWithEmptyEmailThrowsException() { - constructorTest(testUserId, testRole, testFirstName, testLastName, + constructorTest(testUserId, testFirstName, testLastName, "", testPasswordHash, "Email cannot be null or empty"); } @Test void constructorWithNullPasswordHashThrowsException() { - constructorTest(testUserId, testRole, testFirstName, testLastName, + constructorTest(testUserId, testFirstName, testLastName, testEmail, null, "Password hash cannot be null or empty"); } @Test void constructorWithEmptyPasswordHashThrowsException() { - constructorTest(testUserId, testRole, testFirstName, testLastName, + constructorTest(testUserId, testFirstName, testLastName, testEmail, "", "Password hash cannot be null or empty"); } @Test void verifyPasswordReturnsTrueForCorrectPassword() { - User user = new User(testUserId, testRole, testFirstName, + User user = new Customer(testUserId, testFirstName, testLastName, testEmail, testPasswordHash); assertTrue(user.verifyPassword(testPassword)); @@ -130,7 +121,7 @@ void verifyPasswordReturnsTrueForCorrectPassword() { @Test void verifyPasswordReturnsFalseForIncorrectPassword() { - User user = new User(testUserId, testRole, testFirstName, + User user = new Customer(testUserId, testFirstName, testLastName, testEmail, testPasswordHash); assertFalse(user.verifyPassword("wrongPassword")); @@ -138,7 +129,7 @@ void verifyPasswordReturnsFalseForIncorrectPassword() { @Test void verifyPasswordReturnsFalseForNullPassword() { - User user = new User(testUserId, testRole, testFirstName, + User user = new Customer(testUserId, testFirstName, testLastName, testEmail, testPasswordHash); assertFalse(user.verifyPassword(null)); @@ -146,9 +137,9 @@ void verifyPasswordReturnsFalseForNullPassword() { @Test void verifyPasswordReturnsFalseForEmptyPassword() { - User user = new User(testUserId, testRole, testFirstName, + User user = new Customer(testUserId, testFirstName, testLastName, testEmail, testPasswordHash); assertFalse(user.verifyPassword("")); } -} \ No newline at end of file +} From 7c90b5bf41097c9724219436ce1636d38a66b122 Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Mon, 2 Mar 2026 16:21:01 +0100 Subject: [PATCH 03/17] perf: Add exception handling to increase robust code --- .../edu/group5/app/model/user/Customer.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/java/edu/group5/app/model/user/Customer.java b/src/main/java/edu/group5/app/model/user/Customer.java index 5d61aab..3e6465d 100644 --- a/src/main/java/edu/group5/app/model/user/Customer.java +++ b/src/main/java/edu/group5/app/model/user/Customer.java @@ -31,19 +31,25 @@ public Customer(int userId, String email, String passwordHash) { - super(userId, "Customer", firstName, lastName, email, passwordHash); - this.preferences = new ArrayList<>(); + super(userId, "Customer", firstName, lastName, email, passwordHash); + this.preferences = new ArrayList<>(); } - public List getPreferences() { - return preferences; - } + public List getPreferences() { + return preferences; + } - public void addPreference(int orgNumber) { - preferences.add(orgNumber); + public void addPreference(int orgNumber) { + if (!preferences.contains(orgNumber)) { + throw new IllegalArgumentException("Organization number already in preferences"); } + preferences.add(orgNumber); + } - public void removePreference(int orgNumber) { - preferences.remove(Integer.valueOf(orgNumber)); + public void removePreference(int orgNumber) { + if (!preferences.contains(orgNumber)) { + throw new IllegalArgumentException("Organization number not found in preferences"); } + preferences.remove(Integer.valueOf(orgNumber)); + } } \ No newline at end of file From 02c60a53cb623b03112d9f4c92cc6b6785092a37 Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Mon, 2 Mar 2026 16:48:10 +0100 Subject: [PATCH 04/17] Update: update imports to Customer class --- src/main/java/edu/group5/app/model/user/Customer.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/edu/group5/app/model/user/Customer.java b/src/main/java/edu/group5/app/model/user/Customer.java index 3e6465d..ab7bcd2 100644 --- a/src/main/java/edu/group5/app/model/user/Customer.java +++ b/src/main/java/edu/group5/app/model/user/Customer.java @@ -1,7 +1,7 @@ package edu.group5.app.model.user; -import java.util.HashMap; -import java.util.Map; +import java.util.List; +import java.util.ArrayList; import edu.group5.app.model.organization.Organization; @@ -25,11 +25,8 @@ public class Customer extends User { * @param passwordHash the hashed password of the customer * @throws IllegalArgumentException if any required field is invalid */ - public Customer(int userId, - String firstName, - String lastName, - String email, - String passwordHash) { + public Customer(int userId, String firstName, String lastName, + String email, String passwordHash) { super(userId, "Customer", firstName, lastName, email, passwordHash); this.preferences = new ArrayList<>(); From 3335ba2780e2a6eaf24fa586512607e60a7bb8a6 Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Tue, 3 Mar 2026 10:41:20 +0100 Subject: [PATCH 05/17] fix: fix logic when adding preferance --- src/main/java/edu/group5/app/model/user/Customer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/group5/app/model/user/Customer.java b/src/main/java/edu/group5/app/model/user/Customer.java index ab7bcd2..4f52b19 100644 --- a/src/main/java/edu/group5/app/model/user/Customer.java +++ b/src/main/java/edu/group5/app/model/user/Customer.java @@ -37,7 +37,7 @@ public List getPreferences() { } public void addPreference(int orgNumber) { - if (!preferences.contains(orgNumber)) { + if (preferences.contains(orgNumber)) { throw new IllegalArgumentException("Organization number already in preferences"); } preferences.add(orgNumber); From c4b2eac9c134a3cd29aa24114a1aab43de00a868 Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Tue, 3 Mar 2026 11:36:15 +0100 Subject: [PATCH 06/17] update: update relationship between Customer and intermideary DonationService --- .../app/model/donation/DonationService.java | 61 +++++++++++++------ .../edu/group5/app/model/user/Customer.java | 36 ++++++++--- 2 files changed, 68 insertions(+), 29 deletions(-) diff --git a/src/main/java/edu/group5/app/model/donation/DonationService.java b/src/main/java/edu/group5/app/model/donation/DonationService.java index ecd13da..6f12f91 100644 --- a/src/main/java/edu/group5/app/model/donation/DonationService.java +++ b/src/main/java/edu/group5/app/model/donation/DonationService.java @@ -8,34 +8,55 @@ import edu.group5.app.model.user.Customer; import edu.group5.app.model.donation.DonationRepository; +/** + * DonationService class provides functionality for handling donations in the system. + * It interacts with the DonationRepository to manage donation records + * and the OrganizationRepository to validate organization information. + * The donate method allows a customer to make a donation to a specified organization, + * ensuring that the customer, organization, and donation amount are valid before processing the donation. + */ public class DonationService { private final DonationRepository donationRepository; private final OrganizationRepository organizationRepository; + /** + * Constructor for DonationService. Initializes the service with the required repositories. + * @param donationRepository the repository for managing donation records + * @param organizationRepository the repository for managing organization information + * @throws IllegalArgumentException if either repository is null + */ public DonationService(DonationRepository donationRepository, OrganizationRepository organizationRepository) { - this.donationRepository = donationRepository; - this.organizationRepository = organizationRepository; + if (donationRepository == null) { + throw new IllegalArgumentException("DonationRepository cannot be null"); + } + if (organizationRepository == null) { + throw new IllegalArgumentException("OrganizationRepository cannot be null"); + } + this.donationRepository = donationRepository; + this.organizationRepository = organizationRepository; } - public boolean donate(Customer customer, - int orgNumber, - BigDecimal amount) { - if (customer == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) { - return false; - } - Organization org = organizationRepository.findByOrgNumber(orgNumber); - if (org == null) { - return false; - } - Donation donation = new Donation( - customer.getUserId(), - org.getOrgNumber(), - amount, - new Timestamp(System.currentTimeMillis()) - ); - donationRepository.addDonation(donation); - return true; + /** + * Processes a donation from a customer to a specified organization with a given amount. + * Validates the customer, organization number, and donation amount before creating a donation record. + * @param customer the customer making the donation + * @param orgNumber the organization number to which the donation is made + * @param amount the amount of the donation + * @return true if the donation is successfully processed, false otherwise + */ + public boolean donate(Customer customer, int orgNumber, BigDecimal amount) { + if (customer == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) { + return false; } + Organization org = organizationRepository.findByOrgNumber(orgNumber); + if (org == null) { + return false; + } + Donation donation = new Donation(customer.getUserId(), org.getOrgNumber(), + amount, new Timestamp(System.currentTimeMillis())); + donationRepository.addDonation(donation); + return true; + } } \ No newline at end of file diff --git a/src/main/java/edu/group5/app/model/user/Customer.java b/src/main/java/edu/group5/app/model/user/Customer.java index 4f52b19..6ae6109 100644 --- a/src/main/java/edu/group5/app/model/user/Customer.java +++ b/src/main/java/edu/group5/app/model/user/Customer.java @@ -36,17 +36,35 @@ public List getPreferences() { return preferences; } - public void addPreference(int orgNumber) { - if (preferences.contains(orgNumber)) { - throw new IllegalArgumentException("Organization number already in preferences"); + public boolean addPreference(int orgNumber) { + try { + if (orgNumber <= 0) { + throw new IllegalArgumentException("Organization number must be a positive integer"); + } + if (preferences.contains(orgNumber)) { + throw new IllegalStateException("Organization number already in preferences"); + } + preferences.add(orgNumber); + return true; + } catch (Exception e) { + System.out.println("Add preference failed: " + e.getMessage()); + return false; } - preferences.add(orgNumber); } - public void removePreference(int orgNumber) { - if (!preferences.contains(orgNumber)) { - throw new IllegalArgumentException("Organization number not found in preferences"); + public boolean removePreference(int orgNumber) { + try { + if (orgNumber <= 0) { + throw new IllegalArgumentException("Organization number must be a positive integer"); + } + if (!preferences.contains(orgNumber)) { + throw new IllegalStateException("Organization number not found in preferences"); + } + preferences.remove(Integer.valueOf(orgNumber)); + return true; + } catch (Exception e) { + System.out.println("Remove preference failed: " + e.getMessage()); + return false; } - preferences.remove(Integer.valueOf(orgNumber)); } -} \ No newline at end of file +} From 6b313cc4ef9b9a4f7c7a264d6dd5e51b93df0d2f Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Tue, 3 Mar 2026 13:44:02 +0100 Subject: [PATCH 07/17] update[Customer]: update customer class to better suit the application --- .../edu/group5/app/model/user/Customer.java | 91 ++++++++++--------- 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/src/main/java/edu/group5/app/model/user/Customer.java b/src/main/java/edu/group5/app/model/user/Customer.java index 6ae6109..2c221b0 100644 --- a/src/main/java/edu/group5/app/model/user/Customer.java +++ b/src/main/java/edu/group5/app/model/user/Customer.java @@ -14,57 +14,64 @@ public class Customer extends User { private List preferences; - /** - * Constructs a Customer object. - * Calls the {@link User} superclass constructor to initialize common user fields - * and initializes the preferences list. - * @param userId the unique identifier for the user, must be a positive integer - * @param firstName the first name of the customer - * @param lastName the last name of the customer - * @param email the email address of the customer - * @param passwordHash the hashed password of the customer - * @throws IllegalArgumentException if any required field is invalid - */ - public Customer(int userId, String firstName, String lastName, - String email, String passwordHash) { - +/** + * Constructs a new Customer object for new registrations. + * The role is automatically set to "Customer" and the preferences list is initialized empty. + * + * @param userId the unique identifier for the user, must be positive + * @param firstName the first name of the customer + * @param lastName the last name of the customer + * @param email the email address of the customer + * @param passwordHash the hashed password of the customer + * @throws IllegalArgumentException if any parameter is invalid (null, empty, or userId ≤ 0) + */ +public Customer(int userId, String firstName, String lastName, + String email, String passwordHash) { super(userId, "Customer", firstName, lastName, email, passwordHash); this.preferences = new ArrayList<>(); - } +} + +/** + * Constructs a Customer object for existing users loaded from the database + * or for cases where the role is already defined. + * The role should normally be "Customer", but this constructor allows flexibility + * (e.g., for testing or future user types). Preferences list is initialized empty. + * + * @param userId the unique identifier for the user, must be positive + * @param role the role of the user (should be "Customer" for customer instances) + * @param firstName the first name of the customer + * @param lastName the last name of the customer + * @param email the email address of the customer + * @param passwordHash the hashed password of the customer + * @throws IllegalArgumentException if any parameter is invalid (null, empty, or userId ≤ 0) + */ +public Customer(int userId, String role, String firstName, String lastName, + String email, String passwordHash) { + super(userId, role, firstName, lastName, email, passwordHash); + this.preferences = new ArrayList<>(); +} public List getPreferences() { return preferences; } - public boolean addPreference(int orgNumber) { - try { - if (orgNumber <= 0) { - throw new IllegalArgumentException("Organization number must be a positive integer"); - } - if (preferences.contains(orgNumber)) { - throw new IllegalStateException("Organization number already in preferences"); - } - preferences.add(orgNumber); - return true; - } catch (Exception e) { - System.out.println("Add preference failed: " + e.getMessage()); - return false; + public void addPreference(int orgNumber) { + if (orgNumber <= 0) { + throw new IllegalArgumentException("Organization number must be a positive integer"); } + if (preferences.contains(orgNumber)) { + throw new IllegalArgumentException("Organization number already in preferences"); + } + preferences.add(orgNumber); } - public boolean removePreference(int orgNumber) { - try { - if (orgNumber <= 0) { - throw new IllegalArgumentException("Organization number must be a positive integer"); - } - if (!preferences.contains(orgNumber)) { - throw new IllegalStateException("Organization number not found in preferences"); - } - preferences.remove(Integer.valueOf(orgNumber)); - return true; - } catch (Exception e) { - System.out.println("Remove preference failed: " + e.getMessage()); - return false; + public void removePreference(int orgNumber) { + if (orgNumber <= 0) { + throw new IllegalArgumentException("Organization number must be a positive integer"); + } + if (!preferences.contains(orgNumber)) { + throw new IllegalArgumentException("Organization number not found in preferences"); } + preferences.remove(Integer.valueOf(orgNumber)); } -} +} \ No newline at end of file From a8f6f9fb7c2ff4ab417b41771d931c51aefc0326 Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Tue, 3 Mar 2026 13:48:05 +0100 Subject: [PATCH 08/17] fix[DonationService]: Change timestamp syntax to better readability and usability --- .../java/edu/group5/app/model/donation/DonationService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/group5/app/model/donation/DonationService.java b/src/main/java/edu/group5/app/model/donation/DonationService.java index 6f12f91..270b00e 100644 --- a/src/main/java/edu/group5/app/model/donation/DonationService.java +++ b/src/main/java/edu/group5/app/model/donation/DonationService.java @@ -1,4 +1,5 @@ package edu.group5.app.service; +import java.time.Instant; import java.math.BigDecimal; import java.sql.Timestamp; @@ -55,7 +56,7 @@ public boolean donate(Customer customer, int orgNumber, BigDecimal amount) { return false; } Donation donation = new Donation(customer.getUserId(), org.getOrgNumber(), - amount, new Timestamp(System.currentTimeMillis())); + amount, Timestamp.from(Instant.now())); donationRepository.addDonation(donation); return true; } From dcc32bf2a0de62c848f1a06f34db3a6c104a7fcd Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Tue, 3 Mar 2026 17:15:35 +0100 Subject: [PATCH 09/17] perf: increase performance of Organizationrepository --- .../app/model/donation/DonationService.java | 2 +- .../organization/OrganizationRepository.java | 65 ++++++++++--------- .../model/organization/OrganizationTest.java | 2 +- 3 files changed, 37 insertions(+), 32 deletions(-) diff --git a/src/main/java/edu/group5/app/model/donation/DonationService.java b/src/main/java/edu/group5/app/model/donation/DonationService.java index 270b00e..0b6aafd 100644 --- a/src/main/java/edu/group5/app/model/donation/DonationService.java +++ b/src/main/java/edu/group5/app/model/donation/DonationService.java @@ -55,7 +55,7 @@ public boolean donate(Customer customer, int orgNumber, BigDecimal amount) { if (org == null) { return false; } - Donation donation = new Donation(customer.getUserId(), org.getOrgNumber(), + Donation donation = new Donation(customer.getUserId(), org.orgNumber(), amount, Timestamp.from(Instant.now())); donationRepository.addDonation(donation); return true; diff --git a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java index 98e2f0a..e48be9e 100644 --- a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java +++ b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java @@ -1,58 +1,63 @@ package edu.group5.app.model.organization; import edu.group5.app.model.Repository; +import tools.jackson.core.type.TypeReference; import tools.jackson.databind.ObjectMapper; import java.util.HashMap; import java.util.Map; -import java.util.Objects; /** * Handles the business logic associated with organizations */ public class OrganizationRepository extends Repository { + private final HashMap grandMap; - @SuppressWarnings("unchecked") public OrganizationRepository(Object[] input) { + grandMap = new HashMap<>(); + ObjectMapper mapper = new ObjectMapper(); - if (input == null) { - throw new NullPointerException("Input cannot be null"); - } for (Object obj : input) { - Map map = (Map) obj; + HashMap contentMap = + mapper.convertValue(obj, new TypeReference>() {}); + + String orgNumberStr = ((String) contentMap.get("org_number")).replaceAll("\\s", ""); + int orgNumber = Integer.parseInt(orgNumberStr); - int orgNumber = Integer.parseInt((String) map.get("org_number")); - String name = (String) map.get("name"); + String name = (String) contentMap.get("name"); - boolean trusted = - "approved".equalsIgnoreCase((String) map.get("status")); - String url = (String) map.get("url"); - - boolean isPreApproved = (Boolean) map.get("is_pre_approved"); + boolean trusted = "approved".equalsIgnoreCase((String) contentMap.get("status")); - Organization organization = new Organization( - orgNumber, name, trusted, url, - isPreApproved, - "" // description not included in API - ); + String websiteURL = (String) contentMap.get("url"); + + boolean isPreApproved = true; + if (!contentMap.containsKey("is_pre_approved")) { + isPreApproved = false; + } else if ((boolean)contentMap.get("is_pre_approved") == false) { + isPreApproved = false; + } - content.put(orgNumber, organization); + String description = "Information about " + name; + + Organization org = new Organization(orgNumber, name, trusted, websiteURL, isPreApproved, description); + + grandMap.put(org.orgNumber(), org); } -} + } - /** - * Gets all trusted organizations in the repository - * @return all organizations with trusted = true - */ - public Map getTrustedOrganizations() { +/** + * Gets all trusted organizations in the repository + * @return all organizations with trusted = true + */ +public Map getTrustedOrganizations() { Map trustedOrganizations = new HashMap<>(); - content.forEach((orgNr, org) -> { - if (org.trusted()) { - trustedOrganizations.put(orgNr, org); - } + grandMap.forEach((orgNr, org) -> { + if (org.trusted()) { + trustedOrganizations.put(orgNr, org); + } }); return trustedOrganizations; - } +} } diff --git a/src/test/java/edu/group5/app/model/organization/OrganizationTest.java b/src/test/java/edu/group5/app/model/organization/OrganizationTest.java index 7f1b898..3e3f338 100644 --- a/src/test/java/edu/group5/app/model/organization/OrganizationTest.java +++ b/src/test/java/edu/group5/app/model/organization/OrganizationTest.java @@ -20,7 +20,7 @@ void constructor_CreatesAnOrganizationWhenInputIsValid() { assertAll( () -> assertEquals(1, org.orgNumber()), () -> assertEquals("Org", org.name()), - () -> assertTrue(org.trusted()), + () -> assertTrue(org.status()), () -> assertEquals("org.com", org.websiteURL()), () -> assertTrue(org.isPreApproved()), () -> assertEquals("Org description", org.description()) From adce1166037961ef1d22d892800e0055de324fb6 Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Wed, 4 Mar 2026 12:39:57 +0100 Subject: [PATCH 10/17] fix: fix Organizational classes by fixing constructor and JUnit tests --- .../organization/OrganizationRepository.java | 3 ++ .../OrganizationRepositoryTest.java | 54 ++++++++++++++----- .../model/organization/OrganizationTest.java | 2 +- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java index e48be9e..4ba589e 100644 --- a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java +++ b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java @@ -14,6 +14,9 @@ public class OrganizationRepository extends Repository { private final HashMap grandMap; public OrganizationRepository(Object[] input) { + if (input == null) { + throw new IllegalArgumentException("The input cannot be null"); + } grandMap = new HashMap<>(); ObjectMapper mapper = new ObjectMapper(); diff --git a/src/test/java/edu/group5/app/model/organization/OrganizationRepositoryTest.java b/src/test/java/edu/group5/app/model/organization/OrganizationRepositoryTest.java index 3a3392e..184b5e7 100644 --- a/src/test/java/edu/group5/app/model/organization/OrganizationRepositoryTest.java +++ b/src/test/java/edu/group5/app/model/organization/OrganizationRepositoryTest.java @@ -3,7 +3,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.HashMap; +import edu.group5.app.model.user.Customer; + import java.util.Map; import static org.junit.jupiter.api.Assertions.*; @@ -14,24 +15,49 @@ class OrganizationRepositoryTest { @BeforeEach void setUp() { - Map content = new HashMap<>(); - - Organization trustedOrganization1 = new Organization(1, "Trusted Org1", true, "org.com", true, "description"); - Organization trustedOrganization2 = new Organization(2, "Trusted Org2", true, "org.com", true, "description"); - Organization untrustedOrganization1 = new Organization(3, "Untrusted Org1", false, "org.com", true, "description"); - Organization untrustedOrganization2 = new Organization(4, "Untrusted Org2", false, "org.com", true, "description"); - - content.put(1, trustedOrganization1); - content.put(2, trustedOrganization2); - content.put(3, untrustedOrganization1); - content.put(4, untrustedOrganization2); - + Object[] content = new Object[] { + Map.of( + "org_number", "1", + "name", "Trusted Org1", + "status", "approved", + "url", "org.com", + "is_pre_approved", true + ), + Map.of( + "org_number", "2", + "name", "Trusted Org2", + "status", "approved", + "url", "org.com", + "is_pre_approved", true + ), + Map.of( + "org_number", "3", + "name", "Untrusted Org1", + "status", "pending", + "url", "org.com", + "is_pre_approved", true + ), + Map.of( + "org_number", "4", + "name", "Untrusted Org2", + "status", "pending", + "url", "org.com", + "is_pre_approved", true + ) + }; repository = new OrganizationRepository(content); } + private void constructorTest(Object[] input, String expectedMessage) { + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> new OrganizationRepository(input) + ); + assertEquals(expectedMessage, exception.getMessage()); + } @Test void constructor_ThrowsWhenContentIsNull() { - assertThrows(NullPointerException.class, () -> new OrganizationRepository(null)); + constructorTest(null, "The input cannot be null"); } @Test diff --git a/src/test/java/edu/group5/app/model/organization/OrganizationTest.java b/src/test/java/edu/group5/app/model/organization/OrganizationTest.java index 3e3f338..7f1b898 100644 --- a/src/test/java/edu/group5/app/model/organization/OrganizationTest.java +++ b/src/test/java/edu/group5/app/model/organization/OrganizationTest.java @@ -20,7 +20,7 @@ void constructor_CreatesAnOrganizationWhenInputIsValid() { assertAll( () -> assertEquals(1, org.orgNumber()), () -> assertEquals("Org", org.name()), - () -> assertTrue(org.status()), + () -> assertTrue(org.trusted()), () -> assertEquals("org.com", org.websiteURL()), () -> assertTrue(org.isPreApproved()), () -> assertEquals("Org description", org.description()) From f81c347ae397acf0e457cd1ea27fb834be5254b6 Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Thu, 5 Mar 2026 08:53:15 +0100 Subject: [PATCH 11/17] fix: fix login method's unnecessary String casting of char[] argument --- src/main/java/edu/group5/app/model/user/UserService.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/edu/group5/app/model/user/UserService.java b/src/main/java/edu/group5/app/model/user/UserService.java index 5497e1a..a979eee 100644 --- a/src/main/java/edu/group5/app/model/user/UserService.java +++ b/src/main/java/edu/group5/app/model/user/UserService.java @@ -28,7 +28,7 @@ public boolean registerUser(String role, String firstName, String lastName, return true; } - public boolean login(String email, char[] password) { + public boolean login(String email, String password) { if (email == null || email.trim().isEmpty() || password == null) { return false; } @@ -36,8 +36,7 @@ public boolean login(String email, char[] password) { if (user == null) { return false; } - String passwordString = new String(password); - if (user.verifyPassword(passwordString)) { + if (user.verifyPassword(password)) { return true; } return false; From be3995b616c7d1d3fcb98e11a9320a99c86f2ea7 Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Thu, 5 Mar 2026 09:25:52 +0100 Subject: [PATCH 12/17] style[OrganizationRepository]: change style on method for better readability --- .../model/organization/OrganizationRepository.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java index 4ba589e..3147116 100644 --- a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java +++ b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java @@ -52,15 +52,15 @@ public OrganizationRepository(Object[] input) { * Gets all trusted organizations in the repository * @return all organizations with trusted = true */ -public Map getTrustedOrganizations() { + public Map getTrustedOrganizations() { Map trustedOrganizations = new HashMap<>(); grandMap.forEach((orgNr, org) -> { - if (org.trusted()) { - trustedOrganizations.put(orgNr, org); - } + if (org.trusted()) { + trustedOrganizations.put(orgNr, org); + } }); - return trustedOrganizations; -} + } + } From 6c34cfcfe52906247daeba0027d48c74ceb83700 Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Thu, 5 Mar 2026 12:47:13 +0100 Subject: [PATCH 13/17] Fix: Fix OrganizationRepository constructor and update related classes --- .../edu/group5/app/model/DBRepository.java | 4 -- .../java/edu/group5/app/model/Repository.java | 2 +- .../app/model/donation/DonationService.java | 12 ++--- .../organization/OrganizationRepository.java | 17 ++++--- .../edu/group5/app/model/user/Customer.java | 2 - .../group5/app/model/user/UserRepository.java | 48 +++++++++++++++++++ .../group5/app/model/user/UserService.java | 16 +++---- .../OrganizationRepositoryTest.java | 2 - 8 files changed, 70 insertions(+), 33 deletions(-) create mode 100644 src/main/java/edu/group5/app/model/user/UserRepository.java diff --git a/src/main/java/edu/group5/app/model/DBRepository.java b/src/main/java/edu/group5/app/model/DBRepository.java index 1196eae..9dac3c0 100644 --- a/src/main/java/edu/group5/app/model/DBRepository.java +++ b/src/main/java/edu/group5/app/model/DBRepository.java @@ -1,9 +1,5 @@ package edu.group5.app.model; - - -import java.util.HashMap; import java.util.Map; - /** * Abstract base class for repositories that store their data * in a database-related structure. diff --git a/src/main/java/edu/group5/app/model/Repository.java b/src/main/java/edu/group5/app/model/Repository.java index 5396b2f..032f307 100644 --- a/src/main/java/edu/group5/app/model/Repository.java +++ b/src/main/java/edu/group5/app/model/Repository.java @@ -12,7 +12,7 @@ public abstract class Repository { * * @param content the underlying data structure used to store entities */ - public Repository(Map content) { + protected Repository(Map content) { this.content = content; } diff --git a/src/main/java/edu/group5/app/model/donation/DonationService.java b/src/main/java/edu/group5/app/model/donation/DonationService.java index 0b6aafd..11dde6c 100644 --- a/src/main/java/edu/group5/app/model/donation/DonationService.java +++ b/src/main/java/edu/group5/app/model/donation/DonationService.java @@ -1,13 +1,11 @@ -package edu.group5.app.service; +package edu.group5.app.model.donation; import java.time.Instant; import java.math.BigDecimal; import java.sql.Timestamp; -import edu.group5.app.model.donation.Donation; import edu.group5.app.model.organization.Organization; import edu.group5.app.model.organization.OrganizationRepository; import edu.group5.app.model.user.Customer; -import edu.group5.app.model.donation.DonationRepository; /** * DonationService class provides functionality for handling donations in the system. @@ -47,16 +45,16 @@ public DonationService(DonationRepository donationRepository, * @param amount the amount of the donation * @return true if the donation is successfully processed, false otherwise */ - public boolean donate(Customer customer, int orgNumber, BigDecimal amount) { - if (customer == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) { + public boolean donate(Customer customer, int orgNumber, BigDecimal amount, String paymentMethod) { + if (customer == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0 || paymentMethod.isBlank()) { return false; } Organization org = organizationRepository.findByOrgNumber(orgNumber); if (org == null) { return false; } - Donation donation = new Donation(customer.getUserId(), org.orgNumber(), - amount, Timestamp.from(Instant.now())); + Donation donation = new Donation(1, customer.getUserId(), org.orgNumber(), /* TODO fix incrementation of donation */ + amount, Timestamp.from(Instant.now()), paymentMethod); donationRepository.addDonation(donation); return true; } diff --git a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java index 3147116..53b4241 100644 --- a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java +++ b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java @@ -14,10 +14,11 @@ public class OrganizationRepository extends Repository { private final HashMap grandMap; public OrganizationRepository(Object[] input) { + super(new HashMap<>()); + grandMap = new HashMap<>(); if (input == null) { throw new IllegalArgumentException("The input cannot be null"); } - grandMap = new HashMap<>(); ObjectMapper mapper = new ObjectMapper(); for (Object obj : input) { @@ -33,12 +34,7 @@ public OrganizationRepository(Object[] input) { String websiteURL = (String) contentMap.get("url"); - boolean isPreApproved = true; - if (!contentMap.containsKey("is_pre_approved")) { - isPreApproved = false; - } else if ((boolean)contentMap.get("is_pre_approved") == false) { - isPreApproved = false; - } + boolean isPreApproved = Boolean.TRUE.equals(contentMap.get("is_pre_approved")); String description = "Information about " + name; @@ -63,4 +59,11 @@ public Map getTrustedOrganizations() { return trustedOrganizations; } + public Organization findByOrgNumber(int orgNumber) { + if (orgNumber == 0 || orgNumber <= 0) { + throw new IllegalArgumentException("The Organization number must be a positive integer"); + } + return grandMap.get(orgNumber); + } + } diff --git a/src/main/java/edu/group5/app/model/user/Customer.java b/src/main/java/edu/group5/app/model/user/Customer.java index 2c221b0..5d3fa7e 100644 --- a/src/main/java/edu/group5/app/model/user/Customer.java +++ b/src/main/java/edu/group5/app/model/user/Customer.java @@ -3,8 +3,6 @@ import java.util.List; import java.util.ArrayList; -import edu.group5.app.model.organization.Organization; - /** * Customer class represents a customer in the system. * It extends the User class and includes additional functionality specific to customers, diff --git a/src/main/java/edu/group5/app/model/user/UserRepository.java b/src/main/java/edu/group5/app/model/user/UserRepository.java new file mode 100644 index 0000000..18bbd09 --- /dev/null +++ b/src/main/java/edu/group5/app/model/user/UserRepository.java @@ -0,0 +1,48 @@ +package edu.group5.app.model.user; + +import java.util.HashMap; + +import edu.group5.app.model.DBRepository; + +public class UserRepository extends DBRepository{ + + /** + * Constructs DonationRepository using Hashmap, + * and extends the content from DBRepository. + * @param content the underlying map used to store donations, + * where the key represents the donation ID + */ + public UserRepository(HashMap content) { + super(content); + if (content == null) { + throw new IllegalArgumentException("Content cannot be null"); + } + } + + /** + * Adds a new donation to the repository + *

+ * The donation is stored using its {@code donationId} as the key. + * If a donation with the same ID already exists, the donation + * will not be added. + *

+ * + * @param donation the donation to add + * @return {@code true} if the donation was successfully added, and + * {@code false} if a donation with the same ID already exists + */ + public boolean addUser(User user) { + if (user == null) { + throw new IllegalArgumentException("Donation cannot be null"); + } + if (content.containsKey(user.getUserId())){ + return false; + } + content.put(user.getUserId(), user); + return true; + } + + public User findUserByEmail(String email) { + return null; + } +} diff --git a/src/main/java/edu/group5/app/model/user/UserService.java b/src/main/java/edu/group5/app/model/user/UserService.java index a979eee..d75d380 100644 --- a/src/main/java/edu/group5/app/model/user/UserService.java +++ b/src/main/java/edu/group5/app/model/user/UserService.java @@ -23,7 +23,9 @@ public boolean registerUser(String role, String firstName, String lastName, User user; if (role.equalsIgnoreCase("Customer")) { user = new Customer(0, firstName, lastName, email, passwordHash); - } + } else { + return false; + } userRepository.addUser(user); return true; } @@ -32,13 +34,7 @@ public boolean login(String email, String password) { if (email == null || email.trim().isEmpty() || password == null) { return false; } - User user = userRepository.findByEmail(email); - if (user == null) { - return false; - } - if (user.verifyPassword(password)) { - return true; - } - return false; -} + User user = userRepository.findUserByEmail(email); + return user != null && user.verifyPassword(password); + } } diff --git a/src/test/java/edu/group5/app/model/organization/OrganizationRepositoryTest.java b/src/test/java/edu/group5/app/model/organization/OrganizationRepositoryTest.java index 184b5e7..1913988 100644 --- a/src/test/java/edu/group5/app/model/organization/OrganizationRepositoryTest.java +++ b/src/test/java/edu/group5/app/model/organization/OrganizationRepositoryTest.java @@ -3,8 +3,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import edu.group5.app.model.user.Customer; - import java.util.Map; import static org.junit.jupiter.api.Assertions.*; From 4ac5cdd56622da99b2eca3567443193829478cf8 Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Sat, 7 Mar 2026 11:52:24 +0100 Subject: [PATCH 14/17] perf: unify repository constructors, unify Backend classes, standardize exports, update services, and add comprehensive tests --- pom.xml | 35 ++- .../edu/group5/app/model/DBRepository.java | 24 ++ .../model/donation/DonationRepository.java | 74 ++++- .../app/model/donation/DonationService.java | 8 +- .../organization/OrganizationRepository.java | 61 ++++- .../edu/group5/app/model/user/Customer.java | 28 +- .../java/edu/group5/app/model/user/User.java | 17 +- .../group5/app/model/user/UserRepository.java | 110 ++++++-- .../group5/app/model/user/UserService.java | 37 ++- .../donation/DonationRepositoryTest.java | 256 +++++++++++------- .../model/donation/DonationServiceTest.java | 116 ++++++++ .../OrganizationRepositoryTest.java | 65 +++++ .../group5/app/model/user/CustomerTest.java | 64 +++++ .../app/model/user/UserRepositoryTest.java | 124 +++++++++ .../app/model/user/UserServiceTest.java | 108 ++++++++ 15 files changed, 949 insertions(+), 178 deletions(-) create mode 100644 src/test/java/edu/group5/app/model/donation/DonationServiceTest.java create mode 100644 src/test/java/edu/group5/app/model/user/UserRepositoryTest.java create mode 100644 src/test/java/edu/group5/app/model/user/UserServiceTest.java diff --git a/pom.xml b/pom.xml index 6320c10..7a55551 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,13 @@ org.openjfx javafx-controls ${javafx.version} + win + + + org.openjfx + javafx-graphics + ${javafx.version} + win @@ -38,11 +45,6 @@ spring-security-crypto 7.0.2 - - commons-logging - commons-logging - 1.3.5 - tools.jackson.core @@ -50,6 +52,16 @@ 3.1.0 compile + + org.springframework + spring-core + 6.1.10 + + + org.slf4j + slf4j-simple + 2.0.9 + @@ -111,6 +123,19 @@ shade + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + META-INF/NOTICE + META-INF/LICENSE + META-INF.versions.9.module-info + + + edu.group5.app.App diff --git a/src/main/java/edu/group5/app/model/DBRepository.java b/src/main/java/edu/group5/app/model/DBRepository.java index 9dac3c0..799a977 100644 --- a/src/main/java/edu/group5/app/model/DBRepository.java +++ b/src/main/java/edu/group5/app/model/DBRepository.java @@ -1,5 +1,7 @@ package edu.group5.app.model; +import java.util.HashMap; import java.util.Map; +import java.util.List; /** * Abstract base class for repositories that store their data * in a database-related structure. @@ -10,6 +12,7 @@ *

*/ public abstract class DBRepository extends Repository { + protected final Map contentLock; /** * Constructs a DBRepository with the given content. * @@ -17,5 +20,26 @@ public abstract class DBRepository extends Repository { */ protected DBRepository(Map content) { super(content); + this.contentLock = new HashMap<>(); } + + protected void updateContentLock() { + synchronized (contentLock) { + contentLock.clear(); + contentLock.putAll(this.content); + } + } + + public void addContent(K key, V value) { + synchronized (contentLock) { + content.put(key, value); + } + } + + /** + * Exports the repository content as a list of Object arrays, where each array represents a row of data. + * This method is intended for converting the repository content into a format suitable for database storage or export. + * @return a List of Object arrays representing the repository content for database export + */ + public abstract List export(); } diff --git a/src/main/java/edu/group5/app/model/donation/DonationRepository.java b/src/main/java/edu/group5/app/model/donation/DonationRepository.java index 339e7ca..54845a5 100644 --- a/src/main/java/edu/group5/app/model/donation/DonationRepository.java +++ b/src/main/java/edu/group5/app/model/donation/DonationRepository.java @@ -7,6 +7,9 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.stream.Collectors; +import java.util.List; +import java.math.BigDecimal; +import java.sql.Timestamp; /** * Repository class for Donation. @@ -19,17 +22,70 @@ public class DonationRepository extends DBRepository { private final HashMap content; /** - * Constructs DonationRepository using Hashmap, - * and extends the content from DBRepository. - * @param content the underlying map used to store donations, - * where the key represents the donation ID + * Constructs DonationRepository from a list of Object[] rows from the database. + * @param rows List of Object[] representing donations from the DB. + * Each row must have 6 elements: + * [donationId, userId, organizationId, amount, date, paymentMethod] + * @throws IllegalArgumentException if the input list is null or any row is invalid */ - public DonationRepository(HashMap content){ - if (content == null) { - throw new IllegalArgumentException("Content cannot be null"); + public DonationRepository(List rows) { + super(new HashMap<>()); + if (rows == null) { + throw new IllegalArgumentException("The list of rows cannot be null"); } - super(content); - this.content = content; + this.content = new HashMap<>(); + for (Object[] row : rows) { + if (row == null || row.length != 6 ) { + throw new IllegalArgumentException("Each row must contain exactly 6 elements"); + } + int donationId = (int) row[0]; + int customerId = (int) row[1]; + int organizationId = (int) row[2]; + BigDecimal amount = (BigDecimal) row[3]; + Timestamp date = (Timestamp) row[4]; + String paymentMethod = (String) row[5]; + + Donation donation = new Donation(donationId, customerId, organizationId, amount, date, paymentMethod); + this.content.put(donationId, donation); + } + super.updateContentLock(); + } + + @Override + public List export() { + return content.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .map(entry -> { Donation donation = entry.getValue(); + return new Object[] { + donation.donationId(), donation.userId(), + donation.organizationId(), donation.amount(), + donation.date(), donation.paymentMethod()};}) + .toList(); + } + + /** + * Retrieves a donation by its ID. + * @param donationId the ID of the donation to retrieve + * @return the Donation object with the specified ID, or null if not found + * @throws IllegalArgumentException if the donationId is not positive + */ + public Donation getDonationById(int donationId) { + if (donationId <= 0) { + throw new IllegalArgumentException("Donation ID must be positive"); + } + return content.get(donationId); + } + + /** + * Generates the next donation ID based on the current maximum ID in the repository. + * @return the next donation ID to be used for a new donation + */ + public int getNextDonationId() { + return content.keySet().stream().max(Integer::compareTo).orElse(0) + 1; + } /* TODO change this when data database is introduced */ + + public Map getAllDonations() { + return new HashMap<>(content); } /** diff --git a/src/main/java/edu/group5/app/model/donation/DonationService.java b/src/main/java/edu/group5/app/model/donation/DonationService.java index 11dde6c..d8fef60 100644 --- a/src/main/java/edu/group5/app/model/donation/DonationService.java +++ b/src/main/java/edu/group5/app/model/donation/DonationService.java @@ -46,15 +46,17 @@ public DonationService(DonationRepository donationRepository, * @return true if the donation is successfully processed, false otherwise */ public boolean donate(Customer customer, int orgNumber, BigDecimal amount, String paymentMethod) { - if (customer == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0 || paymentMethod.isBlank()) { + if (customer == null || amount == null + || amount.compareTo(BigDecimal.ZERO) <= 0 || paymentMethod.isBlank()) { return false; } Organization org = organizationRepository.findByOrgNumber(orgNumber); if (org == null) { return false; } - Donation donation = new Donation(1, customer.getUserId(), org.orgNumber(), /* TODO fix incrementation of donation */ - amount, Timestamp.from(Instant.now()), paymentMethod); + Donation donation = + new Donation(donationRepository.getNextDonationId(), + customer.getUserId(), org.orgNumber(), amount, Timestamp.from(Instant.now()), paymentMethod); donationRepository.addDonation(donation); return true; } diff --git a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java index 53b4241..4499288 100644 --- a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java +++ b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java @@ -8,11 +8,22 @@ import java.util.Map; /** + * Repository class for managing Organization entities. It provides methods to retrieve trusted organizations, + * find organizations by their organization number or name, and initializes the repository with input data. + * The repository uses a HashMap to store Organization objects for efficient retrieval based on their organization number. * Handles the business logic associated with organizations */ public class OrganizationRepository extends Repository { private final HashMap grandMap; + /** + * Initializes the repository with the given input data, c + * onverting it into Organization objects and storing them in a map for efficient retrieval. + * The input is expected to be an array of objects, where each object contains + * the necessary information to create an Organization. + * @param input the input data used to populate the repository, must not be null + * @throws IllegalArgumentException if the input is null + */ public OrganizationRepository(Object[] input) { super(new HashMap<>()); grandMap = new HashMap<>(); @@ -27,23 +38,38 @@ public OrganizationRepository(Object[] input) { String orgNumberStr = ((String) contentMap.get("org_number")).replaceAll("\\s", ""); int orgNumber = Integer.parseInt(orgNumberStr); - String name = (String) contentMap.get("name"); - boolean trusted = "approved".equalsIgnoreCase((String) contentMap.get("status")); - String websiteURL = (String) contentMap.get("url"); - boolean isPreApproved = Boolean.TRUE.equals(contentMap.get("is_pre_approved")); - String description = "Information about " + name; - Organization org = new Organization(orgNumber, name, trusted, websiteURL, isPreApproved, description); grandMap.put(org.orgNumber(), org); } } + /** + * Exports the organization data in a format suitable for output, such as JSON. Each organization is represented as a map containing its attributes. + * @return an array of maps, where each map represents an organization with its attributes (org_number, name, status, url, is_pre_approved) + * @throws IllegalStateException if the repository is empty + */ + public Object[] export() { + if (grandMap.isEmpty()) { + throw new IllegalStateException("The repository is empty"); + } + return grandMap.values().stream() + .map(org -> { Map orgMap = new HashMap<>(); + orgMap.put("org_number", org.orgNumber()); + orgMap.put("name", org.name()); + orgMap.put("status", org.trusted() ? "approved" : "unapproved"); + orgMap.put("url", org.websiteURL()); + orgMap.put("is_pre_approved", org.isPreApproved()); + return orgMap; + }) + .toArray(); + } + /** * Gets all trusted organizations in the repository * @return all organizations with trusted = true @@ -59,11 +85,32 @@ public Map getTrustedOrganizations() { return trustedOrganizations; } + /** + * Finds an organization by its organization number + * @param orgNumber the organization number of the Organization + * @return the Organization with the given organization number, or null if not found + * @throws IllegalArgumentException if the organization number is not a positive integer + */ public Organization findByOrgNumber(int orgNumber) { - if (orgNumber == 0 || orgNumber <= 0) { + if (orgNumber <= 0) { throw new IllegalArgumentException("The Organization number must be a positive integer"); } return grandMap.get(orgNumber); } + /** + * Finds an organization by its name, ignoring case + * @param name the name of the Organization + * @return the Organization with the given name, or null if not found + * @throws IllegalArgumentException if the name is null or empty + */ + public Organization findByOrgName(String name) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("The name cannot be null"); + } + return grandMap.values().stream() + .filter(org -> org.name().equalsIgnoreCase(name)) + .findFirst() + .orElse(null); + } } diff --git a/src/main/java/edu/group5/app/model/user/Customer.java b/src/main/java/edu/group5/app/model/user/Customer.java index 5d3fa7e..93f03ac 100644 --- a/src/main/java/edu/group5/app/model/user/Customer.java +++ b/src/main/java/edu/group5/app/model/user/Customer.java @@ -11,7 +11,6 @@ */ public class Customer extends User { private List preferences; - /** * Constructs a new Customer object for new registrations. * The role is automatically set to "Customer" and the preferences list is initialized empty. @@ -25,27 +24,7 @@ public class Customer extends User { */ public Customer(int userId, String firstName, String lastName, String email, String passwordHash) { - super(userId, "Customer", firstName, lastName, email, passwordHash); - this.preferences = new ArrayList<>(); -} - -/** - * Constructs a Customer object for existing users loaded from the database - * or for cases where the role is already defined. - * The role should normally be "Customer", but this constructor allows flexibility - * (e.g., for testing or future user types). Preferences list is initialized empty. - * - * @param userId the unique identifier for the user, must be positive - * @param role the role of the user (should be "Customer" for customer instances) - * @param firstName the first name of the customer - * @param lastName the last name of the customer - * @param email the email address of the customer - * @param passwordHash the hashed password of the customer - * @throws IllegalArgumentException if any parameter is invalid (null, empty, or userId ≤ 0) - */ -public Customer(int userId, String role, String firstName, String lastName, - String email, String passwordHash) { - super(userId, role, firstName, lastName, email, passwordHash); + super(userId, firstName, lastName, email, passwordHash); this.preferences = new ArrayList<>(); } @@ -53,6 +32,11 @@ public List getPreferences() { return preferences; } + @Override + public String getRole() { + return UserRepository.ROLE_CUSTOMER; + } + public void addPreference(int orgNumber) { if (orgNumber <= 0) { throw new IllegalArgumentException("Organization number must be a positive integer"); diff --git a/src/main/java/edu/group5/app/model/user/User.java b/src/main/java/edu/group5/app/model/user/User.java index e20e4f4..bdb4700 100644 --- a/src/main/java/edu/group5/app/model/user/User.java +++ b/src/main/java/edu/group5/app/model/user/User.java @@ -11,7 +11,6 @@ */ public abstract class User { private int userId; - private String role; private String firstName; private String lastName; private String email; @@ -21,20 +20,16 @@ public abstract class User { * Constructor for User class. Validates that all required fields * are provided and throws an IllegalArgumentException if any of the fields are null or empty. * @param userId the unique identifier for the user, must be a positive integer - * @param role the role of the user (e.g., "Donor", "Recipient", "Admin") * @param firstName the first name of the user * @param lastName the last name of the user * @param email the email address of the user * @param passwordHash the hashed password of the user, used for authentication purposes */ - public User(int userId, String role, String firstName, + public User(int userId, String firstName, String lastName, String email, String passwordHash) { if (userId <= 0) { throw new IllegalArgumentException("User ID must be positive"); } - if (role == null || role.trim().isEmpty()) { - throw new IllegalArgumentException("Role cannot be null or empty"); - } if (firstName == null || firstName.trim().isEmpty()) { throw new IllegalArgumentException("First name cannot be null or empty"); } @@ -48,7 +43,6 @@ public User(int userId, String role, String firstName, throw new IllegalArgumentException("Password hash cannot be null or empty"); } this.userId = userId; - this.role = role.trim(); this.firstName = firstName.trim(); this.lastName = lastName.trim(); this.email = email.trim(); @@ -62,14 +56,13 @@ public User(int userId, String role, String firstName, public int getUserId() { return userId; } - + /** - * Gets the role of the user, which defines their permissions in the system. + * Gets the role of the user (e.g., "Customer", "Admin"). * @return the role of the user */ - public String getRole() { - return role; - } + public abstract String getRole(); + /** * Gets the first name of the user. diff --git a/src/main/java/edu/group5/app/model/user/UserRepository.java b/src/main/java/edu/group5/app/model/user/UserRepository.java index 18bbd09..8ccb790 100644 --- a/src/main/java/edu/group5/app/model/user/UserRepository.java +++ b/src/main/java/edu/group5/app/model/user/UserRepository.java @@ -1,39 +1,106 @@ package edu.group5.app.model.user; import java.util.HashMap; +import java.util.List; +import java.util.Map; import edu.group5.app.model.DBRepository; public class UserRepository extends DBRepository{ - + public final static String ROLE_CUSTOMER = "Customer"; /** - * Constructs DonationRepository using Hashmap, + * Constructs UserRepository using Hashmap, * and extends the content from DBRepository. - * @param content the underlying map used to store donations, - * where the key represents the donation ID + * @param content the underlying map used to store users, + * where the key represents the user ID */ - public UserRepository(HashMap content) { - super(content); - if (content == null) { - throw new IllegalArgumentException("Content cannot be null"); + public UserRepository(List rows) { + super(new HashMap<>()); + if (rows == null) { + throw new IllegalArgumentException("The list of rows cannot be null"); + } + for (Object[] row : rows) { + if (row == null || row.length != 6 ) { + throw new IllegalArgumentException("Each row must contain exactly 6 elements"); + } + int userId = (int) row[0]; + String role = (String) row[1]; + String firstName = (String) row[2]; + String lastName = (String) row[3]; + String email = (String) row[4]; + String passwordHash = (String) row[5]; + + User user; + if (ROLE_CUSTOMER.equalsIgnoreCase(role)) { + user = new Customer(userId, firstName, lastName, email, passwordHash); + } else { + throw new IllegalArgumentException("Unknown role: " + role); + } + this.content.put(userId, user); } + super.updateContentLock(); + } + + + @Override + public List export() { + return content.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .map(entry -> { User user = entry.getValue(); + return new Object[]{user.getUserId(), user.getRole(), + user.getFirstName(), user.getLastName(), + user.getEmail(), user.getPasswordHash()};}) + .toList(); } + public HashMap getUsers() { + return new HashMap<>(content); + } /** - * Adds a new donation to the repository + * Retrieves a user by their unique identifier. + * @param userId the unique identifier of the user to retrieve + * @return the user with the specified ID, or {@code null} if no such user exists + * @throws IllegalArgumentException if the userId is not positive + */ + public User getUserById(int userId) { + if (userId <= 0) { + throw new IllegalArgumentException("User ID must be positive"); + } + return content.get(userId); + } + + /** + * Generates the next user ID based on repository size. + * Uses size+1 and then moves forward if that ID is already taken. + * @return the next available user ID + * @throws IllegalStateException if no available user ID can be found + */ + public int getNextUserId() { + int nextId = content.size() + 1; + while (content.containsKey(nextId)) { + nextId++; + if (nextId <= 0) { + throw new IllegalStateException("No available user ID found"); + } + } + return nextId; + } /* TODO change this when data database is introduced */ + + /** + * Adds a new user to the repository *

- * The donation is stored using its {@code donationId} as the key. - * If a donation with the same ID already exists, the donation + * The user is stored using its {@code userId} as the key. + * If a user with the same ID already exists, the user * will not be added. *

* - * @param donation the donation to add - * @return {@code true} if the donation was successfully added, and - * {@code false} if a donation with the same ID already exists + * @param user the user to add + * @return {@code true} if the user was successfully added, and + * {@code false} if a user with the same ID already exists */ public boolean addUser(User user) { if (user == null) { - throw new IllegalArgumentException("Donation cannot be null"); + throw new IllegalArgumentException("User cannot be null"); } if (content.containsKey(user.getUserId())){ return false; @@ -42,7 +109,18 @@ public boolean addUser(User user) { return true; } + /** + * Finds a user by their email address. + * @param email the email address of the user to find + * @return the user with the specified email address, or {@code null} if no such user exists + */ public User findUserByEmail(String email) { - return null; + if (email == null || email.trim().isEmpty()) { + throw new IllegalArgumentException("Email cannot be null or empty"); + } + return content.values().stream() + .filter(user -> user.getEmail().equals(email)) + .findFirst() + .orElse(null); } } diff --git a/src/main/java/edu/group5/app/model/user/UserService.java b/src/main/java/edu/group5/app/model/user/UserService.java index d75d380..7a9dc53 100644 --- a/src/main/java/edu/group5/app/model/user/UserService.java +++ b/src/main/java/edu/group5/app/model/user/UserService.java @@ -1,9 +1,18 @@ package edu.group5.app.model.user; +/** + * Service class for managing user-related operations, such as registration and login. + * It interacts with the UserRepository to perform these operations and contains the business logic + * associated with user management, including validation of input data and handling of user authentication. + */ public class UserService { private UserRepository userRepository; - + /** + * Constructs a UserService with the given UserRepository. + * @param userRepository the UserRepository to use for managing user data; must not be null + * @throws IllegalArgumentException if userRepository is null + */ public UserService(UserRepository userRepository) { if (userRepository == null) { throw new IllegalArgumentException("UserRepository cannot be null"); @@ -11,6 +20,19 @@ public UserService(UserRepository userRepository) { this.userRepository = userRepository; } + /** + * Registers a new user with the given information. Validates the input data and creates a new User object + * based on the specified role. Currently supports registration for customers only. + * @param role the role of the user (e.g., "Customer"); must not be null or empty + * @param firstName the first name of the user; must not be null or empty + * @param lastName the last name of the user; must not be null or empty + * @param email the email address of the user; must not be null or empty + * @param passwordHash the hashed password of the user; must not be null or empty + * @return true if the user was successfully registered, false if any input is invalid or + * if the role is not supported + * @throws IllegalArgumentException if any of the input parameters are null or empty + * or if the role is not supported + */ public boolean registerUser(String role, String firstName, String lastName, String email, String passwordHash) { if (role == null || role.trim().isEmpty() || @@ -22,14 +44,23 @@ public boolean registerUser(String role, String firstName, String lastName, } User user; if (role.equalsIgnoreCase("Customer")) { - user = new Customer(0, firstName, lastName, email, passwordHash); - } else { + user = new Customer(userRepository.getNextUserId(), firstName, lastName, email, passwordHash); + } else { /* TODO when you switch to a real DB, + replace getNextUserId with DB auto-increment/identity and ignore manual ID generation in service*/ return false; } userRepository.addUser(user); return true; } + /** + * Authenticates a user based on the provided email and password. + * @param email the email address of the user attempting to log in; must not be null or empty + * @param password the plaintext password of the user attempting to log in; must not be + * @return true if the login is successful + * (i.e., the user exists and the password is correct), false otherwise + * @throws IllegalArgumentException if email is null or empty, or if password is null + */ public boolean login(String email, String password) { if (email == null || email.trim().isEmpty() || password == null) { return false; diff --git a/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java b/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java index b55ef57..04d4626 100644 --- a/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java +++ b/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java @@ -5,24 +5,20 @@ import java.math.BigDecimal; import java.sql.Timestamp; -import java.util.HashMap; +import java.util.*; import static org.junit.jupiter.api.Assertions.*; public class DonationRepositoryTest { + private DonationRepository repo; - private Timestamp now; - private Donation donation1; - private Donation donation2; - private Donation donation3; - private Donation donation4; + private Donation donation1, donation2, donation3, donation4; + private Timestamp ts1, ts2, now; @BeforeEach void setUp() { - repo = new DonationRepository(new HashMap<>()); - - Timestamp ts1 = Timestamp.valueOf("2025-01-01 10:00:00"); - Timestamp ts2 = Timestamp.valueOf("2025-01-01 10:00:01"); + ts1 = Timestamp.valueOf("2025-01-01 10:00:00"); + ts2 = Timestamp.valueOf("2025-01-01 10:00:01"); now = new Timestamp(System.currentTimeMillis()); donation1 = new Donation(1, 102, 202, new BigDecimal("500.0"), ts1, "Card"); @@ -30,159 +26,217 @@ void setUp() { donation3 = new Donation(3, 103, 203, new BigDecimal("200.0"), now, "PayPal"); donation4 = new Donation(1, 101, 201, new BigDecimal("500.0"), now, "Card"); + repo = new DonationRepository(new ArrayList<>()); + } + @Test + void constructorThrowsIfNullList() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> new DonationRepository(null)); + assertEquals("The list of rows cannot be null", ex.getMessage()); } + @Test - void testThrowsExceptionIfContentIsNull() { - IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, - () -> new DonationRepository(null)); - assertEquals("Content cannot be null", thrown.getMessage()); + void constructorThrowsIfRowInvalidLength() { + List invalidRows = new ArrayList<>(); + invalidRows.add(new Object[]{1, 2}); + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> new DonationRepository(invalidRows)); + assertEquals("Each row must contain exactly 6 elements", ex.getMessage()); } @Test - void testAddDonation() { - assertTrue(repo.addDonation(donation1)); - assertEquals(1, repo.getContent().size()); + void constructorParsesRowSuccessfully() { + List rows = new ArrayList<>(); + Timestamp ts = Timestamp.valueOf("2025-01-01 10:00:00"); + rows.add(new Object[]{1, 101, 201, new BigDecimal("100"), ts, "Card"}); + DonationRepository repoWithRow = new DonationRepository(rows); + Donation d = repoWithRow.getDonationById(1); + assertNotNull(d); + assertEquals(101, d.userId()); + assertEquals(201, d.organizationId()); + assertEquals(new BigDecimal("100"), d.amount()); + assertEquals(ts, d.date()); + assertEquals("Card", d.paymentMethod()); + } + - assertTrue(repo.addDonation(donation3)); - assertEquals(2, repo.getContent().size()); + @Test + void constructorThrowsIfRowIsNull() { + List rows = new ArrayList<>(); + rows.add(null); + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> new DonationRepository(rows)); + assertEquals("Each row must contain exactly 6 elements", ex.getMessage()); } + @Test - void testAddDonationWithDuplicateId() { + void addDonationSuccessfully() { assertTrue(repo.addDonation(donation1)); + assertEquals(1, repo.getAllDonations().size()); + assertTrue(repo.getAllDonations().containsValue(donation1)); + } + + @Test + void addDonationDuplicateIdFails() { + repo.addDonation(donation1); assertFalse(repo.addDonation(donation4)); - assertEquals(1, repo.getContent().size()); + assertEquals(1, repo.getAllDonations().size()); } + @Test - void testAddNullDonation() { - IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, - () -> repo.addDonation(null)); - assertEquals("Donation cannot be null",thrown.getMessage()); + void addDonationNullThrows() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> repo.addDonation(null)); + assertEquals("Donation cannot be null", ex.getMessage()); } @Test - void testSortByDate() { - repo.addDonation(donation3); + void getDonationByIdSuccessfully() { repo.addDonation(donation1); - repo.addDonation(donation2); - - HashMap sorted = repo.sortByDate(); + Donation retrieved = repo.getDonationById(1); + assertEquals(donation1, retrieved); + } - Integer[] keys = sorted.keySet().toArray(new Integer[0]); - assertEquals(1, keys[0]); - assertEquals(2, keys[1]); - assertEquals(3, keys[2]); + @Test + void getDonationByIdThrowsIfNegative() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> repo.getDonationById(0)); + assertEquals("Donation ID must be positive", ex.getMessage()); + } + @Test + void getDonationByIdReturnsNullIfNotFound() { + assertNull(repo.getDonationById(999)); } + @Test - void testSortByDateWithSameDate() { + void getAllDonationsReturnsCopy() { + repo.addDonation(donation1); + Map all = repo.getAllDonations(); + assertEquals(1, all.size()); + all.clear(); + assertEquals(1, repo.getAllDonations().size()); + } + @Test + void sortByDateAscending() { repo.addDonation(donation3); - repo.addDonation(donation4); - - HashMap sorted = repo.sortByDate(); + repo.addDonation(donation1); + repo.addDonation(donation2); - assertEquals(2, sorted.size()); - assertTrue(sorted.containsKey(1)); - assertTrue(sorted.containsKey(3)); + Map sorted = repo.sortByDate(); + Integer[] keys = sorted.keySet().toArray(new Integer[0]); + assertArrayEquals(new Integer[]{1, 2, 3}, keys); } - @Test - void testSortByDateWithSameDonation() { - Timestamp sameDate = Timestamp.valueOf("2025-01-01 10:00:00"); - - Donation d1 = new Donation(1, 101, 201, - new BigDecimal("500.0"), sameDate, "Card"); - repo.addDonation(d1); + @Test + void sortByDateWithSameDateKeepsAll() { + repo.addDonation(donation1); repo.addDonation(donation4); - - HashMap sorted = repo.sortByDate(); - + Map sorted = repo.sortByDate(); assertEquals(1, sorted.size()); assertTrue(sorted.containsKey(1)); - assertFalse(sorted.containsKey(2)); } + @Test - void TestSortByAmountTest() { - repo.addDonation(donation2); + void sortByAmountAscending() { repo.addDonation(donation3); + repo.addDonation(donation1); - HashMap sorted = repo.sortByAmount(); + Map sorted = repo.sortByAmount(); Integer[] keys = sorted.keySet().toArray(new Integer[0]); - - assertEquals(3, keys[0]); - assertEquals(2, keys[1]); - - + assertArrayEquals(new Integer[]{3, 1}, keys); } + @Test - void testSortByAmountWithSameAmount() { + void sortByAmountWithSameAmount() { repo.addDonation(donation1); repo.addDonation(donation2); - - HashMap sorted = repo.sortByAmount(); - + Map sorted = repo.sortByAmount(); assertEquals(2, sorted.size()); assertTrue(sorted.containsKey(1)); assertTrue(sorted.containsKey(2)); } - @Test - void testSortByAmountWithSameDonation() { - - Donation d1 = new Donation(1, 101, 201, - new BigDecimal("500.0"), now, "Card"); - - repo.addDonation(d1); - repo.addDonation(donation4); - HashMap sorted = repo.sortByAmount(); - - assertEquals(1, sorted.size()); - assertTrue(sorted.containsKey(1)); - assertFalse(sorted.containsKey(2)); - } @Test - void testFilterByOrganization() { + void filterByOrganizationMatches() { repo.addDonation(donation1); repo.addDonation(donation2); + repo.addDonation(donation3); - HashMap filtered = repo.filterByOrganization(202); - + Map filtered = repo.filterByOrganization(202); assertEquals(2, filtered.size()); assertTrue(filtered.containsKey(1)); assertTrue(filtered.containsKey(2)); - assertFalse(filtered.containsKey(3)); } + @Test - void testFilterByOrganizationNoMatch() { + void filterByOrganizationNoMatch() { repo.addDonation(donation1); - repo.addDonation(donation2); - - HashMap filtered = repo.filterByOrganization(999); - + Map filtered = repo.filterByOrganization(999); assertTrue(filtered.isEmpty()); } + @Test - void testThrowsExceptionWhenOrgNumberIsNegative() { - IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, - () -> repo.filterByOrganization(-1)); - assertEquals("Organization number must be positive",thrown.getMessage()); + void filterByOrganizationThrowsIfNegative() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> repo.filterByOrganization(0)); + assertEquals("Organization number must be positive", ex.getMessage()); } + @Test - void testFilterByOrganizationWithSameDonation() { - Timestamp sameDate = Timestamp.valueOf("2025-01-01 10:00:00"); + void exportReturnsAllRowsSortedById() { + repo.addDonation(donation2); + repo.addDonation(donation1); - Donation d1 = new Donation(1, 101, 201, - new BigDecimal("500.0"), sameDate, "Card"); + List exported = repo.export(); + assertEquals(2, exported.size()); + assertArrayEquals(new Object[]{1, 102, 202, new BigDecimal("500.0"), ts1, "Card"}, exported.get(0)); + assertArrayEquals(new Object[]{2, 102, 202, new BigDecimal("500.0"), ts2, "PayPal"}, exported.get(1)); + } - repo.addDonation(d1); + @Test + void getNextDonationIdReturnsCorrectNextId() { + assertEquals(1, repo.getNextDonationId()); + repo.addDonation(donation1); repo.addDonation(donation4); + assertEquals(2, repo.getNextDonationId()); + } - HashMap sorted = repo.filterByOrganization(201); + @Test + void exportEmptyRepositoryReturnsEmptyList() { + List exported = repo.export(); + assertTrue(exported.isEmpty()); + } - assertEquals(1, sorted.size()); - assertTrue(sorted.containsKey(1)); - assertFalse(sorted.containsKey(2)); + @Test + void sortByDateHandlesDuplicateKeysWithLambda() { + Donation d1 = new Donation(1, 101, 201, new BigDecimal("100"), ts1, "Card"); + Donation d2 = new Donation(1, 102, 202, new BigDecimal("200"), ts2, "PayPal"); + repo.addDonation(d1); + repo.getAllDonations().put(d2.donationId(), d2); + Map sorted = repo.sortByDate(); + assertEquals(d1, sorted.get(1)); + } + + @Test + void sortByAmountHandlesDuplicateKeysWithLambda() { + Donation d1 = new Donation(1, 101, 201, new BigDecimal("100"), ts1, "Card"); + Donation d2 = new Donation(1, 102, 202, new BigDecimal("100"), ts2, "PayPal"); + repo.addDonation(d1); + repo.getAllDonations().put(d2.donationId(), d2); + Map sorted = repo.sortByAmount(); + assertEquals(d1, sorted.get(1)); } -} + @Test + void filterByOrganizationHandlesDuplicateKeysWithLambda() { + Donation d1 = new Donation(1, 101, 201, new BigDecimal("100"), ts1, "Card"); + Donation d2 = new Donation(1, 102, 201, new BigDecimal("200"), ts2, "PayPal"); + repo.addDonation(d1); + repo.getAllDonations().put(d2.donationId(), d2); + Map filtered = repo.filterByOrganization(201); + assertEquals(d1, filtered.get(1)); + } +} \ No newline at end of file diff --git a/src/test/java/edu/group5/app/model/donation/DonationServiceTest.java b/src/test/java/edu/group5/app/model/donation/DonationServiceTest.java new file mode 100644 index 0000000..5117373 --- /dev/null +++ b/src/test/java/edu/group5/app/model/donation/DonationServiceTest.java @@ -0,0 +1,116 @@ +package edu.group5.app.model.donation; + +import edu.group5.app.model.organization.Organization; +import edu.group5.app.model.organization.OrganizationRepository; +import edu.group5.app.model.user.Customer; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class DonationServiceTest { + + private DonationRepository donationRepository; + private OrganizationRepository organizationRepository; + private DonationService donationService; + private Customer customer; + + @BeforeEach + void setUp() { + HashMap orgMap = new HashMap<>(); + orgMap.put("org_number", "101"); + orgMap.put("name", "CharityOrg"); + orgMap.put("status", "approved"); + orgMap.put("url", "https://charity.org"); + orgMap.put("is_pre_approved", true); + + Object[] orgInput = new Object[]{ orgMap }; + organizationRepository = new OrganizationRepository(orgInput); + + donationRepository = new DonationRepository(new ArrayList<>()); + + + donationService = new DonationService(donationRepository, organizationRepository); + + customer = new Customer(1, "John", "Doe", "john@example.com", "$2a$10$hashed"); + } + + @Test + void testConstructorThrowsIfDonationRepositoryIsNull() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + new DonationService(null, organizationRepository); + }); + assertEquals("DonationRepository cannot be null", exception.getMessage()); + } + + @Test + void testConstructorThrowsIfOrganizationRepositoryIsNull() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + new DonationService(donationRepository, null); + }); + assertEquals("OrganizationRepository cannot be null", exception.getMessage()); + } + + @Test + void donateReturnsFalseIfCustomerNull() { + boolean result = donationService.donate(null, + 101, BigDecimal.TEN, "Card"); + assertFalse(result); + } + + @Test + void donateReturnsFalseIfAmountInvalid() { + assertFalse(donationService.donate(customer, + 101, null, "Card")); + assertFalse(donationService.donate(customer, + 101, BigDecimal.ZERO, "Card")); + assertFalse(donationService.donate(customer, + 101, new BigDecimal("-5"), "Card")); + } + + @Test + void donateReturnsFalseIfPaymentMethodBlank() { + assertFalse(donationService.donate(customer, + 101, BigDecimal.TEN, "")); + assertFalse(donationService.donate(customer, + 101, BigDecimal.TEN, " ")); + } + + @Test + void donateReturnsFalseIfOrganizationNotFound() { + boolean result = donationService.donate(customer, 999, BigDecimal.TEN, "Card"); + assertFalse(result); + } + + @Test + void testDonateAddsDonationSuccessfully() { + BigDecimal amount = new BigDecimal("50.00"); + String paymentMethod = "PayPal"; + boolean result = donationService.donate(customer, 101, amount, paymentMethod); + assertTrue(result); + assertEquals(1, donationRepository.getAllDonations().size()); + + Donation donation = donationRepository.getAllDonations().values().iterator().next(); + assertEquals(customer.getUserId(), donation.userId()); + assertEquals(101, donation.organizationId()); + assertEquals(amount, donation.amount()); + assertEquals(paymentMethod, donation.paymentMethod()); + assertTrue(donation.date().before(Timestamp.from(Instant.now())) || + donation.date().equals(Timestamp.from(Instant.now()))); + } + + @Test + void testDonateWithMultipleDonations() { + donationService.donate(customer, 101, new BigDecimal("10.00"), "PayPal"); + donationService.donate(customer, 101, new BigDecimal("25.00"), "Card"); + assertEquals(2, donationRepository.getAllDonations().size()); + } +} \ No newline at end of file diff --git a/src/test/java/edu/group5/app/model/organization/OrganizationRepositoryTest.java b/src/test/java/edu/group5/app/model/organization/OrganizationRepositoryTest.java index 1913988..7a5ece5 100644 --- a/src/test/java/edu/group5/app/model/organization/OrganizationRepositoryTest.java +++ b/src/test/java/edu/group5/app/model/organization/OrganizationRepositoryTest.java @@ -69,4 +69,69 @@ void getTrustedOrganizations_OnlyReturnsTrustedOrganizations() { assertFalse(trusted.containsKey(4)); assertTrue(trusted.values().stream().allMatch(Organization::trusted)); } + + @Test + void testFindByOrgNumberReturnsOrganization() { + assertEquals(new Organization(1, "Trusted Org1", true, + "org.com", true, "Information about Trusted Org1"), + repository.findByOrgNumber(1)); + } + + @Test + void testFindByOrgNumberIfOrgNumberIsIllegal() { + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> repository.findByOrgNumber(-1)); + assertEquals("The Organization number must be a positive integer", exception.getMessage()); + } + + @Test + void testFindByOrgNumberIfOrgNumberNotFound() { + assertNull(repository.findByOrgNumber(999)); + } + + @Test + void testFindByOrgNameReturnsOrganization() { + assertEquals(new Organization(1, "Trusted Org1", true, + "org.com", true, "Information about Trusted Org1"), + repository.findByOrgName("Trusted Org1")); + } + + @Test + void testFindByOrgNameIfNameIsIllegalThrowsException() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> repository.findByOrgName(null)); + assertEquals("The name cannot be null", exception.getMessage()); + + IllegalArgumentException exception2 = assertThrows(IllegalArgumentException.class, + () -> repository.findByOrgName("")); + assertEquals("The name cannot be null", exception2.getMessage()); + } + + @Test + void testFindByOrgNameIfNameNotFound() { + assertNull(repository.findByOrgName("Nonexistent Org")); + } + + @Test + void testFindByOrgNameIsCaseInsensitive() { + assertEquals(new Organization(1, "Trusted Org1", true, + "org.com", true, "Information about Trusted Org1"), + repository.findByOrgName("trusted org1")); + } + + @Test + void testExportAllOrganizations() { + Object[] allOrgs = repository.export(); + assertEquals(4, allOrgs.length); + } + + @Test + void testExportAllOrganizationsThrowsWhenRepositoryIsEmpty() { + OrganizationRepository emptyRepo = new OrganizationRepository(new Object[0]); + IllegalStateException exception = assertThrows( + IllegalStateException.class, () -> emptyRepo.export() + ); + assertEquals("The repository is empty", exception.getMessage()); + } } \ No newline at end of file diff --git a/src/test/java/edu/group5/app/model/user/CustomerTest.java b/src/test/java/edu/group5/app/model/user/CustomerTest.java index 8678fc9..bd607f5 100644 --- a/src/test/java/edu/group5/app/model/user/CustomerTest.java +++ b/src/test/java/edu/group5/app/model/user/CustomerTest.java @@ -142,4 +142,68 @@ void verifyPasswordReturnsFalseForEmptyPassword() { assertFalse(user.verifyPassword("")); } + + @Test + void getRoleReturnsCustomer() { + User user = new Customer(testUserId, testFirstName, + testLastName, testEmail, testPasswordHash); + assertEquals(UserRepository.ROLE_CUSTOMER, user.getRole()); + } + + @Test + void addPreferenceAddsOrganizationNumber() { + Customer customer = new Customer(testUserId, testFirstName, testLastName, testEmail, testPasswordHash); + customer.addPreference(123); + assertTrue(customer.getPreferences().contains(123)); + } + + @Test + void addPreferenceWithNegativeOrgNumberThrowsException() { + Customer customer = new Customer(testUserId, testFirstName, testLastName, testEmail, testPasswordHash); + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> customer.addPreference(-1) + ); + assertEquals("Organization number must be a positive integer", exception.getMessage()); + } + + @Test + void addPreferenceWithExistingOrgNumberThrowsException() { + Customer customer = new Customer(testUserId, testFirstName, testLastName, testEmail, testPasswordHash); + customer.addPreference(123); + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> customer.addPreference(123) + ); + assertEquals("Organization number already in preferences", exception.getMessage()); + } + + @Test + void removePreferenceRemovesOrganizationNumber() { + Customer customer = new Customer(testUserId, testFirstName, testLastName, testEmail, testPasswordHash); + customer.addPreference(123); + customer.removePreference(123); + assertFalse(customer.getPreferences().contains(123)); + } + + @Test + void removePreferenceWithNegativeOrgNumberThrowsException() { + Customer customer = new Customer(testUserId, testFirstName, testLastName, testEmail, testPasswordHash); + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> customer.removePreference(-1) + ); + assertEquals("Organization number must be a positive integer", exception.getMessage()); + } + + @Test + void removePreferenceWithNonExistingOrgNumberThrowsException() { + Customer customer = new Customer(testUserId, testFirstName, testLastName, testEmail, testPasswordHash); + customer.addPreference(123); + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> customer.removePreference(456) + ); + assertEquals("Organization number not found in preferences", exception.getMessage()); + } } diff --git a/src/test/java/edu/group5/app/model/user/UserRepositoryTest.java b/src/test/java/edu/group5/app/model/user/UserRepositoryTest.java new file mode 100644 index 0000000..1b19c88 --- /dev/null +++ b/src/test/java/edu/group5/app/model/user/UserRepositoryTest.java @@ -0,0 +1,124 @@ +package edu.group5.app.model.user; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class UserRepositoryTest { + +private UserRepository repo; +private List rows; + + @BeforeEach + void setUp() { + rows = new ArrayList<>(); + rows.add(new Object[]{1, "Customer", "John", "Cena", "john@example.com", "hashedpass"}); + rows.add(new Object[]{2, "Customer", "Jane", "Doe", "jane@example.com", "hashedpass"}); + repo = new UserRepository(rows); + } + + @Test + void constructorThrowsIfNull() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> new UserRepository(null)); + assertEquals("The list of rows cannot be null", ex.getMessage()); + } + + @Test + void constructorThrowsIfInvalidRowLength() { + List invalid = new ArrayList<>(); + invalid.add(new Object[]{1, "Customer"}); + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> new UserRepository(invalid)); + assertEquals("Each row must contain exactly 6 elements", ex.getMessage()); + } + + @Test + void constructorThrowsIfUnknownRole() { + List badRole = new ArrayList<>(); + badRole.add(new Object[]{3, "Admin", "Bob", "Smith", "bob@example.com", "pass"}); + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> new UserRepository(badRole)); + assertEquals("Unknown role: Admin", ex.getMessage()); + } + + @Test + void addUserSuccessfully() { + Customer user = new Customer(3, "Bob", "Smith", "bob@example.com", "pass"); + assertTrue(repo.addUser(user)); + assertEquals(3, repo.getUsers().size()); + } + + @Test + void addUserNullThrows() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> repo.addUser(null)); + assertEquals("User cannot be null", ex.getMessage()); + } + + @Test + void addUserDuplicateReturnsFalse() { + Customer user = new Customer(1, "John", "Cena", "john@example.com", "pass"); + assertFalse(repo.addUser(user)); + } + + @Test + void findUserByEmailSuccessfully() { + User u = repo.findUserByEmail("jane@example.com"); + assertEquals("Jane", u.getFirstName()); + } + + @Test + void findUserByEmailReturnsNullIfNotFound() { + assertNull(repo.findUserByEmail("notfound@example.com")); + } + + @Test + void findUserByEmailThrowsIfNullOrEmpty() { + IllegalArgumentException ex1 = assertThrows(IllegalArgumentException.class, + () -> repo.findUserByEmail(null)); + assertEquals("Email cannot be null or empty", ex1.getMessage()); + + IllegalArgumentException ex2 = assertThrows(IllegalArgumentException.class, + () -> repo.findUserByEmail(" ")); + assertEquals("Email cannot be null or empty", ex2.getMessage()); + } + +@Test +void getUserByIdSuccessfully() { + User u = repo.getUserById(1); + assertEquals("John", u.getFirstName()); + } + +@Test +void getUserByIdReturnsNullIfNotFound() { + assertNull(repo.getUserById(999)); + } + + @Test + void getUserByIdThrowsIfNonPositive() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> repo.getUserById(0)); + assertEquals("User ID must be positive", ex.getMessage()); + } + + + @Test + void getNextUserIdReturnsNextAvailable() { + int nextId = repo.getNextUserId(); + assertEquals(3, nextId); + } + + @Test + void exportContainsAllUsers() { + List exported = repo.export(); + assertEquals(2, exported.size()); + assertEquals(1, exported.get(0)[0]); + assertEquals(2, exported.get(1)[0]); + assertEquals("Customer", exported.get(0)[1]); + } +} \ No newline at end of file diff --git a/src/test/java/edu/group5/app/model/user/UserServiceTest.java b/src/test/java/edu/group5/app/model/user/UserServiceTest.java new file mode 100644 index 0000000..57d4bf2 --- /dev/null +++ b/src/test/java/edu/group5/app/model/user/UserServiceTest.java @@ -0,0 +1,108 @@ +package edu.group5.app.model.user; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +public class UserServiceTest { + private UserRepository repo; + private UserService service; + + @BeforeEach + void setUp() { + List rows = new ArrayList<>(); + rows.add(new Object[]{1, "Customer", "John", "Cena", "john.cena@example.com", "$2a$10$hashed"}); + rows.add(new Object[]{2, "Customer", "Jane", "Doe", "jane.doe@example.com", "$2a$10$hashed"}); + repo = new UserRepository(rows); + service = new UserService(repo); + } + @Test + void constructorthrowsIfNull() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> new UserService(null)); + assertEquals("UserRepository cannot be null", ex.getMessage()); + } + + @Test + void registerUserValid() { + boolean result = service.registerUser("Customer", "Alice", "Smith", + "alice@example.com", "$2a$10$hashed"); + assertTrue(result); + assertEquals(3, repo.getUsers().size()); + } + + @Test + void registerUserRoleCaseInsensitive() { + boolean result = service.registerUser("customer", "Bob", "Smith", + "bob@example.com", "$2a$10$hashed"); + assertTrue(result); + } + + @Test + void registerUserInvalidInputsReturnFalse() { + assertFalse(service.registerUser(null, "A", "B", "a@b.com", "pass")); + assertFalse(service.registerUser("Customer", null, "B", "a@b.com", "pass")); + assertFalse(service.registerUser("Customer", "A", null, "a@b.com", "pass")); + assertFalse(service.registerUser("Customer", "A", "B", null, "pass")); + assertFalse(service.registerUser("Customer", "A", "B", "a@b.com", null)); + assertFalse(service.registerUser("", "A", "B", "a@b.com", "pass")); + assertFalse(service.registerUser("Customer", " ", "B", "a@b.com", "pass")); + } + + @Test + void registerUserDuplicateEmailAllowedInCurrentCode() { + boolean result = service.registerUser("Customer", "John", "Cena", + "john.cena@example.com", "$2a$10$hashed"); + assertTrue(result); + assertEquals(3, repo.getUsers().size()); + } + + @Test + void registerUserInvalidRoleReturnsFalse() { + boolean result = service.registerUser("Admin", "X", "Y", "x@y.com", "pass"); + assertFalse(result); + } + + @Test + void loginValidPassword() { + String plainPassword = "password123"; + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + String hashedPassword = encoder.encode(plainPassword); + User testUser = new Customer(10, "Test", "User", "test@example.com", hashedPassword); + repo.addUser(testUser); + + boolean result = service.login("test@example.com", plainPassword); + assertTrue(result); + } + + @Test + void loginInvalidPassword() { + String plainPassword = "password123"; + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + String hashedPassword = encoder.encode(plainPassword); + User testUser = new Customer(10, "Test", "User", "test@example.com", hashedPassword); + repo.addUser(testUser); + + boolean result = service.login("test@example.com", "wrongpass"); + assertFalse(result); + } + + @Test + void loginInvalidEmail() { + boolean result = service.login("nonexist@example.com", "password"); + boolean result2 = service.login(null, "password"); + boolean result3 = service.login(" ", "password"); + assertFalse(result); + assertFalse(result2); + assertFalse(result3); + } +} \ No newline at end of file From c3b7e4072e3c1b501309f8a065d4bdaa7c27b50e Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Sat, 7 Mar 2026 12:46:09 +0100 Subject: [PATCH 15/17] feat: merge release/v1.0.0 into feat/User, fix up on differences in branches and test that everythings alright with maven commands --- src/main/java/edu/group5/app/App.java | 9 +-------- .../app/model/organization/OrganizationRepository.java | 2 +- src/main/java/edu/group5/app/model/user/UserService.java | 3 +-- .../group5/app/model/donation/DonationServiceTest.java | 3 +-- .../group5/app/model/organization/OrganizationTest.java | 2 +- 5 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/main/java/edu/group5/app/App.java b/src/main/java/edu/group5/app/App.java index dc19a9f..887fe4d 100644 --- a/src/main/java/edu/group5/app/App.java +++ b/src/main/java/edu/group5/app/App.java @@ -1,17 +1,10 @@ package edu.group5.app; -import java.sql.Wrapper; -import java.util.HashMap; - -import edu.group5.app.control.OrgAPIWrapper; -import tools.jackson.core.type.TypeReference; -import tools.jackson.databind.ObjectMapper; - /** * Hello world! */ public class App { public static void main(String[] args) throws InterruptedException { - Wrapper wrap = new Wrapper(); + System.out.println("Hello World!"); } } diff --git a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java index 4499288..a47b3d5 100644 --- a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java +++ b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java @@ -63,7 +63,7 @@ public Object[] export() { orgMap.put("org_number", org.orgNumber()); orgMap.put("name", org.name()); orgMap.put("status", org.trusted() ? "approved" : "unapproved"); - orgMap.put("url", org.websiteURL()); + orgMap.put("url", org.websiteUrl()); orgMap.put("is_pre_approved", org.isPreApproved()); return orgMap; }) diff --git a/src/main/java/edu/group5/app/model/user/UserService.java b/src/main/java/edu/group5/app/model/user/UserService.java index 7a9dc53..dfe66a1 100644 --- a/src/main/java/edu/group5/app/model/user/UserService.java +++ b/src/main/java/edu/group5/app/model/user/UserService.java @@ -45,8 +45,7 @@ public boolean registerUser(String role, String firstName, String lastName, User user; if (role.equalsIgnoreCase("Customer")) { user = new Customer(userRepository.getNextUserId(), firstName, lastName, email, passwordHash); - } else { /* TODO when you switch to a real DB, - replace getNextUserId with DB auto-increment/identity and ignore manual ID generation in service*/ + } else { /* TODO when you switch to a real DB, replace getNextUserId with DB auto-increment/identity and ignore manual ID generation in service*/ return false; } userRepository.addUser(user); diff --git a/src/test/java/edu/group5/app/model/donation/DonationServiceTest.java b/src/test/java/edu/group5/app/model/donation/DonationServiceTest.java index 5117373..bd7f11a 100644 --- a/src/test/java/edu/group5/app/model/donation/DonationServiceTest.java +++ b/src/test/java/edu/group5/app/model/donation/DonationServiceTest.java @@ -1,6 +1,5 @@ package edu.group5.app.model.donation; -import edu.group5.app.model.organization.Organization; import edu.group5.app.model.organization.OrganizationRepository; import edu.group5.app.model.user.Customer; @@ -12,7 +11,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; -import java.util.List; + import static org.junit.jupiter.api.Assertions.*; diff --git a/src/test/java/edu/group5/app/model/organization/OrganizationTest.java b/src/test/java/edu/group5/app/model/organization/OrganizationTest.java index 7f1b898..f921b60 100644 --- a/src/test/java/edu/group5/app/model/organization/OrganizationTest.java +++ b/src/test/java/edu/group5/app/model/organization/OrganizationTest.java @@ -21,7 +21,7 @@ void constructor_CreatesAnOrganizationWhenInputIsValid() { () -> assertEquals(1, org.orgNumber()), () -> assertEquals("Org", org.name()), () -> assertTrue(org.trusted()), - () -> assertEquals("org.com", org.websiteURL()), + () -> assertEquals("org.com", org.websiteUrl()), () -> assertTrue(org.isPreApproved()), () -> assertEquals("Org description", org.description()) ); From a875a553041d212434031f4f142235a91bed1790 Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Sat, 7 Mar 2026 19:06:00 +0100 Subject: [PATCH 16/17] feat[DonationRepository]: Add filterByUser() method and respective JUnit test for it --- .../model/donation/DonationRepository.java | 22 ++++++++++++++++- .../donation/DonationRepositoryTest.java | 24 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/group5/app/model/donation/DonationRepository.java b/src/main/java/edu/group5/app/model/donation/DonationRepository.java index 54845a5..8f9b4ec 100644 --- a/src/main/java/edu/group5/app/model/donation/DonationRepository.java +++ b/src/main/java/edu/group5/app/model/donation/DonationRepository.java @@ -154,9 +154,9 @@ public HashMap sortByAmount() { /** * Returns all donations associated with a specific organization. - * * @param orgNumber the organization ID to filter by * @return a map containing all donations that belong to the given organization + * @throws IllegalArgumentException if the orgNumber is not positive */ public HashMap filterByOrganization(int orgNumber) { if (orgNumber <= 0) { @@ -172,4 +172,24 @@ public HashMap filterByOrganization(int orgNumber) { LinkedHashMap::new )); } + + /** + * Returns all donations made by a specific user. + * @param userId the user ID to filter by + * @return a map containing all donations that belong to the given user + * @throws IllegalArgumentException if the userId is not positive + */ + public HashMap filterByUser(int userId) { + if (userId <= 0) { + throw new IllegalArgumentException("User ID must be positive"); + } + return content.entrySet().stream() + .filter(entry -> entry.getValue().userId() == userId) + .collect(Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (e1, e2) -> e1, + LinkedHashMap::new + )); + } } diff --git a/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java b/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java index 04d4626..fd7ec1e 100644 --- a/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java +++ b/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java @@ -239,4 +239,28 @@ void filterByOrganizationHandlesDuplicateKeysWithLambda() { Map filtered = repo.filterByOrganization(201); assertEquals(d1, filtered.get(1)); } + + @Test + void filterByUserIdMatches() { + repo.addDonation(donation1); + repo.addDonation(donation3); + + Map filtered = repo.filterByUser(102); + assertEquals(1, filtered.size()); + assertTrue(filtered.containsKey(1)); + } + + @Test + void filterByUserIdNoMatch() { + repo.addDonation(donation1); + Map filtered = repo.filterByUser(999); + assertTrue(filtered.isEmpty()); + } + + @Test + void filterByUserIdThrowsIfNegative() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> repo.filterByUser(0)); + assertEquals("User ID must be positive", ex.getMessage()); + } } \ No newline at end of file From 6982dd95ca14b9f91a79b513ac2a6cc570e8a758 Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Tue, 10 Mar 2026 13:14:19 +0100 Subject: [PATCH 17/17] Update&Test[Repository]: Update Repository and JUnit tests --- .../edu/group5/app/model/DBRepository.java | 6 +- .../model/donation/DonationRepository.java | 7 +- .../app/model/donation/DonationService.java | 2 +- .../group5/app/model/user/UserRepository.java | 16 ++--- .../group5/app/model/user/UserService.java | 8 +-- .../donation/DonationRepositoryTest.java | 64 +++++++++---------- .../app/model/user/UserRepositoryTest.java | 36 +++++++++-- .../app/model/user/UserServiceTest.java | 18 +++++- 8 files changed, 96 insertions(+), 61 deletions(-) diff --git a/src/main/java/edu/group5/app/model/DBRepository.java b/src/main/java/edu/group5/app/model/DBRepository.java index 799a977..f3363b7 100644 --- a/src/main/java/edu/group5/app/model/DBRepository.java +++ b/src/main/java/edu/group5/app/model/DBRepository.java @@ -30,11 +30,7 @@ protected void updateContentLock() { } } - public void addContent(K key, V value) { - synchronized (contentLock) { - content.put(key, value); - } - } + public abstract boolean addContent(V value); /** * Exports the repository content as a list of Object arrays, where each array represents a row of data. diff --git a/src/main/java/edu/group5/app/model/donation/DonationRepository.java b/src/main/java/edu/group5/app/model/donation/DonationRepository.java index 8f9b4ec..6181460 100644 --- a/src/main/java/edu/group5/app/model/donation/DonationRepository.java +++ b/src/main/java/edu/group5/app/model/donation/DonationRepository.java @@ -100,14 +100,15 @@ public Map getAllDonations() { * @return {@code true} if the donation was successfully added, and * {@code false} if a donation with the same ID already exists */ - public boolean addDonation(Donation donation) { + @Override + public boolean addContent(Donation donation) { if (donation == null) { throw new IllegalArgumentException("Donation cannot be null"); } if (content.containsKey(donation.donationId())){ - return false; + return false; } - content.put(donation.donationId(), donation); + this.content.put(donation.donationId(), donation); return true; } diff --git a/src/main/java/edu/group5/app/model/donation/DonationService.java b/src/main/java/edu/group5/app/model/donation/DonationService.java index d8fef60..804d901 100644 --- a/src/main/java/edu/group5/app/model/donation/DonationService.java +++ b/src/main/java/edu/group5/app/model/donation/DonationService.java @@ -57,7 +57,7 @@ public boolean donate(Customer customer, int orgNumber, BigDecimal amount, Strin Donation donation = new Donation(donationRepository.getNextDonationId(), customer.getUserId(), org.orgNumber(), amount, Timestamp.from(Instant.now()), paymentMethod); - donationRepository.addDonation(donation); + donationRepository.addContent(donation); return true; } } \ No newline at end of file diff --git a/src/main/java/edu/group5/app/model/user/UserRepository.java b/src/main/java/edu/group5/app/model/user/UserRepository.java index 8ccb790..692126f 100644 --- a/src/main/java/edu/group5/app/model/user/UserRepository.java +++ b/src/main/java/edu/group5/app/model/user/UserRepository.java @@ -76,13 +76,12 @@ public User getUserById(int userId) { * @throws IllegalStateException if no available user ID can be found */ public int getNextUserId() { - int nextId = content.size() + 1; - while (content.containsKey(nextId)) { - nextId++; - if (nextId <= 0) { - throw new IllegalStateException("No available user ID found"); - } + if (content.isEmpty()) { + return 1; } + int maxKey = content.keySet().stream().max(Integer::compareTo).orElseThrow( + () -> new IllegalStateException("No keys found")); + int nextId = maxKey + 1; return nextId; } /* TODO change this when data database is introduced */ @@ -98,14 +97,15 @@ public int getNextUserId() { * @return {@code true} if the user was successfully added, and * {@code false} if a user with the same ID already exists */ - public boolean addUser(User user) { + @Override + public boolean addContent(User user) { if (user == null) { throw new IllegalArgumentException("User cannot be null"); } if (content.containsKey(user.getUserId())){ return false; } - content.put(user.getUserId(), user); + this.content.put(user.getUserId(), user); return true; } diff --git a/src/main/java/edu/group5/app/model/user/UserService.java b/src/main/java/edu/group5/app/model/user/UserService.java index dfe66a1..d430a59 100644 --- a/src/main/java/edu/group5/app/model/user/UserService.java +++ b/src/main/java/edu/group5/app/model/user/UserService.java @@ -48,20 +48,20 @@ public boolean registerUser(String role, String firstName, String lastName, } else { /* TODO when you switch to a real DB, replace getNextUserId with DB auto-increment/identity and ignore manual ID generation in service*/ return false; } - userRepository.addUser(user); + userRepository.addContent(user); return true; } /** * Authenticates a user based on the provided email and password. * @param email the email address of the user attempting to log in; must not be null or empty - * @param password the plaintext password of the user attempting to log in; must not be + * @param password the plaintext password of the user attempting to log in; must not be null or empty * @return true if the login is successful * (i.e., the user exists and the password is correct), false otherwise - * @throws IllegalArgumentException if email is null or empty, or if password is null + * @throws IllegalArgumentException if email is null or empty, or if password is null or empty */ public boolean login(String email, String password) { - if (email == null || email.trim().isEmpty() || password == null) { + if (email == null || email.trim().isEmpty() || password == null || password.trim().isEmpty()) { return false; } User user = userRepository.findUserByEmail(email); diff --git a/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java b/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java index fd7ec1e..bdf0110 100644 --- a/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java +++ b/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java @@ -71,29 +71,29 @@ void constructorThrowsIfRowIsNull() { } @Test - void addDonationSuccessfully() { - assertTrue(repo.addDonation(donation1)); + void addContentSuccessfully() { + assertTrue(repo.addContent(donation1)); assertEquals(1, repo.getAllDonations().size()); assertTrue(repo.getAllDonations().containsValue(donation1)); } @Test - void addDonationDuplicateIdFails() { - repo.addDonation(donation1); - assertFalse(repo.addDonation(donation4)); + void addContentDuplicateIdFails() { + repo.addContent(donation1); + assertFalse(repo.addContent(donation4)); assertEquals(1, repo.getAllDonations().size()); } @Test - void addDonationNullThrows() { + void addContentNullThrows() { IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, - () -> repo.addDonation(null)); + () -> repo.addContent(null)); assertEquals("Donation cannot be null", ex.getMessage()); } @Test void getDonationByIdSuccessfully() { - repo.addDonation(donation1); + repo.addContent(donation1); Donation retrieved = repo.getDonationById(1); assertEquals(donation1, retrieved); } @@ -112,7 +112,7 @@ void getDonationByIdReturnsNullIfNotFound() { @Test void getAllDonationsReturnsCopy() { - repo.addDonation(donation1); + repo.addContent(donation1); Map all = repo.getAllDonations(); assertEquals(1, all.size()); all.clear(); @@ -121,9 +121,9 @@ void getAllDonationsReturnsCopy() { @Test void sortByDateAscending() { - repo.addDonation(donation3); - repo.addDonation(donation1); - repo.addDonation(donation2); + repo.addContent(donation3); + repo.addContent(donation1); + repo.addContent(donation2); Map sorted = repo.sortByDate(); Integer[] keys = sorted.keySet().toArray(new Integer[0]); @@ -132,8 +132,8 @@ void sortByDateAscending() { @Test void sortByDateWithSameDateKeepsAll() { - repo.addDonation(donation1); - repo.addDonation(donation4); + repo.addContent(donation1); + repo.addContent(donation4); Map sorted = repo.sortByDate(); assertEquals(1, sorted.size()); assertTrue(sorted.containsKey(1)); @@ -141,8 +141,8 @@ void sortByDateWithSameDateKeepsAll() { @Test void sortByAmountAscending() { - repo.addDonation(donation3); - repo.addDonation(donation1); + repo.addContent(donation3); + repo.addContent(donation1); Map sorted = repo.sortByAmount(); Integer[] keys = sorted.keySet().toArray(new Integer[0]); @@ -151,8 +151,8 @@ void sortByAmountAscending() { @Test void sortByAmountWithSameAmount() { - repo.addDonation(donation1); - repo.addDonation(donation2); + repo.addContent(donation1); + repo.addContent(donation2); Map sorted = repo.sortByAmount(); assertEquals(2, sorted.size()); assertTrue(sorted.containsKey(1)); @@ -161,9 +161,9 @@ void sortByAmountWithSameAmount() { @Test void filterByOrganizationMatches() { - repo.addDonation(donation1); - repo.addDonation(donation2); - repo.addDonation(donation3); + repo.addContent(donation1); + repo.addContent(donation2); + repo.addContent(donation3); Map filtered = repo.filterByOrganization(202); assertEquals(2, filtered.size()); @@ -173,7 +173,7 @@ void filterByOrganizationMatches() { @Test void filterByOrganizationNoMatch() { - repo.addDonation(donation1); + repo.addContent(donation1); Map filtered = repo.filterByOrganization(999); assertTrue(filtered.isEmpty()); } @@ -187,8 +187,8 @@ void filterByOrganizationThrowsIfNegative() { @Test void exportReturnsAllRowsSortedById() { - repo.addDonation(donation2); - repo.addDonation(donation1); + repo.addContent(donation2); + repo.addContent(donation1); List exported = repo.export(); assertEquals(2, exported.size()); @@ -199,8 +199,8 @@ void exportReturnsAllRowsSortedById() { @Test void getNextDonationIdReturnsCorrectNextId() { assertEquals(1, repo.getNextDonationId()); - repo.addDonation(donation1); - repo.addDonation(donation4); + repo.addContent(donation1); + repo.addContent(donation4); assertEquals(2, repo.getNextDonationId()); } @@ -214,7 +214,7 @@ void exportEmptyRepositoryReturnsEmptyList() { void sortByDateHandlesDuplicateKeysWithLambda() { Donation d1 = new Donation(1, 101, 201, new BigDecimal("100"), ts1, "Card"); Donation d2 = new Donation(1, 102, 202, new BigDecimal("200"), ts2, "PayPal"); - repo.addDonation(d1); + repo.addContent(d1); repo.getAllDonations().put(d2.donationId(), d2); Map sorted = repo.sortByDate(); assertEquals(d1, sorted.get(1)); @@ -224,7 +224,7 @@ void sortByDateHandlesDuplicateKeysWithLambda() { void sortByAmountHandlesDuplicateKeysWithLambda() { Donation d1 = new Donation(1, 101, 201, new BigDecimal("100"), ts1, "Card"); Donation d2 = new Donation(1, 102, 202, new BigDecimal("100"), ts2, "PayPal"); - repo.addDonation(d1); + repo.addContent(d1); repo.getAllDonations().put(d2.donationId(), d2); Map sorted = repo.sortByAmount(); assertEquals(d1, sorted.get(1)); @@ -234,7 +234,7 @@ void sortByAmountHandlesDuplicateKeysWithLambda() { void filterByOrganizationHandlesDuplicateKeysWithLambda() { Donation d1 = new Donation(1, 101, 201, new BigDecimal("100"), ts1, "Card"); Donation d2 = new Donation(1, 102, 201, new BigDecimal("200"), ts2, "PayPal"); - repo.addDonation(d1); + repo.addContent(d1); repo.getAllDonations().put(d2.donationId(), d2); Map filtered = repo.filterByOrganization(201); assertEquals(d1, filtered.get(1)); @@ -242,8 +242,8 @@ void filterByOrganizationHandlesDuplicateKeysWithLambda() { @Test void filterByUserIdMatches() { - repo.addDonation(donation1); - repo.addDonation(donation3); + repo.addContent(donation1); + repo.addContent(donation3); Map filtered = repo.filterByUser(102); assertEquals(1, filtered.size()); @@ -252,7 +252,7 @@ void filterByUserIdMatches() { @Test void filterByUserIdNoMatch() { - repo.addDonation(donation1); + repo.addContent(donation1); Map filtered = repo.filterByUser(999); assertTrue(filtered.isEmpty()); } diff --git a/src/test/java/edu/group5/app/model/user/UserRepositoryTest.java b/src/test/java/edu/group5/app/model/user/UserRepositoryTest.java index 1b19c88..0700828 100644 --- a/src/test/java/edu/group5/app/model/user/UserRepositoryTest.java +++ b/src/test/java/edu/group5/app/model/user/UserRepositoryTest.java @@ -47,23 +47,41 @@ void constructorThrowsIfUnknownRole() { } @Test - void addUserSuccessfully() { + void constructorThrowsIfRowNull() { + List invalid = new ArrayList<>(); + invalid.add(null); + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> new UserRepository(invalid)); + assertEquals("Each row must contain exactly 6 elements", ex.getMessage()); + } + + @Test + void constructorThrowsIfRowHasIncorrectLength() { + List invalid = new ArrayList<>(); + invalid.add(new Object[]{1, "Customer", "John"}); + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> new UserRepository(invalid)); + assertEquals("Each row must contain exactly 6 elements", ex.getMessage()); + } + + @Test + void addContentSuccessfully() { Customer user = new Customer(3, "Bob", "Smith", "bob@example.com", "pass"); - assertTrue(repo.addUser(user)); + assertTrue(repo.addContent(user)); assertEquals(3, repo.getUsers().size()); } @Test - void addUserNullThrows() { + void addContentNullThrows() { IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, - () -> repo.addUser(null)); + () -> repo.addContent(null)); assertEquals("User cannot be null", ex.getMessage()); } @Test - void addUserDuplicateReturnsFalse() { + void addContentDuplicateReturnsFalse() { Customer user = new Customer(1, "John", "Cena", "john@example.com", "pass"); - assertFalse(repo.addUser(user)); + assertFalse(repo.addContent(user)); } @Test @@ -113,6 +131,12 @@ void getNextUserIdReturnsNextAvailable() { assertEquals(3, nextId); } + @Test + void getNextUserIdReturns1IfEmpty() { + UserRepository emptyRepo = new UserRepository(new ArrayList<>()); + assertEquals(1, emptyRepo.getNextUserId()); + } + @Test void exportContainsAllUsers() { List exported = repo.export(); diff --git a/src/test/java/edu/group5/app/model/user/UserServiceTest.java b/src/test/java/edu/group5/app/model/user/UserServiceTest.java index 57d4bf2..20397a6 100644 --- a/src/test/java/edu/group5/app/model/user/UserServiceTest.java +++ b/src/test/java/edu/group5/app/model/user/UserServiceTest.java @@ -54,8 +54,18 @@ void registerUserInvalidInputsReturnFalse() { assertFalse(service.registerUser("Customer", "A", null, "a@b.com", "pass")); assertFalse(service.registerUser("Customer", "A", "B", null, "pass")); assertFalse(service.registerUser("Customer", "A", "B", "a@b.com", null)); + assertFalse(service.registerUser("", "A", "B", "a@b.com", "pass")); + assertFalse(service.registerUser("Customer", "", "B", "a@b.com", "pass")); + assertFalse(service.registerUser("Customer", "A", "", "a@b.com", "pass")); + assertFalse(service.registerUser("Customer", "A", "B", "", "pass")); + assertFalse(service.registerUser("Customer", "A", "B", "a@b.com", "")); + + assertFalse(service.registerUser(" ", "A", "B", "a@b.com", "pass")); assertFalse(service.registerUser("Customer", " ", "B", "a@b.com", "pass")); + assertFalse(service.registerUser("Customer", "A", " ", "a@b.com", "pass")); + assertFalse(service.registerUser("Customer", "A", "B", " ", "pass")); + assertFalse(service.registerUser("Customer", "A", "B", "a@b.com", " ")); } @Test @@ -78,7 +88,7 @@ void loginValidPassword() { BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); String hashedPassword = encoder.encode(plainPassword); User testUser = new Customer(10, "Test", "User", "test@example.com", hashedPassword); - repo.addUser(testUser); + repo.addContent(testUser); boolean result = service.login("test@example.com", plainPassword); assertTrue(result); @@ -90,10 +100,14 @@ void loginInvalidPassword() { BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); String hashedPassword = encoder.encode(plainPassword); User testUser = new Customer(10, "Test", "User", "test@example.com", hashedPassword); - repo.addUser(testUser); + repo.addContent(testUser); boolean result = service.login("test@example.com", "wrongpass"); + boolean result2 = service.login("test@example.com", null); + boolean result3 = service.login("test@example.com", " "); assertFalse(result); + assertFalse(result2); + assertFalse(result3); } @Test