From a9d8680f368d69c172566de96906e26e6d0b9a2c Mon Sep 17 00:00:00 2001 From: AdrianBalunan Date: Sun, 12 Apr 2026 22:08:07 +0200 Subject: [PATCH] Fix: Maven clean and correct functions --- .../database/Readers/DonationSelect.java | 24 +- .../team6/database/Readers/UserSelect.java | 81 +- .../team6/models/user/Language.java | 1 + .../team6/database/DatabaseSetupTest.java | 3 - .../database/Readers/DonationSelectTest.java | 396 +++++---- .../database/Readers/UserSelectTest.java | 772 +++++++++--------- 6 files changed, 632 insertions(+), 645 deletions(-) diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/DonationSelect.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/DonationSelect.java index 075e0d0..26690ab 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/DonationSelect.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/DonationSelect.java @@ -12,11 +12,10 @@ /** * Data access class responsible for reading donation data from the database. * - *

Retrieves donations along with their associated charity information by - * performing an INNER JOIN between the {@code Donations} and {@code Charities} tables. - * Only donations that have a matching charity record are returned.

+ *

Retrieves donations along with their associated charity information by performing an INNER + * JOIN between the {@code Donations} and {@code Charities} tables. Only donations that have a + * matching charity record are returned. */ - public class DonationSelect { /** The database connection used for all queries in this class. */ @@ -25,23 +24,22 @@ public class DonationSelect { /** * Constructs a new {@code DonationSelect} with the given database connection. * - * @param connection the {@link DatabaseConnection} to use for executing queries; - * must not be {@code null} + * @param connection the {@link DatabaseConnection} to use for executing queries; must not be + * {@code null} */ public DonationSelect(DatabaseConnection connection) { this.connection = connection; } /** - * Retrieves all donations from the database, each populated with its associated - * {@link Charity}. + * Retrieves all donations from the database, each populated with its associated {@link Charity}. * - *

The query performs an INNER JOIN between the {@code Donations} and - * {@code Charities} tables on the charity UUID foreign key. Donations without a - * matching charity are excluded from the result.

+ *

The query performs an INNER JOIN between the {@code Donations} and {@code Charities} tables + * on the charity UUID foreign key. Donations without a matching charity are excluded from the + * result. * - * @return a {@link DonationRegistry} containing all matched donations; - * never {@code null}, but may be empty if no rows are returned + * @return a {@link DonationRegistry} containing all matched donations; never {@code null}, but + * may be empty if no rows are returned * @throws RuntimeException if a {@link SQLException} occurs while executing the query */ public DonationRegistry getDonationFromDB() { diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/UserSelect.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/UserSelect.java index 36d2f0b..ce41742 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/UserSelect.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/UserSelect.java @@ -11,13 +11,11 @@ /** * Data access class responsible for reading user-related data from the database. * - *

Provides methods to retrieve individual users (by credentials or UUID), - * all users, a user's settings, and a user's inbox. Queries use LEFT JOINs - * across the {@code User}, {@code Settings}, and {@code Messages} tables to - * assemble fully populated {@link User} objects in a single round trip where - * possible.

+ *

Provides methods to retrieve individual users (by credentials or UUID), all users, a user's + * settings, and a user's inbox. Queries use LEFT JOINs across the {@code User}, {@code Settings}, + * and {@code Messages} tables to assemble fully populated {@link User} objects in a single round + * trip where possible. */ - public class UserSelect { /** The database connection used for all queries in this class. */ private final DatabaseConnection connection; @@ -25,30 +23,29 @@ public class UserSelect { /** * Constructs a new {@code UserSelect} with the given database connection. * - * @param connection the {@link DatabaseConnection} to use for executing queries; - * must not be {@code null} + * @param connection the {@link DatabaseConnection} to use for executing queries; must not be + * {@code null} */ public UserSelect(DatabaseConnection connection) { this.connection = connection; } /** - * Retrieves a single {@link User} from the database matching the given username - * and password. + * Retrieves a single {@link User} from the database matching the given username and password. * - *

The password is hashed via {@link PasswordHasher} before being compared - * against the stored value. If a matching user is found, their {@link Settings} - * (when present) and {@link Inbox} (including any {@link Message} objects) are - * also populated. Returns {@code null} if no matching user is found.

+ *

The password is hashed via {@link PasswordHasher} before being compared against the stored + * value. If a matching user is found, their {@link Settings} (when present) and {@link Inbox} + * (including any {@link Message} objects) are also populated. Returns {@code null} if no matching + * user is found. * - *

Note: the current SQL query compares both parameters against - * {@code user_password}; the {@code user_name} column is not yet included in the - * WHERE clause, which may be a bug.

+ *

Note: the current SQL query compares both parameters against {@code + * user_password}; the {@code user_name} column is not yet included in the WHERE clause, which may + * be a bug. * * @param username the plain-text username to look up * @param password the plain-text password; hashed internally before the query runs - * @return the matching {@link User} with settings and inbox populated, - * or {@code null} if no match is found + * @return the matching {@link User} with settings and inbox populated, or {@code null} if no + * match is found * @throws RuntimeException if a {@link SQLException} occurs while executing the query */ public User getUserFromDBUsernameAndPassword(String username, String password) { @@ -122,13 +119,13 @@ public User getUserFromDBUsernameAndPassword(String username, String password) { /** * Retrieves a single {@link User} from the database by their UUID. * - *

The returned user is fully populated with {@link Settings} (when present) - * and an {@link Inbox} containing any associated {@link Message} objects. - * Returns {@code null} if no user with the given UUID exists.

+ *

The returned user is fully populated with {@link Settings} (when present) and an {@link + * Inbox} containing any associated {@link Message} objects. Returns {@code null} if no user with + * the given UUID exists. * * @param user_id the UUID string of the user to retrieve; must not be {@code null} - * @return the matching {@link User} with settings and inbox populated, - * or {@code null} if no user is found + * @return the matching {@link User} with settings and inbox populated, or {@code null} if no user + * is found * @throws RuntimeException if a {@link SQLException} occurs while executing the query */ public User getUserFromDBUuid(String user_id) { @@ -195,15 +192,15 @@ public User getUserFromDBUuid(String user_id) { } /** - * Retrieves all users from the database, each fully populated with their - * {@link Settings} and {@link Inbox}. + * Retrieves all users from the database, each fully populated with their {@link Settings} and + * {@link Inbox}. * - *

The query LEFT JOINs {@code User}, {@code Settings}, and {@code Messages}. - * Multiple rows for the same user UUID (due to multiple messages) are collapsed - * into a single {@link User} object with all messages appended to its inbox.

+ *

The query LEFT JOINs {@code User}, {@code Settings}, and {@code Messages}. Multiple rows for + * the same user UUID (due to multiple messages) are collapsed into a single {@link User} object + * with all messages appended to its inbox. * - * @return a {@link UserRegistry} containing all users found in the database; - * never {@code null}, but may be empty if no users exist + * @return a {@link UserRegistry} containing all users found in the database; never {@code null}, + * but may be empty if no users exist * @throws RuntimeException if a {@link SQLException} occurs while executing the query */ public UserRegistry getUsersFromDB() { @@ -274,11 +271,11 @@ public UserRegistry getUsersFromDB() { /** * Retrieves the {@link Settings} for a specific user by their UUID. * - *

At most one row is fetched (via {@code setMaxRows(1)}). Returns {@code null} - * if no settings row exists for the given user.

+ *

At most one row is fetched (via {@code setMaxRows(1)}). Returns {@code null} if no settings + * row exists for the given user. * - * @param user_id the UUID string of the user whose settings should be retrieved; - * must not be {@code null} + * @param user_id the UUID string of the user whose settings should be retrieved; must not be + * {@code null} * @return the user's {@link Settings}, or {@code null} if none are found * @throws RuntimeException if a {@link SQLException} occurs while executing the query */ @@ -315,16 +312,14 @@ public Settings getSettingsForUser(String user_id) { } /** - * Retrieves the {@link Inbox} for a specific user by their UUID, populated with - * all of their {@link Message} objects. + * Retrieves the {@link Inbox} for a specific user by their UUID, populated with all of their + * {@link Message} objects. * - *

Returns an empty {@link Inbox} (never {@code null}) if no messages exist - * for the given user.

+ *

Returns an empty {@link Inbox} (never {@code null}) if no messages exist for the given user. * - * @param user_id the UUID string of the user whose inbox should be retrieved; - * must not be {@code null} - * @return an {@link Inbox} containing all messages for the user; - * empty if no messages are found + * @param user_id the UUID string of the user whose inbox should be retrieved; must not be {@code + * null} + * @return an {@link Inbox} containing all messages for the user; empty if no messages are found * @throws RuntimeException if a {@link SQLException} occurs while executing the query */ public Inbox getInboxForUser(String user_id) { diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Language.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Language.java index c568ede..cbd66cf 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Language.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/user/Language.java @@ -6,5 +6,6 @@ * @author Robin Strand Prestmo */ public enum Language { + NORWEGIAN, ENGLISH } diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DatabaseSetupTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DatabaseSetupTest.java index 4501cd8..809d251 100644 --- a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DatabaseSetupTest.java +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DatabaseSetupTest.java @@ -3,12 +3,10 @@ import static org.junit.jupiter.api.Assertions.*; import java.sql.*; -import java.util.ArrayList; import java.util.List; import ntnu.systemutvikling.team6.database.Readers.CharitySelect; import ntnu.systemutvikling.team6.database.Readers.DonationSelect; import ntnu.systemutvikling.team6.models.Charity; -import ntnu.systemutvikling.team6.models.CharityRegistry; import ntnu.systemutvikling.team6.service.APIToDatabaseService; import org.junit.jupiter.api.*; @@ -45,7 +43,6 @@ void createCharitiesTableShouldCreateTableSuccessfully() throws SQLException { } } - @Test void tempTableShouldNotExistAfterUpdating() throws SQLException { Charity charity = new Charity("99999", "https://temp.no", "Temp Charity", false, "approved"); diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/DonationSelectTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/DonationSelectTest.java index f7d8b36..5c3545c 100644 --- a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/DonationSelectTest.java +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/DonationSelectTest.java @@ -1,9 +1,14 @@ package ntnu.systemutvikling.team6.database.Readers; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +import java.sql.*; +import java.time.LocalDate; import ntnu.systemutvikling.team6.database.DatabaseConnection; import ntnu.systemutvikling.team6.models.Donation; import ntnu.systemutvikling.team6.models.DonationRegistry; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -11,208 +16,201 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.sql.*; -import java.time.LocalDate; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; - /** * Unit tests for {@link DonationSelect}. * - *

Uses Mockito to mock the entire JDBC stack so no real database - * connection is required.

+ *

Uses Mockito to mock the entire JDBC stack so no real database connection is required. */ @ExtendWith(MockitoExtension.class) class DonationSelectTest { - @Mock private DatabaseConnection mockDatabaseConnection; - @Mock private Connection mockConnection; - @Mock private Statement mockStatement; - @Mock private ResultSet mockResultSet; - @Mock private Date mockSqlDate; - - private DonationSelect donationSelect; - - @BeforeEach - void setUp() { - donationSelect = new DonationSelect(mockDatabaseConnection); - } - - // ------------------------------------------------------------------------- - // getDonationFromDB - // ------------------------------------------------------------------------- - - @Test - @DisplayName("getDonationFromDB – empty result set returns an empty registry") - void getDonationFromDB_emptyResultSet_returnsEmptyRegistry() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.createStatement()).thenReturn(mockStatement); - when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); - when(mockResultSet.next()).thenReturn(false); - - DonationRegistry registry = donationSelect.getDonationFromDB(); - - assertNotNull(registry); - assertTrue(registry.getDonations().isEmpty(), - "Registry should be empty when the result set has no rows"); - } - - @Test - @DisplayName("getDonationFromDB – single row returns one Donation with correct data") - void getDonationFromDB_singleRow_returnsSingleDonation() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.createStatement()).thenReturn(mockStatement); - when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); - - when(mockResultSet.next()).thenReturn(true, false); - stubCharityColumns("charity-uuid-1", "123456789", "Test Charity", - "https://example.org", true, "ACTIVE"); - stubDonationColumns("donation-uuid-1", 250.0, LocalDate.of(2024, 5, 20)); - - DonationRegistry registry = donationSelect.getDonationFromDB(); - - assertEquals(1, registry.getDonations().size()); - Donation donation = registry.getDonations().get(0); - assertEquals("donation-uuid-1", donation.getUUID()); - assertEquals(250.0, donation.getAmount()); - assertEquals(LocalDate.of(2024, 5, 20), donation.getDate()); - } - - @Test - @DisplayName("getDonationFromDB – single row maps charity fields onto the Donation correctly") - void getDonationFromDB_singleRow_charityMappedCorrectly() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.createStatement()).thenReturn(mockStatement); - when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); - - when(mockResultSet.next()).thenReturn(true, false); - stubCharityColumns("charity-uuid-1", "987654321", "Help Fund", - "https://helpfund.org", false, "PENDING"); - stubDonationColumns("donation-uuid-1", 100.0, LocalDate.of(2024, 1, 1)); - - DonationRegistry registry = donationSelect.getDonationFromDB(); - - Donation donation = registry.getDonations().get(0); - assertEquals("charity-uuid-1", donation.getCharity().getUUID()); - assertEquals("987654321", donation.getCharity().getOrgNumber()); - assertEquals("Help Fund", donation.getCharity().getCharityName()); - assertEquals("https://helpfund.org", donation.getCharity().getCharityLink()); - assertFalse(donation.getCharity().isPreApproved()); - assertEquals("PENDING", donation.getCharity().getStatus()); - } - - @Test - @DisplayName("getDonationFromDB – two rows returns two Donation objects") - void getDonationFromDB_twoRows_returnsTwoDonations() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.createStatement()).thenReturn(mockStatement); - when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); - - when(mockResultSet.next()).thenReturn(true, true, false); - - when(mockResultSet.getString("UUID_charities")).thenReturn("charity-uuid-1", "charity-uuid-2"); - when(mockResultSet.getString("org_number")).thenReturn("111111111", "222222222"); - when(mockResultSet.getString("charity_name")).thenReturn("Charity A", "Charity B"); - when(mockResultSet.getString("charity_link")).thenReturn("https://a.org", "https://b.org"); - when(mockResultSet.getBoolean("pre_approved")).thenReturn(true, false); - when(mockResultSet.getString("status")).thenReturn("ACTIVE", "INACTIVE"); - - when(mockResultSet.getString("UUID_Donations")).thenReturn("donation-uuid-1", "donation-uuid-2"); - when(mockResultSet.getDouble("amount")).thenReturn(500.0, 750.0); - - Date sqlDate = Date.valueOf(LocalDate.of(2024, 8, 10)); - when(mockResultSet.getDate("date")).thenReturn(sqlDate); - - DonationRegistry registry = donationSelect.getDonationFromDB(); - - assertEquals(2, registry.getDonations().size(), - "Registry should contain two donations for two result rows"); - } - - @Test - @DisplayName("getDonationFromDB – donation amount of zero is stored correctly") - void getDonationFromDB_zeroAmount_storedCorrectly() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.createStatement()).thenReturn(mockStatement); - when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); - - when(mockResultSet.next()).thenReturn(true, false); - stubCharityColumns("charity-uuid-1", "123456789", "Test Charity", - "https://example.org", true, "ACTIVE"); - stubDonationColumns("donation-uuid-zero", 0.0, LocalDate.of(2024, 1, 1)); - - DonationRegistry registry = donationSelect.getDonationFromDB(); - - assertEquals(0.0, registry.getDonations().get(0).getAmount()); - } - - @Test - @DisplayName("getDonationFromDB – large donation amount is stored correctly") - void getDonationFromDB_largeAmount_storedCorrectly() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.createStatement()).thenReturn(mockStatement); - when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); - - when(mockResultSet.next()).thenReturn(true, false); - stubCharityColumns("charity-uuid-1", "123456789", "Test Charity", - "https://example.org", true, "ACTIVE"); - stubDonationColumns("donation-uuid-big", 1_000_000.99, LocalDate.of(2024, 12, 31)); - - DonationRegistry registry = donationSelect.getDonationFromDB(); - - assertEquals(1_000_000.99, registry.getDonations().get(0).getAmount(), 0.001); - } - - @Test - @DisplayName("getDonationFromDB – SQLException is wrapped in RuntimeException") - void getDonationFromDB_sqlException_throwsRuntimeException() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.createStatement()).thenThrow(new SQLException("DB error")); - - assertThrows(RuntimeException.class, () -> donationSelect.getDonationFromDB(), - "A SQLException should be rethrown as a RuntimeException"); - } - - @Test - @DisplayName("getDonationFromDB – RuntimeException message contains expected error text") - void getDonationFromDB_sqlException_runtimeExceptionHasExpectedMessage() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.createStatement()).thenThrow(new SQLException("DB error")); - - RuntimeException ex = assertThrows(RuntimeException.class, - () -> donationSelect.getDonationFromDB()); - assertTrue(ex.getMessage().contains("ERROR"), - "RuntimeException message should contain 'ERROR'"); - } - - // ------------------------------------------------------------------------- - // Helpers - // ------------------------------------------------------------------------- - - /** - * Stubs all charity-related columns on the mock ResultSet. - */ - private void stubCharityColumns(String uuid, String orgNumber, String name, - String link, boolean preApproved, String status) - throws SQLException { - when(mockResultSet.getString("UUID_charities")).thenReturn(uuid); - when(mockResultSet.getString("org_number")).thenReturn(orgNumber); - when(mockResultSet.getString("charity_name")).thenReturn(name); - when(mockResultSet.getString("charity_link")).thenReturn(link); - when(mockResultSet.getBoolean("pre_approved")).thenReturn(preApproved); - when(mockResultSet.getString("status")).thenReturn(status); - } - - /** - * Stubs all donation-related columns on the mock ResultSet. - */ - private void stubDonationColumns(String uuid, double amount, LocalDate date) - throws SQLException { - when(mockResultSet.getString("UUID_Donations")).thenReturn(uuid); - when(mockResultSet.getDouble("amount")).thenReturn(amount); - Date sqlDate = Date.valueOf(date); - when(mockResultSet.getDate("date")).thenReturn(sqlDate); - } -} \ No newline at end of file + @Mock private DatabaseConnection mockDatabaseConnection; + @Mock private Connection mockConnection; + @Mock private Statement mockStatement; + @Mock private ResultSet mockResultSet; + @Mock private Date mockSqlDate; + + private DonationSelect donationSelect; + + @BeforeEach + void setUp() { + donationSelect = new DonationSelect(mockDatabaseConnection); + } + + // ------------------------------------------------------------------------- + // getDonationFromDB + // ------------------------------------------------------------------------- + + @Test + @DisplayName("getDonationFromDB – empty result set returns an empty registry") + void getDonationFromDB_emptyResultSet_returnsEmptyRegistry() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + when(mockResultSet.next()).thenReturn(false); + + DonationRegistry registry = donationSelect.getDonationFromDB(); + + assertNotNull(registry); + assertTrue( + registry.getAllDonations().isEmpty(), + "Registry should be empty when the result set has no rows"); + } + + @Test + @DisplayName("getDonationFromDB – single row returns one Donation with correct data") + void getDonationFromDB_singleRow_returnsSingleDonation() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, false); + stubCharityColumns( + "charity-uuid-1", "123456789", "Test Charity", "https://example.org", true, "ACTIVE"); + stubDonationColumns("donation-uuid-1", 250.0, LocalDate.of(2024, 5, 20)); + + DonationRegistry registry = donationSelect.getDonationFromDB(); + + assertEquals(1, registry.getAllDonations().size()); + Donation donation = registry.getAllDonations().get(0); + assertEquals("donation-uuid-1", donation.getCharityId()); + assertEquals(250.0, donation.getAmount()); + assertEquals(LocalDate.of(2024, 5, 20), donation.getDate()); + } + + @Test + @DisplayName("getDonationFromDB – single row maps charity fields onto the Donation correctly") + void getDonationFromDB_singleRow_charityMappedCorrectly() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, false); + stubCharityColumns( + "charity-uuid-1", "987654321", "Help Fund", "https://helpfund.org", false, "PENDING"); + stubDonationColumns("donation-uuid-1", 100.0, LocalDate.of(2024, 1, 1)); + + DonationRegistry registry = donationSelect.getDonationFromDB(); + + Donation donation = registry.getAllDonations().get(0); + assertEquals("charity-uuid-1", donation.getCharity().getUUID()); + assertEquals("987654321", donation.getCharity().getOrg_number()); + assertEquals("Help Fund", donation.getCharity().getName()); + assertEquals("https://helpfund.org", donation.getCharity().getDescription()); + assertFalse(donation.getCharity().getPreApproved()); + assertEquals("PENDING", donation.getCharity().getStatus()); + } + + @Test + @DisplayName("getDonationFromDB – two rows returns two Donation objects") + void getDonationFromDB_twoRows_returnsTwoDonations() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, true, false); + + when(mockResultSet.getString("UUID_charities")).thenReturn("charity-uuid-1", "charity-uuid-2"); + when(mockResultSet.getString("org_number")).thenReturn("111111111", "222222222"); + when(mockResultSet.getString("charity_name")).thenReturn("Charity A", "Charity B"); + when(mockResultSet.getString("charity_link")).thenReturn("https://a.org", "https://b.org"); + when(mockResultSet.getBoolean("pre_approved")).thenReturn(true, false); + when(mockResultSet.getString("status")).thenReturn("ACTIVE", "INACTIVE"); + + when(mockResultSet.getString("UUID_Donations")) + .thenReturn("donation-uuid-1", "donation-uuid-2"); + when(mockResultSet.getDouble("amount")).thenReturn(500.0, 750.0); + + Date sqlDate = Date.valueOf(LocalDate.of(2024, 8, 10)); + when(mockResultSet.getDate("date")).thenReturn(sqlDate); + + DonationRegistry registry = donationSelect.getDonationFromDB(); + + assertEquals( + 2, + registry.getAllDonations().size(), + "Registry should contain two donations for two result rows"); + } + + @Test + @DisplayName("getDonationFromDB – donation amount of zero is stored correctly") + void getDonationFromDB_zeroAmount_storedCorrectly() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, false); + stubCharityColumns( + "charity-uuid-1", "123456789", "Test Charity", "https://example.org", true, "ACTIVE"); + stubDonationColumns("donation-uuid-zero", 0.0, LocalDate.of(2024, 1, 1)); + + DonationRegistry registry = donationSelect.getDonationFromDB(); + + assertEquals(0.0, registry.getAllDonations().get(0).getAmount()); + } + + @Test + @DisplayName("getDonationFromDB – large donation amount is stored correctly") + void getDonationFromDB_largeAmount_storedCorrectly() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, false); + stubCharityColumns( + "charity-uuid-1", "123456789", "Test Charity", "https://example.org", true, "ACTIVE"); + stubDonationColumns("donation-uuid-big", 1_000_000.99, LocalDate.of(2024, 12, 31)); + + DonationRegistry registry = donationSelect.getDonationFromDB(); + + assertEquals(1_000_000.99, registry.getAllDonations().get(0).getAmount(), 0.001); + } + + @Test + @DisplayName("getDonationFromDB – SQLException is wrapped in RuntimeException") + void getDonationFromDB_sqlException_throwsRuntimeException() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenThrow(new SQLException("DB error")); + + assertThrows( + RuntimeException.class, + () -> donationSelect.getDonationFromDB(), + "A SQLException should be rethrown as a RuntimeException"); + } + + @Test + @DisplayName("getDonationFromDB – RuntimeException message contains expected error text") + void getDonationFromDB_sqlException_runtimeExceptionHasExpectedMessage() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenThrow(new SQLException("DB error")); + + RuntimeException ex = + assertThrows(RuntimeException.class, () -> donationSelect.getDonationFromDB()); + assertTrue( + ex.getMessage().contains("ERROR"), "RuntimeException message should contain 'ERROR'"); + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + /** Stubs all charity-related columns on the mock ResultSet. */ + private void stubCharityColumns( + String uuid, String orgNumber, String name, String link, boolean preApproved, String status) + throws SQLException { + when(mockResultSet.getString("UUID_charities")).thenReturn(uuid); + when(mockResultSet.getString("org_number")).thenReturn(orgNumber); + when(mockResultSet.getString("charity_name")).thenReturn(name); + when(mockResultSet.getString("charity_link")).thenReturn(link); + when(mockResultSet.getBoolean("pre_approved")).thenReturn(preApproved); + when(mockResultSet.getString("status")).thenReturn(status); + } + + /** Stubs all donation-related columns on the mock ResultSet. */ + private void stubDonationColumns(String uuid, double amount, LocalDate date) throws SQLException { + when(mockResultSet.getString("UUID_Donations")).thenReturn(uuid); + when(mockResultSet.getDouble("amount")).thenReturn(amount); + Date sqlDate = Date.valueOf(date); + when(mockResultSet.getDate("date")).thenReturn(sqlDate); + } +} diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/UserSelectTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/UserSelectTest.java index b736459..5394fa1 100644 --- a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/UserSelectTest.java +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/UserSelectTest.java @@ -1,9 +1,14 @@ package ntnu.systemutvikling.team6.database.Readers; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +import java.sql.*; +import java.util.UUID; import ntnu.systemutvikling.team6.database.DatabaseConnection; import ntnu.systemutvikling.team6.models.UserRegistry; import ntnu.systemutvikling.team6.models.user.*; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -11,396 +16,389 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.sql.*; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; - /** * Unit tests for {@link UserSelect}. * - *

Uses Mockito to mock the entire JDBC stack ({@link DatabaseConnection}, - * {@link Connection}, {@link Statement}, {@link PreparedStatement}, {@link ResultSet}) - * so that no real database connection is required.

+ *

Uses Mockito to mock the entire JDBC stack ({@link DatabaseConnection}, {@link Connection}, + * {@link Statement}, {@link PreparedStatement}, {@link ResultSet}) so that no real database + * connection is required. */ @ExtendWith(MockitoExtension.class) class UserSelectTest { - @Mock private DatabaseConnection mockDatabaseConnection; - @Mock private Connection mockConnection; - @Mock private Statement mockStatement; - @Mock private PreparedStatement mockPreparedStatement; - @Mock private ResultSet mockResultSet; - - private static final String USER_UUID = "user-uuid-1"; - private static final String CHARITY_UUID = UUID.randomUUID().toString(); - private static final String MESSAGE_UUID = "msg-uuid-1"; - - private UserSelect userSelect; - - @BeforeEach - void setUp() { - userSelect = new UserSelect(mockDatabaseConnection); - } - - // ------------------------------------------------------------------------- - // getUserFromDBUuid - // ------------------------------------------------------------------------- - - @Test - @DisplayName("getUserFromDBUuid – no matching row returns null") - void getUserFromDBUuid_noRow_returnsNull() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); - when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); - when(mockResultSet.next()).thenReturn(false); - - User result = userSelect.getUserFromDBUuid(USER_UUID); - - assertNull(result, "Should return null when no user is found"); - } - - @Test - @DisplayName("getUserFromDBUuid – single row without settings returns User with null settings") - void getUserFromDBUuid_noSettings_userSettingsNull() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); - when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); - - when(mockResultSet.next()).thenReturn(true, false); - stubCoreUserColumns(); - when(mockResultSet.getString("isAnonymous")).thenReturn(null); - when(mockResultSet.getString("UUID_message")).thenReturn(null); - - User result = userSelect.getUserFromDBUuid(USER_UUID); - - assertNotNull(result); - assertNull(result.getSettings(), "Settings should be null when isAnonymous is null"); - assertNotNull(result.getInbox(), "Inbox should always be initialised"); - } - - @Test - @DisplayName("getUserFromDBUuid – single row with settings populates Settings correctly") - void getUserFromDBUuid_withSettings_settingsPopulated() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); - when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); - - when(mockResultSet.next()).thenReturn(true, false); - stubCoreUserColumns(); - stubSettingsColumns(false, "ENGLISH", true); - when(mockResultSet.getString("UUID_message")).thenReturn(null); - - User result = userSelect.getUserFromDBUuid(USER_UUID); - - assertNotNull(result.getSettings()); - assertFalse(result.getSettings().isAnonymous()); - assertEquals(Language.ENGLISH, result.getSettings().getLanguage()); - assertTrue(result.getSettings().isLightmode()); - } - - @Test - @DisplayName("getUserFromDBUuid – row with a message adds it to the inbox") - void getUserFromDBUuid_withMessage_messageAddedToInbox() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); - when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); - - when(mockResultSet.next()).thenReturn(true, false); - stubCoreUserColumns(); - when(mockResultSet.getString("isAnonymous")).thenReturn(null); - stubMessageColumns(); - - User result = userSelect.getUserFromDBUuid(USER_UUID); - - assertEquals(1, result.getInbox().getMessages().size(), - "Inbox should contain exactly one message"); - } - - @Test - @DisplayName("getUserFromDBUuid – two rows for same UUID adds two messages, one User") - void getUserFromDBUuid_twoRowsSameUuid_oneUserTwoMessages() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); - when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); - - when(mockResultSet.next()).thenReturn(true, true, false); - when(mockResultSet.getString("UUID_User")).thenReturn(USER_UUID); - when(mockResultSet.getString("user_name")).thenReturn("Alice"); - when(mockResultSet.getString("user_email")).thenReturn("alice@example.com"); - when(mockResultSet.getString("user_password")).thenReturn("hashedpw"); - when(mockResultSet.getString("role")).thenReturn("USER"); - when(mockResultSet.getString("isAnonymous")).thenReturn(null); - when(mockResultSet.getString("UUID_message")).thenReturn("msg-1", "msg-2"); - when(mockResultSet.getString("message_title")).thenReturn("Title 1", "Title 2"); - when(mockResultSet.getString("sender_charity_id")).thenReturn(CHARITY_UUID); - when(mockResultSet.getString("message_content")).thenReturn("Content"); - when(mockResultSet.getString("message_date")).thenReturn("2024-04-01"); - - User result = userSelect.getUserFromDBUuid(USER_UUID); - - assertEquals(2, result.getInbox().getMessages().size()); - } - - @Test - @DisplayName("getUserFromDBUuid – UUID is bound to PreparedStatement parameter 1") - void getUserFromDBUuid_uuidBoundCorrectly() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); - when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); - when(mockResultSet.next()).thenReturn(false); - - userSelect.getUserFromDBUuid(USER_UUID); - - verify(mockPreparedStatement).setString(1, USER_UUID); - } - - @Test - @DisplayName("getUserFromDBUuid – SQLException is wrapped in RuntimeException") - void getUserFromDBUuid_sqlException_throwsRuntimeException() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.prepareStatement(anyString())).thenThrow(new SQLException("DB error")); - - assertThrows(RuntimeException.class, () -> userSelect.getUserFromDBUuid(USER_UUID)); - } - - // ------------------------------------------------------------------------- - // getUsersFromDB - // ------------------------------------------------------------------------- - - @Test - @DisplayName("getUsersFromDB – empty result set returns empty registry") - void getUsersFromDB_emptyResultSet_returnsEmptyRegistry() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.createStatement()).thenReturn(mockStatement); - when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); - when(mockResultSet.next()).thenReturn(false); - - UserRegistry registry = userSelect.getUsersFromDB(); - - assertNotNull(registry); - assertTrue(registry.getUsers().isEmpty()); - } - - @Test - @DisplayName("getUsersFromDB – two distinct UUIDs produce two User objects") - void getUsersFromDB_twoDistinctUuids_twoUsersInRegistry() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.createStatement()).thenReturn(mockStatement); - when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); - - when(mockResultSet.next()).thenReturn(true, true, false); - when(mockResultSet.getString("UUID_User")).thenReturn("uuid-A", "uuid-B"); - when(mockResultSet.getString("user_name")).thenReturn("Alice", "Bob"); - when(mockResultSet.getString("user_email")).thenReturn("a@x.com", "b@x.com"); - when(mockResultSet.getString("user_password")).thenReturn("pw1", "pw2"); - when(mockResultSet.getString("role")).thenReturn("USER", "ADMIN"); - when(mockResultSet.getString("isAnonymous")).thenReturn(null); - when(mockResultSet.getString("UUID_message")).thenReturn(null); - - UserRegistry registry = userSelect.getUsersFromDB(); - - assertEquals(2, registry.getUsers().size()); - } - - @Test - @DisplayName("getUsersFromDB – same UUID across two rows deduplicates to one User") - void getUsersFromDB_sameUuidTwoRows_oneUserWithTwoMessages() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.createStatement()).thenReturn(mockStatement); - when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); - - when(mockResultSet.next()).thenReturn(true, true, false); - when(mockResultSet.getString("UUID_User")).thenReturn(USER_UUID); - when(mockResultSet.getString("user_name")).thenReturn("Alice"); - when(mockResultSet.getString("user_email")).thenReturn("alice@example.com"); - when(mockResultSet.getString("user_password")).thenReturn("hashedpw"); - when(mockResultSet.getString("role")).thenReturn("USER"); - when(mockResultSet.getString("isAnonymous")).thenReturn(null); - when(mockResultSet.getString("UUID_message")).thenReturn("msg-1", "msg-2"); - when(mockResultSet.getString("message_title")).thenReturn("T1", "T2"); - when(mockResultSet.getString("sender_charity_id")).thenReturn(CHARITY_UUID); - when(mockResultSet.getString("message_content")).thenReturn("Body"); - when(mockResultSet.getString("message_date")).thenReturn("2024-05-01"); - - UserRegistry registry = userSelect.getUsersFromDB(); - - assertEquals(1, registry.getUsers().size(), "Same UUID should not produce duplicate users"); - assertEquals(2, registry.getUsers().get(0).getInbox().getMessages().size()); - } - - @Test - @DisplayName("getUsersFromDB – SQLException is wrapped in RuntimeException") - void getUsersFromDB_sqlException_throwsRuntimeException() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.createStatement()).thenThrow(new SQLException("DB error")); - - assertThrows(RuntimeException.class, () -> userSelect.getUsersFromDB()); - } - - // ------------------------------------------------------------------------- - // getSettingsForUser - // ------------------------------------------------------------------------- - - @Test - @DisplayName("getSettingsForUser – no row returns null") - void getSettingsForUser_noRow_returnsNull() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); - when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); - when(mockResultSet.next()).thenReturn(false); - - Settings result = userSelect.getSettingsForUser(USER_UUID); - - assertNull(result, "Should return null when no settings row exists"); - } - - @Test - @DisplayName("getSettingsForUser – matching row returns populated Settings") - void getSettingsForUser_matchingRow_returnsSettings() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); - when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); - - when(mockResultSet.next()).thenReturn(true, false); - stubSettingsColumns(true, "NORWEGIAN", false); - - Settings result = userSelect.getSettingsForUser(USER_UUID); - - assertNotNull(result); - assertTrue(result.isAnonymous()); - assertEquals(Language.NORWEGIAN, result.getLanguage()); - assertFalse(result.isLightmode()); - } - - @Test - @DisplayName("getSettingsForUser – UUID is bound to PreparedStatement and maxRows set to 1") - void getSettingsForUser_correctBindingAndMaxRows() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); - when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); - when(mockResultSet.next()).thenReturn(false); - - userSelect.getSettingsForUser(USER_UUID); - - verify(mockPreparedStatement).setString(1, USER_UUID); - verify(mockPreparedStatement).setMaxRows(1); - } - - @Test - @DisplayName("getSettingsForUser – SQLException is wrapped in RuntimeException") - void getSettingsForUser_sqlException_throwsRuntimeException() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.prepareStatement(anyString())).thenThrow(new SQLException("DB error")); - - assertThrows(RuntimeException.class, () -> userSelect.getSettingsForUser(USER_UUID)); - } - - // ------------------------------------------------------------------------- - // getInboxForUser - // ------------------------------------------------------------------------- - - @Test - @DisplayName("getInboxForUser – no messages returns empty Inbox (never null)") - void getInboxForUser_noMessages_returnsEmptyInbox() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); - when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); - when(mockResultSet.next()).thenReturn(false); - - Inbox result = userSelect.getInboxForUser(USER_UUID); - - assertNotNull(result, "Inbox should never be null"); - assertTrue(result.getMessages().isEmpty()); - } - - @Test - @DisplayName("getInboxForUser – one message row returns Inbox with one Message") - void getInboxForUser_oneRow_inboxContainsOneMessage() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); - when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); - - when(mockResultSet.next()).thenReturn(true, false); - when(mockResultSet.getString("message_title")).thenReturn("Hello"); - when(mockResultSet.getString("sender_charity_id")).thenReturn(CHARITY_UUID); - when(mockResultSet.getString("message_date")).thenReturn("2024-06-15"); - - Inbox result = userSelect.getInboxForUser(USER_UUID); - - assertEquals(1, result.getMessages().size()); - assertEquals("Hello", result.getMessages().get(0).getTitle()); - } - - @Test - @DisplayName("getInboxForUser – two message rows returns Inbox with two Messages") - void getInboxForUser_twoRows_inboxContainsTwoMessages() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); - when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); - - when(mockResultSet.next()).thenReturn(true, true, false); - when(mockResultSet.getString("message_title")).thenReturn("Msg 1", "Msg 2"); - when(mockResultSet.getString("sender_charity_id")).thenReturn(CHARITY_UUID); - when(mockResultSet.getString("message_date")).thenReturn("2024-06-15"); - - Inbox result = userSelect.getInboxForUser(USER_UUID); - - assertEquals(2, result.getMessages().size()); - } - - @Test - @DisplayName("getInboxForUser – UUID is bound to PreparedStatement parameter 1") - void getInboxForUser_uuidBoundCorrectly() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); - when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); - when(mockResultSet.next()).thenReturn(false); - - userSelect.getInboxForUser(USER_UUID); - - verify(mockPreparedStatement).setString(1, USER_UUID); - } - - @Test - @DisplayName("getInboxForUser – SQLException is wrapped in RuntimeException") - void getInboxForUser_sqlException_throwsRuntimeException() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.prepareStatement(anyString())).thenThrow(new SQLException("DB error")); - - assertThrows(RuntimeException.class, () -> userSelect.getInboxForUser(USER_UUID)); - } - - // ------------------------------------------------------------------------- - // Helpers - // ------------------------------------------------------------------------- - - /** Stubs the core User columns on the mock ResultSet. */ - private void stubCoreUserColumns() throws SQLException { - when(mockResultSet.getString("UUID_User")).thenReturn(USER_UUID); - when(mockResultSet.getString("user_name")).thenReturn("Alice"); - when(mockResultSet.getString("user_email")).thenReturn("alice@example.com"); - when(mockResultSet.getString("user_password")).thenReturn("hashedpw"); - when(mockResultSet.getString("role")).thenReturn("USER"); - } - - /** Stubs the Settings columns on the mock ResultSet. */ - private void stubSettingsColumns(boolean isAnonymous, String language, boolean lightmode) - throws SQLException { - when(mockResultSet.getString("isAnonymous")).thenReturn(String.valueOf(isAnonymous)); - when(mockResultSet.getBoolean("isAnonymous")).thenReturn(isAnonymous); - when(mockResultSet.getString("language")).thenReturn(language); - when(mockResultSet.getBoolean("lightmode")).thenReturn(lightmode); - } - - /** Stubs the Message columns on the mock ResultSet for a single message row. */ - private void stubMessageColumns() throws SQLException { - when(mockResultSet.getString("UUID_message")).thenReturn(MESSAGE_UUID); - when(mockResultSet.getString("message_title")).thenReturn("Test Message"); - when(mockResultSet.getString("sender_charity_id")).thenReturn(CHARITY_UUID); - when(mockResultSet.getString("message_content")).thenReturn("Hello!"); - when(mockResultSet.getString("message_date")).thenReturn("2024-03-01"); - } -} \ No newline at end of file + @Mock private DatabaseConnection mockDatabaseConnection; + @Mock private Connection mockConnection; + @Mock private Statement mockStatement; + @Mock private PreparedStatement mockPreparedStatement; + @Mock private ResultSet mockResultSet; + + private static final String USER_UUID = "user-uuid-1"; + private static final String CHARITY_UUID = UUID.randomUUID().toString(); + private static final String MESSAGE_UUID = "msg-uuid-1"; + + private UserSelect userSelect; + + @BeforeEach + void setUp() { + userSelect = new UserSelect(mockDatabaseConnection); + } + + // ------------------------------------------------------------------------- + // getUserFromDBUuid + // ------------------------------------------------------------------------- + + @Test + @DisplayName("getUserFromDBUuid – no matching row returns null") + void getUserFromDBUuid_noRow_returnsNull() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + when(mockResultSet.next()).thenReturn(false); + + User result = userSelect.getUserFromDBUuid(USER_UUID); + + assertNull(result, "Should return null when no user is found"); + } + + @Test + @DisplayName("getUserFromDBUuid – single row without settings returns User with null settings") + void getUserFromDBUuid_noSettings_userSettingsNull() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, false); + stubCoreUserColumns(); + when(mockResultSet.getString("isAnonymous")).thenReturn(null); + when(mockResultSet.getString("UUID_message")).thenReturn(null); + + User result = userSelect.getUserFromDBUuid(USER_UUID); + + assertNotNull(result); + assertNull(result.getSettings(), "Settings should be null when isAnonymous is null"); + assertNotNull(result.getInbox(), "Inbox should always be initialised"); + } + + @Test + @DisplayName("getUserFromDBUuid – single row with settings populates Settings correctly") + void getUserFromDBUuid_withSettings_settingsPopulated() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, false); + stubCoreUserColumns(); + stubSettingsColumns(false, "ENGLISH", true); + when(mockResultSet.getString("UUID_message")).thenReturn(null); + + User result = userSelect.getUserFromDBUuid(USER_UUID); + + assertNotNull(result.getSettings()); + assertFalse(result.getSettings().isAnonymous()); + assertEquals(Language.ENGLISH, result.getSettings().getLanguage()); + assertTrue(result.getSettings().isLightMode()); + } + + @Test + @DisplayName("getUserFromDBUuid – row with a message adds it to the inbox") + void getUserFromDBUuid_withMessage_messageAddedToInbox() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, false); + stubCoreUserColumns(); + when(mockResultSet.getString("isAnonymous")).thenReturn(null); + stubMessageColumns(); + + User result = userSelect.getUserFromDBUuid(USER_UUID); + + assertEquals( + 1, result.getInbox().getMessages().size(), "Inbox should contain exactly one message"); + } + + @Test + @DisplayName("getUserFromDBUuid – two rows for same UUID adds two messages, one User") + void getUserFromDBUuid_twoRowsSameUuid_oneUserTwoMessages() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, true, false); + when(mockResultSet.getString("UUID_User")).thenReturn(USER_UUID); + when(mockResultSet.getString("user_name")).thenReturn("Alice"); + when(mockResultSet.getString("user_email")).thenReturn("alice@example.com"); + when(mockResultSet.getString("user_password")).thenReturn("hashedpw"); + when(mockResultSet.getString("role")).thenReturn("USER"); + when(mockResultSet.getString("isAnonymous")).thenReturn(null); + when(mockResultSet.getString("UUID_message")).thenReturn("msg-1", "msg-2"); + when(mockResultSet.getString("message_title")).thenReturn("Title 1", "Title 2"); + when(mockResultSet.getString("sender_charity_id")).thenReturn(CHARITY_UUID); + when(mockResultSet.getString("message_content")).thenReturn("Content"); + when(mockResultSet.getString("message_date")).thenReturn("2024-04-01"); + + User result = userSelect.getUserFromDBUuid(USER_UUID); + + assertEquals(2, result.getInbox().getMessages().size()); + } + + @Test + @DisplayName("getUserFromDBUuid – UUID is bound to PreparedStatement parameter 1") + void getUserFromDBUuid_uuidBoundCorrectly() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + when(mockResultSet.next()).thenReturn(false); + + userSelect.getUserFromDBUuid(USER_UUID); + + verify(mockPreparedStatement).setString(1, USER_UUID); + } + + @Test + @DisplayName("getUserFromDBUuid – SQLException is wrapped in RuntimeException") + void getUserFromDBUuid_sqlException_throwsRuntimeException() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenThrow(new SQLException("DB error")); + + assertThrows(RuntimeException.class, () -> userSelect.getUserFromDBUuid(USER_UUID)); + } + + // ------------------------------------------------------------------------- + // getUsersFromDB + // ------------------------------------------------------------------------- + + @Test + @DisplayName("getUsersFromDB – empty result set returns empty registry") + void getUsersFromDB_emptyResultSet_returnsEmptyRegistry() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + when(mockResultSet.next()).thenReturn(false); + + UserRegistry registry = userSelect.getUsersFromDB(); + + assertNotNull(registry); + assertTrue(registry.getAllUsers().isEmpty()); + } + + @Test + @DisplayName("getUsersFromDB – two distinct UUIDs produce two User objects") + void getUsersFromDB_twoDistinctUuids_twoUsersInRegistry() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, true, false); + when(mockResultSet.getString("UUID_User")).thenReturn("uuid-A", "uuid-B"); + when(mockResultSet.getString("user_name")).thenReturn("Alice", "Bob"); + when(mockResultSet.getString("user_email")).thenReturn("a@x.com", "b@x.com"); + when(mockResultSet.getString("user_password")).thenReturn("pw1", "pw2"); + when(mockResultSet.getString("role")).thenReturn("USER", "ADMIN"); + when(mockResultSet.getString("isAnonymous")).thenReturn(null); + when(mockResultSet.getString("UUID_message")).thenReturn(null); + + UserRegistry registry = userSelect.getUsersFromDB(); + + assertEquals(2, registry.getAllUsers().size()); + } + + @Test + @DisplayName("getUsersFromDB – same UUID across two rows deduplicates to one User") + void getUsersFromDB_sameUuidTwoRows_oneUserWithTwoMessages() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, true, false); + when(mockResultSet.getString("UUID_User")).thenReturn(USER_UUID); + when(mockResultSet.getString("user_name")).thenReturn("Alice"); + when(mockResultSet.getString("user_email")).thenReturn("alice@example.com"); + when(mockResultSet.getString("user_password")).thenReturn("hashedpw"); + when(mockResultSet.getString("role")).thenReturn("USER"); + when(mockResultSet.getString("isAnonymous")).thenReturn(null); + when(mockResultSet.getString("UUID_message")).thenReturn("msg-1", "msg-2"); + when(mockResultSet.getString("message_title")).thenReturn("T1", "T2"); + when(mockResultSet.getString("sender_charity_id")).thenReturn(CHARITY_UUID); + when(mockResultSet.getString("message_content")).thenReturn("Body"); + when(mockResultSet.getString("message_date")).thenReturn("2024-05-01"); + + UserRegistry registry = userSelect.getUsersFromDB(); + + assertEquals(1, registry.getAllUsers().size(), "Same UUID should not produce duplicate users"); + assertEquals(2, registry.getAllUsers().get(0).getInbox().getMessages().size()); + } + + @Test + @DisplayName("getUsersFromDB – SQLException is wrapped in RuntimeException") + void getUsersFromDB_sqlException_throwsRuntimeException() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.createStatement()).thenThrow(new SQLException("DB error")); + + assertThrows(RuntimeException.class, () -> userSelect.getUsersFromDB()); + } + + // ------------------------------------------------------------------------- + // getSettingsForUser + // ------------------------------------------------------------------------- + + @Test + @DisplayName("getSettingsForUser – no row returns null") + void getSettingsForUser_noRow_returnsNull() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + when(mockResultSet.next()).thenReturn(false); + + Settings result = userSelect.getSettingsForUser(USER_UUID); + + assertNull(result, "Should return null when no settings row exists"); + } + + @Test + @DisplayName("getSettingsForUser – matching row returns populated Settings") + void getSettingsForUser_matchingRow_returnsSettings() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, false); + stubSettingsColumns(true, "NORWEGIAN", false); + + Settings result = userSelect.getSettingsForUser(USER_UUID); + + assertNotNull(result); + assertTrue(result.isAnonymous()); + assertEquals(Language.NORWEGIAN, result.getLanguage()); + assertFalse(result.isLightMode()); + } + + @Test + @DisplayName("getSettingsForUser – UUID is bound to PreparedStatement and maxRows set to 1") + void getSettingsForUser_correctBindingAndMaxRows() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + when(mockResultSet.next()).thenReturn(false); + + userSelect.getSettingsForUser(USER_UUID); + + verify(mockPreparedStatement).setString(1, USER_UUID); + verify(mockPreparedStatement).setMaxRows(1); + } + + @Test + @DisplayName("getSettingsForUser – SQLException is wrapped in RuntimeException") + void getSettingsForUser_sqlException_throwsRuntimeException() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenThrow(new SQLException("DB error")); + + assertThrows(RuntimeException.class, () -> userSelect.getSettingsForUser(USER_UUID)); + } + + // ------------------------------------------------------------------------- + // getInboxForUser + // ------------------------------------------------------------------------- + + @Test + @DisplayName("getInboxForUser – no messages returns empty Inbox (never null)") + void getInboxForUser_noMessages_returnsEmptyInbox() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + when(mockResultSet.next()).thenReturn(false); + + Inbox result = userSelect.getInboxForUser(USER_UUID); + + assertNotNull(result, "Inbox should never be null"); + assertTrue(result.getMessages().isEmpty()); + } + + @Test + @DisplayName("getInboxForUser – one message row returns Inbox with one Message") + void getInboxForUser_oneRow_inboxContainsOneMessage() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, false); + when(mockResultSet.getString("message_title")).thenReturn("Hello"); + when(mockResultSet.getString("sender_charity_id")).thenReturn(CHARITY_UUID); + when(mockResultSet.getString("message_date")).thenReturn("2024-06-15"); + + Inbox result = userSelect.getInboxForUser(USER_UUID); + + assertEquals(1, result.getMessages().size()); + assertEquals("Hello", result.getMessages().get(0).getTitle()); + } + + @Test + @DisplayName("getInboxForUser – two message rows returns Inbox with two Messages") + void getInboxForUser_twoRows_inboxContainsTwoMessages() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + + when(mockResultSet.next()).thenReturn(true, true, false); + when(mockResultSet.getString("message_title")).thenReturn("Msg 1", "Msg 2"); + when(mockResultSet.getString("sender_charity_id")).thenReturn(CHARITY_UUID); + when(mockResultSet.getString("message_date")).thenReturn("2024-06-15"); + + Inbox result = userSelect.getInboxForUser(USER_UUID); + + assertEquals(2, result.getMessages().size()); + } + + @Test + @DisplayName("getInboxForUser – UUID is bound to PreparedStatement parameter 1") + void getInboxForUser_uuidBoundCorrectly() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); + when(mockResultSet.next()).thenReturn(false); + + userSelect.getInboxForUser(USER_UUID); + + verify(mockPreparedStatement).setString(1, USER_UUID); + } + + @Test + @DisplayName("getInboxForUser – SQLException is wrapped in RuntimeException") + void getInboxForUser_sqlException_throwsRuntimeException() throws Exception { + when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); + when(mockConnection.prepareStatement(anyString())).thenThrow(new SQLException("DB error")); + + assertThrows(RuntimeException.class, () -> userSelect.getInboxForUser(USER_UUID)); + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + /** Stubs the core User columns on the mock ResultSet. */ + private void stubCoreUserColumns() throws SQLException { + when(mockResultSet.getString("UUID_User")).thenReturn(USER_UUID); + when(mockResultSet.getString("user_name")).thenReturn("Alice"); + when(mockResultSet.getString("user_email")).thenReturn("alice@example.com"); + when(mockResultSet.getString("user_password")).thenReturn("hashedpw"); + when(mockResultSet.getString("role")).thenReturn("USER"); + } + + /** Stubs the Settings columns on the mock ResultSet. */ + private void stubSettingsColumns(boolean isAnonymous, String language, boolean lightmode) + throws SQLException { + when(mockResultSet.getString("isAnonymous")).thenReturn(String.valueOf(isAnonymous)); + when(mockResultSet.getBoolean("isAnonymous")).thenReturn(isAnonymous); + when(mockResultSet.getString("language")).thenReturn(language); + when(mockResultSet.getBoolean("lightmode")).thenReturn(lightmode); + } + + /** Stubs the Message columns on the mock ResultSet for a single message row. */ + private void stubMessageColumns() throws SQLException { + when(mockResultSet.getString("UUID_message")).thenReturn(MESSAGE_UUID); + when(mockResultSet.getString("message_title")).thenReturn("Test Message"); + when(mockResultSet.getString("sender_charity_id")).thenReturn(CHARITY_UUID); + when(mockResultSet.getString("message_content")).thenReturn("Hello!"); + when(mockResultSet.getString("message_date")).thenReturn("2024-03-01"); + } +}