From 4ac5cdd56622da99b2eca3567443193829478cf8 Mon Sep 17 00:00:00 2001
From: Fredrik Marjoni
Date: Sat, 7 Mar 2026 11:52:24 +0100
Subject: [PATCH] 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