diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/CharitySelect.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/CharitySelect.java index bdc0432..4c9ffb1 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/CharitySelect.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/CharitySelect.java @@ -1,30 +1,57 @@ package ntnu.systemutvikling.team6.database.Readers; +import java.sql.*; +import java.time.LocalDate; +import java.util.ArrayList; import ntnu.systemutvikling.team6.database.DatabaseConnection; import ntnu.systemutvikling.team6.models.Charity; import ntnu.systemutvikling.team6.models.CharityRegistry; import ntnu.systemutvikling.team6.models.Feedback; import ntnu.systemutvikling.team6.models.user.User; -import java.sql.*; -import java.time.LocalDate; -import java.util.ArrayList; - +/** + * Data access class responsible for reading charity-related data from the database. + * + *
Provides methods to retrieve all charities (with their associated feedback and users) as well + * as feedback entries for a specific charity by UUID. + * + *
All queries are executed against a MySQL database via a {@link DatabaseConnection}. + */ public class CharitySelect { - private final DatabaseConnection connection; + private final DatabaseConnection connection; - public CharitySelect(DatabaseConnection connection){ - this.connection = connection; - } + /** + * Constructs a new {@code CharitySelect} with the given database connection. + * + * @param connection the {@link DatabaseConnection} to use for executing queries; must not be + * {@code null} + */ + public CharitySelect(DatabaseConnection connection) { + this.connection = connection; + } - public CharityRegistry getCharitiesFromDB() { - CharityRegistry registry = null; - Connection conn = null; - try { - conn = connection.getMySqlConnection(); - String sql_query = - """ - SELECT + /** + * Retrieves all charities from the database, including their associated feedback and the users + * who submitted each piece of feedback. + * + *
The query performs a LEFT JOIN between the {@code Charities}, {@code Feedback}, and {@code + * User} tables. Each unique charity is added once to the registry; any feedback rows found for + * that charity are appended to its feedback list. + * + *
Note: charities with no feedback are still included in the result due to the LEFT JOIN.
+ *
+ * @return a {@link CharityRegistry} containing all charities found in the database, each
+ * populated with its associated {@link Feedback} objects (if any)
+ * @throws RuntimeException if a {@link SQLException} occurs while executing the query
+ */
+ public CharityRegistry getCharitiesFromDB() {
+ CharityRegistry registry = null;
+ Connection conn = null;
+ try {
+ conn = connection.getMySqlConnection();
+ String sql_query =
+ """
+ SELECT
c.UUID_charities, c.org_number, c.charity_name, c.charity_link, c.pre_approved, c.status,
f.UUID_feedback, f.feedback_comment, f.feedback_date, f.isAnonymous, f.charity_id, f.user_id,
u.UUID_user, u.user_name, u.user_email, u.user_password, u.role
@@ -32,95 +59,110 @@ public CharityRegistry getCharitiesFromDB() {
LEFT JOIN Feedback f ON f.charity_id = c.UUID_charities
LEFT JOIN User u ON f.user_id = u.UUID_user
""";
- Statement stmt = conn.createStatement();
- ResultSet rs = stmt.executeQuery(sql_query);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(sql_query);
- Charity currentCharity = null;
- String lastCharity = null;
+ Charity currentCharity = null;
+ String lastCharity = null;
- registry = new CharityRegistry();
- while (rs.next()) {
- String currentId = rs.getString("UUID_charities");
+ registry = new CharityRegistry();
+ while (rs.next()) {
+ String currentId = rs.getString("UUID_charities");
- if (lastCharity == null || !currentId.equals(lastCharity)) {
- currentCharity =
- new Charity(
- rs.getString("UUID_charities"),
- rs.getString("org_number"),
- rs.getString("charity_link"),
- rs.getString("charity_name"),
- rs.getBoolean("pre_approved"),
- rs.getString("status")
- );
- registry.addCharity(currentCharity);
- lastCharity = currentId;
- }
- String feedbackId = rs.getString("UUID_feedback");
- if (feedbackId != null){
- User userWithNoSettingsAndInbox = new User(
- rs.getString("UUID_User"),
- rs.getString("user_name"),
- rs.getString("user_email"),
- rs.getString("user_password"),
- rs.getString("role")
- );
+ if (lastCharity == null || !currentId.equals(lastCharity)) {
+ currentCharity =
+ new Charity(
+ rs.getString("UUID_charities"),
+ rs.getString("org_number"),
+ rs.getString("charity_link"),
+ rs.getString("charity_name"),
+ rs.getBoolean("pre_approved"),
+ rs.getString("status"));
+ registry.addCharity(currentCharity);
+ lastCharity = currentId;
+ }
+ String feedbackId = rs.getString("UUID_feedback");
+ if (feedbackId != null) {
+ User userWithNoSettingsAndInbox =
+ new User(
+ rs.getString("UUID_User"),
+ rs.getString("user_name"),
+ rs.getString("user_email"),
+ rs.getString("user_password"),
+ rs.getString("role"));
- Feedback feedback = new Feedback(
- rs.getString("UUID_feedback"),
- userWithNoSettingsAndInbox,
- rs.getString("feedback_comment"),
- LocalDate.parse(rs.getString("feedback_date"))
- );
+ Feedback feedback =
+ new Feedback(
+ rs.getString("UUID_feedback"),
+ userWithNoSettingsAndInbox,
+ rs.getString("feedback_comment"),
+ LocalDate.parse(rs.getString("feedback_date")));
- currentCharity.getFeedbacks().add(feedback);
- }
- }
- } catch (SQLException e) {
- e.printStackTrace();
- throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
+ currentCharity.getFeedbacks().add(feedback);
}
- return registry;
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
}
- public ArrayList Each {@link Feedback} object is populated with the associated {@link User} (without settings
+ * or inbox data). The query uses a LEFT JOIN between the {@code Feedback} and {@code User}
+ * tables, filtered by {@code charity_id}.
+ *
+ * @param charity_uuid the UUID of the charity whose feedback should be retrieved; must not be
+ * {@code null}
+ * @return an {@link ArrayList} of {@link Feedback} objects for the given charity; returns an
+ * empty list if no feedback exists for that charity
+ * @throws RuntimeException if any exception occurs while executing the query, wrapping the
+ * original cause
+ */
+ public ArrayList Uses Mockito to mock {@link DatabaseConnection}, {@link Connection}, {@link Statement}, {@link
+ * PreparedStatement}, and {@link ResultSet} so that no real database connection is required.
+ */
+@ExtendWith(MockitoExtension.class)
+class CharitySelectTest {
+
+ @Mock private DatabaseConnection mockDatabaseConnection;
+ @Mock private Connection mockConnection;
+ @Mock private Statement mockStatement;
+ @Mock private PreparedStatement mockPreparedStatement;
+ @Mock private ResultSet mockResultSet;
+
+ private CharitySelect charitySelect;
+
+ @BeforeEach
+ void setUp() {
+ charitySelect = new CharitySelect(mockDatabaseConnection);
+ }
+
+ // -------------------------------------------------------------------------
+ // getCharitiesFromDB
+ // -------------------------------------------------------------------------
+
+ @Test
+ @DisplayName("getCharitiesFromDB – empty result set returns an empty registry")
+ void getCharitiesFromDB_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);
+
+ CharityRegistry registry = charitySelect.getCharitiesFromDB();
+
+ assertNotNull(registry);
+ assertTrue(
+ registry.getAllCharities().isEmpty(),
+ "Registry should contain no charities when the result set is empty");
+ }
+
+ @Test
+ @DisplayName("getCharitiesFromDB – single charity with no feedback is added once")
+ void getCharitiesFromDB_singleCharityNoFeedback_addedOnce() throws Exception {
+ when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection);
+ when(mockConnection.createStatement()).thenReturn(mockStatement);
+ when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet);
+
+ // One row, no feedback
+ when(mockResultSet.next()).thenReturn(true, false);
+ when(mockResultSet.getString("UUID_charities")).thenReturn("charity-uuid-1");
+ when(mockResultSet.getString("org_number")).thenReturn("123456789");
+ when(mockResultSet.getString("charity_link")).thenReturn("https://example.org");
+ when(mockResultSet.getString("charity_name")).thenReturn("Test Charity");
+ when(mockResultSet.getBoolean("pre_approved")).thenReturn(true);
+ when(mockResultSet.getString("status")).thenReturn("ACTIVE");
+ when(mockResultSet.getString("UUID_feedback")).thenReturn(null);
+
+ CharityRegistry registry = charitySelect.getCharitiesFromDB();
+
+ assertEquals(
+ 1, registry.getAllCharities().size(), "Registry should contain exactly one charity");
+ Charity charity = registry.getAllCharities().get(0);
+ assertEquals("Test Charity", charity.getName());
+ assertTrue(charity.getFeedbacks().isEmpty(), "Charity should have no feedback");
+ }
+
+ @Test
+ @DisplayName("getCharitiesFromDB – single charity with one feedback entry is populated correctly")
+ void getCharitiesFromDB_singleCharityWithFeedback_feedbackAdded() throws Exception {
+ when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection);
+ when(mockConnection.createStatement()).thenReturn(mockStatement);
+ when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet);
+
+ // One row with feedback
+ when(mockResultSet.next()).thenReturn(true, false);
+ when(mockResultSet.getString("UUID_charities")).thenReturn("charity-uuid-1");
+ when(mockResultSet.getString("org_number")).thenReturn("123456789");
+ when(mockResultSet.getString("charity_link")).thenReturn("https://example.org");
+ when(mockResultSet.getString("charity_name")).thenReturn("Test Charity");
+ when(mockResultSet.getBoolean("pre_approved")).thenReturn(false);
+ when(mockResultSet.getString("status")).thenReturn("PENDING");
+
+ when(mockResultSet.getString("UUID_feedback")).thenReturn("feedback-uuid-1");
+ when(mockResultSet.getString("UUID_User")).thenReturn("user-uuid-1");
+ 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("feedback_comment")).thenReturn("Great work!");
+ when(mockResultSet.getString("feedback_date")).thenReturn("2024-03-15");
+
+ CharityRegistry registry = charitySelect.getCharitiesFromDB();
+
+ assertEquals(1, registry.getAllCharities().size());
+ Charity charity = registry.getAllCharities().get(0);
+ assertEquals(
+ 1, charity.getFeedbacks().size(), "Charity should have exactly one feedback entry");
+
+ Feedback feedback = charity.getFeedbacks().get(0);
+ assertEquals("feedback-uuid-1", feedback.getFeedbackId());
+ assertEquals("Great work!", feedback.getComment());
+ }
+
+ @Test
+ @DisplayName("getCharitiesFromDB – two different charities across two rows are both added")
+ void getCharitiesFromDB_twoCharities_bothAdded() throws Exception {
+ when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection);
+ when(mockConnection.createStatement()).thenReturn(mockStatement);
+ when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet);
+
+ // First row: charity A, no feedback
+ // Second row: charity B, no feedback
+ when(mockResultSet.next()).thenReturn(true, true, false);
+ when(mockResultSet.getString("UUID_charities")).thenReturn("charity-uuid-A", "charity-uuid-B");
+ when(mockResultSet.getString("org_number")).thenReturn("111111111", "222222222");
+ when(mockResultSet.getString("charity_link")).thenReturn("https://a.org", "https://b.org");
+ when(mockResultSet.getString("charity_name")).thenReturn("Charity A", "Charity B");
+ when(mockResultSet.getBoolean("pre_approved")).thenReturn(true, false);
+ when(mockResultSet.getString("status")).thenReturn("ACTIVE", "INACTIVE");
+ when(mockResultSet.getString("UUID_feedback")).thenReturn(null, null);
+
+ CharityRegistry registry = charitySelect.getCharitiesFromDB();
+
+ assertEquals(2, registry.getAllCharities().size(), "Registry should contain two charities");
+ }
+
+ @Test
+ @DisplayName(
+ "getCharitiesFromDB – same charity UUID across two rows adds feedback without duplicating the charity")
+ void getCharitiesFromDB_sameCharityTwoRows_onlyOneCharityWithTwoFeedbacks() 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);
+ // Both rows share the same charity UUID
+ when(mockResultSet.getString("UUID_charities")).thenReturn("charity-uuid-1");
+ when(mockResultSet.getString("org_number")).thenReturn("123456789");
+ when(mockResultSet.getString("charity_link")).thenReturn("https://example.org");
+ when(mockResultSet.getString("charity_name")).thenReturn("Test Charity");
+ when(mockResultSet.getBoolean("pre_approved")).thenReturn(true);
+ when(mockResultSet.getString("status")).thenReturn("ACTIVE");
+
+ when(mockResultSet.getString("UUID_feedback")).thenReturn("feedback-uuid-1", "feedback-uuid-2");
+ when(mockResultSet.getString("UUID_User")).thenReturn("user-uuid-1");
+ 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("feedback_comment")).thenReturn("First comment", "Second comment");
+ when(mockResultSet.getString("feedback_date")).thenReturn("2024-03-15");
+
+ CharityRegistry registry = charitySelect.getCharitiesFromDB();
+
+ assertEquals(1, registry.getAllCharities().size(), "The same charity should not be duplicated");
+ assertEquals(
+ 2,
+ registry.getAllCharities().get(0).getFeedbacks().size(),
+ "Both feedback entries should be attached to the single charity");
+ }
+
+ @Test
+ @DisplayName("getCharitiesFromDB – SQLException is wrapped in RuntimeException")
+ void getCharitiesFromDB_sqlException_throwsRuntimeException() throws Exception {
+ when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection);
+ when(mockConnection.createStatement()).thenThrow(new SQLException("DB error"));
+
+ assertThrows(
+ RuntimeException.class,
+ () -> charitySelect.getCharitiesFromDB(),
+ "A SQLException should be rethrown as a RuntimeException");
+ }
+
+ // -------------------------------------------------------------------------
+ // getFeedbackforCharityUUID
+ // -------------------------------------------------------------------------
+
+ @Test
+ @DisplayName("getFeedbackforCharityUUID – empty result set returns empty list")
+ void getFeedbackforCharityUUID_emptyResultSet_returnsEmptyList() throws Exception {
+ when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection);
+ when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement);
+ when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet);
+ when(mockResultSet.next()).thenReturn(false);
+
+ ArrayList