diff --git a/pom.xml b/pom.xml index 0bb117f..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 @@ -45,13 +52,17 @@ 3.1.0 compile - - - commons-logging - commons-logging - 1.3.5 - - + + org.springframework + spring-core + 6.1.10 + + + org.slf4j + slf4j-simple + 2.0.9 + + @@ -112,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/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/DBRepository.java b/src/main/java/edu/group5/app/model/DBRepository.java index 1196eae..f3363b7 100644 --- a/src/main/java/edu/group5/app/model/DBRepository.java +++ b/src/main/java/edu/group5/app/model/DBRepository.java @@ -1,9 +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. @@ -14,6 +12,7 @@ *

*/ public abstract class DBRepository extends Repository { + protected final Map contentLock; /** * Constructs a DBRepository with the given content. * @@ -21,5 +20,22 @@ 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 abstract boolean addContent(V 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/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/DonationRepository.java b/src/main/java/edu/group5/app/model/donation/DonationRepository.java index 339e7ca..6181460 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); } /** @@ -44,14 +100,15 @@ public DonationRepository(HashMap content){ * @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; } @@ -98,9 +155,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) { @@ -116,4 +173,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/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..804d901 --- /dev/null +++ b/src/main/java/edu/group5/app/model/donation/DonationService.java @@ -0,0 +1,63 @@ +package edu.group5.app.model.donation; +import java.time.Instant; + +import java.math.BigDecimal; +import java.sql.Timestamp; +import edu.group5.app.model.organization.Organization; +import edu.group5.app.model.organization.OrganizationRepository; +import edu.group5.app.model.user.Customer; + +/** + * 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) { + 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; + } + + /** + * 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, 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(donationRepository.getNextDonationId(), + customer.getUserId(), org.orgNumber(), amount, Timestamp.from(Instant.now()), paymentMethod); + donationRepository.addContent(donation); + return true; + } +} \ No newline at end of file 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 14a77d0..a47b3d5 100644 --- a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java +++ b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java @@ -1,38 +1,116 @@ 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; /** + * 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; + /** - * Creates a new Organization Repository - * - * @param content holds all current organizations in the repository; must not be null - * @throws NullPointerException if content is null + * 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(Map content) { - super(Objects.requireNonNull(content, "content cannot be null")); + public OrganizationRepository(Object[] input) { + super(new HashMap<>()); + grandMap = new HashMap<>(); + if (input == null) { + throw new IllegalArgumentException("The input cannot be null"); + } + ObjectMapper mapper = new ObjectMapper(); + + for (Object obj : input) { + HashMap contentMap = + mapper.convertValue(obj, new TypeReference>() {}); + + 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); + } } /** - * Gets all trusted organizations in the repository - * @return all organizations with trusted = true + * 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 + */ public Map getTrustedOrganizations() { Map trustedOrganizations = new HashMap<>(); - content.forEach((orgNr, org) -> { + grandMap.forEach((orgNr, org) -> { if (org.trusted()) { - trustedOrganizations.put(orgNr, org); + trustedOrganizations.put(orgNr, org); } }); - 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) { + 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 new file mode 100644 index 0000000..93f03ac --- /dev/null +++ b/src/main/java/edu/group5/app/model/user/Customer.java @@ -0,0 +1,59 @@ +package edu.group5.app.model.user; + +import java.util.List; +import java.util.ArrayList; + +/** + * 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 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, firstName, lastName, email, passwordHash); + this.preferences = new ArrayList<>(); +} + + 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"); + } + if (preferences.contains(orgNumber)) { + throw new IllegalArgumentException("Organization number already in preferences"); + } + preferences.add(orgNumber); + } + + 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 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..bdb4700 100644 --- a/src/main/java/edu/group5/app/model/user/User.java +++ b/src/main/java/edu/group5/app/model/user/User.java @@ -9,9 +9,8 @@ * 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; private String lastName; private String email; @@ -21,20 +20,16 @@ public 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 new file mode 100644 index 0000000..692126f --- /dev/null +++ b/src/main/java/edu/group5/app/model/user/UserRepository.java @@ -0,0 +1,126 @@ +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 UserRepository using Hashmap, + * and extends the content from DBRepository. + * @param content the underlying map used to store users, + * where the key represents the user ID + */ + 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); + } + /** + * 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() { + 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 */ + + /** + * Adds a new user to the repository + *

+ * 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 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 + */ + @Override + public boolean addContent(User user) { + if (user == null) { + throw new IllegalArgumentException("User cannot be null"); + } + if (content.containsKey(user.getUserId())){ + return false; + } + this.content.put(user.getUserId(), 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) { + 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 new file mode 100644 index 0000000..d430a59 --- /dev/null +++ b/src/main/java/edu/group5/app/model/user/UserService.java @@ -0,0 +1,70 @@ +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"); + } + 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() || + firstName == null || firstName.trim().isEmpty() || + lastName == null || lastName.trim().isEmpty() || + email == null || email.trim().isEmpty() || + passwordHash == null || passwordHash.trim().isEmpty()) { + return false; + } + 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*/ + return false; + } + 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 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 or empty + */ + public boolean login(String email, String password) { + if (email == null || email.trim().isEmpty() || password == null || password.trim().isEmpty()) { + return false; + } + User user = userRepository.findUserByEmail(email); + return user != null && user.verifyPassword(password); + } +} 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..bdf0110 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,241 @@ 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 testThrowsExceptionIfContentIsNull() { - IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, - () -> new DonationRepository(null)); - assertEquals("Content cannot be null", thrown.getMessage()); + void constructorThrowsIfNullList() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> new DonationRepository(null)); + assertEquals("The list of rows cannot be null", ex.getMessage()); } @Test - void testAddDonation() { - assertTrue(repo.addDonation(donation1)); - assertEquals(1, repo.getContent().size()); - - assertTrue(repo.addDonation(donation3)); - assertEquals(2, repo.getContent().size()); + 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 testAddDonationWithDuplicateId() { - assertTrue(repo.addDonation(donation1)); - assertFalse(repo.addDonation(donation4)); - 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()); } + + @Test - void testAddNullDonation() { - IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, - () -> repo.addDonation(null)); - assertEquals("Donation cannot be null",thrown.getMessage()); + 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 testSortByDate() { - repo.addDonation(donation3); - repo.addDonation(donation1); - repo.addDonation(donation2); - - HashMap sorted = repo.sortByDate(); - - Integer[] keys = sorted.keySet().toArray(new Integer[0]); - assertEquals(1, keys[0]); - assertEquals(2, keys[1]); - assertEquals(3, keys[2]); - + void addContentSuccessfully() { + assertTrue(repo.addContent(donation1)); + assertEquals(1, repo.getAllDonations().size()); + assertTrue(repo.getAllDonations().containsValue(donation1)); } + @Test - void testSortByDateWithSameDate() { + void addContentDuplicateIdFails() { + repo.addContent(donation1); + assertFalse(repo.addContent(donation4)); + assertEquals(1, repo.getAllDonations().size()); + } - repo.addDonation(donation3); - repo.addDonation(donation4); + @Test + void addContentNullThrows() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> repo.addContent(null)); + assertEquals("Donation cannot be null", ex.getMessage()); + } - HashMap sorted = repo.sortByDate(); + @Test + void getDonationByIdSuccessfully() { + repo.addContent(donation1); + Donation retrieved = repo.getDonationById(1); + assertEquals(donation1, retrieved); + } - assertEquals(2, sorted.size()); - assertTrue(sorted.containsKey(1)); - assertTrue(sorted.containsKey(3)); + @Test + void getDonationByIdThrowsIfNegative() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> repo.getDonationById(0)); + assertEquals("Donation ID must be positive", ex.getMessage()); } + @Test - void testSortByDateWithSameDonation() { - Timestamp sameDate = Timestamp.valueOf("2025-01-01 10:00:00"); + void getDonationByIdReturnsNullIfNotFound() { + assertNull(repo.getDonationById(999)); + } - Donation d1 = new Donation(1, 101, 201, - new BigDecimal("500.0"), sameDate, "Card"); + @Test + void getAllDonationsReturnsCopy() { + repo.addContent(donation1); + Map all = repo.getAllDonations(); + assertEquals(1, all.size()); + all.clear(); + assertEquals(1, repo.getAllDonations().size()); + } - repo.addDonation(d1); - repo.addDonation(donation4); + @Test + void sortByDateAscending() { + repo.addContent(donation3); + repo.addContent(donation1); + repo.addContent(donation2); - HashMap sorted = repo.sortByDate(); + Map sorted = repo.sortByDate(); + Integer[] keys = sorted.keySet().toArray(new Integer[0]); + assertArrayEquals(new Integer[]{1, 2, 3}, keys); + } + @Test + void sortByDateWithSameDateKeepsAll() { + repo.addContent(donation1); + repo.addContent(donation4); + Map sorted = repo.sortByDate(); assertEquals(1, sorted.size()); assertTrue(sorted.containsKey(1)); - assertFalse(sorted.containsKey(2)); } + @Test - void TestSortByAmountTest() { - repo.addDonation(donation2); - repo.addDonation(donation3); + void sortByAmountAscending() { + repo.addContent(donation3); + repo.addContent(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() { - repo.addDonation(donation1); - repo.addDonation(donation2); - - HashMap sorted = repo.sortByAmount(); + @Test + void sortByAmountWithSameAmount() { + repo.addContent(donation1); + repo.addContent(donation2); + 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() { - repo.addDonation(donation1); - repo.addDonation(donation2); - - HashMap filtered = repo.filterByOrganization(202); + void filterByOrganizationMatches() { + repo.addContent(donation1); + repo.addContent(donation2); + repo.addContent(donation3); + Map filtered = repo.filterByOrganization(202); assertEquals(2, filtered.size()); assertTrue(filtered.containsKey(1)); assertTrue(filtered.containsKey(2)); - assertFalse(filtered.containsKey(3)); } + @Test - void testFilterByOrganizationNoMatch() { - repo.addDonation(donation1); - repo.addDonation(donation2); + void filterByOrganizationNoMatch() { + repo.addContent(donation1); + Map filtered = repo.filterByOrganization(999); + assertTrue(filtered.isEmpty()); + } - HashMap filtered = repo.filterByOrganization(999); + @Test + void filterByOrganizationThrowsIfNegative() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> repo.filterByOrganization(0)); + assertEquals("Organization number must be positive", ex.getMessage()); + } - assertTrue(filtered.isEmpty()); + @Test + void exportReturnsAllRowsSortedById() { + repo.addContent(donation2); + repo.addContent(donation1); + + 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)); } + @Test - void testThrowsExceptionWhenOrgNumberIsNegative() { - IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, - () -> repo.filterByOrganization(-1)); - assertEquals("Organization number must be positive",thrown.getMessage()); + void getNextDonationIdReturnsCorrectNextId() { + assertEquals(1, repo.getNextDonationId()); + repo.addContent(donation1); + repo.addContent(donation4); + assertEquals(2, repo.getNextDonationId()); } + @Test - void testFilterByOrganizationWithSameDonation() { - Timestamp sameDate = Timestamp.valueOf("2025-01-01 10:00:00"); + void exportEmptyRepositoryReturnsEmptyList() { + List exported = repo.export(); + assertTrue(exported.isEmpty()); + } - Donation d1 = new Donation(1, 101, 201, - new BigDecimal("500.0"), sameDate, "Card"); + @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.addContent(d1); + repo.getAllDonations().put(d2.donationId(), d2); + Map sorted = repo.sortByDate(); + assertEquals(d1, sorted.get(1)); + } - repo.addDonation(d1); - repo.addDonation(donation4); + @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.addContent(d1); + repo.getAllDonations().put(d2.donationId(), d2); + Map sorted = repo.sortByAmount(); + assertEquals(d1, sorted.get(1)); + } - HashMap sorted = repo.filterByOrganization(201); + @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.addContent(d1); + repo.getAllDonations().put(d2.donationId(), d2); + Map filtered = repo.filterByOrganization(201); + assertEquals(d1, filtered.get(1)); + } - assertEquals(1, sorted.size()); - assertTrue(sorted.containsKey(1)); - assertFalse(sorted.containsKey(2)); + @Test + void filterByUserIdMatches() { + repo.addContent(donation1); + repo.addContent(donation3); + + Map filtered = repo.filterByUser(102); + assertEquals(1, filtered.size()); + assertTrue(filtered.containsKey(1)); + } + + @Test + void filterByUserIdNoMatch() { + repo.addContent(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 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..bd7f11a --- /dev/null +++ b/src/test/java/edu/group5/app/model/donation/DonationServiceTest.java @@ -0,0 +1,115 @@ +package edu.group5.app.model.donation; + +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 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 3a3392e..7a5ece5 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,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.HashMap; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; @@ -14,24 +13,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 @@ -45,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/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()) ); diff --git a/src/test/java/edu/group5/app/model/user/CustomerTest.java b/src/test/java/edu/group5/app/model/user/CustomerTest.java new file mode 100644 index 0000000..bd607f5 --- /dev/null +++ b/src/test/java/edu/group5/app/model/user/CustomerTest.java @@ -0,0 +1,209 @@ +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 CustomerTest { + + private int testUserId; + private String testFirstName; + private String testLastName; + private String testEmail; + private String testPassword; + private String testPasswordHash; + + @BeforeEach + void setUp() { + testUserId = 1; + testFirstName = "John"; + testLastName = "Doe"; + testEmail = "john.doe@example.com"; + testPassword = "password123"; + testPasswordHash = new BCryptPasswordEncoder().encode(testPassword); + } + + private void constructorTest(int userId, String firstName, + String lastName, String email, String passwordHash, + String expectedMessage) { + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> new Customer(userId, firstName, lastName, email, passwordHash) + ); + + assertEquals(expectedMessage, exception.getMessage()); + } + + @Test + void constructorCreatesValidUser() { + User user = new Customer(testUserId, testFirstName, + testLastName, testEmail, testPasswordHash); + assertEquals(testUserId, user.getUserId()); + assertEquals(testFirstName, user.getFirstName()); + assertEquals(testLastName, user.getLastName()); + assertEquals(testEmail, user.getEmail()); + assertEquals(testPasswordHash, user.getPasswordHash()); + } + + @Test + void testInstanceOfCustomer() { + User user = new Customer(testUserId, testFirstName, + testLastName, testEmail, testPasswordHash); + assertTrue(user instanceof Customer); + } + + @Test + void constructorWithNegativeUserIdThrowsException() { + constructorTest(-1, testFirstName, testLastName, + testEmail, testPasswordHash, "User ID must be positive"); + } + + @Test + void constructorWithNullFirstNameThrowsException() { + constructorTest(testUserId, null, testLastName, + testEmail, testPasswordHash, "First name cannot be null or empty"); + } + + @Test + void constructorWithEmptyFirstNameThrowsException() { + constructorTest(testUserId, "", testLastName, + testEmail, testPasswordHash, "First name cannot be null or empty"); + } + + @Test + void constructorWithNullLastNameThrowsException() { + constructorTest(testUserId, testFirstName, null, + testEmail, testPasswordHash, "Last name cannot be null or empty"); + } + + @Test + void constructorWithEmptyLastNameThrowsException() { + constructorTest(testUserId, testFirstName, + "", testEmail, testPasswordHash, "Last name cannot be null or empty"); + } + + @Test + void constructorWithNullEmailThrowsException() { + constructorTest(testUserId, testFirstName, testLastName, + null, testPasswordHash, "Email cannot be null or empty"); + } + + @Test + void constructorWithEmptyEmailThrowsException() { + constructorTest(testUserId, testFirstName, testLastName, + "", testPasswordHash, "Email cannot be null or empty"); + } + + @Test + void constructorWithNullPasswordHashThrowsException() { + constructorTest(testUserId, testFirstName, testLastName, + testEmail, null, "Password hash cannot be null or empty"); + } + + @Test + void constructorWithEmptyPasswordHashThrowsException() { + constructorTest(testUserId, testFirstName, testLastName, + testEmail, "", "Password hash cannot be null or empty"); + } + + + @Test + void verifyPasswordReturnsTrueForCorrectPassword() { + User user = new Customer(testUserId, testFirstName, + testLastName, testEmail, testPasswordHash); + + assertTrue(user.verifyPassword(testPassword)); + } + + @Test + void verifyPasswordReturnsFalseForIncorrectPassword() { + User user = new Customer(testUserId, testFirstName, + testLastName, testEmail, testPasswordHash); + + assertFalse(user.verifyPassword("wrongPassword")); + } + + @Test + void verifyPasswordReturnsFalseForNullPassword() { + User user = new Customer(testUserId, testFirstName, + testLastName, testEmail, testPasswordHash); + + assertFalse(user.verifyPassword(null)); + } + + @Test + void verifyPasswordReturnsFalseForEmptyPassword() { + User user = new Customer(testUserId, testFirstName, + testLastName, testEmail, testPasswordHash); + + 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..0700828 --- /dev/null +++ b/src/test/java/edu/group5/app/model/user/UserRepositoryTest.java @@ -0,0 +1,148 @@ +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 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.addContent(user)); + assertEquals(3, repo.getUsers().size()); + } + + @Test + void addContentNullThrows() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> repo.addContent(null)); + assertEquals("User cannot be null", ex.getMessage()); + } + + @Test + void addContentDuplicateReturnsFalse() { + Customer user = new Customer(1, "John", "Cena", "john@example.com", "pass"); + assertFalse(repo.addContent(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 getNextUserIdReturns1IfEmpty() { + UserRepository emptyRepo = new UserRepository(new ArrayList<>()); + assertEquals(1, emptyRepo.getNextUserId()); + } + + @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..20397a6 --- /dev/null +++ b/src/test/java/edu/group5/app/model/user/UserServiceTest.java @@ -0,0 +1,122 @@ +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")); + 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 + 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.addContent(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.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 + 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 diff --git a/src/test/java/edu/group5/app/model/user/UserTest.java b/src/test/java/edu/group5/app/model/user/UserTest.java deleted file mode 100644 index 1e7277a..0000000 --- a/src/test/java/edu/group5/app/model/user/UserTest.java +++ /dev/null @@ -1,136 +0,0 @@ -package edu.group5.app.model.user; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -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 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); - } - } - - @Test - public void constructorThrowsNoException() { - constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, false); - } - - @Test - public void constructorWithNegativeUserIdThrowsException() { - constructorTest(WRONG_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, true); - } - - @Test - public void constructorWithEmptyRoleThrowsException() { - constructorTest(TEST_USER_ID, WRONG_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, true); - } - - @Test - public void constructorWithEmptyFirstNameThrowsException() { - constructorTest(TEST_USER_ID, TEST_ROLE, WRONG_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, true); - } - - @Test - public void constructorWithEmptyLastNameThrowsException() { - constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, WRONG_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, true); - } - - @Test - public void constructorWithEmptyEmailThrowsException() { - constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, WRONG_EMAIL, TEST_PASSWORD_HASH, true); - } - - @Test - public void constructorWithEmptyPasswordHashThrowsException() { - constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, WRONG_PASSWORD_HASH, true); - } - - 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 - 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); - } - - @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)); - } - - @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")); - } - - @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)); - } - - @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("")); - } - - @Test - public void constructorWithNullRoleThrowsException() { - constructorTest(TEST_USER_ID, null, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, true); - } - - @Test - public void constructorWithNullFirstNameThrowsException() { - constructorTest(TEST_USER_ID, TEST_ROLE, null, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, true); - } - - @Test - public void constructorWithNullLastNameThrowsException() { - constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, null, TEST_EMAIL, TEST_PASSWORD_HASH, true); - } - - @Test - public void constructorWithNullEmailThrowsException() { - constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, null, TEST_PASSWORD_HASH, true); - } - - @Test - public void constructorWithNullPasswordHashThrowsException() { - constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, null, true); - } -}