diff --git a/docs/SqlDatabase/ER-Diagram Final.png b/docs/SqlDatabase/ER-Diagram Final.png new file mode 100644 index 0000000..3a556cf Binary files /dev/null and b/docs/SqlDatabase/ER-Diagram Final.png differ diff --git a/docs/SqlDatabase/ER-DiagramFile.mwb b/docs/SqlDatabase/ER-DiagramFile.mwb index 5105a8b..7535f5b 100644 Binary files a/docs/SqlDatabase/ER-DiagramFile.mwb and b/docs/SqlDatabase/ER-DiagramFile.mwb differ diff --git a/docs/SqlDatabase/ER-DiagramFile.mwb.bak b/docs/SqlDatabase/ER-DiagramFile.mwb.bak index eb0ba08..5105a8b 100644 Binary files a/docs/SqlDatabase/ER-DiagramFile.mwb.bak and b/docs/SqlDatabase/ER-DiagramFile.mwb.bak differ diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java index 0f4a58b..ba6064b 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java @@ -6,7 +6,6 @@ import java.util.Objects; import javafx.application.Application; import javafx.fxml.FXMLLoader; -import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.stage.Stage; @@ -14,9 +13,6 @@ import ntnu.systemutvikling.team6.database.DAO.UserDAO; import ntnu.systemutvikling.team6.database.DatabaseConnection; import ntnu.systemutvikling.team6.database.DatabaseSetup; -import ntnu.systemutvikling.team6.database.Readers.UserSelect; -import ntnu.systemutvikling.team6.models.Charity; -import ntnu.systemutvikling.team6.models.registry.CharityRegistry; import ntnu.systemutvikling.team6.scraper.FullCharityScrape; import ntnu.systemutvikling.team6.service.APIToDatabaseService; import ntnu.systemutvikling.team6.service.AuthenticationService; @@ -25,7 +21,7 @@ public class HmHApplication extends Application { @Override public void start(Stage stage) throws Exception { DatabaseConnection conn = new DatabaseConnection(); - AuthenticationService authToken = new AuthenticationService(new UserSelect(conn), new UserDAO(conn)); + AuthenticationService authToken = new AuthenticationService(new UserDAO(conn)); FXMLLoader fxmlLoader = new FXMLLoader(HmHApplication.class.getResource("/fxml/frontPage.fxml")); diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/AvailableOrganizationController.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/AvailableOrganizationController.java index b7d1444..1621d8d 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/AvailableOrganizationController.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/AvailableOrganizationController.java @@ -11,7 +11,7 @@ import javafx.scene.layout.FlowPane; import ntnu.systemutvikling.team6.controller.components.*; import ntnu.systemutvikling.team6.database.DatabaseConnection; -import ntnu.systemutvikling.team6.database.Readers.CharitySelect; +import ntnu.systemutvikling.team6.database.DAO.CharityDAO; import ntnu.systemutvikling.team6.models.Charity; import ntnu.systemutvikling.team6.models.registry.CharityRegistry; @@ -49,7 +49,7 @@ protected void authTokenisSet(){ @FXML public void initialize() { DatabaseConnection conn = new DatabaseConnection(); - CharitySelect db = new CharitySelect(conn); + CharityDAO db = new CharityDAO(conn); CharityRegistry charities = db.getCharitiesFromDB(); allCharities = charities.getAllCharities(); diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/FrontpageController.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/FrontpageController.java index 041f32d..0bcef6d 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/FrontpageController.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/FrontpageController.java @@ -6,21 +6,18 @@ import java.util.Objects; import java.util.Random; -import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; -import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.Label; -import javafx.scene.control.TextField; import javafx.scene.layout.FlowPane; import ntnu.systemutvikling.team6.controller.components.*; +import ntnu.systemutvikling.team6.database.DAO.DonationDAO; import ntnu.systemutvikling.team6.database.DatabaseConnection; -import ntnu.systemutvikling.team6.database.Readers.CategorySelect; -import ntnu.systemutvikling.team6.database.Readers.CharitySelect; -import ntnu.systemutvikling.team6.database.Readers.DonationSelect; +import ntnu.systemutvikling.team6.database.DAO.CategoryDAO; +import ntnu.systemutvikling.team6.database.DAO.CharityDAO; import ntnu.systemutvikling.team6.models.Charity; import ntnu.systemutvikling.team6.models.Donation; import ntnu.systemutvikling.team6.models.registry.CharityRegistry; @@ -76,9 +73,9 @@ public void initialize() { private void loadPage(){ try { DatabaseConnection conn = new DatabaseConnection(); - CharitySelect cdb = new CharitySelect(conn); - DonationSelect ddb = new DonationSelect(conn); - CategorySelect categoryselect = new CategorySelect(conn); + CharityDAO cdb = new CharityDAO(conn); + DonationDAO ddb = new DonationDAO(conn); + CategoryDAO categoryselect = new CategoryDAO(conn); CharityRegistry charities = cdb.getCharitiesFromDB(); DonationRegistry donations = ddb.getDonationFromDB(); List categories = categoryselect.getCategoriesFromDB(); diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/GiveFeedbackController.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/GiveFeedbackController.java index 1870da0..e6d599f 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/GiveFeedbackController.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/GiveFeedbackController.java @@ -13,7 +13,7 @@ import ntnu.systemutvikling.team6.controller.components.*; import ntnu.systemutvikling.team6.database.DAO.FeedbackDAO; import ntnu.systemutvikling.team6.database.DatabaseConnection; -import ntnu.systemutvikling.team6.database.Readers.CharitySelect; +import ntnu.systemutvikling.team6.database.DAO.CharityDAO; import ntnu.systemutvikling.team6.models.Charity; import ntnu.systemutvikling.team6.models.Feedback; @@ -58,8 +58,8 @@ protected void authTokenisSet() { } private void populateFields(){ DatabaseConnection conn = new DatabaseConnection(); - CharitySelect charitySelect = new CharitySelect(conn); - ArrayList feedbacks = charitySelect.getFeedbackforCharityUUID(charity.getUUID().toString()); + FeedbackDAO feedbackDAO = new FeedbackDAO(conn); + ArrayList feedbacks = feedbackDAO.getFeedbackforCharityUUID(charity.getUUID().toString()); displayFeedbacks(feedbacks); } private void displayFeedbacks(ArrayList feedbacks){ diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/profileCharity/profileOrgInboxController.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/profileCharity/profileOrgInboxController.java index 40d8549..335e944 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/profileCharity/profileOrgInboxController.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/profileCharity/profileOrgInboxController.java @@ -12,19 +12,16 @@ import javafx.scene.layout.FlowPane; import javafx.stage.Stage; import ntnu.systemutvikling.team6.controller.components.*; +import ntnu.systemutvikling.team6.database.DAO.FeedbackDAO; import ntnu.systemutvikling.team6.database.DAO.MessageDAO; import ntnu.systemutvikling.team6.database.DatabaseConnection; -import ntnu.systemutvikling.team6.database.Readers.CharitySelect; +import ntnu.systemutvikling.team6.database.DAO.CharityDAO; import ntnu.systemutvikling.team6.models.Charity; import ntnu.systemutvikling.team6.models.Feedback; -import ntnu.systemutvikling.team6.models.user.Inbox; import ntnu.systemutvikling.team6.models.user.Message; -import ntnu.systemutvikling.team6.models.user.User; import java.io.IOException; import java.util.ArrayList; -import java.util.Date; -import java.util.List; public class profileOrgInboxController extends BaseController { @FXML @@ -60,8 +57,8 @@ public void populateFields() { // Messages DatabaseConnection conn = new DatabaseConnection(); - CharitySelect charitySelect = new CharitySelect(conn); - ArrayList feedbacks = charitySelect.getFeedbackforCharityUUID(authToken.isCharityUser().getUUID().toString()); + FeedbackDAO feedbackDAO = new FeedbackDAO(conn); + ArrayList feedbacks = feedbackDAO.getFeedbackforCharityUUID(authToken.isCharityUser().getUUID().toString()); displayFeedbacks(feedbacks); } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/profileCharity/profileOrgPaymentsController.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/profileCharity/profileOrgPaymentsController.java index e1baa79..5d505e3 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/profileCharity/profileOrgPaymentsController.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/profileCharity/profileOrgPaymentsController.java @@ -10,8 +10,8 @@ import javafx.scene.layout.VBox; import javafx.stage.Stage; import ntnu.systemutvikling.team6.controller.components.*; +import ntnu.systemutvikling.team6.database.DAO.DonationDAO; import ntnu.systemutvikling.team6.database.DatabaseConnection; -import ntnu.systemutvikling.team6.database.Readers.DonationSelect; import ntnu.systemutvikling.team6.models.Charity; import ntnu.systemutvikling.team6.models.Donation; import ntnu.systemutvikling.team6.models.registry.DonationRegistry; @@ -51,8 +51,8 @@ public void populateFields() { // DonationHistory DatabaseConnection conn = new DatabaseConnection(); - DonationSelect donationSelect = new DonationSelect(conn); - DonationRegistry donationRegistry = donationSelect.getDonationForCharity(authToken.isCharityUser().getUUID().toString()); + DonationDAO donationDAO = new DonationDAO(conn); + DonationRegistry donationRegistry = donationDAO.getDonationForCharity(authToken.isCharityUser().getUUID().toString()); displayDonations(donationRegistry); } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/profileCharity/profileOrgSettingsController.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/profileCharity/profileOrgSettingsController.java index c8b93db..f9d3acc 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/profileCharity/profileOrgSettingsController.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/profileCharity/profileOrgSettingsController.java @@ -12,17 +12,10 @@ import javafx.stage.Stage; import ntnu.systemutvikling.team6.controller.components.*; import ntnu.systemutvikling.team6.database.DAO.CharityUserDAO; -import ntnu.systemutvikling.team6.database.DAO.UserDAO; import ntnu.systemutvikling.team6.database.DatabaseConnection; -import ntnu.systemutvikling.team6.database.Readers.UserSelect; import ntnu.systemutvikling.team6.models.Charity; -import ntnu.systemutvikling.team6.models.Donation; -import ntnu.systemutvikling.team6.models.registry.DonationRegistry; -import ntnu.systemutvikling.team6.models.user.User; -import ntnu.systemutvikling.team6.security.PasswordHasher; import java.io.IOException; -import java.util.Base64; import java.util.List; public class profileOrgSettingsController extends BaseController { diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/profileUser/profileUserHistoryController.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/profileUser/profileUserHistoryController.java index 32b11a9..2881058 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/profileUser/profileUserHistoryController.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/profileUser/profileUserHistoryController.java @@ -11,8 +11,8 @@ import javafx.scene.layout.VBox; import javafx.stage.Stage; import ntnu.systemutvikling.team6.controller.components.*; +import ntnu.systemutvikling.team6.database.DAO.DonationDAO; import ntnu.systemutvikling.team6.database.DatabaseConnection; -import ntnu.systemutvikling.team6.database.Readers.DonationSelect; import ntnu.systemutvikling.team6.models.Donation; import ntnu.systemutvikling.team6.models.registry.DonationRegistry; import ntnu.systemutvikling.team6.models.user.Inbox; @@ -57,8 +57,8 @@ public void populateFields() { // DonationHistory DatabaseConnection conn = new DatabaseConnection(); - DonationSelect donationSelect = new DonationSelect(conn); - DonationRegistry donationRegistry = donationSelect.getDonationForUser(authToken.getCurrentUser().getId().toString()); + DonationDAO donationDAO = new DonationDAO(conn); + DonationRegistry donationRegistry = donationDAO.getDonationForUser(authToken.getCurrentUser().getId().toString()); double ammount = donationRegistry.getAllDonations().stream().mapToDouble(d->d.getAmount()).sum(); totalAmmount.setText(String.valueOf(ammount)); displayDonations(donationRegistry); diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/profileUser/profileUserSettingsController.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/profileUser/profileUserSettingsController.java index 89cbc7d..8e0ef07 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/profileUser/profileUserSettingsController.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/controller/profileUser/profileUserSettingsController.java @@ -10,7 +10,6 @@ import ntnu.systemutvikling.team6.controller.components.NavbarController; import ntnu.systemutvikling.team6.database.DAO.UserDAO; import ntnu.systemutvikling.team6.database.DatabaseConnection; -import ntnu.systemutvikling.team6.database.Readers.UserSelect; import ntnu.systemutvikling.team6.models.user.*; import ntnu.systemutvikling.team6.security.PasswordHasher; @@ -119,10 +118,9 @@ private void handleNewProfile(ActionEvent event){ boolean updateSuccess; DatabaseConnection conn = new DatabaseConnection(); UserDAO userDataObject = new UserDAO(conn); - UserSelect userReaderObject = new UserSelect(conn); try { if (!emailText.equals(authToken.getCurrentUser().getEmail())) { - boolean isEmailTaken = userReaderObject.isEmailTaken(emailText); + boolean isEmailTaken = userDataObject.isEmailTaken(emailText); if (!isEmailTaken) { updateSuccess = userDataObject.updateUserDetails(newUserOnlyCredentials); } else { diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/CategorySelect.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/CategoryDAO.java similarity index 86% rename from helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/CategorySelect.java rename to helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/CategoryDAO.java index 5ce735e..49a0ae7 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/CategorySelect.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/CategoryDAO.java @@ -1,4 +1,4 @@ -package ntnu.systemutvikling.team6.database.Readers; +package ntnu.systemutvikling.team6.database.DAO; import java.sql.Connection; import java.sql.ResultSet; @@ -13,16 +13,16 @@ * *

All queries are executed against a MySQL database via a {@link DatabaseConnection}. */ -public class CategorySelect { +public class CategoryDAO { private final DatabaseConnection connection; /** - *Constructs a new {@code CharitySelect} with the given database connection. + * Constructs a new {@code CategoryDAO} with the given database connection. * * @param connection the {@link DatabaseConnection} to use for executing queries; must not be * {@code null} */ - public CategorySelect(DatabaseConnection connection) { + public CategoryDAO(DatabaseConnection connection) { this.connection = connection; } @@ -47,7 +47,6 @@ public List getCategoriesFromDB() { throw new RuntimeException( "ERROR: Something went wrong during fetching categories from database."); } - return categories; } } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/CharitySelect.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/CharityDAO.java similarity index 92% rename from helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/CharitySelect.java rename to helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/CharityDAO.java index 3c81035..85e73dc 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/CharitySelect.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/CharityDAO.java @@ -1,4 +1,4 @@ -package ntnu.systemutvikling.team6.database.Readers; +package ntnu.systemutvikling.team6.database.DAO; import java.sql.*; import java.time.LocalDate; @@ -14,14 +14,14 @@ import ntnu.systemutvikling.team6.models.user.User; /** - * 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 + * Data access class responsible for acsessing charity-related data from the database. + *

+ * Primarily provides read 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}. + *

+ * All queries are executed against a MySQL database via a {@link DatabaseConnection}. */ -public class CharitySelect { +public class CharityDAO { private final DatabaseConnection connection; /** @@ -30,7 +30,7 @@ public class CharitySelect { * @param connection the {@link DatabaseConnection} to use for executing queries; must not be * {@code null} */ - public CharitySelect(DatabaseConnection connection) { + public CharityDAO(DatabaseConnection connection) { this.connection = connection; } @@ -39,7 +39,7 @@ public CharitySelect(DatabaseConnection connection) { * who submitted each piece of feedback. * *

The query performs a LEFT JOIN between the {@code Charities}, {@code Feedback}, {@code - * User}, {@code CharityVanity}, and {@code category(s)} tables. Each unique charity is added once + * User} and {@code category(s)} tables and a INNER JOIN with {@code CharityVanity} table. 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 and categories are still included in the result due to the @@ -139,6 +139,8 @@ public CharityRegistry getCharitiesFromDB() { } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } finally { + conn = null; } return registry; diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/CharityUserDAO.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/CharityUserDAO.java index 99d9de8..ee324f7 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/CharityUserDAO.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/CharityUserDAO.java @@ -7,15 +7,41 @@ import java.sql.Connection; import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; +/** + * This Data Access Object is responsible for communication to the Database for a potensial user that is also a CharityUser. + * + *

+ * CharityUsers have additional features and priviliges that regular users don't have. Methods + * specified provide the opportunity to save a new name or description. + *

+ *

+ * All queries are executed against a MySQL database via a {@link DatabaseConnection}. + *

+ * + */ public class CharityUserDAO { private DatabaseConnection connection; + /** + * Constructs a new {@code CharityUserDAO} with the given database connection. + * + * @param connection the {@link DatabaseConnection} to use for executing queries; must not be + * {@code null} + */ public CharityUserDAO(DatabaseConnection connection) { this.connection = connection; } + + /** + * Updates the Charity's name in the {@code CharityVanity} table in the database by getting the Charity object in question. + * + * @param charity Charity containing the new name for the database + * @return True or False based on if the update succeed or not + */ public boolean updateCharityVanityName(Charity charity){ Connection conn = null; String sql = """ @@ -40,6 +66,13 @@ public boolean updateCharityVanityName(Charity charity){ } } + + /** + * Updates the Charity's description in the {@code CharityVanity} table in the database by getting the Charity object in question. + * + * @param charity Charity containing the new name for the database + * @return True or False based on if the update succeed or not + */ public boolean updateCharityVanityDescription(Charity charity){ Connection conn = null; String sql = """ @@ -64,4 +97,69 @@ public boolean updateCharityVanityDescription(Charity charity){ } } + + /** + * Gets the Charity that the User is connected to by using {@code CharityUsers} as an identifie/bridge. + * + * @param uuid Id of the User. + * @return The Charity the CharityUser is connected to. + */ + public Charity getUserCharityUser(String uuid){ + if (uuid == null || uuid.isBlank()) { + throw new IllegalArgumentException( + "UUID cannot be null or blank"); + } + Charity currentCharity = null; + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + String sql_query = + """ + SELECT + c.UUID_charities, c.org_number, c.pre_approved, c.status, + cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB, + cat.category + FROM CharityUsers cu + INNER JOIN Charities c ON c.UUID_charities = cu.TheCharity + INNER JOIN CharityVanity cv ON cv.UUID_charity = c.UUID_charities + LEFT JOIN Charity_Categories cc ON cc.Charities_UUID_charities = c.UUID_charities + LEFT JOIN Categories cat ON cat.category_id = cc.Categories_category_id + WHERE CharityUserId = ? + """; + PreparedStatement stmt = conn.prepareStatement(sql_query); + stmt.setString(1, uuid); + ResultSet rs = stmt.executeQuery(); + + String lastCharity = null; + + 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_name"), + rs.getString("charity_link"), + rs.getString("status"), + rs.getBoolean("pre_approved"), + rs.getString("description"), + rs.getString("logoURL"), + rs.getString("key_values"), + rs.getBytes("logoBLOB")); + lastCharity = currentId; + } + + String categoryName = rs.getString("category"); + if (categoryName != null && !currentCharity.getCategory().contains(categoryName)) { + currentCharity.getCategory().add(categoryName); + } + } + + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } + return currentCharity; + } } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/DonationDAO.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/DonationDAO.java index dbcdf38..8fd3b1a 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/DonationDAO.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/DonationDAO.java @@ -5,25 +5,238 @@ import ntnu.systemutvikling.team6.database.DatabaseConnection; import ntnu.systemutvikling.team6.models.Charity; import ntnu.systemutvikling.team6.models.Donation; +import ntnu.systemutvikling.team6.models.registry.DonationRegistry; import ntnu.systemutvikling.team6.models.user.User; /** - * This class is responsible for sending concurrent information about the donation to the Donation - * Database. Usally called from the DonationPageController, where the user confirms their donation. + * This class is responsible for sending and receiving concurrent information about the donation to and from the Donation + * Database. Usually called from the Donation related controller, where the user is able to view their donations. */ public class DonationDAO { private final DatabaseConnection connection; + /** + * Constructs a new {@code CharityUserDAO} with the given database connection. + * + * @param connection the {@link DatabaseConnection} to use for executing queries; must not be + * {@code null} + */ public DonationDAO(DatabaseConnection connection) { this.connection = connection; } /** - * Gets the total ammount of donations for a given charity, and sends it to the database throught + * Retrieves all donations from the database, each populated with its associated {@link Charity}. + * + *

The query performs an INNER JOIN between the {@code Donations}, {@code Charities} and {@code User} tables + * on the donations row Charity_id and user_id. 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 + * @throws RuntimeException if a {@link SQLException} occurs while executing the query + */ + public DonationRegistry getDonationFromDB() { + DonationRegistry registry = null; + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + String sql_query = + """ + SELECT + d.UUID_Donations, d.amount, d.isAnonymous, d.date, d.charity_id, d.user_id, + c.UUID_charities, c.org_number, c.pre_approved, c.status, + u.UUID_User, u.user_name, u.user_email, u.user_password, u.role + FROM Donations d + INNER JOIN Charities c ON d.charity_id = c.UUID_charities + INNER JOIN User u ON d.user_id = u.UUID_user + """; + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql_query); + + registry = new DonationRegistry(); + while (rs.next()) { + Charity charity = + new Charity( + rs.getString("UUID_charities"), + rs.getString("org_number"), + rs.getBoolean("pre_approved"), + rs.getString("status")); + + User user = + new User( + rs.getString("UUID_User"), + rs.getString("user_name"), + rs.getString("user_email"), + rs.getString("user_password"), + rs.getString("role")); + Donation donation = + new Donation( + rs.getString("UUID_Donations"), + rs.getDouble("amount"), + rs.getDate("date").toLocalDate(), + charity, + user, + rs.getBoolean("isAnonymous")); + registry.addDonation(donation); + } + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } + return registry; + } + + /** + * Retrieves all donation data from the {@code Donations} table for a spesific user using its ID. + * + *

+ * Query selects from {@code Donations}, then INNER JOINS with {@code Charities} and {@code CharityVanity} + * to get names among other thing, and {@code User} table to get the donor. At the end uses a WHERE-clause to + * get the specified User. + *

+ * + * @param uuid the java.UUID for the user but in string. + * @return Returns a DOnationRegistry with all donations. + */ + public DonationRegistry getDonationForUser(String uuid) { + DonationRegistry registry = null; + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + String sql_query = + """ + SELECT + d.UUID_Donations, d.amount, d.isAnonymous, d.date, d.charity_id, d.user_id, + c.UUID_charities, c.org_number, c.pre_approved, c.status, + cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB, + u.UUID_User, u.user_name, u.user_email, u.user_password, u.role + FROM Donations d + INNER JOIN Charities c ON d.charity_id = c.UUID_charities + INNER JOIN CharityVanity cv ON cv.UUID_charity = c.UUID_charities + INNER JOIN User u ON d.user_id = u.UUID_user + WHERE d.user_id = ? + """; + PreparedStatement stmt = conn.prepareStatement(sql_query); + stmt.setString(1, uuid); + ResultSet rs = stmt.executeQuery(); + + registry = new DonationRegistry(); + while (rs.next()) { + Charity charity = + new Charity( + rs.getString("UUID_charities"), + rs.getString("org_number"), + rs.getString("charity_name"), + rs.getString("charity_link"), + rs.getString("status"), + rs.getBoolean("pre_approved"), + rs.getString("description"), + rs.getString("logoURL"), + rs.getString("key_values"), + rs.getBytes("logoBLOB")); + User user = + new User( + rs.getString("UUID_User"), + rs.getString("user_name"), + rs.getString("user_email"), + rs.getString("user_password"), + rs.getString("role")); + Donation donation = + new Donation( + rs.getString("UUID_Donations"), + rs.getDouble("amount"), + rs.getDate("date").toLocalDate(), + charity, + user, + rs.getBoolean("isAnonymous")); + registry.addDonation(donation); + } + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } + return registry; + } + + /** + * Retrieves all donation data from the {@code Donations} table for a specific Charity using its ID. + * + *

+ * Mostly used by Charity Users to view how much money they have. + * Query selects from {@code Donations}, then INNER JOINS with {@code Charities} and {@code CharityVanity} + * to get names among other thing, and {@code User} table to get the donor. At the end uses a WHERE - clause + * to get the Charity in question. + *

+ * + * @param uuid the java.UUID for the user but in string. + * @return Returns a DOnationRegistry with all donations. + */ + public DonationRegistry getDonationForCharity(String uuid) { + DonationRegistry registry = null; + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + String sql_query = + """ + SELECT + d.UUID_Donations, d.amount, d.isAnonymous, d.date, d.charity_id, d.user_id, + c.UUID_charities, c.org_number, c.pre_approved, c.status, + cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB, + u.UUID_User, u.user_name, u.user_email, u.user_password, u.role + FROM Donations d + INNER JOIN Charities c ON d.charity_id = c.UUID_charities + INNER JOIN CharityVanity cv ON cv.UUID_charity = c.UUID_charities + INNER JOIN User u ON d.user_id = u.UUID_user + WHERE c.UUID_charities = ? + """; + PreparedStatement stmt = conn.prepareStatement(sql_query); + stmt.setString(1, uuid); + ResultSet rs = stmt.executeQuery(); + + registry = new DonationRegistry(); + while (rs.next()) { + Charity charity = + new Charity( + rs.getString("UUID_charities"), + rs.getString("org_number"), + rs.getString("charity_name"), + rs.getString("charity_link"), + rs.getString("status"), + rs.getBoolean("pre_approved"), + rs.getString("description"), + rs.getString("logoURL"), + rs.getString("key_values"), + rs.getBytes("logoBLOB")); + User user = + new User( + rs.getString("UUID_User"), + rs.getString("user_name"), + rs.getString("user_email"), + rs.getString("user_password"), + rs.getString("role")); + Donation donation = + new Donation( + rs.getString("UUID_Donations"), + rs.getDouble("amount"), + rs.getDate("date").toLocalDate(), + charity, + user, + rs.getBoolean("isAnonymous")); + registry.addDonation(donation); + } + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } + return registry; + } + + /** + * Gets a Donation object for a given charity and by a User, and sends it to the database through * MySQL. * - * @param charity - * @param amount + * @param donation Donation object containing all relevant information to send to database. */ public void addDonation(Donation donation) { String sql_query = diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/FavouritesDAO.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/FavouritesDAO.java index 0f6fd2d..2c15d9d 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/FavouritesDAO.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/FavouritesDAO.java @@ -2,24 +2,53 @@ import ntnu.systemutvikling.team6.database.DatabaseConnection; import ntnu.systemutvikling.team6.models.Charity; +import ntnu.systemutvikling.team6.models.Feedback; import ntnu.systemutvikling.team6.models.registry.CharityRegistry; +import ntnu.systemutvikling.team6.models.user.Language; +import ntnu.systemutvikling.team6.models.user.Settings; import ntnu.systemutvikling.team6.models.user.User; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; +/** + * Data access class responsible for getting managing favourites for a logged inn user in the application. + *

+ * Methods include gettng users favourites, adding a favourite and checking if the charity was favourited. + *

+ * + *

All queries are executed against a MySQL database via a {@link DatabaseConnection}. + */ public class FavouritesDAO { private final DatabaseConnection connection; + /** + * Constructs a new {@code FavouritesDAO} with the given database connection. + * + * @param connection the {@link DatabaseConnection} to use for executing queries; must not be + * {@code null} + */ public FavouritesDAO(DatabaseConnection connection) { this.connection = connection; } + /** + * Checks if both {@code User} and {@code Charity} are in the same row in the {@code User_has_favourites } table. + * If it is, the charity is considered a favourite by the User + * + *

+ * Uses a Select query with a WHERE-clause that searches for a row with both ids, which considered a favourite. + *

+ * @param user User in question + * @param charity Charity in question of being favourite by User. + * @return + */ public boolean isFavourite(User user, Charity charity) { String sql = "SELECT * FROM User_has_favourites WHERE Favourer = ? AND Favourite_Charity = ?"; @@ -37,6 +66,15 @@ public boolean isFavourite(User user, Charity charity) { return false; } } + + /** + * Send a quick insert query to the {@code User_has_favourites} table, adding a row with favourer and the + * favourite Charity + * + * @param user User or the Favourer in the database + * @param charity Favourite charity in question. + * @return True or False based on if it succeeded or not + */ public boolean addFavourite(User user, Charity charity) { String sql = "INSERT INTO User_has_favourites (Favourer, Favourite_charity) VALUES (?, ?)"; @@ -53,6 +91,18 @@ public boolean addFavourite(User user, Charity charity) { } } + /** + * Does a quick remove query to delete a previous favourite charity. + * + *

+ * This will not invoke and Argument-Exception because the remove query is avabile only after being added + * throught {@code addFavourite} + *

+ * @param user user in question of having a favourite + * @param charity Charity about to be removed a favourite + * @return + */ + public boolean removeFavourite(User user, Charity charity) { String sql = "DELETE FROM User_has_favourites WHERE Favourer = ? AND Favourite_charity = ?"; @@ -68,6 +118,18 @@ public boolean removeFavourite(User user, Charity charity) { return false; } } + + /** + * Get all detailts about the users favourites. + * + *

+ * Uses a Select-query, INNER JOIN between {@code Charities} and {@code CharityVanity} tables, and LEFT JOIN + * {@code Charity_Categories} and {@code Categries} tables to get fill all attributes in the {@code Charity} + * object. At the end, uses a WHERE-clause to grab the right favourer. + *

+ * @param user_id UUID in String for the Favourer + * @return An empty or non-empty List of Charities that have been favourited by User + */ public List getFavouritesForUser(String user_id) { CharityRegistry registry = null; Connection conn = null; @@ -128,5 +190,4 @@ public List getFavouritesForUser(String user_id) { } return registry.getAllCharities(); } - } \ No newline at end of file diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/FeedbackDAO.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/FeedbackDAO.java index 705929a..4b7a5a3 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/FeedbackDAO.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/FeedbackDAO.java @@ -3,19 +3,39 @@ import ntnu.systemutvikling.team6.database.DatabaseConnection; import ntnu.systemutvikling.team6.models.Charity; import ntnu.systemutvikling.team6.models.Feedback; +import ntnu.systemutvikling.team6.models.user.Language; +import ntnu.systemutvikling.team6.models.user.Settings; +import ntnu.systemutvikling.team6.models.user.User; -import java.sql.Connection; -import java.sql.Date; -import java.sql.PreparedStatement; -import java.sql.SQLException; +import java.sql.*; +import java.time.LocalDate; +import java.util.ArrayList; +/** + * This Data Access Object is mainly responsible for handling Feedbacks and communitcating with the DataBase. + * Primarily interacting with the {@code Feedback} table. + */ public class FeedbackDAO { private final DatabaseConnection connection; + /** + * Constructs a new {@code FeedbackDAO} with the given database connection. + * + * @param connection the {@link DatabaseConnection} to use for executing queries; must not be + * {@code null} + */ public FeedbackDAO(DatabaseConnection connection){ this.connection = connection; } + /** + * Send a simple insert query to the {@code Feedback} table. Containing the comment, date, if its an Anonymous + * Feedback and recipients. + * + * @param feedback Feedback object contain all relevant info, as said above. + * @param toCharity Dedicated Charity object, the recipient. + * @return + */ public boolean addFeedbackToCharity(Feedback feedback, Charity toCharity){ String sql = """ INSERT INTO Feedback @@ -41,4 +61,68 @@ public boolean addFeedbackToCharity(Feedback feedback, Charity toCharity){ } } + /** + * A helper function that retrieves all feedback entries associated with a specific charity, + * identified by its UUID. Currently, has no use. + * + *

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 getFeedbackforCharityUUID(String charity_uuid) { + ArrayList Feedbacks = new ArrayList<>(); + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + String sql_query = + """ + SELECT + 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, + s.language, s.lightmode + FROM Feedback f + LEFT JOIN User u ON f.user_id = u.UUID_user + RIGHT JOIN Settings s ON u.UUID_user = s.UUID_user + WHERE f.charity_id = ?; + """; + PreparedStatement stmt = conn.prepareStatement(sql_query); + stmt.setString(1, charity_uuid); + ResultSet rs = stmt.executeQuery(); + + while (rs.next()) { + User userWithSettingsAndNoInbox = + new User( + rs.getString("UUID_User"), + rs.getString("user_name"), + rs.getString("user_email"), + rs.getString("user_password"), + rs.getString("role")); + userWithSettingsAndNoInbox.setSettings(new Settings( + rs.getBoolean("isAnonymous"), + Language.valueOf(rs.getString("language")), + rs.getBoolean("lightmode") + )); + Feedback feedback = + new Feedback( + rs.getString("UUID_feedback"), + userWithSettingsAndNoInbox, + rs.getString("feedback_comment"), + LocalDate.parse(rs.getString("feedback_date"))); + Feedbacks.add(feedback); + } + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(e); + } finally { + conn = null; + } + return Feedbacks; + } } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/MessageDAO.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/MessageDAO.java index 8545863..0ad6b9a 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/MessageDAO.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/MessageDAO.java @@ -12,12 +12,34 @@ import java.util.List; import java.util.UUID; +/** + * This Data Access Object is mainly responsible for handling Messages and Message object to communitcate correctly + * with the DataBase. + * Primarily interacting with the {@code Messages} table. + */ public class MessageDAO { private final DatabaseConnection connection; + /** + * Constructs a new {@code FeedbackDAO} with the given database connection. + * + * @param connection the {@link DatabaseConnection} to use for executing queries; must not be + * {@code null} + */ public MessageDAO(DatabaseConnection connection){ this.connection = connection; } + + /** + * Uses a repeated INSERT-query to send a message to all donor (as in Users that have donated). + * + *

+ * First grabs all donors (all Users that have donated to the charity) ids by getting + * the Charity_id throught the attribute. + *

+ * @param message Message object contain all relevant info, including the Charity id. + * @return True or False based on if it succeeded or not + */ public boolean addMessage(Message message){ // First fetch all unique donors for this charity List donorIds = getDonorIdsForCharity(message.getFrom().getUUID().toString()); @@ -51,6 +73,18 @@ public boolean addMessage(Message message){ } } + /** + * Private helper function used in {@code addMessage} method, which finds all distinct User Ids that have donated + * to given Charity. + * + *

+ * Uses a Select query with a distinct modifier to not get duplicate user_id. At the end uses an WHERE-clause + * to get the spesified charity. + *

+ * + * @param charityId Charity's I'd to search for. + * @return An empty or non-empty list of Ids as String. + */ private List getDonorIdsForCharity(String charityId) { List donorIds = new ArrayList<>(); String sql = """ diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/UserDAO.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/UserDAO.java index 2357894..b570d8b 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/UserDAO.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DAO/UserDAO.java @@ -1,26 +1,500 @@ package ntnu.systemutvikling.team6.database.DAO; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; +import java.sql.*; +import java.time.LocalDate; +import java.util.HashSet; + import ntnu.systemutvikling.team6.database.DatabaseConnection; -import ntnu.systemutvikling.team6.models.user.Settings; -import ntnu.systemutvikling.team6.models.user.User; +import ntnu.systemutvikling.team6.models.Charity; +import ntnu.systemutvikling.team6.models.registry.UserRegistry; +import ntnu.systemutvikling.team6.models.user.*; +import ntnu.systemutvikling.team6.security.PasswordHasher; /** * This class is responsible for sending concurrent information about the user to the User database, * and user settings to the settings database. * * @author Robin Strand Prestmo + * @author Adrian Paul Limpiado Balunan */ public class UserDAO { private final DatabaseConnection connection; + /** + * Constructs a new {@code UserDAO} with the given database connection. + * + * @param connection the {@link DatabaseConnection} to use for executing queries; must not be + * {@code null} + */ public UserDAO(DatabaseConnection connection) { this.connection = connection; } + /** + * Uses a select query to check if the provided Email already exists in {@code User} table. Return value is based on if finds a row or not. + * + *

+ * Check if email is a valid email. + * Uses a Select query to get a row containing the provided email. + * If it exists, return true. + * If not, return false + * Used in tandem with {@code LoginPageController} prevent duplicate Emails appearing on database. + *

+ * @param email Email in question. Check + * @return + */ + public boolean isEmailTaken(String email){ + if (email == null || email.isBlank() || !email.contains("@") || !email.contains(".")) { + throw new IllegalArgumentException( + "Email cannot be null or blank," + " and must contain '@' and '.'"); + } + try (Connection conn = connection.getMySqlConnection()) { + + String mysql = + """ + SELECT UUID_User FROM User WHERE user_email = ? + """; + PreparedStatement statement = conn.prepareStatement(mysql); + statement.setString(1, email); + ResultSet rs = statement.executeQuery(); + + if (rs.next()) { + System.out.println("Email Taken already"); + return true; + } + + } catch (SQLException e) { + e.printStackTrace(); + } + return false; + } + + + /** + * 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. + * + * @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 + * @throws RuntimeException if a {@link SQLException} occurs while executing the query + */ + public User getUserFromDBUuid(String user_id) { + User user = null; + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + String sql_query = + """ + SELECT + u.UUID_User, u.user_name, u.user_email, u.user_password, u.role, + s.UUID_user, s.isAnonymous, s.language, s.lightmode, + m.UUID_message, m.message_title, m.message_content, m.message_date, m.sender_user_id, m.sender_charity_id, m.user_id, + cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB, + c.UUID_charities, c.org_number, c.pre_approved, c.status + FROM User u + LEFT JOIN Settings s ON u.UUID_User = s.UUID_user + LEFT JOIN Messages m ON u.UUID_User = m.user_id + LEFT JOIN Charities c ON m.sender_charity_id = c.UUID_charities + LEFT JOIN CharityVanity cv ON c.UUID_charities = cv.UUID_charity + WHERE u.UUID_User = ?; + """; + PreparedStatement stmt = conn.prepareStatement(sql_query); + stmt.setString(1, user_id); + ResultSet rs = stmt.executeQuery(); + + String lastUserid = null; + HashSet addedMessageIds = new HashSet<>(); + while (rs.next()) { + String userId = rs.getString("UUID_User"); + if (lastUserid == null || !userId.equals(lastUserid)) { + user = + new User( + userId, + rs.getString("user_name"), + rs.getString("user_email"), + rs.getString("user_password"), + rs.getString("role")); + if (rs.getString("isAnonymous") != null) { + Settings settings = + new Settings( + rs.getBoolean("isAnonymous"), + Language.valueOf(rs.getString("language").toUpperCase()), + rs.getBoolean("lightmode")); + user.setSettings(settings); + } + user.setInbox(new Inbox()); + lastUserid = userId; + } + String messageId = rs.getString("UUID_message"); + if (messageId != null && !addedMessageIds.contains(messageId)) { + addedMessageIds.add(messageId); + Charity fromCharity = null; + String charityId = rs.getString("UUID_charities"); + if (charityId != null) { + fromCharity = new Charity( + charityId, + rs.getString("org_number"), + rs.getString("charity_name"), + rs.getString("charity_link"), + rs.getString("status"), + rs.getBoolean("pre_approved"), + rs.getString("description"), + rs.getString("logoURL"), + rs.getString("key_values"), + rs.getBytes("logoBLOB") + ); + } + + if (fromCharity != null) { + Message message = new Message( + rs.getString("message_title"), + fromCharity, + rs.getString("message_content"), + LocalDate.parse(rs.getString("message_date")) + ); + user.getInbox().addMessage(message); + } + } + } + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } finally { + conn = null; + } + return user; + } + /** + * 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. + * + *

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 email the email 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 + * @throws RuntimeException if a {@link SQLException} occurs while executing the query + */ + public User getUserFromDBEmailAndPassword(String email, String password) { + PasswordHasher hasher = new PasswordHasher(); + String hashedpassword = hasher.getHashPassword(password); + + User user = null; + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + String sql_query = + """ + SELECT + u.UUID_User, u.user_name, u.user_email, u.user_password, u.role, + s.UUID_user, s.isAnonymous, s.language, s.lightmode, + m.UUID_message, m.message_title, m.message_content, m.message_date, m.sender_user_id, m.sender_charity_id, m.user_id, + cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB, + c.UUID_charities, c.org_number, c.pre_approved, c.status + FROM User u + LEFT JOIN Settings s ON u.UUID_User = s.UUID_user + LEFT JOIN Messages m ON u.UUID_User = m.user_id + LEFT JOIN Charities c ON m.sender_charity_id = c.UUID_charities + LEFT JOIN CharityVanity cv ON c.UUID_charities = cv.UUID_charity + WHERE u.user_email = ?; + """; + PreparedStatement stmt = conn.prepareStatement(sql_query); + stmt.setString(1, email); + + + + ResultSet rs = stmt.executeQuery(); + HashSet addedMessageIds = new HashSet<>(); + while (rs.next()) { + String userId = rs.getString("UUID_User"); + System.out.println(rs.getString("user_name")); + + if (user == null) { + String storedHash = rs.getString("user_password"); + + if (!new PasswordHasher().isValidPassword(password, storedHash)){ + return null; + } + user = + new User( + userId, + rs.getString("user_name"), + rs.getString("user_email"), + rs.getString("user_password"), + rs.getString("role")); + if (rs.getString("isAnonymous") != null) { + Settings settings = + new Settings( + rs.getBoolean("isAnonymous"), + Language.valueOf(rs.getString("language").toUpperCase()), + rs.getBoolean("lightmode")); + user.setSettings(settings); + } + user.setInbox(new Inbox()); + } + String messageId = rs.getString("UUID_message"); + if (messageId != null && !addedMessageIds.contains(messageId)) { + addedMessageIds.add(messageId); + Charity fromCharity = null; + String charityId = rs.getString("UUID_charities"); + if (charityId != null) { + fromCharity = new Charity( + charityId, + rs.getString("org_number"), + rs.getString("charity_name"), + rs.getString("charity_link"), + rs.getString("status"), + rs.getBoolean("pre_approved"), + rs.getString("description"), + rs.getString("logoURL"), + rs.getString("key_values"), + rs.getBytes("logoBLOB") + ); + } + + if (fromCharity != null) { + Message message = new Message( + rs.getString("message_title"), + fromCharity, + rs.getString("message_content"), + LocalDate.parse(rs.getString("message_date")) + ); + user.getInbox().addMessage(message); + } + } + } + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } finally { + conn = null; + } + return user; + } + + /** + * 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. + * + * @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() { + UserRegistry registry = new UserRegistry(); + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + String sql_query = + """ + SELECT + u.UUID_User, u.user_name, u.user_email, u.user_password, u.role, + s.UUID_user, s.isAnonymous, s.language, s.lightmode, + m.UUID_message, m.message_title, m.message_content, m.message_date, m.sender_user_id, m.sender_charity_id, m.user_id, + cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB, + c.UUID_charities, c.org_number, c.pre_approved, c.status + FROM User u + LEFT JOIN Settings s ON u.UUID_User = s.UUID_user + LEFT JOIN Messages m ON u.UUID_User = m.user_id + LEFT JOIN Charities c ON m.sender_charity_id = c.UUID_charities + LEFT JOIN CharityVanity cv ON c.UUID_charities = cv.UUID_charity + """; + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql_query); + + User currentUser = null; + String lastUserid = null; + HashSet addedMessageIds = new HashSet<>(); + + while (rs.next()) { + String userId = rs.getString("UUID_User"); + + if (lastUserid == null || !userId.equals(lastUserid)) { + currentUser = + new User( + userId, + rs.getString("user_name"), + rs.getString("user_email"), + rs.getString("user_password"), + rs.getString("role")); + if (rs.getString("isAnonymous") != null) { + Settings settings = + new Settings( + rs.getBoolean("isAnonymous"), + Language.valueOf(rs.getString("language").toUpperCase()), + rs.getBoolean("lightmode")); + currentUser.setSettings(settings); + } + currentUser.setInbox(new Inbox()); + registry.addUser(currentUser); + lastUserid = userId; + } + String messageId = rs.getString("UUID_message"); + if (messageId != null && !addedMessageIds.contains(messageId)) { + addedMessageIds.add(messageId); + Charity fromCharity = null; + String charityId = rs.getString("UUID_charities"); + if (charityId != null) { + fromCharity = new Charity( + charityId, + rs.getString("org_number"), + rs.getString("charity_name"), + rs.getString("charity_link"), + rs.getString("status"), + rs.getBoolean("pre_approved"), + rs.getString("description"), + rs.getString("logoURL"), + rs.getString("key_values"), + rs.getBytes("logoBLOB") + ); + } + + if (fromCharity != null) { + Message message = new Message( + rs.getString("message_title"), + fromCharity, + rs.getString("message_content"), + LocalDate.parse(rs.getString("message_date")) + ); + currentUser.getInbox().addMessage(message); + } + } + } + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } finally { + conn = null; + } + return registry; + } + + /** + * 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. + * + * @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) { + Inbox inbox = new Inbox(); + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + String sql_query = + """ + SELECT + m.UUID_message, m.message_title, m.message_content, m.message_date, m.sender_user_id, m.sender_charity_id, m.user_id, + cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB, + c.UUID_charities, c.org_number, c.pre_approved, c.status + FROM Messages m + LEFT JOIN Charities c ON m.sender_charity_id = c.UUID_charities + LEFT JOIN CharityVanity cv ON c.UUID_charities = cv.UUID_charity + WHERE user_id = ?; + """; + PreparedStatement stmt = conn.prepareStatement(sql_query); + stmt.setString(1, user_id); + ResultSet rs = stmt.executeQuery(); + + while (rs.next()) { + Charity fromCharity = null; + String charityId = rs.getString("UUID_charities"); + if (charityId != null) { + fromCharity = new Charity( + charityId, + rs.getString("org_number"), + rs.getString("charity_name"), + rs.getString("charity_link"), + rs.getString("status"), + rs.getBoolean("pre_approved"), + rs.getString("description"), + rs.getString("logoURL"), + rs.getString("key_values"), + rs.getBytes("logoBLOB") + ); + } + + if (fromCharity != null) { + Message message = new Message( + rs.getString("message_title"), + fromCharity, + rs.getString("message_content"), + LocalDate.parse(rs.getString("message_date")) + ); + inbox.addMessage(message); + } + } + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } finally { + conn = null; + } + return inbox; + } + + /** + * 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. + * + * @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 + */ + public Settings getSettingsForUser(String user_id) { + Settings settings = null; + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + String sql_query = + """ + SELECT UUID_user, isAnonymous, language, lightmode FROM Settings + WHERE UUID_user = ?; + """; + PreparedStatement stmt = conn.prepareStatement(sql_query); + stmt.setString(1, user_id); + stmt.setMaxRows(1); + ResultSet rs = stmt.executeQuery(); + + while (rs.next()) { + settings = + new Settings( + rs.getBoolean("isAnonymous"), + Language.valueOf(rs.getString("language").toUpperCase()), + rs.getBoolean("lightmode")); + } + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } finally { + conn = null; + } + + return settings; + } /** * Gets the user and settings information and sends it to the database through MySQL. * @@ -89,6 +563,14 @@ INSERT INTO Settings ( } } + /** + * Updates the Users settings in the {@code Settings} table. New values are stored in the Settings object. + * + * @param user User that is going to recieve changes + * @param settings Settings object containing new attributes + * @return True or False based on if the update succeed or not + */ + public boolean updateUserSettings(User user, Settings settings){ Connection conn = null; String sql = """ @@ -117,6 +599,12 @@ public boolean updateUserSettings(User user, Settings settings){ } + /** + * Updates the Users name, email and password in the {@code User} table. New values are stored in oncoming User param. + * + * @param user User that is going to recieve changes + * @return True or False based on if the update succeed or not + */ public boolean updateUserDetails(User user){ Connection conn = null; String sql = """ 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 deleted file mode 100644 index a577880..0000000 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/DonationSelect.java +++ /dev/null @@ -1,218 +0,0 @@ -package ntnu.systemutvikling.team6.database.Readers; - -import java.sql.*; - -import ntnu.systemutvikling.team6.database.DatabaseConnection; -import ntnu.systemutvikling.team6.models.Charity; -import ntnu.systemutvikling.team6.models.Donation; -import ntnu.systemutvikling.team6.models.registry.DonationRegistry; -import ntnu.systemutvikling.team6.models.user.User; - -/** - * Data access class responsible for reading donation data from the database. - * - *

Retrieves donations along with their associated {@link Charity} by performing an INNER JOIN - * between the {@code Donations} and {@code Charities} tables. Only donations with a matching - * charity record are included. Donor ({@link User}) and {@code CharityVanity} details are - * intentionally excluded to keep this query lightweight — join those separately if richer data is - * needed. - * - *

Note: {@code CharityVanity} fields (name, link, description, logo) are NOT fetched here since - * they live in a separate table. The {@link Charity} objects returned will only contain the core - * fields present in the {@code Charities} table. - */ -public class DonationSelect { - - /** The database connection used for all queries in this class. */ - private final DatabaseConnection connection; - - /** - * 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} - */ - public DonationSelect(DatabaseConnection connection) { - this.connection = connection; - } - - /** - * 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. - * - * @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() { - DonationRegistry registry = null; - Connection conn = null; - try { - conn = connection.getMySqlConnection(); - String sql_query = - """ - SELECT - d.UUID_Donations, d.amount, d.isAnonymous, d.date, d.charity_id, d.user_id, - c.UUID_charities, c.org_number, c.pre_approved, c.status, - u.UUID_User, u.user_name, u.user_email, u.user_password, u.role - FROM Donations d - INNER JOIN Charities c ON d.charity_id = c.UUID_charities - INNER JOIN User u ON d.user_id = u.UUID_user - """; - Statement stmt = conn.createStatement(); - ResultSet rs = stmt.executeQuery(sql_query); - - registry = new DonationRegistry(); - while (rs.next()) { - Charity charity = - new Charity( - rs.getString("UUID_charities"), - rs.getString("org_number"), - rs.getBoolean("pre_approved"), - rs.getString("status")); - - User user = - new User( - rs.getString("UUID_User"), - rs.getString("user_name"), - rs.getString("user_email"), - rs.getString("user_password"), - rs.getString("role")); - Donation donation = - new Donation( - rs.getString("UUID_Donations"), - rs.getDouble("amount"), - rs.getDate("date").toLocalDate(), - charity, - user, - rs.getBoolean("isAnonymous")); - registry.addDonation(donation); - } - } catch (SQLException e) { - e.printStackTrace(); - throw new RuntimeException("ERROR: Something went wrong during updating charities table."); - } - return registry; - } - public DonationRegistry getDonationForUser(String uuid) { - DonationRegistry registry = null; - Connection conn = null; - try { - conn = connection.getMySqlConnection(); - String sql_query = - """ - SELECT - d.UUID_Donations, d.amount, d.isAnonymous, d.date, d.charity_id, d.user_id, - c.UUID_charities, c.org_number, c.pre_approved, c.status, - cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB, - u.UUID_User, u.user_name, u.user_email, u.user_password, u.role - FROM Donations d - INNER JOIN Charities c ON d.charity_id = c.UUID_charities - INNER JOIN CharityVanity cv ON cv.UUID_charity = c.UUID_charities - INNER JOIN User u ON d.user_id = u.UUID_user - WHERE d.user_id = ? - """; - PreparedStatement stmt = conn.prepareStatement(sql_query); - stmt.setString(1, uuid); - ResultSet rs = stmt.executeQuery(); - - registry = new DonationRegistry(); - while (rs.next()) { - Charity charity = - new Charity( - rs.getString("UUID_charities"), - rs.getString("org_number"), - rs.getString("charity_name"), - rs.getString("charity_link"), - rs.getString("status"), - rs.getBoolean("pre_approved"), - rs.getString("description"), - rs.getString("logoURL"), - rs.getString("key_values"), - rs.getBytes("logoBLOB")); - User user = - new User( - rs.getString("UUID_User"), - rs.getString("user_name"), - rs.getString("user_email"), - rs.getString("user_password"), - rs.getString("role")); - Donation donation = - new Donation( - rs.getString("UUID_Donations"), - rs.getDouble("amount"), - rs.getDate("date").toLocalDate(), - charity, - user, - rs.getBoolean("isAnonymous")); - registry.addDonation(donation); - } - } catch (SQLException e) { - e.printStackTrace(); - throw new RuntimeException("ERROR: Something went wrong during updating charities table."); - } - return registry; - } - public DonationRegistry getDonationForCharity(String uuid) { - DonationRegistry registry = null; - Connection conn = null; - try { - conn = connection.getMySqlConnection(); - String sql_query = - """ - SELECT - d.UUID_Donations, d.amount, d.isAnonymous, d.date, d.charity_id, d.user_id, - c.UUID_charities, c.org_number, c.pre_approved, c.status, - cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB, - u.UUID_User, u.user_name, u.user_email, u.user_password, u.role - FROM Donations d - INNER JOIN Charities c ON d.charity_id = c.UUID_charities - INNER JOIN CharityVanity cv ON cv.UUID_charity = c.UUID_charities - INNER JOIN User u ON d.user_id = u.UUID_user - WHERE c.UUID_charities = ? - """; - PreparedStatement stmt = conn.prepareStatement(sql_query); - stmt.setString(1, uuid); - ResultSet rs = stmt.executeQuery(); - - registry = new DonationRegistry(); - while (rs.next()) { - Charity charity = - new Charity( - rs.getString("UUID_charities"), - rs.getString("org_number"), - rs.getString("charity_name"), - rs.getString("charity_link"), - rs.getString("status"), - rs.getBoolean("pre_approved"), - rs.getString("description"), - rs.getString("logoURL"), - rs.getString("key_values"), - rs.getBytes("logoBLOB")); - User user = - new User( - rs.getString("UUID_User"), - rs.getString("user_name"), - rs.getString("user_email"), - rs.getString("user_password"), - rs.getString("role")); - Donation donation = - new Donation( - rs.getString("UUID_Donations"), - rs.getDouble("amount"), - rs.getDate("date").toLocalDate(), - charity, - user, - rs.getBoolean("isAnonymous")); - registry.addDonation(donation); - } - } catch (SQLException e) { - e.printStackTrace(); - throw new RuntimeException("ERROR: Something went wrong during updating charities table."); - } - return registry; - } -} 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 deleted file mode 100644 index bc5da17..0000000 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/Readers/UserSelect.java +++ /dev/null @@ -1,551 +0,0 @@ -package ntnu.systemutvikling.team6.database.Readers; - -import java.sql.*; -import java.time.LocalDate; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; -import ntnu.systemutvikling.team6.database.DatabaseConnection; -import ntnu.systemutvikling.team6.models.Charity; -import ntnu.systemutvikling.team6.models.Feedback; -import ntnu.systemutvikling.team6.models.registry.CharityRegistry; -import ntnu.systemutvikling.team6.models.registry.UserRegistry; -import ntnu.systemutvikling.team6.models.user.*; -import ntnu.systemutvikling.team6.security.PasswordHasher; - -/** - * 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. - */ -public class UserSelect { - /** The database connection used for all queries in this class. */ - private final DatabaseConnection connection; - - /** - * 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} - */ - public UserSelect(DatabaseConnection connection) { - this.connection = connection; - } - - public boolean isEmailTaken(String email){ - if (email == null || email.isBlank() || !email.contains("@") || !email.contains(".")) { - throw new IllegalArgumentException( - "Email cannot be null or blank," + " and must contain '@' and '.'"); - } - try (Connection conn = connection.getMySqlConnection()) { - - String mysql = - """ - SELECT UUID_User FROM User WHERE user_email = ? - """; - PreparedStatement statement = conn.prepareStatement(mysql); - statement.setString(1, email); - ResultSet rs = statement.executeQuery(); - - if (rs.next()) { - System.out.println("Email Taken already"); - return true; - } - - } catch (SQLException e) { - e.printStackTrace(); - } - return false; - } - - public Charity getUserCharityUser(String uuid){ - if (uuid == null || uuid.isBlank()) { - throw new IllegalArgumentException( - "UUID cannot be null or blank"); - } - Charity currentCharity = null; - Connection conn = null; - try { - conn = connection.getMySqlConnection(); - String sql_query = - """ - SELECT - c.UUID_charities, c.org_number, c.pre_approved, c.status, - cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB, - cat.category - FROM CharityUsers cu - INNER JOIN Charities c ON c.UUID_charities = cu.TheCharity - INNER JOIN CharityVanity cv ON cv.UUID_charity = c.UUID_charities - LEFT JOIN Charity_Categories cc ON cc.Charities_UUID_charities = c.UUID_charities - LEFT JOIN Categories cat ON cat.category_id = cc.Categories_category_id - WHERE CharityUserId = ? - """; - PreparedStatement stmt = conn.prepareStatement(sql_query); - stmt.setString(1, uuid); - ResultSet rs = stmt.executeQuery(); - - String lastCharity = null; - - 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_name"), - rs.getString("charity_link"), - rs.getString("status"), - rs.getBoolean("pre_approved"), - rs.getString("description"), - rs.getString("logoURL"), - rs.getString("key_values"), - rs.getBytes("logoBLOB")); - lastCharity = currentId; - } - - String categoryName = rs.getString("category"); - if (categoryName != null && !currentCharity.getCategory().contains(categoryName)) { - currentCharity.getCategory().add(categoryName); - } - } - - } catch (SQLException e) { - e.printStackTrace(); - throw new RuntimeException("ERROR: Something went wrong during updating charities table."); - } - return currentCharity; - } - - - /** - * 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. - * - *

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 email the email 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 - * @throws RuntimeException if a {@link SQLException} occurs while executing the query - */ - public User getUserFromDBEmailAndPassword(String email, String password) { - PasswordHasher hasher = new PasswordHasher(); - String hashedpassword = hasher.getHashPassword(password); - - User user = null; - Connection conn = null; - try { - conn = connection.getMySqlConnection(); - String sql_query = - """ - SELECT - u.UUID_User, u.user_name, u.user_email, u.user_password, u.role, - s.UUID_user, s.isAnonymous, s.language, s.lightmode, - m.UUID_message, m.message_title, m.message_content, m.message_date, m.sender_user_id, m.sender_charity_id, m.user_id, - cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB, - c.UUID_charities, c.org_number, c.pre_approved, c.status - FROM User u - LEFT JOIN Settings s ON u.UUID_User = s.UUID_user - LEFT JOIN Messages m ON u.UUID_User = m.user_id - LEFT JOIN Charities c ON m.sender_charity_id = c.UUID_charities - LEFT JOIN CharityVanity cv ON c.UUID_charities = cv.UUID_charity - WHERE u.user_email = ?; - """; - PreparedStatement stmt = conn.prepareStatement(sql_query); - stmt.setString(1, email); - - - - ResultSet rs = stmt.executeQuery(); - HashSet addedMessageIds = new HashSet<>(); - while (rs.next()) { - String userId = rs.getString("UUID_User"); - System.out.println(rs.getString("user_name")); - - if (user == null) { - String storedHash = rs.getString("user_password"); - - if (!new PasswordHasher().isValidPassword(password, storedHash)){ - return null; - } - user = - new User( - userId, - rs.getString("user_name"), - rs.getString("user_email"), - rs.getString("user_password"), - rs.getString("role")); - if (rs.getString("isAnonymous") != null) { - Settings settings = - new Settings( - rs.getBoolean("isAnonymous"), - Language.valueOf(rs.getString("language").toUpperCase()), - rs.getBoolean("lightmode")); - user.setSettings(settings); - } - user.setInbox(new Inbox()); - } - String messageId = rs.getString("UUID_message"); - if (messageId != null && !addedMessageIds.contains(messageId)) { - addedMessageIds.add(messageId); - Charity fromCharity = null; - String charityId = rs.getString("UUID_charities"); - if (charityId != null) { - fromCharity = new Charity( - charityId, - rs.getString("org_number"), - rs.getString("charity_name"), - rs.getString("charity_link"), - rs.getString("status"), - rs.getBoolean("pre_approved"), - rs.getString("description"), - rs.getString("logoURL"), - rs.getString("key_values"), - rs.getBytes("logoBLOB") - ); - } - - if (fromCharity != null) { - Message message = new Message( - rs.getString("message_title"), - fromCharity, - rs.getString("message_content"), - LocalDate.parse(rs.getString("message_date")) - ); - user.getInbox().addMessage(message); - } - } - } - } catch (SQLException e) { - e.printStackTrace(); - throw new RuntimeException("ERROR: Something went wrong during updating charities table."); - } finally { - conn = null; - } - return user; - } - - /** - * 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. - * - * @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 - * @throws RuntimeException if a {@link SQLException} occurs while executing the query - */ - public User getUserFromDBUuid(String user_id) { - User user = null; - Connection conn = null; - try { - conn = connection.getMySqlConnection(); - String sql_query = - """ - SELECT - u.UUID_User, u.user_name, u.user_email, u.user_password, u.role, - s.UUID_user, s.isAnonymous, s.language, s.lightmode, - m.UUID_message, m.message_title, m.message_content, m.message_date, m.sender_user_id, m.sender_charity_id, m.user_id, - cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB, - c.UUID_charities, c.org_number, c.pre_approved, c.status - FROM User u - LEFT JOIN Settings s ON u.UUID_User = s.UUID_user - LEFT JOIN Messages m ON u.UUID_User = m.user_id - LEFT JOIN Charities c ON m.sender_charity_id = c.UUID_charities - LEFT JOIN CharityVanity cv ON c.UUID_charities = cv.UUID_charity - WHERE u.UUID_User = ?; - """; - PreparedStatement stmt = conn.prepareStatement(sql_query); - stmt.setString(1, user_id); - ResultSet rs = stmt.executeQuery(); - - String lastUserid = null; - HashSet addedMessageIds = new HashSet<>(); - while (rs.next()) { - String userId = rs.getString("UUID_User"); - if (lastUserid == null || !userId.equals(lastUserid)) { - user = - new User( - userId, - rs.getString("user_name"), - rs.getString("user_email"), - rs.getString("user_password"), - rs.getString("role")); - if (rs.getString("isAnonymous") != null) { - Settings settings = - new Settings( - rs.getBoolean("isAnonymous"), - Language.valueOf(rs.getString("language").toUpperCase()), - rs.getBoolean("lightmode")); - user.setSettings(settings); - } - user.setInbox(new Inbox()); - lastUserid = userId; - } - String messageId = rs.getString("UUID_message"); - if (messageId != null && !addedMessageIds.contains(messageId)) { - addedMessageIds.add(messageId); - Charity fromCharity = null; - String charityId = rs.getString("UUID_charities"); - if (charityId != null) { - fromCharity = new Charity( - charityId, - rs.getString("org_number"), - rs.getString("charity_name"), - rs.getString("charity_link"), - rs.getString("status"), - rs.getBoolean("pre_approved"), - rs.getString("description"), - rs.getString("logoURL"), - rs.getString("key_values"), - rs.getBytes("logoBLOB") - ); - } - - if (fromCharity != null) { - Message message = new Message( - rs.getString("message_title"), - fromCharity, - rs.getString("message_content"), - LocalDate.parse(rs.getString("message_date")) - ); - user.getInbox().addMessage(message); - } - } - } - } catch (SQLException e) { - e.printStackTrace(); - throw new RuntimeException("ERROR: Something went wrong during updating charities table."); - } finally { - conn = null; - } - return user; - } - - /** - * 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. - * - * @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() { - UserRegistry registry = new UserRegistry(); - Connection conn = null; - try { - conn = connection.getMySqlConnection(); - String sql_query = - """ - SELECT - u.UUID_User, u.user_name, u.user_email, u.user_password, u.role, - s.UUID_user, s.isAnonymous, s.language, s.lightmode, - m.UUID_message, m.message_title, m.message_content, m.message_date, m.sender_user_id, m.sender_charity_id, m.user_id, - cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB, - c.UUID_charities, c.org_number, c.pre_approved, c.status - FROM User u - LEFT JOIN Settings s ON u.UUID_User = s.UUID_user - LEFT JOIN Messages m ON u.UUID_User = m.user_id - LEFT JOIN Charities c ON m.sender_charity_id = c.UUID_charities - LEFT JOIN CharityVanity cv ON c.UUID_charities = cv.UUID_charity - """; - Statement stmt = conn.createStatement(); - ResultSet rs = stmt.executeQuery(sql_query); - - User currentUser = null; - String lastUserid = null; - HashSet addedMessageIds = new HashSet<>(); - - while (rs.next()) { - String userId = rs.getString("UUID_User"); - - if (lastUserid == null || !userId.equals(lastUserid)) { - currentUser = - new User( - userId, - rs.getString("user_name"), - rs.getString("user_email"), - rs.getString("user_password"), - rs.getString("role")); - if (rs.getString("isAnonymous") != null) { - Settings settings = - new Settings( - rs.getBoolean("isAnonymous"), - Language.valueOf(rs.getString("language").toUpperCase()), - rs.getBoolean("lightmode")); - currentUser.setSettings(settings); - } - currentUser.setInbox(new Inbox()); - registry.addUser(currentUser); - lastUserid = userId; - } - String messageId = rs.getString("UUID_message"); - if (messageId != null && !addedMessageIds.contains(messageId)) { - addedMessageIds.add(messageId); - Charity fromCharity = null; - String charityId = rs.getString("UUID_charities"); - if (charityId != null) { - fromCharity = new Charity( - charityId, - rs.getString("org_number"), - rs.getString("charity_name"), - rs.getString("charity_link"), - rs.getString("status"), - rs.getBoolean("pre_approved"), - rs.getString("description"), - rs.getString("logoURL"), - rs.getString("key_values"), - rs.getBytes("logoBLOB") - ); - } - - if (fromCharity != null) { - Message message = new Message( - rs.getString("message_title"), - fromCharity, - rs.getString("message_content"), - LocalDate.parse(rs.getString("message_date")) - ); - currentUser.getInbox().addMessage(message); - } - } - } - } catch (SQLException e) { - e.printStackTrace(); - throw new RuntimeException("ERROR: Something went wrong during updating charities table."); - } finally { - conn = null; - } - return registry; - } - - /** - * 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. - * - * @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 - */ - public Settings getSettingsForUser(String user_id) { - Settings settings = null; - Connection conn = null; - try { - conn = connection.getMySqlConnection(); - String sql_query = - """ - SELECT UUID_user, isAnonymous, language, lightmode FROM Settings - WHERE UUID_user = ?; - """; - PreparedStatement stmt = conn.prepareStatement(sql_query); - stmt.setString(1, user_id); - stmt.setMaxRows(1); - ResultSet rs = stmt.executeQuery(); - - while (rs.next()) { - settings = - new Settings( - rs.getBoolean("isAnonymous"), - Language.valueOf(rs.getString("language").toUpperCase()), - rs.getBoolean("lightmode")); - } - } catch (SQLException e) { - e.printStackTrace(); - throw new RuntimeException("ERROR: Something went wrong during updating charities table."); - } finally { - conn = null; - } - - return settings; - } - - /** - * 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. - * - * @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) { - Inbox inbox = new Inbox(); - Connection conn = null; - try { - conn = connection.getMySqlConnection(); - String sql_query = - """ - SELECT - m.UUID_message, m.message_title, m.message_content, m.message_date, m.sender_user_id, m.sender_charity_id, m.user_id, - cv.charity_name, cv.charity_link, cv.description, cv.logoURL, cv.key_values, cv.logoBLOB, - c.UUID_charities, c.org_number, c.pre_approved, c.status - FROM Messages m - LEFT JOIN Charities c ON m.sender_charity_id = c.UUID_charities - LEFT JOIN CharityVanity cv ON c.UUID_charities = cv.UUID_charity - WHERE user_id = ?; - """; - PreparedStatement stmt = conn.prepareStatement(sql_query); - stmt.setString(1, user_id); - ResultSet rs = stmt.executeQuery(); - - while (rs.next()) { - Charity fromCharity = null; - String charityId = rs.getString("UUID_charities"); - if (charityId != null) { - fromCharity = new Charity( - charityId, - rs.getString("org_number"), - rs.getString("charity_name"), - rs.getString("charity_link"), - rs.getString("status"), - rs.getBoolean("pre_approved"), - rs.getString("description"), - rs.getString("logoURL"), - rs.getString("key_values"), - rs.getBytes("logoBLOB") - ); - } - - if (fromCharity != null) { - Message message = new Message( - rs.getString("message_title"), - fromCharity, - rs.getString("message_content"), - LocalDate.parse(rs.getString("message_date")) - ); - inbox.addMessage(message); - } - } - } catch (SQLException e) { - e.printStackTrace(); - throw new RuntimeException("ERROR: Something went wrong during updating charities table."); - } finally { - conn = null; - } - return inbox; - } - - -} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/APIToDatabaseService.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/APIToDatabaseService.java index 9e73c5c..e49b8ce 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/APIToDatabaseService.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/APIToDatabaseService.java @@ -176,7 +176,7 @@ AND NOT EXISTS ( SELECT 1 FROM CharityVanity cv WHERE cv.UUID_charity = c.UUID_charities ) AND NOT EXISTS ( - SELECT 1 FROM CharityUsers cu WHERE cu.Charities_UUID_charities = c.UUID_charities + SELECT 1 FROM CharityUsers cu WHERE cu.TheCharity = c.UUID_charities ); """; diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/AuthenticationService.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/AuthenticationService.java index 032d138..0d96d14 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/AuthenticationService.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/service/AuthenticationService.java @@ -1,7 +1,8 @@ package ntnu.systemutvikling.team6.service; +import ntnu.systemutvikling.team6.database.DAO.CharityUserDAO; import ntnu.systemutvikling.team6.database.DAO.UserDAO; -import ntnu.systemutvikling.team6.database.Readers.UserSelect; +import ntnu.systemutvikling.team6.database.DatabaseConnection; import ntnu.systemutvikling.team6.models.Charity; import ntnu.systemutvikling.team6.models.user.Inbox; import ntnu.systemutvikling.team6.models.user.Role; @@ -18,9 +19,8 @@ */ public class AuthenticationService { /** Handles read operations for user data from the database. */ - private final UserSelect userDataReader; + private final UserDAO userDataAcsessObject; /** Handles write operations for user data to the database. */ - private final UserDAO userDataSender; /** The currently authenticated user, or {@code null} if no user is logged in. */ private User currentUser; @@ -30,12 +30,10 @@ public class AuthenticationService { /** * Constructs an {@code AuthenticationService} with the specified data access objects. * - * @param userDataReader the data reader used to query user information from the database - * @param userDataSender the DAO used to persist new user registrations to the database + * @param userDAO the data reader used to query user information from the database */ - public AuthenticationService(UserSelect userDataReader, UserDAO userDataSender) { - this.userDataReader = userDataReader; - this.userDataSender = userDataSender; + public AuthenticationService(UserDAO userDAO) { + this.userDataAcsessObject = userDAO; } /** @@ -50,11 +48,12 @@ public AuthenticationService(UserSelect userDataReader, UserDAO userDataSender) * @return {@code true} if authentication was successful; {@code false} otherwise */ public boolean login(String email, String password){ - User user = userDataReader.getUserFromDBEmailAndPassword(email, password); + User user = userDataAcsessObject.getUserFromDBEmailAndPassword(email, password); if (user != null){ currentUser = user; - isCharityUser = userDataReader.getUserCharityUser(currentUser.getId().toString()); + CharityUserDAO charityUserDAO = new CharityUserDAO(new DatabaseConnection()); + isCharityUser = charityUserDAO.getUserCharityUser(currentUser.getId().toString()); if (isCharityUser != null){ currentUser.setRole(Role.CHARITY_USER); } @@ -83,11 +82,11 @@ public boolean login(String email, String password){ public boolean register(String username, String email, String password ){ User newUser = new User(username, email, password, Role.NORMAL_USER, new Settings(), new Inbox()); - if(userDataReader.isEmailTaken(email)){ + if(userDataAcsessObject.isEmailTaken(email)){ throw new IllegalArgumentException("Email already taken"); } - boolean success = userDataSender.registerUser(newUser); + boolean success = userDataAcsessObject.registerUser(newUser); if (success){ // currentUser = newUser; diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/CategoryDAOTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/CategoryDAOTest.java new file mode 100644 index 0000000..1b93019 --- /dev/null +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/CategoryDAOTest.java @@ -0,0 +1,68 @@ +package ntnu.systemutvikling.team6.database.DAO; + +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import org.junit.jupiter.api.*; +import java.sql.*; +import java.util.List; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Tests made by Claude AI: Sonnet 4.6, on 24.04.206 + */ +public class CategoryDAOTest { + + // --- Mocks --- + private DatabaseConnection mockDbConnection; + private Connection mockConn; + private Statement mockStmt; + private ResultSet mockRs; + + private CategoryDAO categoryDAO; + + @BeforeEach + void setUp() throws SQLException { + mockDbConnection = mock(DatabaseConnection.class); + mockConn = mock(Connection.class); + mockStmt = mock(Statement.class); + mockRs = mock(ResultSet.class); + + when(mockDbConnection.getMySqlConnection()).thenReturn(mockConn); + when(mockConn.createStatement()).thenReturn(mockStmt); + when(mockStmt.executeQuery(anyString())).thenReturn(mockRs); + + categoryDAO = new CategoryDAO(mockDbConnection); + } + + @Test + void getCategoriesFromDB_returnsAllCategories() throws SQLException { + when(mockRs.next()).thenReturn(true, true, true, false); + when(mockRs.getString("category")).thenReturn("Education", "Health", "Youth"); + + List result = categoryDAO.getCategoriesFromDB(); + + assertNotNull(result); + assertEquals(3, result.size()); + assertTrue(result.contains("Education")); + assertTrue(result.contains("Health")); + assertTrue(result.contains("Youth")); + } + + @Test + void getCategoriesFromDB_returnsSingleCategory() throws SQLException { + when(mockRs.next()).thenReturn(true, false); + when(mockRs.getString("category")).thenReturn("Environment"); + + List result = categoryDAO.getCategoriesFromDB(); + + assertEquals(1, result.size()); + assertEquals("Environment", result.getFirst()); + } + + @Test + void getCategoriesFromDB_throwsRuntimeExceptionOnSQLException() throws SQLException { + when(mockConn.createStatement()).thenThrow(new SQLException("DB down")); + + assertThrows(RuntimeException.class, () -> categoryDAO.getCategoriesFromDB()); + } +} \ No newline at end of file diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/CharityDAOTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/CharityDAOTest.java new file mode 100644 index 0000000..857a207 --- /dev/null +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/CharityDAOTest.java @@ -0,0 +1,332 @@ +package ntnu.systemutvikling.team6.database.DAO; + +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.models.Charity; +import ntnu.systemutvikling.team6.models.Feedback; +import ntnu.systemutvikling.team6.models.registry.CharityRegistry; +import ntnu.systemutvikling.team6.models.user.Language; +import ntnu.systemutvikling.team6.models.user.Role; +import org.junit.jupiter.api.*; +import java.sql.*; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.UUID; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Tests made by Claude AI: Sonnet 4.6, on 24.04.206 + */ +public class CharityDAOTest { + + // --- Mocks --- + private DatabaseConnection mockDbConnection; + private Connection mockConn; + private Statement mockRawStmt; // getCharitiesFromDB() uses createStatement() + private PreparedStatement mockStmt; // getFeedbackForCharityUUID() uses prepareStatement() + private ResultSet mockRs; + + private CharityDAO charityDAO; + + @BeforeEach + void setUp() throws SQLException { + mockDbConnection = mock(DatabaseConnection.class); + mockConn = mock(Connection.class); + mockRawStmt = mock(Statement.class); + mockStmt = mock(PreparedStatement.class); + mockRs = mock(ResultSet.class); + + when(mockDbConnection.getMySqlConnection()).thenReturn(mockConn); + when(mockConn.createStatement()).thenReturn(mockRawStmt); + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + + charityDAO = new CharityDAO(mockDbConnection); + } + + // ---------------------------------------------------------------- + // Helpers + // ---------------------------------------------------------------- + + /** Stubs the full set of charity columns for a single row. */ + private void stubCharityRow(String charityId) throws SQLException { + when(mockRs.getString("UUID_charities")).thenReturn(charityId); + when(mockRs.getString("org_number")).thenReturn("123456789"); + when(mockRs.getString("charity_name")).thenReturn("HelpOrg"); + when(mockRs.getString("charity_link")).thenReturn("helporg.com"); + when(mockRs.getString("status")).thenReturn("active"); + when(mockRs.getBoolean("pre_approved")).thenReturn(true); + when(mockRs.getString("description")).thenReturn("We help people"); + when(mockRs.getString("logoURL")).thenReturn(null); + when(mockRs.getString("key_values")).thenReturn(null); + when(mockRs.getBytes("logoBLOB")).thenReturn(null); + when(mockRs.getString("category")).thenReturn(null); + when(mockRs.getString("UUID_feedback")).thenReturn(null); // no feedback by default + } + + /** Stubs a feedback + user block on top of an already-stubbed charity row. */ + private void stubFeedbackRow(String feedbackId, String userId) throws SQLException { + when(mockRs.getString("UUID_feedback")).thenReturn(feedbackId); + when(mockRs.getString("feedback_comment")).thenReturn("Great charity!"); + when(mockRs.getString("feedback_date")).thenReturn(LocalDate.now().toString()); + when(mockRs.getString("UUID_User")).thenReturn(userId); + when(mockRs.getString("user_name")).thenReturn("Alice"); + when(mockRs.getString("user_email")).thenReturn("alice@example.com"); + when(mockRs.getString("user_password")).thenReturn("hashedpw"); + when(mockRs.getString("role")).thenReturn(Role.NORMAL_USER.toString()); + } + + // ---------------------------------------------------------------- + // getCharitiesFromDB() — uses createStatement() + // ---------------------------------------------------------------- + + @Test + void getCharitiesFromDB_returnsRegistryWithOneCharity() throws SQLException { + when(mockRawStmt.executeQuery(anyString())).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, false); + + String charityId = UUID.randomUUID().toString(); + stubCharityRow(charityId); + + CharityRegistry registry = charityDAO.getCharitiesFromDB(); + + assertNotNull(registry); + assertEquals(1, registry.getAllCharities().size()); + assertEquals(charityId, registry.getAllCharities().getFirst().getUUID().toString()); + assertEquals("HelpOrg", registry.getAllCharities().getFirst().getName()); + } + + @Test + void getCharitiesFromDB_returnsEmptyRegistryWhenNoRows() throws SQLException { + when(mockRawStmt.executeQuery(anyString())).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); + + CharityRegistry registry = charityDAO.getCharitiesFromDB(); + + assertNotNull(registry); + assertTrue(registry.getAllCharities().isEmpty()); + } + + @Test + void getCharitiesFromDB_doesNotDuplicateCharityAcrossMultipleRows() throws SQLException { + // Same charity appearing in two rows (e.g. two categories) → only one Charity object + when(mockRawStmt.executeQuery(anyString())).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, true, false); + + String charityId = UUID.randomUUID().toString(); + when(mockRs.getString("UUID_charities")).thenReturn(charityId); + when(mockRs.getString("org_number")).thenReturn("111"); + when(mockRs.getString("charity_name")).thenReturn("EduOrg"); + when(mockRs.getString("charity_link")).thenReturn("eduorg.com"); + when(mockRs.getString("status")).thenReturn("active"); + when(mockRs.getBoolean("pre_approved")).thenReturn(false); + when(mockRs.getString("description")).thenReturn("Education"); + when(mockRs.getString("logoURL")).thenReturn(null); + when(mockRs.getString("key_values")).thenReturn(null); + when(mockRs.getBytes("logoBLOB")).thenReturn(null); + when(mockRs.getString("category")).thenReturn("Education", "Youth"); + when(mockRs.getString("UUID_feedback")).thenReturn(null); + + CharityRegistry registry = charityDAO.getCharitiesFromDB(); + + assertEquals(1, registry.getAllCharities().size()); + assertEquals(2, registry.getAllCharities().getFirst().getCategory().size()); + assertTrue(registry.getAllCharities().getFirst().getCategory().contains("Education")); + assertTrue(registry.getAllCharities().getFirst().getCategory().contains("Youth")); + } + + @Test + void getCharitiesFromDB_returnsMultipleDistinctCharities() throws SQLException { + when(mockRawStmt.executeQuery(anyString())).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, true, false); + + String charityId1 = UUID.randomUUID().toString(); + String charityId2 = UUID.randomUUID().toString(); + + when(mockRs.getString("UUID_charities")).thenReturn(charityId1, charityId2); + when(mockRs.getString("org_number")).thenReturn("111", "222"); + when(mockRs.getString("charity_name")).thenReturn("OrgA", "OrgB"); + when(mockRs.getString("charity_link")).thenReturn("orga.com", "orgb.com"); + when(mockRs.getString("status")).thenReturn("active"); + when(mockRs.getBoolean("pre_approved")).thenReturn(true); + when(mockRs.getString("description")).thenReturn("Desc"); + when(mockRs.getString("logoURL")).thenReturn(null); + when(mockRs.getString("key_values")).thenReturn(null); + when(mockRs.getBytes("logoBLOB")).thenReturn(null); + when(mockRs.getString("category")).thenReturn(null); + when(mockRs.getString("UUID_feedback")).thenReturn(null); + + CharityRegistry registry = charityDAO.getCharitiesFromDB(); + + assertEquals(2, registry.getAllCharities().size()); + } + + @Test + void getCharitiesFromDB_appendsFeedbackToCharity() throws SQLException { + when(mockRawStmt.executeQuery(anyString())).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, false); + + String charityId = UUID.randomUUID().toString(); + String feedbackId = UUID.randomUUID().toString(); + String userId = UUID.randomUUID().toString(); + + stubCharityRow(charityId); + stubFeedbackRow(feedbackId, userId); + + CharityRegistry registry = charityDAO.getCharitiesFromDB(); + + Charity charity = registry.getAllCharities().getFirst(); + assertEquals(1, charity.getFeedbacks().size()); + assertEquals(feedbackId, charity.getFeedbacks().getFirst().getFeedbackId().toString()); + assertEquals("Great charity!", charity.getFeedbacks().getFirst().getComment()); + assertEquals(userId, charity.getFeedbacks().getFirst().getUser().getId().toString()); + } + + @Test + void getCharitiesFromDB_doesNotDuplicateFeedbackAcrossRows() throws SQLException { + // Same feedbackId appearing in two rows (e.g. two categories) → only one Feedback added + when(mockRawStmt.executeQuery(anyString())).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, true, false); + + String charityId = UUID.randomUUID().toString(); + String feedbackId = UUID.randomUUID().toString(); + String userId = UUID.randomUUID().toString(); + + when(mockRs.getString("UUID_charities")).thenReturn(charityId); + when(mockRs.getString("org_number")).thenReturn("111"); + when(mockRs.getString("charity_name")).thenReturn("Org"); + when(mockRs.getString("charity_link")).thenReturn("org.com"); + when(mockRs.getString("status")).thenReturn("active"); + when(mockRs.getBoolean("pre_approved")).thenReturn(false); + when(mockRs.getString("description")).thenReturn("desc"); + when(mockRs.getString("logoURL")).thenReturn(null); + when(mockRs.getString("key_values")).thenReturn(null); + when(mockRs.getBytes("logoBLOB")).thenReturn(null); + when(mockRs.getString("category")).thenReturn("Health", "Health"); + stubFeedbackRow(feedbackId, userId); // same feedbackId both rows + + CharityRegistry registry = charityDAO.getCharitiesFromDB(); + + assertEquals(1, registry.getAllCharities().getFirst().getFeedbacks().size()); + } + + @Test + void getCharitiesFromDB_clearsSeenFeedbackIdsWhenNewCharityStarts() throws SQLException { + // feedbackId seen on charity1 must NOT be deduplicated against charity2 + when(mockRawStmt.executeQuery(anyString())).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, true, false); + + String charityId1 = UUID.randomUUID().toString(); + String charityId2 = UUID.randomUUID().toString(); + String feedbackId = UUID.randomUUID().toString(); // same UUID reused on both charities + String userId = UUID.randomUUID().toString(); + + when(mockRs.getString("UUID_charities")).thenReturn(charityId1, charityId2); + when(mockRs.getString("org_number")).thenReturn("111", "222"); + when(mockRs.getString("charity_name")).thenReturn("OrgA", "OrgB"); + when(mockRs.getString("charity_link")).thenReturn("a.com", "b.com"); + when(mockRs.getString("status")).thenReturn("active"); + when(mockRs.getBoolean("pre_approved")).thenReturn(true); + when(mockRs.getString("description")).thenReturn("desc"); + when(mockRs.getString("logoURL")).thenReturn(null); + when(mockRs.getString("key_values")).thenReturn(null); + when(mockRs.getBytes("logoBLOB")).thenReturn(null); + when(mockRs.getString("category")).thenReturn(null); + when(mockRs.getString("UUID_feedback")).thenReturn(feedbackId); + when(mockRs.getString("feedback_comment")).thenReturn("Good"); + when(mockRs.getString("feedback_date")).thenReturn(LocalDate.now().toString()); + when(mockRs.getString("UUID_User")).thenReturn(userId); + when(mockRs.getString("user_name")).thenReturn("Bob"); + when(mockRs.getString("user_email")).thenReturn("bob@example.com"); + when(mockRs.getString("user_password")).thenReturn("pw"); + when(mockRs.getString("role")).thenReturn(Role.NORMAL_USER.toString()); + + CharityRegistry registry = charityDAO.getCharitiesFromDB(); + + // Each charity should have its own feedback entry + assertEquals(1, registry.getAllCharities().get(0).getFeedbacks().size()); + assertEquals(1, registry.getAllCharities().get(1).getFeedbacks().size()); + } + + @Test + void getCharitiesFromDB_throwsRuntimeExceptionOnSQLException() throws SQLException { + when(mockConn.createStatement()).thenThrow(new SQLException("DB down")); + + assertThrows(RuntimeException.class, () -> charityDAO.getCharitiesFromDB()); + } + + // ---------------------------------------------------------------- + // getFeedbackForCharityUUID() — uses prepareStatement() + // ---------------------------------------------------------------- + + @Test + void getFeedbackForCharityUUID_returnsFeedbackList() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, false); + + String feedbackId = UUID.randomUUID().toString(); + String userId = UUID.randomUUID().toString(); + + when(mockRs.getString("UUID_feedback")).thenReturn(feedbackId); + when(mockRs.getString("feedback_comment")).thenReturn("Very helpful"); + when(mockRs.getString("feedback_date")).thenReturn(LocalDate.now().toString()); + when(mockRs.getBoolean("isAnonymous")).thenReturn(false); + when(mockRs.getString("UUID_User")).thenReturn(userId); + when(mockRs.getString("user_name")).thenReturn("Carol"); + when(mockRs.getString("user_email")).thenReturn("carol@example.com"); + when(mockRs.getString("user_password")).thenReturn("pw"); + when(mockRs.getString("role")).thenReturn(Role.NORMAL_USER.toString()); + when(mockRs.getString("language")).thenReturn(Language.ENGLISH.toString()); + when(mockRs.getBoolean("lightmode")).thenReturn(false); + + ArrayList result = charityDAO.getFeedbackforCharityUUID(UUID.randomUUID().toString()); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(feedbackId, result.getFirst().getFeedbackId().toString()); + assertEquals("Very helpful", result.getFirst().getComment()); + assertEquals(userId, result.getFirst().getUser().getId().toString()); + } + + @Test + void getFeedbackForCharityUUID_returnsEmptyListWhenNoFeedback() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); + + ArrayList result = charityDAO.getFeedbackforCharityUUID(UUID.randomUUID().toString()); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + void getFeedbackForCharityUUID_returnsMultipleEntries() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, true, false); + + when(mockRs.getString("UUID_feedback")).thenReturn( + UUID.randomUUID().toString(), UUID.randomUUID().toString()); + when(mockRs.getString("feedback_comment")).thenReturn("Good", "Excellent"); + when(mockRs.getString("feedback_date")).thenReturn(LocalDate.now().toString()); + when(mockRs.getBoolean("isAnonymous")).thenReturn(false); + when(mockRs.getString("UUID_User")).thenReturn(UUID.randomUUID().toString()); + when(mockRs.getString("user_name")).thenReturn("User"); + when(mockRs.getString("user_email")).thenReturn("u@example.com"); + when(mockRs.getString("user_password")).thenReturn("pw"); + when(mockRs.getString("role")).thenReturn(Role.NORMAL_USER.toString()); + when(mockRs.getString("language")).thenReturn(Language.ENGLISH.toString()); + when(mockRs.getBoolean("lightmode")).thenReturn(false); + + ArrayList result = charityDAO.getFeedbackforCharityUUID(UUID.randomUUID().toString()); + + assertEquals(2, result.size()); + } + + + @Test + void getFeedbackForCharityUUID_throwsRuntimeExceptionOnSQLException() throws SQLException { + when(mockStmt.executeQuery()).thenThrow(new SQLException("Query failed")); + + assertThrows(RuntimeException.class, + () -> charityDAO.getFeedbackforCharityUUID(UUID.randomUUID().toString())); + } +} \ No newline at end of file diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/CharityUserDAOTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/CharityUserDAOTest.java new file mode 100644 index 0000000..3e26569 --- /dev/null +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/CharityUserDAOTest.java @@ -0,0 +1,272 @@ +package ntnu.systemutvikling.team6.database.DAO; + +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.models.Charity; +import org.junit.jupiter.api.*; +import java.sql.*; +import java.util.UUID; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Tests made by Claude AI: Sonnet 4.6, on 24.04.206 + */ +public class CharityUserDAOTest { + + // --- Mocks --- + private DatabaseConnection mockDbConnection; + private Connection mockConn; + private PreparedStatement mockStmt; + private ResultSet mockRs; + + private CharityUserDAO charityUserDAO; + + @BeforeEach + void setUp() throws SQLException { + mockDbConnection = mock(DatabaseConnection.class); + mockConn = mock(Connection.class); + mockStmt = mock(PreparedStatement.class); + mockRs = mock(ResultSet.class); + + when(mockDbConnection.getMySqlConnection()).thenReturn(mockConn); + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + + charityUserDAO = new CharityUserDAO(mockDbConnection); + } + + // ---------------------------------------------------------------- + // Helpers + // ---------------------------------------------------------------- + + private Charity buildTestCharity(String charityId) { + return new Charity( + charityId, + "123456789", + "HelpOrg", + "helporg.com", + "active", + true, + "We help people", + null, + null, + null + ); + } + + private void stubFullCharityRow(String charityId) throws SQLException { + when(mockRs.getString("UUID_charities")).thenReturn(charityId); + when(mockRs.getString("org_number")).thenReturn("123456789"); + when(mockRs.getString("charity_name")).thenReturn("HelpOrg"); + when(mockRs.getString("charity_link")).thenReturn("helporg.com"); + when(mockRs.getString("status")).thenReturn("active"); + when(mockRs.getBoolean("pre_approved")).thenReturn(true); + when(mockRs.getString("description")).thenReturn("We help people"); + when(mockRs.getString("logoURL")).thenReturn(null); + when(mockRs.getString("key_values")).thenReturn(null); + when(mockRs.getBytes("logoBLOB")).thenReturn(null); + when(mockRs.getString("category")).thenReturn(null); + } + + // ---------------------------------------------------------------- + // updateCharityVanityName() + // ---------------------------------------------------------------- + + @Test + void updateCharityVanityName_returnsTrueOnSuccess() throws SQLException { + when(mockStmt.executeUpdate()).thenReturn(1); + + assertTrue(charityUserDAO.updateCharityVanityName( + buildTestCharity(UUID.randomUUID().toString()) + )); + } + + @Test + void updateCharityVanityName_returnsFalseWhenNoRowsAffected() throws SQLException { + when(mockStmt.executeUpdate()).thenReturn(0); + + assertFalse(charityUserDAO.updateCharityVanityName( + buildTestCharity(UUID.randomUUID().toString()) + )); + } + + @Test + void updateCharityVanityName_returnsFalseOnSQLException() throws SQLException { + when(mockStmt.executeUpdate()).thenThrow(new SQLException("Update failed")); + + assertFalse(charityUserDAO.updateCharityVanityName( + buildTestCharity(UUID.randomUUID().toString()) + )); + } + + + // ---------------------------------------------------------------- + // updateCharityVanityDescription() + // ---------------------------------------------------------------- + + @Test + void updateCharityVanityDescription_returnsTrueOnSuccess() throws SQLException { + when(mockStmt.executeUpdate()).thenReturn(1); + + assertTrue(charityUserDAO.updateCharityVanityDescription( + buildTestCharity(UUID.randomUUID().toString()) + )); + } + + @Test + void updateCharityVanityDescription_returnsFalseWhenNoRowsAffected() throws SQLException { + when(mockStmt.executeUpdate()).thenReturn(0); + + assertFalse(charityUserDAO.updateCharityVanityDescription( + buildTestCharity(UUID.randomUUID().toString()) + )); + } + + @Test + void updateCharityVanityDescription_returnsFalseOnSQLException() throws SQLException { + when(mockStmt.executeUpdate()).thenThrow(new SQLException("Update failed")); + + assertFalse(charityUserDAO.updateCharityVanityDescription( + buildTestCharity(UUID.randomUUID().toString()) + )); + } + + @Test + void updateCharityVanityDescription_setsCorrectParameters() throws SQLException { + when(mockStmt.executeUpdate()).thenReturn(1); + + String charityId = UUID.randomUUID().toString(); + Charity charity = buildTestCharity(charityId); + + charityUserDAO.updateCharityVanityDescription(charity); + + verify(mockStmt).setString(1, charity.getDescription()); // description + verify(mockStmt).setString(2, charityId); // UUID_charity + } + + // ---------------------------------------------------------------- + // getUserCharityUser() + // ---------------------------------------------------------------- + + @Test + void getUserCharityUser_throwsOnNullUuid() { + assertThrows(IllegalArgumentException.class, + () -> charityUserDAO.getUserCharityUser(null)); + } + + @Test + void getUserCharityUser_throwsOnBlankUuid() { + assertThrows(IllegalArgumentException.class, + () -> charityUserDAO.getUserCharityUser(" ")); + } + + @Test + void getUserCharityUser_returnsCharityWhenFound() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, false); + + String charityId = UUID.randomUUID().toString(); + stubFullCharityRow(charityId); + + Charity result = charityUserDAO.getUserCharityUser(UUID.randomUUID().toString()); + + assertNotNull(result); + assertEquals(charityId, result.getUUID().toString()); + assertEquals("HelpOrg", result.getName()); + assertEquals("We help people", result.getDescription()); + } + + @Test + void getUserCharityUser_returnsNullWhenNotFound() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); + + Charity result = charityUserDAO.getUserCharityUser(UUID.randomUUID().toString()); + + assertNull(result); + } + + @Test + void getUserCharityUser_accumulatesCategoriesAcrossRows() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, true, true, false); + + String charityId = UUID.randomUUID().toString(); + // Same charity across all rows, different category each time + when(mockRs.getString("UUID_charities")).thenReturn(charityId); + when(mockRs.getString("org_number")).thenReturn("123456789"); + when(mockRs.getString("charity_name")).thenReturn("HelpOrg"); + when(mockRs.getString("charity_link")).thenReturn("helporg.com"); + when(mockRs.getString("status")).thenReturn("active"); + when(mockRs.getBoolean("pre_approved")).thenReturn(true); + when(mockRs.getString("description")).thenReturn("We help"); + when(mockRs.getString("logoURL")).thenReturn(null); + when(mockRs.getString("key_values")).thenReturn(null); + when(mockRs.getBytes("logoBLOB")).thenReturn(null); + when(mockRs.getString("category")).thenReturn("Education", "Youth", "Health"); + + Charity result = charityUserDAO.getUserCharityUser(UUID.randomUUID().toString()); + + assertNotNull(result); + assertEquals(3, result.getCategory().size()); + assertTrue(result.getCategory().contains("Education")); + assertTrue(result.getCategory().contains("Youth")); + assertTrue(result.getCategory().contains("Health")); + } + + @Test + void getUserCharityUser_doesNotAddDuplicateCategories() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, true, false); + + String charityId = UUID.randomUUID().toString(); + when(mockRs.getString("UUID_charities")).thenReturn(charityId); + when(mockRs.getString("org_number")).thenReturn("111"); + when(mockRs.getString("charity_name")).thenReturn("Org"); + when(mockRs.getString("charity_link")).thenReturn("org.com"); + when(mockRs.getString("status")).thenReturn("active"); + when(mockRs.getBoolean("pre_approved")).thenReturn(false); + when(mockRs.getString("description")).thenReturn("desc"); + when(mockRs.getString("logoURL")).thenReturn(null); + when(mockRs.getString("key_values")).thenReturn(null); + when(mockRs.getBytes("logoBLOB")).thenReturn(null); + // Same category name twice — should only be added once + when(mockRs.getString("category")).thenReturn("Health", "Health"); + + Charity result = charityUserDAO.getUserCharityUser(UUID.randomUUID().toString()); + + assertEquals(1, result.getCategory().size()); + } + + @Test + void getUserCharityUser_ignoresNullCategory() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, false); + + String charityId = UUID.randomUUID().toString(); + stubFullCharityRow(charityId); // category stubbed as null + + Charity result = charityUserDAO.getUserCharityUser(UUID.randomUUID().toString()); + + assertNotNull(result); + assertTrue(result.getCategory().isEmpty()); + } + + @Test + void getUserCharityUser_passesCorrectUuidToQuery() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); + + String userId = UUID.randomUUID().toString(); + charityUserDAO.getUserCharityUser(userId); + + verify(mockStmt).setString(1, userId); + } + + @Test + void getUserCharityUser_throwsRuntimeExceptionOnSQLException() throws SQLException { + when(mockConn.prepareStatement(anyString())).thenThrow(new SQLException("DB down")); + + assertThrows(RuntimeException.class, + () -> charityUserDAO.getUserCharityUser(UUID.randomUUID().toString())); + } +} \ No newline at end of file diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/DonationDAOTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/DonationDAOTest.java index 9800db4..52f1177 100644 --- a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/DonationDAOTest.java +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/DonationDAOTest.java @@ -1,69 +1,335 @@ package ntnu.systemutvikling.team6.database.DAO; -import static org.junit.jupiter.api.Assertions.*; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.util.UUID; import ntnu.systemutvikling.team6.database.DatabaseConnection; -import ntnu.systemutvikling.team6.database.DatabaseSetup; import ntnu.systemutvikling.team6.models.Charity; -import ntnu.systemutvikling.team6.models.user.User; -import ntnu.systemutvikling.team6.service.APIToDatabaseService; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import ntnu.systemutvikling.team6.models.Donation; +import ntnu.systemutvikling.team6.models.registry.DonationRegistry; +import ntnu.systemutvikling.team6.models.user.*; +import org.junit.jupiter.api.*; +import java.sql.*; +import java.time.LocalDate; +import java.util.UUID; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Tests made by Claude AI: Sonnet 4.6, on 24.04.206 + */ +public class DonationDAOTest { -class DonationDAOTest { + // --- Mocks --- + private DatabaseConnection mockDbConnection; + private Connection mockConn; + private PreparedStatement mockStmt; + private Statement mockRawStmt; // getDonationFromDB() uses createStatement(), not prepareStatement() + private ResultSet mockRs; - private Charity charity; + private DonationDAO donationDAO; @BeforeEach - void setUp() { - DatabaseConnection conn = new DatabaseConnection(); - DatabaseSetup manager = new DatabaseSetup(conn); - manager.createTables(); - - charity = - new Charity( - "123456", - "https://www.innsamlingskontrollen.no/organisasjoner/adra-norge-adventist-development-and-relief-agency-norway/", - "Test Charity", + void setUp() throws SQLException { + mockDbConnection = mock(DatabaseConnection.class); + mockConn = mock(Connection.class); + mockStmt = mock(PreparedStatement.class); + mockRawStmt = mock(Statement.class); + mockRs = mock(ResultSet.class); + + when(mockDbConnection.getMySqlConnection()).thenReturn(mockConn); + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + when(mockConn.createStatement()).thenReturn(mockRawStmt); + + donationDAO = new DonationDAO(mockDbConnection); + } + + // ---------------------------------------------------------------- + // Helpers + // ---------------------------------------------------------------- + + private User buildTestUser(String userId) { + User user = new User( + userId, + "TestUser", + "test@example.com", + "hashedpassword", + Role.NORMAL_USER.toString() + ); + user.setSettings(new Settings(false, Language.ENGLISH, true)); + user.setInbox(new Inbox()); + return user; + } + + private Charity buildTestCharity(String charityId) { + return new Charity( + charityId, + "123456789", + "HelpOrg", + "helporg.com", + "active", true, - "approved"); + "We help people", + null, + null, + null + ); + } - APIToDatabaseService service = new APIToDatabaseService(conn); - service.addAPIDataToTable(java.util.List.of(charity)); + private Donation buildTestDonation(String donationId, User user, Charity charity) { + return new Donation( + donationId, + 100.0, + LocalDate.now(), + charity, + user, + false + ); } + /** Stubs all ResultSet columns shared by getDonationFromDB, getDonationForUser, getDonationForCharity. */ + private void stubFullDonationRow(String donationId, String charityId, String userId) throws SQLException { + when(mockRs.getString("UUID_Donations")).thenReturn(donationId); + when(mockRs.getDouble("amount")).thenReturn(250.0); + when(mockRs.getBoolean("isAnonymous")).thenReturn(false); + when(mockRs.getDate("date")).thenReturn(Date.valueOf(LocalDate.now())); + + when(mockRs.getString("UUID_charities")).thenReturn(charityId); + when(mockRs.getString("org_number")).thenReturn("9999"); + when(mockRs.getString("charity_name")).thenReturn("HelpOrg"); + when(mockRs.getString("charity_link")).thenReturn("helporg.com"); + when(mockRs.getString("status")).thenReturn("active"); + when(mockRs.getBoolean("pre_approved")).thenReturn(true); + when(mockRs.getString("description")).thenReturn("We help"); + when(mockRs.getString("logoURL")).thenReturn(null); + when(mockRs.getString("key_values")).thenReturn(null); + when(mockRs.getBytes("logoBLOB")).thenReturn(null); + + when(mockRs.getString("UUID_User")).thenReturn(userId); + when(mockRs.getString("user_name")).thenReturn("Alice"); + when(mockRs.getString("user_email")).thenReturn("alice@example.com"); + when(mockRs.getString("user_password")).thenReturn("hashedpw"); + when(mockRs.getString("role")).thenReturn(Role.NORMAL_USER.toString()); + } + + // ---------------------------------------------------------------- + // getDonationFromDB() — uses createStatement(), not prepareStatement() + // ---------------------------------------------------------------- + @Test - void addDonationShouldInsertDonationIntoDatabase() throws Exception { - double amount = 100.0; + void getDonationFromDB_returnsRegistryWithDonations() throws SQLException { + when(mockRawStmt.executeQuery(anyString())).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, false); - DonationDAO donationDAO = new DonationDAO(new DatabaseConnection()); + String donationId = UUID.randomUUID().toString(); + String charityId = UUID.randomUUID().toString(); + String userId = UUID.randomUUID().toString(); + stubFullDonationRow(donationId, charityId, userId); - donationDAO.addDonation( - charity, - new User( - UUID.randomUUID().toString(), - "ad", - "aduser", - "dwad@ca.com", - "secret", - "NORMAL_USER"), - amount); + DonationRegistry registry = donationDAO.getDonationFromDB(); + + assertNotNull(registry); + assertEquals(1, registry.getAllDonations().size()); + assertEquals(donationId, registry.getAllDonations().getFirst().getDonationID().toString()); + assertEquals(250.0, registry.getAllDonations().getFirst().getAmount()); + assertEquals(charityId, registry.getAllDonations().getFirst().getCharity().getUUID().toString()); + assertEquals(userId, registry.getAllDonations().getFirst().getDonor().getId().toString()); + } - try (Connection conn = new DatabaseConnection().getMySqlConnection()) { - PreparedStatement stmt = - conn.prepareStatement("SELECT amount FROM Donations WHERE charity_id = ?"); + @Test + void getDonationFromDB_returnsEmptyRegistryWhenNoDonations() throws SQLException { + when(mockRawStmt.executeQuery(anyString())).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); + + DonationRegistry registry = donationDAO.getDonationFromDB(); + + assertNotNull(registry); + assertTrue(registry.getAllDonations().isEmpty()); + } + + @Test + void getDonationFromDB_returnsMultipleDonations() throws SQLException { + when(mockRawStmt.executeQuery(anyString())).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, true, false); - stmt.setString(1, charity.getUUID().toString()); + when(mockRs.getString("UUID_Donations")).thenReturn( + UUID.randomUUID().toString(), UUID.randomUUID().toString()); + when(mockRs.getDouble("amount")).thenReturn(100.0, 200.0); + when(mockRs.getBoolean("isAnonymous")).thenReturn(false); + when(mockRs.getDate("date")).thenReturn(Date.valueOf(LocalDate.now())); + when(mockRs.getString("UUID_charities")).thenReturn(UUID.randomUUID().toString()); + when(mockRs.getString("org_number")).thenReturn("1111"); + when(mockRs.getBoolean("pre_approved")).thenReturn(true); + when(mockRs.getString("status")).thenReturn("active"); + when(mockRs.getString("UUID_User")).thenReturn(UUID.randomUUID().toString()); + when(mockRs.getString("user_name")).thenReturn("Bob"); + when(mockRs.getString("user_email")).thenReturn("bob@example.com"); + when(mockRs.getString("user_password")).thenReturn("pw"); + when(mockRs.getString("role")).thenReturn(Role.NORMAL_USER.toString()); - ResultSet rs = stmt.executeQuery(); + DonationRegistry registry = donationDAO.getDonationFromDB(); - assertTrue(rs.next(), "Donation should exist in database"); + assertEquals(2, registry.getAllDonations().size()); + } + + @Test + void getDonationFromDB_throwsRuntimeExceptionOnSQLException() throws SQLException { + when(mockConn.createStatement()).thenThrow(new SQLException("DB down")); - assertEquals(amount, rs.getDouble("amount")); - } + assertThrows(RuntimeException.class, () -> donationDAO.getDonationFromDB()); } -} + + // ---------------------------------------------------------------- + // getDonationForUser() + // ---------------------------------------------------------------- + + @Test + void getDonationForUser_returnsRegistryWithDonations() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, false); + + String donationId = UUID.randomUUID().toString(); + String charityId = UUID.randomUUID().toString(); + String userId = UUID.randomUUID().toString(); + stubFullDonationRow(donationId, charityId, userId); + + DonationRegistry registry = donationDAO.getDonationForUser(userId); + + assertNotNull(registry); + assertEquals(1, registry.getAllDonations().size()); + assertEquals(donationId, registry.getAllDonations().getFirst().getDonationID().toString()); + assertEquals(userId, registry.getAllDonations().getFirst().getDonor().getId().toString()); + } + + @Test + void getDonationForUser_returnsEmptyRegistryWhenNoDonations() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); + + DonationRegistry registry = donationDAO.getDonationForUser(UUID.randomUUID().toString()); + + assertNotNull(registry); + assertTrue(registry.getAllDonations().isEmpty()); + } + + @Test + void getDonationForUser_passesCorrectUserIdToQuery() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); + + String userId = UUID.randomUUID().toString(); + donationDAO.getDonationForUser(userId); + + verify(mockStmt).setString(1, userId); + } + + @Test + void getDonationForUser_throwsRuntimeExceptionOnSQLException() throws SQLException { + when(mockConn.prepareStatement(anyString())).thenThrow(new SQLException("Query failed")); + + assertThrows(RuntimeException.class, + () -> donationDAO.getDonationForUser(UUID.randomUUID().toString())); + } + + // ---------------------------------------------------------------- + // getDonationForCharity() + // ---------------------------------------------------------------- + + @Test + void getDonationForCharity_returnsRegistryWithDonations() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, false); + + String donationId = UUID.randomUUID().toString(); + String charityId = UUID.randomUUID().toString(); + String userId = UUID.randomUUID().toString(); + stubFullDonationRow(donationId, charityId, userId); + + DonationRegistry registry = donationDAO.getDonationForCharity(charityId); + + assertNotNull(registry); + assertEquals(1, registry.getAllDonations().size()); + assertEquals(donationId, registry.getAllDonations().getFirst().getDonationID().toString()); + assertEquals(charityId, registry.getAllDonations().getFirst().getCharity().getUUID().toString()); + } + + @Test + void getDonationForCharity_returnsEmptyRegistryWhenNoDonations() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); + + DonationRegistry registry = donationDAO.getDonationForCharity(UUID.randomUUID().toString()); + + assertNotNull(registry); + assertTrue(registry.getAllDonations().isEmpty()); + } + + @Test + void getDonationForCharity_passesCorrectCharityIdToQuery() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); + + String charityId = UUID.randomUUID().toString(); + donationDAO.getDonationForCharity(charityId); + + verify(mockStmt).setString(1, charityId); + } + + @Test + void getDonationForCharity_throwsRuntimeExceptionOnSQLException() throws SQLException { + when(mockConn.prepareStatement(anyString())).thenThrow(new SQLException("Query failed")); + + assertThrows(RuntimeException.class, + () -> donationDAO.getDonationForCharity(UUID.randomUUID().toString())); + } + + // ---------------------------------------------------------------- + // addDonation() + // ---------------------------------------------------------------- + + @Test + void addDonation_executesSuccessfully() throws SQLException { + String donationId = UUID.randomUUID().toString(); + String charityId = UUID.randomUUID().toString(); + String userId = UUID.randomUUID().toString(); + User user = buildTestUser(userId); + Donation donation = buildTestDonation(donationId, user, buildTestCharity(charityId)); + + donationDAO.addDonation(donation); + + verify(mockStmt).executeUpdate(); + verify(mockConn).setAutoCommit(false); + verify(mockConn).commit(); + } + + @Test + void addDonation_setsCorrectParameterOrder() throws SQLException { + String donationId = UUID.randomUUID().toString(); + String charityId = UUID.randomUUID().toString(); + String userId = UUID.randomUUID().toString(); + User user = buildTestUser(userId); + Donation donation = buildTestDonation(donationId, user, buildTestCharity(charityId)); + + donationDAO.addDonation(donation); + + verify(mockStmt).setString(1, donationId); // UUID_Donations + verify(mockStmt).setDouble(2, donation.getAmount()); // amount + verify(mockStmt).setBoolean(3, user.getSettings().isAnonymous()); // isAnonymous + verify(mockStmt).setDate(4, Date.valueOf(donation.getDate())); // date + verify(mockStmt).setString(5, charityId); // charity_id + verify(mockStmt).setString(6, userId); // user_id + } + + @Test + void addDonation_setsAnonymousTrueWhenUserIsAnonymous() throws SQLException { + String userId = UUID.randomUUID().toString(); + User anonymousUser = buildTestUser(userId); + anonymousUser.setSettings(new Settings(true, Language.ENGLISH, false)); + + donationDAO.addDonation(buildTestDonation( + UUID.randomUUID().toString(), + anonymousUser, + buildTestCharity(UUID.randomUUID().toString()) + )); + + verify(mockStmt).setBoolean(3, true); + } + +} \ No newline at end of file diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/FavouritesDAOTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/FavouritesDAOTest.java new file mode 100644 index 0000000..b11e934 --- /dev/null +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/FavouritesDAOTest.java @@ -0,0 +1,323 @@ +package ntnu.systemutvikling.team6.database.DAO; + +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.models.Charity; +import ntnu.systemutvikling.team6.models.user.*; +import org.junit.jupiter.api.*; +import java.sql.*; +import java.util.List; +import java.util.UUID; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Tests made by Claude AI: Sonnet 4.6, on 24.04.206 + */ +public class FavouritesDAOTest { + + // --- Mocks --- + private DatabaseConnection mockDbConnection; + private Connection mockConn; + private PreparedStatement mockStmt; + private ResultSet mockRs; + + private FavouritesDAO favouritesDAO; + + @BeforeEach + void setUp() throws SQLException { + mockDbConnection = mock(DatabaseConnection.class); + mockConn = mock(Connection.class); + mockStmt = mock(PreparedStatement.class); + mockRs = mock(ResultSet.class); + + when(mockDbConnection.getMySqlConnection()).thenReturn(mockConn); + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + + favouritesDAO = new FavouritesDAO(mockDbConnection); + } + + // ---------------------------------------------------------------- + // Helpers + // ---------------------------------------------------------------- + + private User buildTestUser(String userId) { + User user = new User( + userId, + "TestUser", + "test@example.com", + "hashedpassword", + Role.NORMAL_USER.toString() + ); + user.setSettings(new Settings(false, Language.ENGLISH, true)); + user.setInbox(new Inbox()); + return user; + } + + private Charity buildTestCharity(String charityId) { + return new Charity( + charityId, + "123456789", + "HelpOrg", + "helporg.com", + "active", + true, + "We help people", + null, + null, + null + ); + } + + // ---------------------------------------------------------------- + // isFavourite() + // ---------------------------------------------------------------- + + @Test + void isFavourite_returnsTrueWhenRowExists() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true); + + String userId = UUID.randomUUID().toString(); + String charityId = UUID.randomUUID().toString(); + + assertTrue(favouritesDAO.isFavourite(buildTestUser(userId), buildTestCharity(charityId))); + } + + @Test + void isFavourite_returnsFalseWhenNoRowExists() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); + + assertFalse(favouritesDAO.isFavourite( + buildTestUser(UUID.randomUUID().toString()), + buildTestCharity(UUID.randomUUID().toString()) + )); + } + + @Test + void isFavourite_returnsFalseOnSQLException() throws SQLException { + when(mockStmt.executeQuery()).thenThrow(new SQLException("Query failed")); + + assertFalse(favouritesDAO.isFavourite( + buildTestUser(UUID.randomUUID().toString()), + buildTestCharity(UUID.randomUUID().toString()) + )); + } + + @Test + void isFavourite_setsCorrectParameters() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); + + String userId = UUID.randomUUID().toString(); + String charityId = UUID.randomUUID().toString(); + + favouritesDAO.isFavourite(buildTestUser(userId), buildTestCharity(charityId)); + + verify(mockStmt).setString(1, userId); // Favourer + verify(mockStmt).setString(2, charityId); // Favourite_Charity + } + + // ---------------------------------------------------------------- + // addFavourite() + // ---------------------------------------------------------------- + + @Test + void addFavourite_returnsTrueOnSuccess() throws SQLException { + when(mockStmt.executeUpdate()).thenReturn(1); + + assertTrue(favouritesDAO.addFavourite( + buildTestUser(UUID.randomUUID().toString()), + buildTestCharity(UUID.randomUUID().toString()) + )); + } + + @Test + void addFavourite_returnsFalseWhenNoRowsAffected() throws SQLException { + when(mockStmt.executeUpdate()).thenReturn(0); + + assertFalse(favouritesDAO.addFavourite( + buildTestUser(UUID.randomUUID().toString()), + buildTestCharity(UUID.randomUUID().toString()) + )); + } + + @Test + void addFavourite_returnsFalseOnSQLException() throws SQLException { + when(mockStmt.executeUpdate()).thenThrow(new SQLException("Insert failed")); + + assertFalse(favouritesDAO.addFavourite( + buildTestUser(UUID.randomUUID().toString()), + buildTestCharity(UUID.randomUUID().toString()) + )); + } + + @Test + void addFavourite_setsCorrectParameters() throws SQLException { + when(mockStmt.executeUpdate()).thenReturn(1); + + String userId = UUID.randomUUID().toString(); + String charityId = UUID.randomUUID().toString(); + + favouritesDAO.addFavourite(buildTestUser(userId), buildTestCharity(charityId)); + + verify(mockStmt).setString(1, userId); // Favourer + verify(mockStmt).setString(2, charityId); // Favourite_charity + } + + // ---------------------------------------------------------------- + // removeFavourite() + // ---------------------------------------------------------------- + + @Test + void removeFavourite_returnsTrueOnSuccess() throws SQLException { + when(mockStmt.executeUpdate()).thenReturn(1); + + assertTrue(favouritesDAO.removeFavourite( + buildTestUser(UUID.randomUUID().toString()), + buildTestCharity(UUID.randomUUID().toString()) + )); + } + + @Test + void removeFavourite_returnsFalseWhenNoRowsAffected() throws SQLException { + when(mockStmt.executeUpdate()).thenReturn(0); + + assertFalse(favouritesDAO.removeFavourite( + buildTestUser(UUID.randomUUID().toString()), + buildTestCharity(UUID.randomUUID().toString()) + )); + } + + @Test + void removeFavourite_returnsFalseOnSQLException() throws SQLException { + when(mockStmt.executeUpdate()).thenThrow(new SQLException("Delete failed")); + + assertFalse(favouritesDAO.removeFavourite( + buildTestUser(UUID.randomUUID().toString()), + buildTestCharity(UUID.randomUUID().toString()) + )); + } + + @Test + void removeFavourite_setsCorrectParameters() throws SQLException { + when(mockStmt.executeUpdate()).thenReturn(1); + + String userId = UUID.randomUUID().toString(); + String charityId = UUID.randomUUID().toString(); + + favouritesDAO.removeFavourite(buildTestUser(userId), buildTestCharity(charityId)); + + verify(mockStmt).setString(1, userId); // Favourer + verify(mockStmt).setString(2, charityId); // Favourite_charity + } + + // ---------------------------------------------------------------- + // getFavouritesForUser() + // ---------------------------------------------------------------- + + @Test + void getFavouritesForUser_returnsCharityList() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, false); + + String charityId = UUID.randomUUID().toString(); + when(mockRs.getString("UUID_charities")).thenReturn(charityId); + when(mockRs.getString("org_number")).thenReturn("9999"); + when(mockRs.getString("charity_name")).thenReturn("SaveAll"); + when(mockRs.getString("charity_link")).thenReturn("saveall.org"); + when(mockRs.getString("status")).thenReturn("active"); + when(mockRs.getBoolean("pre_approved")).thenReturn(true); + when(mockRs.getString("description")).thenReturn("Saving people"); + when(mockRs.getString("logoURL")).thenReturn(null); + when(mockRs.getString("key_values")).thenReturn(null); + when(mockRs.getBytes("logoBLOB")).thenReturn(null); + when(mockRs.getString("category")).thenReturn(null); // no category + + List result = favouritesDAO.getFavouritesForUser(UUID.randomUUID().toString()); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(charityId, result.getFirst().getUUID().toString()); + assertEquals("SaveAll", result.getFirst().getName()); + } + + @Test + void getFavouritesForUser_returnsEmptyListWhenNoFavourites() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); + + List result = favouritesDAO.getFavouritesForUser(UUID.randomUUID().toString()); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + void getFavouritesForUser_doesNotDuplicateCharityAcrossMultipleCategoryRows() throws SQLException { + // Same charity appearing in two rows (one per category) should produce only one Charity object + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, true, false); + + String charityId = UUID.randomUUID().toString(); + when(mockRs.getString("UUID_charities")).thenReturn(charityId); + when(mockRs.getString("org_number")).thenReturn("1111"); + when(mockRs.getString("charity_name")).thenReturn("EduOrg"); + when(mockRs.getString("charity_link")).thenReturn("eduorg.com"); + when(mockRs.getString("status")).thenReturn("active"); + when(mockRs.getBoolean("pre_approved")).thenReturn(false); + when(mockRs.getString("description")).thenReturn("Education"); + when(mockRs.getString("logoURL")).thenReturn(null); + when(mockRs.getString("key_values")).thenReturn(null); + when(mockRs.getBytes("logoBLOB")).thenReturn(null); + when(mockRs.getString("category")).thenReturn("Education", "Youth"); + + List result = favouritesDAO.getFavouritesForUser(UUID.randomUUID().toString()); + + assertEquals(1, result.size()); + assertEquals(2, result.getFirst().getCategory().size()); + assertTrue(result.getFirst().getCategory().contains("Education")); + assertTrue(result.getFirst().getCategory().contains("Youth")); + } + + @Test + void getFavouritesForUser_returnsDistinctCharities() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + String userId1 = UUID.randomUUID().toString(); + + when(mockRs.next()).thenReturn(true, true, false); + + String charityId1 = UUID.randomUUID().toString(); + String charityId2 = UUID.randomUUID().toString(); + + when(mockRs.getString("UUID_charities")).thenReturn(charityId1, charityId2); + when(mockRs.getString("org_number")).thenReturn("1111", "2222"); + when(mockRs.getString("charity_name")).thenReturn("OrgA", "OrgB"); + when(mockRs.getString("charity_link")).thenReturn("orga.com", "orgb.com"); + when(mockRs.getString("status")).thenReturn("active"); + when(mockRs.getBoolean("pre_approved")).thenReturn(true); + when(mockRs.getString("description")).thenReturn("Desc"); + when(mockRs.getString("logoURL")).thenReturn(null); + when(mockRs.getString("key_values")).thenReturn(null); + when(mockRs.getBytes("logoBLOB")).thenReturn(null); + when(mockRs.getString("category")).thenReturn(null); + + List result = favouritesDAO.getFavouritesForUser(userId1); + + assertEquals(1, result.size()); + verify(mockStmt).setString(1, userId1); + + } + + + @Test + void getFavouritesForUser_passesCorrectUserIdToQuery() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); + + String userId = UUID.randomUUID().toString(); + favouritesDAO.getFavouritesForUser(userId); + + verify(mockStmt).setString(1, userId); + } +} \ No newline at end of file diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/FeedbackDAOTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/FeedbackDAOTest.java new file mode 100644 index 0000000..8a4b17f --- /dev/null +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/FeedbackDAOTest.java @@ -0,0 +1,242 @@ +package ntnu.systemutvikling.team6.database.DAO; + +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.models.Charity; +import ntnu.systemutvikling.team6.models.Feedback; +import ntnu.systemutvikling.team6.models.user.*; +import org.junit.jupiter.api.*; +import java.sql.*; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.UUID; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Tests made by Claude AI: Sonnet 4.6, on 24.04.206 + */ +public class FeedbackDAOTest { + + private DatabaseConnection mockDbConnection; + private Connection mockConn; + private PreparedStatement mockStmt; + private ResultSet mockRs; + + private FeedbackDAO feedbackDAO; + + @BeforeEach + void setUp() throws SQLException { + mockDbConnection = mock(DatabaseConnection.class); + mockConn = mock(Connection.class); + mockStmt = mock(PreparedStatement.class); + mockRs = mock(ResultSet.class); + + when(mockDbConnection.getMySqlConnection()).thenReturn(mockConn); + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + + feedbackDAO = new FeedbackDAO(mockDbConnection); + } + + // ---------------------------------------------------------------- + // Helpers + // ---------------------------------------------------------------- + + private User buildTestUser(String userId) { + Settings settings = new Settings(false, Language.ENGLISH, true); + User user = new User( + userId, + "TestUser", + "test@example.com", + "hashedpassword", + Role.NORMAL_USER.toString() + ); + user.setSettings(settings); + user.setInbox(new Inbox()); + return user; + } + + private Charity buildTestCharity(String charityId) { + return new Charity( + charityId, + "123456789", + "HelpOrg", + "helporg.com", + "active", + true, + "We help people", + null, + null, + null + ); + } + + private Feedback buildTestFeedback(User user) { + return new Feedback( + UUID.randomUUID().toString(), + user, + "Great charity!", + LocalDate.now() + ); + } + + // ---------------------------------------------------------------- + // addFeedbackToCharity() + // ---------------------------------------------------------------- + + @Test + void addFeedbackToCharity_returnsTrueOnSuccess() throws SQLException { + when(mockStmt.executeUpdate()).thenReturn(1); + + String userId = UUID.randomUUID().toString(); + String charityId = UUID.randomUUID().toString(); + + boolean result = feedbackDAO.addFeedbackToCharity( + buildTestFeedback(buildTestUser(userId)), + buildTestCharity(charityId) + ); + + assertTrue(result); + } + + @Test + void addFeedbackToCharity_returnsFalseWhenNoRowsAffected() throws SQLException { + when(mockStmt.executeUpdate()).thenReturn(0); + + boolean result = feedbackDAO.addFeedbackToCharity( + buildTestFeedback(buildTestUser(UUID.randomUUID().toString())), + buildTestCharity(UUID.randomUUID().toString()) + ); + + assertFalse(result); + } + + @Test + void addFeedbackToCharity_returnsFalseOnSQLException() throws SQLException { + when(mockStmt.executeUpdate()).thenThrow(new SQLException("Insert failed")); + + boolean result = feedbackDAO.addFeedbackToCharity( + buildTestFeedback(buildTestUser(UUID.randomUUID().toString())), + buildTestCharity(UUID.randomUUID().toString()) + ); + + assertFalse(result); + } + + @Test + void addFeedbackToCharity_setsCorrectParameterOrder() throws SQLException { + when(mockStmt.executeUpdate()).thenReturn(1); + + String userId = UUID.randomUUID().toString(); + String charityId = UUID.randomUUID().toString(); + User user = buildTestUser(userId); + Feedback feedback = buildTestFeedback(user); + Charity charity = buildTestCharity(charityId); + + feedbackDAO.addFeedbackToCharity(feedback, charity); + + verify(mockStmt).setString(1, feedback.getFeedbackId().toString()); // UUID_feedback + verify(mockStmt).setString(2, feedback.getComment()); // feedback_comment + verify(mockStmt).setDate(3, Date.valueOf(feedback.getDate())); // feedback_date + verify(mockStmt).setBoolean(4, user.getSettings().isAnonymous()); // isAnonymous + verify(mockStmt).setString(5, charityId); // charity_id + verify(mockStmt).setString(6, userId); // user_id + } + + @Test + void addFeedbackToCharity_setsAnonymousTrueWhenUserIsAnonymous() throws SQLException { + when(mockStmt.executeUpdate()).thenReturn(1); + + String userId = UUID.randomUUID().toString(); + User anonymousUser = buildTestUser(userId); + anonymousUser.setSettings(new Settings(true, Language.ENGLISH, false)); + + feedbackDAO.addFeedbackToCharity( + buildTestFeedback(anonymousUser), + buildTestCharity(UUID.randomUUID().toString()) + ); + + verify(mockStmt).setBoolean(4, true); + } + + // ---------------------------------------------------------------- + // getFeedbackForCharityUUID() + // ---------------------------------------------------------------- + + @Test + void getFeedbackForCharityUUID_returnsFeedbackList() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, false); + + String feedbackId = UUID.randomUUID().toString(); + String userId = UUID.randomUUID().toString(); + + when(mockRs.getString("UUID_feedback")).thenReturn(feedbackId); + when(mockRs.getString("feedback_comment")).thenReturn("Great work!"); + when(mockRs.getString("feedback_date")).thenReturn(LocalDate.now().toString()); + when(mockRs.getBoolean("isAnonymous")).thenReturn(false); + + when(mockRs.getString("UUID_User")).thenReturn(userId); + when(mockRs.getString("user_name")).thenReturn("Alice"); + when(mockRs.getString("user_email")).thenReturn("alice@example.com"); + when(mockRs.getString("user_password")).thenReturn("hashedpw"); + when(mockRs.getString("role")).thenReturn(Role.NORMAL_USER.toString()); + when(mockRs.getString("language")).thenReturn(Language.ENGLISH.toString()); + when(mockRs.getBoolean("lightmode")).thenReturn(true); + + ArrayList result = feedbackDAO.getFeedbackforCharityUUID(UUID.randomUUID().toString()); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(feedbackId, result.getFirst().getFeedbackId().toString()); + assertEquals("Great work!", result.getFirst().getComment()); + assertEquals(userId, result.getFirst().getUser().getId().toString()); + } + + @Test + void getFeedbackForCharityUUID_returnsEmptyListWhenNoFeedback() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); + + ArrayList result = feedbackDAO.getFeedbackforCharityUUID(UUID.randomUUID().toString()); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + void getFeedbackForCharityUUID_returnsMultipleFeedbackEntries() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, true, true, false); + + when(mockRs.getString("UUID_feedback")).thenReturn( + UUID.randomUUID().toString(), + UUID.randomUUID().toString(), + UUID.randomUUID().toString() + ); + when(mockRs.getString("feedback_comment")).thenReturn("Good", "Excellent", "Amazing"); + when(mockRs.getString("feedback_date")).thenReturn(LocalDate.now().toString()); + when(mockRs.getBoolean("isAnonymous")).thenReturn(false); + when(mockRs.getString("UUID_User")).thenReturn(UUID.randomUUID().toString()); + when(mockRs.getString("user_name")).thenReturn("User"); + when(mockRs.getString("user_email")).thenReturn("u@example.com"); + when(mockRs.getString("user_password")).thenReturn("pw"); + when(mockRs.getString("role")).thenReturn(Role.NORMAL_USER.toString()); + when(mockRs.getString("language")).thenReturn(Language.ENGLISH.toString()); + when(mockRs.getBoolean("lightmode")).thenReturn(false); + + ArrayList result = feedbackDAO.getFeedbackforCharityUUID(UUID.randomUUID().toString()); + + assertEquals(3, result.size()); + } + + @Test + void getFeedbackForCharityUUID_passesCorrectCharityIdToQuery() throws SQLException { + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); + + String charityId = UUID.randomUUID().toString(); + feedbackDAO.getFeedbackforCharityUUID(charityId); + + verify(mockStmt).setString(1, charityId); + } +} \ No newline at end of file diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/MessageDAOTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/MessageDAOTest.java new file mode 100644 index 0000000..6d36d76 --- /dev/null +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/MessageDAOTest.java @@ -0,0 +1,161 @@ +package ntnu.systemutvikling.team6.database.DAO; + +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.models.Charity; +import ntnu.systemutvikling.team6.models.user.Message; +import org.junit.jupiter.api.*; +import java.sql.*; +import java.time.LocalDate; +import java.util.UUID; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Tests made by Claude AI: Sonnet 4.6, on 24.04.206 + */ +public class MessageDAOTest { + private DatabaseConnection mockDbConnection; + private Connection mockDonorConn; // used by getDonorIdsForCharity() + private PreparedStatement mockDonorStmt; + private ResultSet mockDonorRs; + + private Connection mockInsertConn; // used by the INSERT batch + private PreparedStatement mockInsertStmt; + + private MessageDAO messageDAO; + + @BeforeEach + void setUp() throws SQLException { + mockDbConnection = mock(DatabaseConnection.class); + + mockDonorConn = mock(Connection.class); + mockDonorStmt = mock(PreparedStatement.class); + mockDonorRs = mock(ResultSet.class); + + mockInsertConn = mock(Connection.class); + mockInsertStmt = mock(PreparedStatement.class); + + + when(mockDbConnection.getMySqlConnection()) + .thenReturn(mockDonorConn) + .thenReturn(mockInsertConn); + + // Wire donor connection + when(mockDonorConn.prepareStatement(anyString())).thenReturn(mockDonorStmt); + when(mockDonorStmt.executeQuery()).thenReturn(mockDonorRs); + + // Wire insert connection + when(mockInsertConn.prepareStatement(anyString())).thenReturn(mockInsertStmt); + + messageDAO = new MessageDAO(mockDbConnection); + } + + // ---------------------------------------------------------------- + // Helpers + // ---------------------------------------------------------------- + + private Message buildTestMessage(String charityId) { + Charity charity = new Charity( + charityId, + "123456789", + "HelpOrg", + "helporg.com", + "active", + true, + "We help people", + null, + null, + null + ); + return new Message("Important Update", charity, "Things are going well.", LocalDate.now()); + } + + // ---------------------------------------------------------------- + // addMessage() + // ---------------------------------------------------------------- + + @Test + void addMessage_returnsTrueWhenDonorsExistAndBatchSucceeds() throws SQLException { + String charityId = UUID.randomUUID().toString(); + String donorId = UUID.randomUUID().toString(); + + // Simulate one donor found + when(mockDonorRs.next()).thenReturn(true, false); + when(mockDonorRs.getString("user_id")).thenReturn(donorId); + + // Batch returns one affected row + when(mockInsertStmt.executeBatch()).thenReturn(new int[]{1}); + + boolean result = messageDAO.addMessage(buildTestMessage(charityId)); + + assertTrue(result); + } + + @Test + void addMessage_returnsFalseWhenNoDonorsExist() throws SQLException { + when(mockDonorRs.next()).thenReturn(false); + + boolean result = messageDAO.addMessage(buildTestMessage(UUID.randomUUID().toString())); + + assertFalse(result); + verify(mockDbConnection, times(1)).getMySqlConnection(); + verifyNoInteractions(mockInsertConn); + } + + @Test + void addMessage_sendsOneBatchEntryPerDonor() throws SQLException { + String charityId = UUID.randomUUID().toString(); + String donorId1 = UUID.randomUUID().toString(); + String donorId2 = UUID.randomUUID().toString(); + String donorId3 = UUID.randomUUID().toString(); + + + when(mockDonorRs.next()).thenReturn(true, true, true, false); + when(mockDonorRs.getString("user_id")).thenReturn(donorId1, donorId2, donorId3); + when(mockInsertStmt.executeBatch()).thenReturn(new int[]{1, 1, 1}); + + messageDAO.addMessage(buildTestMessage(charityId)); + + verify(mockInsertStmt, times(3)).addBatch(); + verify(mockInsertStmt, times(1)).executeBatch(); + } + + @Test + void addMessage_setsCorrectCharityIdOnEveryBatchEntry() throws SQLException { + String charityId = UUID.randomUUID().toString(); + String donorId = UUID.randomUUID().toString(); + + when(mockDonorRs.next()).thenReturn(true, false); + when(mockDonorRs.getString("user_id")).thenReturn(donorId); + when(mockInsertStmt.executeBatch()).thenReturn(new int[]{1}); + + messageDAO.addMessage(buildTestMessage(charityId)); + + verify(mockInsertStmt).setString(5, charityId); + verify(mockInsertStmt).setString(6, donorId); + } + + @Test + void addMessage_throwsRuntimeExceptionWhenDonorQueryFails() throws SQLException { + when(mockDonorConn.prepareStatement(anyString())) + .thenThrow(new SQLException("Donor query failed")); + + assertThrows(RuntimeException.class, + () -> messageDAO.addMessage(buildTestMessage(UUID.randomUUID().toString()))); + } + + + @Test + void addMessage_returnsFalseWhenBatchReturnsEmptyArray() throws SQLException { + String donorId = UUID.randomUUID().toString(); + + when(mockDonorRs.next()).thenReturn(true, false); + when(mockDonorRs.getString("user_id")).thenReturn(donorId); + + when(mockInsertStmt.executeBatch()).thenReturn(new int[]{}); + + boolean result = messageDAO.addMessage(buildTestMessage(UUID.randomUUID().toString())); + + assertFalse(result); + } +} \ No newline at end of file diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/UserDAOTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/UserDAOTest.java new file mode 100644 index 0000000..fbea516 --- /dev/null +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DAO/UserDAOTest.java @@ -0,0 +1,516 @@ +package ntnu.systemutvikling.team6.database.DAO; + +import ntnu.systemutvikling.team6.database.DatabaseConnection; +import ntnu.systemutvikling.team6.models.registry.UserRegistry; +import ntnu.systemutvikling.team6.models.user.*; +import ntnu.systemutvikling.team6.security.PasswordHasher; +import org.junit.jupiter.api.*; +import org.mockito.*; + +import java.sql.*; +import java.time.LocalDate; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Some tests made by Claude AI: Sonnet 4.6, on 24.04.206 + */ +public class UserDAOTest { + + // --- Mocks --- + private DatabaseConnection mockDbConnection; + private Connection mockConn; + private PreparedStatement mockStmt; + private ResultSet mockRs; + + private UserDAO userDAO; + + @BeforeEach + void setUp() throws SQLException { + mockDbConnection = mock(DatabaseConnection.class); + mockConn = mock(Connection.class); + mockStmt = mock(PreparedStatement.class); + mockRs = mock(ResultSet.class); + + // Every test gets a fresh DAO wired to our fake connection + when(mockDbConnection.getMySqlConnection()).thenReturn(mockConn); + + userDAO = new UserDAO(mockDbConnection); + } + + // ---------------------------------------------------------------- + // getUserFromDBUuid() + // ---------------------------------------------------------------- + + @Test + void getUserFromDBUuid_returnsUserWhenFound() throws SQLException { + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, false); + + String userId = UUID.randomUUID().toString(); + String messageId = UUID.randomUUID().toString(); + String charityId = UUID.randomUUID().toString(); + + when(mockRs.getString("UUID_User")).thenReturn(userId); + when(mockRs.getString("user_name")).thenReturn("Bob"); + when(mockRs.getString("user_email")).thenReturn("bob@example.com"); + when(mockRs.getString("user_password")).thenReturn("hashedpw"); + when(mockRs.getString("role")).thenReturn(Role.NORMAL_USER.toString()); + + when(mockRs.getString("isAnonymous")).thenReturn("false"); + when(mockRs.getBoolean("isAnonymous")).thenReturn(false); + when(mockRs.getString("language")).thenReturn(Language.ENGLISH.toString()); + when(mockRs.getBoolean("lightmode")).thenReturn(true); + + when(mockRs.getString("UUID_message")).thenReturn(messageId); + when(mockRs.getString("message_title")).thenReturn("Hello"); + when(mockRs.getString("message_content")).thenReturn("Some content"); + when(mockRs.getString("message_date")).thenReturn(LocalDate.now().toString()); + + when(mockRs.getString("UUID_charities")).thenReturn(charityId); + when(mockRs.getString("org_number")).thenReturn("9999"); + when(mockRs.getString("charity_name")).thenReturn("HelpOrg"); + when(mockRs.getString("charity_link")).thenReturn("helporg.com"); + when(mockRs.getString("status")).thenReturn("active"); + when(mockRs.getBoolean("pre_approved")).thenReturn(true); + when(mockRs.getString("description")).thenReturn("We help"); + when(mockRs.getString("logoURL")).thenReturn(null); + when(mockRs.getString("key_values")).thenReturn(null); + when(mockRs.getBytes("logoBLOB")).thenReturn(null); + + User user = userDAO.getUserFromDBUuid(userId); + + assertNotNull(user); + assertEquals(userId, user.getId().toString()); + assertEquals("Bob", user.getUsername()); + assertEquals(1, user.getInbox().getMessages().size()); + assertEquals("Hello", user.getInbox().getMessages().getFirst().getTitle()); + assertEquals(charityId, user.getInbox().getMessages().getFirst().getFrom().getUUID().toString()); + } + + @Test + void getUserFromDBUuid_returnsNullWhenNotFound() throws SQLException { + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); // no rows + + User user = userDAO.getUserFromDBUuid(UUID.randomUUID().toString()); + + assertNull(user); + } + @Test + void getUserFromDBUuid_throwsRuntimeExceptionOnSQLException() throws SQLException { + when(mockConn.prepareStatement(anyString())) + .thenThrow(new SQLException("Connection lost")); + + assertThrows(RuntimeException.class, + () -> userDAO.getUserFromDBUuid(UUID.randomUUID().toString())); + } + + @Test + void getUserFromDBUuid_doesNotDuplicateMessagesAcrossRows() throws SQLException { + // Same message ID appearing in two rows should only produce one Message in the inbox + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, true, false); + + String userId = UUID.randomUUID().toString(); + String messageId = UUID.randomUUID().toString(); // same ID both rows + String charityId = UUID.randomUUID().toString(); + + when(mockRs.getString("UUID_User")).thenReturn(userId); + when(mockRs.getString("user_name")).thenReturn("Bob"); + when(mockRs.getString("user_email")).thenReturn("bob@example.com"); + when(mockRs.getString("user_password")).thenReturn("hashedpw"); + when(mockRs.getString("role")).thenReturn(Role.NORMAL_USER.toString()); + when(mockRs.getString("isAnonymous")).thenReturn("false"); + when(mockRs.getBoolean("isAnonymous")).thenReturn(false); + when(mockRs.getString("language")).thenReturn(Language.ENGLISH.toString()); + when(mockRs.getBoolean("lightmode")).thenReturn(false); + when(mockRs.getString("UUID_message")).thenReturn(messageId); + when(mockRs.getString("message_title")).thenReturn("Title"); + when(mockRs.getString("message_content")).thenReturn("Content"); + when(mockRs.getString("message_date")).thenReturn(LocalDate.now().toString()); + when(mockRs.getString("UUID_charities")).thenReturn(charityId); + when(mockRs.getString("org_number")).thenReturn("1234"); + when(mockRs.getString("charity_name")).thenReturn("Org"); + when(mockRs.getString("charity_link")).thenReturn("org.com"); + when(mockRs.getString("status")).thenReturn("active"); + when(mockRs.getBoolean("pre_approved")).thenReturn(false); + when(mockRs.getString("description")).thenReturn("desc"); + when(mockRs.getString("logoURL")).thenReturn(null); + when(mockRs.getString("key_values")).thenReturn(null); + when(mockRs.getBytes("logoBLOB")).thenReturn(null); + + User user = userDAO.getUserFromDBUuid(userId); + + assertEquals(1, user.getInbox().getMessages().size()); + } + + // ---------------------------------------------------------------- + // isEmailTaken() + // ---------------------------------------------------------------- + + @Test + void isEmailTaken_returnsTrueWhenEmailExists() throws SQLException { + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true); // simulates a row found + + assertTrue(userDAO.isEmailTaken("test@example.com")); + } + + @Test + void isEmailTaken_returnsFalseWhenEmailDoesNotExist() throws SQLException { + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); // no row → email is free + + assertFalse(userDAO.isEmailTaken("new@example.com")); + } + + @Test + void isEmailTaken_throwsOnInvalidEmail() { + // No DB call should happen — the guard clause throws immediately + assertThrows(IllegalArgumentException.class, + () -> userDAO.isEmailTaken("notanemail")); + + assertThrows(IllegalArgumentException.class, + () -> userDAO.isEmailTaken(null)); + + assertThrows(IllegalArgumentException.class, + () -> userDAO.isEmailTaken(" ")); + } + + // ---------------------------------------------------------------- + // getUsersFromDB() + // ---------------------------------------------------------------- + + @Test + void getUsersFromDB_returnsAllUsers() throws SQLException { + Statement mockStatement = mock(Statement.class); + when(mockConn.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockRs); + + String userId1 = UUID.randomUUID().toString(); + String userId2 = UUID.randomUUID().toString(); + + // Two distinct users, no messages to keep stubbing simple + when(mockRs.next()).thenReturn(true, true, false); + when(mockRs.getString("UUID_User")).thenReturn(userId1, userId2); + when(mockRs.getString("user_name")).thenReturn("Alice", "Bob"); + when(mockRs.getString("user_email")).thenReturn("alice@example.com", "bob@example.com"); + when(mockRs.getString("user_password")).thenReturn("hash1", "hash2"); + when(mockRs.getString("role")).thenReturn(Role.NORMAL_USER.toString()); + when(mockRs.getString("isAnonymous")).thenReturn(null); // no settings row + when(mockRs.getString("UUID_message")).thenReturn(null); // no messages + + UserRegistry registry = userDAO.getUsersFromDB(); + + assertNotNull(registry); + assertEquals(2, registry.getAllUsers().size()); + } + + @Test + void getUsersFromDB_returnsEmptyRegistryWhenNoUsers() throws SQLException { + Statement mockStatement = mock(Statement.class); + when(mockConn.createStatement()).thenReturn(mockStatement); + when(mockStatement.executeQuery(anyString())).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); + + UserRegistry registry = userDAO.getUsersFromDB(); + + assertNotNull(registry); + assertTrue(registry.getAllUsers().isEmpty()); + } + + // ---------------------------------------------------------------- + // getSettingsForUser() + // ---------------------------------------------------------------- + + @Test + void getSettingsForUser_returnsSettingsWhenFound() throws SQLException { + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, false); + when(mockRs.getBoolean("isAnonymous")).thenReturn(true); + when(mockRs.getString("language")).thenReturn(Language.ENGLISH.toString()); + when(mockRs.getBoolean("lightmode")).thenReturn(false); + + Settings settings = userDAO.getSettingsForUser(UUID.randomUUID().toString()); + + assertNotNull(settings); + assertTrue(settings.isAnonymous()); + assertEquals(Language.ENGLISH, settings.getLanguage()); + assertFalse(settings.isLightMode()); + } + + @Test + void getSettingsForUser_returnsNullWhenNotFound() throws SQLException { + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); + + Settings settings = userDAO.getSettingsForUser(UUID.randomUUID().toString()); + + assertNull(settings); + } + + @Test + void getSettingsForUser_appliesMaxRowsLimit() throws SQLException { + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); + + userDAO.getSettingsForUser(UUID.randomUUID().toString()); + + // The DAO must call setMaxRows(1) to guard against multiple settings rows + verify(mockStmt).setMaxRows(1); + } + + @Test + void getSettingsForUser_throwsRuntimeExceptionOnSQLException() throws SQLException { + when(mockConn.prepareStatement(anyString())) + .thenThrow(new SQLException("DB error")); + + assertThrows(RuntimeException.class, + () -> userDAO.getSettingsForUser(UUID.randomUUID().toString())); + } + + // ---------------------------------------------------------------- + // updateUserDetails() + // ---------------------------------------------------------------- + + @Test + void updateUserDetails_returnsTrueOnSuccess() throws SQLException { + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + when(mockStmt.executeUpdate()).thenReturn(1); + + User user = buildTestUser(); + assertTrue(userDAO.updateUserDetails(user)); + + // Verify the three columns are set in the correct parameter order + verify(mockStmt).setString(1, user.getUsername()); + verify(mockStmt).setString(2, user.getEmail()); + verify(mockStmt).setString(3, user.getPasswordHash()); + verify(mockStmt).setString(4, user.getId().toString()); + } + + @Test + void updateUserDetails_returnsFalseWhenNoRowsAffected() throws SQLException { + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + when(mockStmt.executeUpdate()).thenReturn(0); + + assertFalse(userDAO.updateUserDetails(buildTestUser())); + } + + @Test + void updateUserDetails_returnsFalseOnSQLException() throws SQLException { + when(mockConn.prepareStatement(anyString())) + .thenThrow(new SQLException("Update failed")); + + assertFalse(userDAO.updateUserDetails(buildTestUser())); + } + + // ---------------------------------------------------------------- + // getInboxForUser() + // ---------------------------------------------------------------- + + @Test + void getInboxForUser_returnsInboxWithMessages() throws SQLException { + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, false); + + String charityId = UUID.randomUUID().toString(); + when(mockRs.getString("UUID_charities")).thenReturn(charityId); + when(mockRs.getString("org_number")).thenReturn("5678"); + when(mockRs.getString("charity_name")).thenReturn("SaveAll"); + when(mockRs.getString("charity_link")).thenReturn("saveall.org"); + when(mockRs.getString("status")).thenReturn("active"); + when(mockRs.getBoolean("pre_approved")).thenReturn(false); + when(mockRs.getString("description")).thenReturn("We save"); + when(mockRs.getString("logoURL")).thenReturn(null); + when(mockRs.getString("key_values")).thenReturn(null); + when(mockRs.getBytes("logoBLOB")).thenReturn(null); + when(mockRs.getString("message_title")).thenReturn("Update"); + when(mockRs.getString("message_content")).thenReturn("Big news"); + when(mockRs.getString("message_date")).thenReturn(LocalDate.now().toString()); + + Inbox inbox = userDAO.getInboxForUser(UUID.randomUUID().toString()); + + assertNotNull(inbox); + assertEquals(1, inbox.getMessages().size()); + assertEquals("Update", inbox.getMessages().getFirst().getTitle()); + assertEquals(charityId, inbox.getMessages().getFirst().getFrom().getUUID().toString()); + } + + @Test + void getInboxForUser_returnsEmptyInboxWhenNoMessages() throws SQLException { + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(false); + + Inbox inbox = userDAO.getInboxForUser(UUID.randomUUID().toString()); + + assertNotNull(inbox); + assertTrue(inbox.getMessages().isEmpty()); + } + + @Test + void getInboxForUser_throwsRuntimeExceptionOnSQLException() throws SQLException { + when(mockConn.prepareStatement(anyString())) + .thenThrow(new SQLException("Timeout")); + + assertThrows(RuntimeException.class, + () -> userDAO.getInboxForUser(UUID.randomUUID().toString())); + } + + // ---------------------------------------------------------------- + // registerUser() + // ---------------------------------------------------------------- + + @Test + void registerUser_returnsTrueOnSuccess() throws SQLException { + // Two prepared statements are created (User insert + Settings insert) + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + when(mockStmt.executeUpdate()).thenReturn(1); // 1 row affected each time + + User user = buildTestUser(); + assertTrue(userDAO.registerUser(user)); + + // Both inserts should have been executed + verify(mockStmt, times(2)).executeUpdate(); + // Auto-commit should have been disabled and commit called + verify(mockConn).setAutoCommit(false); + verify(mockConn).commit(); + } + + @Test + void registerUser_returnsFalseWhenInsertAffectsNoRows() throws SQLException { + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + when(mockStmt.executeUpdate()).thenReturn(0); // nothing inserted + + assertFalse(userDAO.registerUser(buildTestUser())); + } + + @Test + void registerUser_returnsFalseOnSQLException() throws SQLException { + when(mockConn.prepareStatement(anyString())) + .thenThrow(new SQLException("DB down")); + + assertFalse(userDAO.registerUser(buildTestUser())); + } + + // ---------------------------------------------------------------- + // getUserFromDBEmailAndPassword() + // ---------------------------------------------------------------- + + @Test + void getUserFromDB_returnsNullWhenPasswordDoesNotMatch() throws SQLException { + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + when(mockStmt.executeQuery()).thenReturn(mockRs); + + when(mockRs.next()).thenReturn(true, false); + when(mockRs.getString("UUID_User")).thenReturn("some-uuid"); + when(mockRs.getString("user_name")).thenReturn("Alice"); + when(mockRs.getString("user_password")).thenReturn(new PasswordHasher().getHashPassword("differentPassword")); + + User result = userDAO.getUserFromDBEmailAndPassword("alice@example.com", "wrongPassword"); + + assertNull(result); + } + + @Test + void getUserFromDB_throwsRuntimeExceptionOnSQLException() throws SQLException { + when(mockConn.prepareStatement(anyString())) + .thenThrow(new SQLException("Connection lost")); + + assertThrows(RuntimeException.class, + () -> userDAO.getUserFromDBEmailAndPassword("a@b.com", "pass")); + } + + @Test + void getUserFromDB_returnsAUserWhenCorrect() throws SQLException { + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + when(mockStmt.executeQuery()).thenReturn(mockRs); + when(mockRs.next()).thenReturn(true, false); + String userId = UUID.randomUUID().toString(); + when(mockRs.getString("UUID_User")).thenReturn(userId); + when(mockRs.getString("user_name")).thenReturn("name"); + when(mockRs.getString("user_email")).thenReturn("a@b.com"); + when(mockRs.getString("user_password")).thenReturn(new PasswordHasher().getHashPassword("somePassword")); + when(mockRs.getString("role")).thenReturn(Role.NORMAL_USER.toString()); + when(mockRs.getBoolean("isAnonymous")).thenReturn(false); + when(mockRs.getString("language")).thenReturn(Language.ENGLISH.toString()); + when(mockRs.getBoolean("lightmode")).thenReturn(false); + String messageId = UUID.randomUUID().toString(); + when(mockRs.getString("message_title")).thenReturn("Title"); + when(mockRs.getString("UUID_message")).thenReturn(messageId); + when(mockRs.getString("message_content")).thenReturn("blah blah blah"); + when(mockRs.getString("message_date")).thenReturn(LocalDate.now().toString()); + + String charityId = UUID.randomUUID().toString(); + when(mockRs.getString("org_number")).thenReturn("1234"); + when(mockRs.getString("charity_name")).thenReturn("charity"); + when(mockRs.getString("charity_link")).thenReturn("link.com"); + when(mockRs.getString("status")).thenReturn("Something"); + when(mockRs.getString("UUID_charities")).thenReturn(charityId); + when(mockRs.getString("description")).thenReturn(charityId); + when(mockRs.getString("logoURL")).thenReturn(null); + when(mockRs.getString("key_values")).thenReturn(null); + when(mockRs.getBytes("logoBLOB")).thenReturn(null); + + User user = userDAO.getUserFromDBEmailAndPassword("a@b.com", "somePassword"); + + assertEquals(userId, user.getId().toString()); + assertEquals("Title", user.getInbox().getMessages().getFirst().getTitle()); + assertEquals(charityId, user.getInbox().getMessages().getFirst().getFrom().getUUID().toString()); + } + + // ---------------------------------------------------------------- + // updateUserSettings() + // ---------------------------------------------------------------- + + @Test + void updateUserSettings_returnsTrueOnSuccess() throws SQLException { + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + when(mockStmt.executeUpdate()).thenReturn(1); + + User user = buildTestUser(); + Settings newSettings = new Settings(true, Language.ENGLISH, false); + + assertTrue(userDAO.updateUserSettings(user, newSettings)); + verify(mockStmt).setBoolean(1, true); // isAnonymous + verify(mockStmt).setBoolean(3, false); // lightmode + } + + @Test + void updateUserSettings_returnsFalseOnSQLException() throws SQLException { + when(mockConn.prepareStatement(anyString())) + .thenThrow(new SQLException("Timeout")); + + assertFalse(userDAO.updateUserSettings(buildTestUser(), new Settings(false, Language.ENGLISH, true))); + } + + // ---------------------------------------------------------------- + // Helper + // ---------------------------------------------------------------- + + private User buildTestUser() { + Settings settings = new Settings(false, Language.ENGLISH, true); + User user = new User( + UUID.randomUUID().toString(), + "TestUser", + "test@example.com", + "hashedpassword123", + Role.NORMAL_USER.toString() + ); + user.setSettings(settings); + user.setInbox(new Inbox()); + return user; + } +} \ No newline at end of file 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 809d251..c8503c3 100644 --- a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DatabaseSetupTest.java +++ b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/DatabaseSetupTest.java @@ -4,8 +4,8 @@ import java.sql.*; import java.util.List; -import ntnu.systemutvikling.team6.database.Readers.CharitySelect; -import ntnu.systemutvikling.team6.database.Readers.DonationSelect; +import ntnu.systemutvikling.team6.database.DAO.CharityDAO; +import ntnu.systemutvikling.team6.database.DAO.DonationDAO; import ntnu.systemutvikling.team6.models.Charity; import ntnu.systemutvikling.team6.service.APIToDatabaseService; import org.junit.jupiter.api.*; @@ -14,16 +14,16 @@ class DatabaseSetupTest { private DatabaseSetup dbManager; private APIToDatabaseService service; - private CharitySelect charitySelect; - private DonationSelect donationSelect; + private CharityDAO charitySelect; + private DonationDAO donationDAO; @BeforeEach public void setUp() throws SQLException { DatabaseConnection conn = new DatabaseConnection(); this.dbManager = new DatabaseSetup(conn); this.service = new APIToDatabaseService(conn); - this.charitySelect = new CharitySelect(conn); - this.donationSelect = new DonationSelect(conn); + this.charitySelect = new CharityDAO(conn); + this.donationDAO = new DonationDAO(conn); } // Make sure you're connected to the NTNU network for this to work diff --git a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/CharitySelectTest.java b/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/CharitySelectTest.java deleted file mode 100644 index e338b49..0000000 --- a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/CharitySelectTest.java +++ /dev/null @@ -1,330 +0,0 @@ -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.ArrayList; -import java.util.UUID; -import ntnu.systemutvikling.team6.database.DatabaseConnection; -import ntnu.systemutvikling.team6.models.Charity; -import ntnu.systemutvikling.team6.models.Feedback; -import ntnu.systemutvikling.team6.models.registry.CharityRegistry; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -/** - * Unit tests for {@link CharitySelect}. - * - *

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); - String charityId = UUID.randomUUID().toString(); - when(mockResultSet.getString("UUID_charities")).thenReturn(charityId); - 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("description")).thenReturn("Some description"); - when(mockResultSet.getString("logoURL")).thenReturn("https://logo.png"); - when(mockResultSet.getString("key_values")).thenReturn("80:10:90"); - when(mockResultSet.getBytes("logoBLOB")).thenReturn(null); - when(mockResultSet.getString("category")).thenReturn(null); - - 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); - String charityId = UUID.randomUUID().toString(); - when(mockResultSet.getString("UUID_charities")).thenReturn(charityId); - 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("description")).thenReturn("Some description"); - when(mockResultSet.getString("logoURL")).thenReturn("https://logo.png"); - when(mockResultSet.getString("key_values")).thenReturn("80:10:90"); - when(mockResultSet.getBytes("logoBLOB")).thenReturn(null); - when(mockResultSet.getString("category")).thenReturn(null); - - String feedback1Id = UUID.randomUUID().toString(); - String userId = UUID.randomUUID().toString(); - when(mockResultSet.getString("UUID_feedback")).thenReturn(feedback1Id); - when(mockResultSet.getString("UUID_User")).thenReturn(userId); - 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("NORMAL_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(feedback1Id, feedback.getFeedbackId().toString()); - 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); - String charityId = UUID.randomUUID().toString(); - String charityId2 = UUID.randomUUID().toString(); - - when(mockResultSet.getString("UUID_charities")).thenReturn(charityId, charityId2); - 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("description")).thenReturn("Some description"); - when(mockResultSet.getString("logoURL")).thenReturn("https://logo.png"); - when(mockResultSet.getString("key_values")).thenReturn("80:10:90"); - when(mockResultSet.getBytes("logoBLOB")).thenReturn(null); - when(mockResultSet.getString("category")).thenReturn(null); - - 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 - String charityId = UUID.randomUUID().toString(); - when(mockResultSet.getString("UUID_charities")).thenReturn(charityId); - 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("description")).thenReturn("Some description"); - when(mockResultSet.getString("logoURL")).thenReturn("https://logo.png"); - when(mockResultSet.getString("key_values")).thenReturn("80:10:90"); - when(mockResultSet.getBytes("logoBLOB")).thenReturn(null); - when(mockResultSet.getString("category")).thenReturn(null); - - String feedback1Id = UUID.randomUUID().toString(); - String feedback2Id = UUID.randomUUID().toString(); - String userId = UUID.randomUUID().toString(); - when(mockResultSet.getString("UUID_feedback")).thenReturn(feedback1Id, feedback2Id); - when(mockResultSet.getString("UUID_User")).thenReturn(userId); - 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("NORMAL_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 result = charitySelect.getFeedbackforCharityUUID("charity-uuid-1"); - - assertNotNull(result); - assertTrue( - result.isEmpty(), "Should return an empty list when no feedback exists for the given UUID"); - } - - @Test - @DisplayName("getFeedbackforCharityUUID – one row returns one Feedback with correct data") - void getFeedbackforCharityUUID_oneRow_returnsSingleFeedback() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); - when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); - - when(mockResultSet.next()).thenReturn(true, false); - String feedback1Id = UUID.randomUUID().toString(); - when(mockResultSet.getString("UUID_feedback")).thenReturn(feedback1Id); - String userId = UUID.randomUUID().toString(); - when(mockResultSet.getString("UUID_User")).thenReturn(userId); - when(mockResultSet.getString("user_name")).thenReturn("Bob"); - when(mockResultSet.getString("user_email")).thenReturn("bob@example.com"); - when(mockResultSet.getString("user_password")).thenReturn("secret"); - when(mockResultSet.getString("role")).thenReturn("CHARITY_USER"); - when(mockResultSet.getString("feedback_comment")).thenReturn("Very helpful!"); - when(mockResultSet.getString("feedback_date")).thenReturn("2024-06-01"); - String charityId = UUID.randomUUID().toString(); - ArrayList result = charitySelect.getFeedbackforCharityUUID(charityId); - - assertEquals(1, result.size()); - Feedback feedback = result.get(0); - assertEquals(feedback1Id, feedback.getFeedbackId().toString()); - assertEquals("Very helpful!", feedback.getComment()); - assertEquals("Bob", feedback.getUser().getDisplayName()); - } - - @Test - @DisplayName("getFeedbackforCharityUUID – two rows returns two Feedback objects") - void getFeedbackforCharityUUID_twoRows_returnsTwoFeedbacks() 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); - String feedback1Id = UUID.randomUUID().toString(); - String feedback2Id = UUID.randomUUID().toString(); - when(mockResultSet.getString("UUID_feedback")).thenReturn(feedback1Id, feedback2Id); - String userid = UUID.randomUUID().toString(); - - when(mockResultSet.getString("UUID_User")).thenReturn(userid); - when(mockResultSet.getString("user_name")).thenReturn("Carol"); - when(mockResultSet.getString("user_email")).thenReturn("carol@example.com"); - when(mockResultSet.getString("user_password")).thenReturn("pw"); - when(mockResultSet.getString("role")).thenReturn("NORMAL_USER"); - when(mockResultSet.getString("feedback_comment")).thenReturn("Comment one", "Comment two"); - when(mockResultSet.getString("feedback_date")).thenReturn("2024-07-10"); - - ArrayList result = charitySelect.getFeedbackforCharityUUID("charity-uuid-1"); - - assertEquals( - 2, result.size(), "Should return exactly two Feedback objects for two result rows"); - } - - @Test - @DisplayName("getFeedbackforCharityUUID – UUID is bound to the PreparedStatement parameter") - void getFeedbackforCharityUUID_correctUUIDBindingVerified() throws Exception { - String targetUuid = "charity-uuid-XYZ"; - - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); - when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet); - when(mockResultSet.next()).thenReturn(false); - - charitySelect.getFeedbackforCharityUUID(targetUuid); - - verify(mockPreparedStatement).setString(1, targetUuid); - } - - @Test - @DisplayName("getFeedbackforCharityUUID – exception during query is wrapped in RuntimeException") - void getFeedbackforCharityUUID_exception_throwsRuntimeException() throws Exception { - when(mockDatabaseConnection.getMySqlConnection()).thenReturn(mockConnection); - when(mockConnection.prepareStatement(anyString())) - .thenThrow(new SQLException("Prepared statement failed")); - - assertThrows( - RuntimeException.class, - () -> charitySelect.getFeedbackforCharityUUID("charity-uuid-1"), - "Any exception during query execution should be rethrown as RuntimeException"); - } -} 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 deleted file mode 100644 index 5879231..0000000 --- a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/DonationSelectTest.java +++ /dev/null @@ -1,243 +0,0 @@ -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 java.util.UUID; -import ntnu.systemutvikling.team6.database.DatabaseConnection; -import ntnu.systemutvikling.team6.models.Donation; -import ntnu.systemutvikling.team6.models.registry.DonationRegistry; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -/** - * Unit tests for {@link DonationSelect}. - * - *

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.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); - String charityId = UUID.randomUUID().toString(); - stubCharityColumns( - charityId, "123456789", "Test Charity", "https://example.org", true, "ACTIVE"); - String userId = UUID.randomUUID().toString(); - String donationId = UUID.randomUUID().toString(); - stubDonationColumns(donationId, 250.0, LocalDate.of(2024, 5, 20), userId); - - DonationRegistry registry = donationSelect.getDonationFromDB(); - - assertEquals(1, registry.getAllDonations().size()); - Donation donation = registry.getAllDonations().get(0); - assertEquals(donationId, donation.getDonationID().toString()); - 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); - String charityId = UUID.randomUUID().toString(); - String donationId = UUID.randomUUID().toString(); - stubCharityColumns( - charityId, "987654321", "Help Fund", "https://helpfund.org", false, "PENDING"); - String userId = UUID.randomUUID().toString(); - stubDonationColumns(donationId, 100.0, LocalDate.of(2024, 1, 1), userId); - - DonationRegistry registry = donationSelect.getDonationFromDB(); - - Donation donation = registry.getAllDonations().get(0); - assertEquals(charityId, donation.getCharityId().toString()); - assertEquals("987654321", donation.getCharity().getOrg_number()); - 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); - - String chairtyId = UUID.randomUUID().toString(); - String chairtyId2 = UUID.randomUUID().toString(); - when(mockResultSet.getString("UUID_charities")).thenReturn(chairtyId, chairtyId2); - when(mockResultSet.getString("org_number")).thenReturn("111111111", "222222222"); - when(mockResultSet.getBoolean("pre_approved")).thenReturn(true, false); - when(mockResultSet.getString("status")).thenReturn("ACTIVE", "INACTIVE"); - String donationId = UUID.randomUUID().toString(); - String donationId2 = UUID.randomUUID().toString(); - when(mockResultSet.getString("UUID_Donations")).thenReturn(donationId, donationId2); - when(mockResultSet.getDouble("amount")).thenReturn(500.0, 750.0); - - Date sqlDate = Date.valueOf(LocalDate.of(2024, 8, 10)); - when(mockResultSet.getDate("date")).thenReturn(sqlDate); - - String userId = UUID.randomUUID().toString(); - when(mockResultSet.getString("UUID_User")).thenReturn(userId); - when(mockResultSet.getString("user_name")).thenReturn("Test User"); - when(mockResultSet.getString("user_email")).thenReturn("test@example.com"); - when(mockResultSet.getString("user_password")).thenReturn("password"); - when(mockResultSet.getString("role")).thenReturn("NORMAL_USER"); - - 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); - - String charityId = UUID.randomUUID().toString(); - when(mockResultSet.next()).thenReturn(true, false); - stubCharityColumns( - charityId, "123456789", "Test Charity", "https://example.org", true, "ACTIVE"); - String userId = UUID.randomUUID().toString(); - - String donationId = UUID.randomUUID().toString(); - stubDonationColumns(donationId, 0.0, LocalDate.of(2024, 1, 1), userId); - - 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); - String charityId = UUID.randomUUID().toString(); - String donationId = UUID.randomUUID().toString(); - stubCharityColumns( - charityId, "123456789", "Test Charity", "https://example.org", true, "ACTIVE"); - String userId = UUID.randomUUID().toString(); - stubDonationColumns(donationId, 1_000_000.99, LocalDate.of(2024, 12, 31), userId); - - 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.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, String userId) - throws SQLException { - when(mockResultSet.getString("UUID_Donations")).thenReturn(uuid); - when(mockResultSet.getDouble("amount")).thenReturn(amount); - when(mockResultSet.getBoolean("isAnonymous")).thenReturn(false); - - Date sqlDate = Date.valueOf(date); - when(mockResultSet.getDate("date")).thenReturn(sqlDate); - - // User fields - when(mockResultSet.getString("UUID_User")).thenReturn(userId); - when(mockResultSet.getString("user_name")).thenReturn("Test User"); - when(mockResultSet.getString("user_email")).thenReturn("test@example.com"); - when(mockResultSet.getString("user_password")).thenReturn("password"); - when(mockResultSet.getString("role")).thenReturn("NORMAL_USER"); - } -} 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 deleted file mode 100644 index e408e02..0000000 --- a/helpmehelpapplication/src/test/java/ntnu/systemutvikling/team6/database/Readers/UserSelectTest.java +++ /dev/null @@ -1,409 +0,0 @@ -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.registry.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; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -/** - * 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. - */ -@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 = UUID.randomUUID().toString(); - private static final String CHARITY_UUID = UUID.randomUUID().toString(); - private static final String MESSAGE_UUID = "msg-uuid-1"; - - private UserSelect userSelect; - - @BeforeEach - void setUp() { - reset(mockResultSet); - 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.getString("isAnonymous")).thenReturn("false"); - when(mockResultSet.getBoolean("isAnonymous")).thenReturn(false); - 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("NORMAL_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); - String UserId = UUID.randomUUID().toString(); - String User2Id = UUID.randomUUID().toString(); - when(mockResultSet.getString("UUID_User")).thenReturn(UserId, User2Id); - 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("NORMAL_USER", "CHARITY_USER"); - 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("NORMAL_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, "ENGLISH", false); - - Settings result = userSelect.getSettingsForUser(USER_UUID); - - assertNotNull(result); - // assertTrue(result.isAnonymous()); - assertEquals(Language.ENGLISH, 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"); - when(mockResultSet.getString("message_content")).thenReturn("Hello!"); - - 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"); - when(mockResultSet.getString("message_content")).thenReturn("Hello!"); - - 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("NORMAL_USER"); - } - - /** Stubs the Settings columns on the mock ResultSet. */ - private void stubSettingsColumns(boolean isAnonymous, String language, boolean lightmode) - throws SQLException { - 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"); - } -}