diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eac943d..a4d5bd8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: ci on: push: - branches: [main] + branches: [main, develop] pull_request: - branches: [main] + branches: [main, develop] jobs: test: diff --git a/.gitignore b/.gitignore index 24fca5a..6c18783 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Adrian .vscode/ .idea/ +helpmehelpapplication/target .target/ .helpmehelpapplication\target/ .helpmehelpapplication\.idea/ diff --git a/.vscode/settings.json b/.vscode/settings.json index c5f3f6b..0153b31 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "java.configuration.updateBuildConfiguration": "interactive" + "java.configuration.updateBuildConfiguration": "interactive", + "java.compile.nullAnalysis.mode": "disabled" } \ No newline at end of file diff --git a/README.md b/README.md index c2b4d1f..8ee4bba 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Consists of: Cathrine Kristiansen, Adrian Balunan, Roar Fagerkind, Robin Prestmo - Team number: `6` - Team members: - - Robin Prestmo - Product Owner + - Robin Strand Prestmo - Product Owner - Adrian Balunan - Scrum Master - Cathrine Kristiansen - Archive and Document Responsible - Roar Fagerkind - Software Quality Responsible diff --git "a/docs/M\303\270tedokumenter/Internmeeting, 05.02.2026.pdf" "b/docs/M\303\270tedokumenter/Internmeeting, 05.02.2026.pdf" deleted file mode 100644 index b1b9a0b..0000000 Binary files "a/docs/M\303\270tedokumenter/Internmeeting, 05.02.2026.pdf" and /dev/null differ diff --git "a/docs/M\303\270tedokumenter/Internmeeting, 22.01.2026.pdf" "b/docs/M\303\270tedokumenter/Internmeeting, 2026.01.22.pdf" similarity index 100% rename from "docs/M\303\270tedokumenter/Internmeeting, 22.01.2026.pdf" rename to "docs/M\303\270tedokumenter/Internmeeting, 2026.01.22.pdf" diff --git "a/docs/M\303\270tedokumenter/Internmeeting, 29.01.2026.pdf" "b/docs/M\303\270tedokumenter/Internmeeting, 2026.01.29.pdf" similarity index 100% rename from "docs/M\303\270tedokumenter/Internmeeting, 29.01.2026.pdf" rename to "docs/M\303\270tedokumenter/Internmeeting, 2026.01.29.pdf" diff --git "a/docs/M\303\270tedokumenter/Team Meeting 1.pdf" "b/docs/M\303\270tedokumenter/Team Meeting 1, 2026.02.10.pdf" similarity index 100% rename from "docs/M\303\270tedokumenter/Team Meeting 1.pdf" rename to "docs/M\303\270tedokumenter/Team Meeting 1, 2026.02.10.pdf" diff --git "a/docs/M\303\270tedokumenter/Thursdays Meeting 08.01.2016 (With LA).pdf" "b/docs/M\303\270tedokumenter/Thursdays Meeting 2026.01.08 (With LA).pdf" similarity index 100% rename from "docs/M\303\270tedokumenter/Thursdays Meeting 08.01.2016 (With LA).pdf" rename to "docs/M\303\270tedokumenter/Thursdays Meeting 2026.01.08 (With LA).pdf" diff --git "a/docs/M\303\270tedokumenter/Thursdays Meeting 15.01.2026 (With LA).pdf" "b/docs/M\303\270tedokumenter/Thursdays Meeting 2026.01.15 (With LA).pdf" similarity index 100% rename from "docs/M\303\270tedokumenter/Thursdays Meeting 15.01.2026 (With LA).pdf" rename to "docs/M\303\270tedokumenter/Thursdays Meeting 2026.01.15 (With LA).pdf" diff --git "a/docs/M\303\270tedokumenter/Thursdays Meeting 22.01.2026 (With LA).pdf" "b/docs/M\303\270tedokumenter/Thursdays Meeting 2026.01.22 (With LA).pdf" similarity index 100% rename from "docs/M\303\270tedokumenter/Thursdays Meeting 22.01.2026 (With LA).pdf" rename to "docs/M\303\270tedokumenter/Thursdays Meeting 2026.01.22 (With LA).pdf" diff --git "a/docs/M\303\270tedokumenter/Thursdays Meeting 29.01.2026 (With LA).pdf" "b/docs/M\303\270tedokumenter/Thursdays Meeting 2026.01.29 (With LA).pdf" similarity index 100% rename from "docs/M\303\270tedokumenter/Thursdays Meeting 29.01.2026 (With LA).pdf" rename to "docs/M\303\270tedokumenter/Thursdays Meeting 2026.01.29 (With LA).pdf" diff --git "a/docs/M\303\270tedokumenter/Thursdays Meeting 2026.02.05(With LA).pdf" "b/docs/M\303\270tedokumenter/Thursdays Meeting 2026.02.05(With LA).pdf" new file mode 100644 index 0000000..ae42942 Binary files /dev/null and "b/docs/M\303\270tedokumenter/Thursdays Meeting 2026.02.05(With LA).pdf" differ diff --git "a/docs/M\303\270tedokumenter/Thursdays Meeting 2026.03.05 (With LA).pdf" "b/docs/M\303\270tedokumenter/Thursdays Meeting 2026.03.05 (With LA).pdf" new file mode 100644 index 0000000..2522827 Binary files /dev/null and "b/docs/M\303\270tedokumenter/Thursdays Meeting 2026.03.05 (With LA).pdf" differ diff --git a/helpmehelpapplication/pom.xml b/helpmehelpapplication/pom.xml index 16743a5..2c394ed 100644 --- a/helpmehelpapplication/pom.xml +++ b/helpmehelpapplication/pom.xml @@ -26,9 +26,36 @@ 25.0.1 - org.openjfx - javafx-fxml - 25.0.1 + org.seleniumhq.selenium + selenium-java + 4.41.0 + + + com.opencsv + opencsv + 5.12.0 + + + com.google.code.gson + gson + 2.13.2 + + + org.mockito + mockito-core + 5.21.0 + test + + + com.mysql + mysql-connector-j + 9.6.0 + + + com.h2database + h2 + 2.4.240 + test diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/DAO/CharityDAO.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/DAO/CharityDAO.java new file mode 100644 index 0000000..8f15320 --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/DAO/CharityDAO.java @@ -0,0 +1,266 @@ +package ntnu.sytemutvikling.team6.DAO; + +import ntnu.sytemutvikling.team6.scraper.APICharityData; + +import java.sql.*; +import java.util.List; + +/** + * Manages the charity + *

+ * This class is responsible for establishing connections using environment variables + * and maintaining the {@code charities} table based on data retrieved from the IK API. + *

+ */ + +public class CharityDAO { + + public class DatabaseManager { + private final String databaseURL; + private final String username; + private final String password; + + /** + Constructs a new {@code DatabaseManager} using database credentials + * retrieved from system environment variables. + *

+ * Required environment variables: + *

+ *

+ * + * @throws IllegalStateException if either databaseURL, username, or password is {@code null} or blank + */ + + // Values stored in system environment variables for security (using ntnu's phpmyadmin for this project) + public DatabaseManager() { + this.databaseURL = System.getenv("HMH_DB_URL"); + this.username = System.getenv("HMH_DB_USERNAME"); + this.password = System.getenv("HMH_DB_PASSWORD"); + + if (this.databaseURL == null || this.databaseURL.isBlank()) { + throw new IllegalStateException("Database environment variable URL has not been set"); + } + + if (this.username == null || this.username.isBlank()) { + throw new IllegalStateException("Username environment variable has not been set"); + } + + if (this.password == null || this.password.isBlank()) { + throw new IllegalStateException("Password environment variable has not been set"); + } + } + + /** + * Constructs a new {@code DatabaseManager} using database credentials + *

+ * Used primarily for JUnit tests. Production should use the constructor + * using environment variables. + *

+ * + * @param databaseURL the url to the database + * @param username the username used to log in to the database + * @param password the password used to log in to the database + * @throws IllegalArgumentException if databaseURL, username, or password is + * {@code null} or blank + */ + + public DatabaseManager(String databaseURL, String username, String password) { + this.databaseURL = databaseURL; + this.username = username; + this.password = password; + + if (this.databaseURL == null || this.databaseURL.isBlank()) { + throw new IllegalArgumentException("Database environment variable URL has not been set"); + } + + if (this.username == null || this.username.isBlank()) { + throw new IllegalArgumentException("Username environment variable has not been set"); + } + + if (this.password == null || this.password.isBlank()) { + throw new IllegalArgumentException("Password environment variable has not been set"); + } + } + + /** + * Creates the {@code charities} table if it does not already exist. + *

+ * The table structure is based on fields from {@link APICharityData}. + *

+ * @throws RuntimeException if a {@link SQLException} occurs while creating the table + */ + + public void createTables() { + String sql_query = """ + -- MySQL Workbench Forward Engineering + + SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; + SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; + SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'; + + -- ----------------------------------------------------- + -- Schema HelpMeHelp + -- ----------------------------------------------------- + + -- ----------------------------------------------------- + -- Schema HelpMeHelp + -- ----------------------------------------------------- + --CREATE SCHEMA IF NOT EXISTS `HelpMeHelp` DEFAULT CHARACTER SET utf8 ; + --USE `HelpMeHelp` ; + USE apbaluna; + + -- ----------------------------------------------------- + -- Table `HelpMeHelp`.`Charities` + -- ----------------------------------------------------- + CREATE TABLE IF NOT EXISTS `HelpMeHelp`.`Charities` ( + `UUID` CHAR(36) NOT NULL, + `charity_name` VARCHAR(255) NOT NULL, + `charity_description` VARCHAR(255) NOT NULL, + `isVerified` TINYINT NOT NULL, + `category` VARCHAR(45) NOT NULL, + PRIMARY KEY (`UUID`)) + ENGINE = InnoDB; + + + -- ----------------------------------------------------- + -- Table `HelpMeHelp`.`Donations` + -- ----------------------------------------------------- + CREATE TABLE IF NOT EXISTS `HelpMeHelp`.`Donations` ( + `UUID` CHAR(36) NOT NULL, + `amount` DECIMAL NOT NULL, + `date` DATE NOT NULL, + `Charities_UUID` CHAR(36) NOT NULL, + PRIMARY KEY (`UUID`), + INDEX `fk_Donations_Charities_idx` (`Charities_UUID` ASC) VISIBLE, + CONSTRAINT `fk_Donations_Charities` + FOREIGN KEY (`Charities_UUID`) + REFERENCES `HelpMeHelp`.`Charities` (`UUID`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) + ENGINE = InnoDB; + + + SET SQL_MODE=@OLD_SQL_MODE; + SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; + SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; + + """; + + try (Connection conn = DriverManager.getConnection(databaseURL, username, password); + Statement s = conn.createStatement()) { + + s.execute(sql_query); + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("Error creating table."); + } + } + + /** + * Synchronizes the {@code charities} table with the provided list of APICharityData. + *

+ * Summary of function: + *

+ * A temporary table is used to determine which records should be deleted. + *

+ * + * @param charities a list of ApiCharityDaya objects + * @throws RuntimeException if a {@link SQLException} occurs during database synchronization + */ + + public void updateCharities(List charities) { + Connection conn = null; + try { + conn = DriverManager.getConnection(databaseURL, username, password); + conn.setAutoCommit(false); + // Inserts objects values into charities + String sql_query = """ + INSERT INTO charities (org_number, name, status, url, is_pre_approved) + VALUES (?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + name = VALUES(name), + status = VALUES(status), + url = VALUES(url), + is_pre_approved = VALUES(is_pre_approved) + """; + + try (PreparedStatement s = conn.prepareStatement(sql_query)) { + for (APICharityData charity : charities) { + s.setString(1, charity.getOrg_number().replaceAll("\\s", "")); + s.setString(2, charity.getName()); + s.setString(3, charity.getStatus()); + s.setString(4, charity.getUrl()); + s.setBoolean(5, charity.getIs_pre_approved()); + + s.addBatch(); + } + s.executeBatch(); + } + + + // Temporary table for parity check + sql_query = "CREATE TEMPORARY TABLE temp (org_number VARCHAR(100) PRIMARY KEY)"; + try (PreparedStatement temp_create = conn.prepareStatement(sql_query)) { + temp_create.execute(); + } + + String sql_update_temp_table = "INSERT INTO temp (org_number) VALUES (?)"; + try (PreparedStatement temp_update = conn.prepareStatement(sql_update_temp_table)) { + for (APICharityData charity : charities) { + temp_update.setString(1, charity.getOrg_number().replaceAll("\\s", "")); + temp_update.addBatch(); + } + temp_update.executeBatch(); + } + + + + // Removes records not found in the list from charities table + sql_query = """ + DELETE FROM charities c + WHERE NOT EXISTS ( + SELECT 1 + FROM temp t + WHERE c.org_number = t.org_number + ) + """; + + try (PreparedStatement delete_old_records = conn.prepareStatement(sql_query)){ + delete_old_records.execute(); + } + + conn.commit(); + } catch (SQLException e) { + + if (conn != null) { + try { + conn.rollback(); + } catch (SQLException ex) { + ex.printStackTrace(); + } + } + + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } finally { + if (conn != null) { + try { + conn.setAutoCommit(true); + conn.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + } + + } + } +} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/DAO/DonationDAO.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/DAO/DonationDAO.java new file mode 100644 index 0000000..b902276 --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/DAO/DonationDAO.java @@ -0,0 +1,4 @@ +package ntnu.sytemutvikling.team6.DAO; + +public class DonationDAO { +} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java new file mode 100644 index 0000000..8a5a2e5 --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/HmHApplication.java @@ -0,0 +1,45 @@ +package ntnu.sytemutvikling.team6; + +import ntnu.sytemutvikling.team6.database.DatabaseConnection; +import ntnu.sytemutvikling.team6.database.DatabaseManager; +import ntnu.sytemutvikling.team6.models.Charity; +import ntnu.sytemutvikling.team6.models.CharityRegistry; +import ntnu.sytemutvikling.team6.scraper.APICharityScraper; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.http.HttpClient; + +public class HmHApplication { + public static void main(String[] args) { + System.out.println("Hello world!"); + init(); + + } + public static void init(){ + /* Test and create tables to MySQL if ain't any */ + try { + DatabaseManager db = new DatabaseManager(); + db.testConnection(); + db.createTables(); + } catch (Exception e){ + e.printStackTrace(); + } + /* Test and get data from Innsamlingkontrollen API */ + try { + HttpClient https = HttpClient.newHttpClient(); + APICharityScraper scraper = new APICharityScraper(https); + DatabaseManager db = new DatabaseManager(); + + if (scraper.checkConnection()){ + CharityRegistry charityRegistry = scraper.parseJSON(scraper.getJSONData()); + for (Charity charity : charityRegistry.getAllCharities()){ + System.out.println(charity.toString()); + } + db.addAPIDataToTable(charityRegistry.getAllCharities()); + } + } catch (Exception e){ + e.printStackTrace(); + } + } +} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseConnection.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseConnection.java new file mode 100644 index 0000000..0cb9c52 --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseConnection.java @@ -0,0 +1,83 @@ +package ntnu.sytemutvikling.team6.database; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +/** + * Represents a reusable database connection through enviroment variables. + */ + +public class DatabaseConnection { + private final String databaseURL; + private final String username; + private final String password; + /** + * Constructs a new {@code DatabaseConnection} using database credentials + * retrieved from system environment variables. + * + * @throws IllegalStateException if either databaseURL, username, or password is {@code null} or blank + */ + + // Values stored in system environment variables for security (using ntnu's phpmyadmin for this project) + public DatabaseConnection(){ + this.databaseURL = "jdbc:mysql://namox.idi.ntnu.no:3306/apbaluna?useSSL=false&serverTimezone=UTC"; + this.username = "apbaluna"; + this.password = "GYntUFPG"; + + if (this.databaseURL == null || this.databaseURL.isBlank()) { + throw new IllegalStateException("Database environment variable URL has not been set"); + } + + if (this.username == null || this.username.isBlank()) { + throw new IllegalStateException("Username environment variable has not been set"); + } + + if (this.password == null || this.password.isBlank()) { + throw new IllegalStateException("Password environment variable has not been set"); + } + } + + /** + * Constructs a new {@code DatabaseConnection} using database credentials + *

+ * Used primarily for JUnit tests. Production should use the constructor + * using environment variables. + *

+ * + * @param databaseURL the url to the database + * @param username the username used to log in to the database + * @param password the password used to log in to the database + * @throws IllegalArgumentException if databaseURL, username, or password is + * {@code null} or blank + */ + + public DatabaseConnection(String databaseURL, String username, String password) { + this.databaseURL = databaseURL; + this.username = username; + this.password = password; + + if (this.databaseURL == null || this.databaseURL.isBlank()) { + throw new IllegalArgumentException("Database environment variable URL has not been set"); + } + + if (this.username == null || this.username.isBlank()) { + throw new IllegalArgumentException("Username environment variable has not been set"); + } + + if (this.password == null || this.password.isBlank()) { + throw new IllegalArgumentException("Password environment variable has not been set"); + } + } + + + /** + * Creates and returns a new MySQL database connection. + * + * @return a {@link Connection} to the database + * @throws SQLException if the connection cannot be established + */ + public Connection getMySqlConnection() throws SQLException { + return DriverManager.getConnection(databaseURL, username, password); + } +} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseManager.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseManager.java new file mode 100644 index 0000000..f226f4a --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseManager.java @@ -0,0 +1,164 @@ +package ntnu.sytemutvikling.team6.database; + +import ntnu.sytemutvikling.team6.models.Charity; +import ntnu.sytemutvikling.team6.models.CharityRegistry; +import ntnu.sytemutvikling.team6.scraper.APICharityData; + +import java.sql.*; +import java.util.List; + +/** + * Manages creation of MySQL tables (for now) + *

+ * This class is responsible for creating the tables needed for the application, if not done already + * and maintaining the {@code charities} table based on data retrieved from the IK API. + *

+ */ + +public class DatabaseManager { + private final DatabaseConnection connection; + + /** + * Contractor for DatabaseManager. It uses a DatabaseConnection object that contains a connection credentials + * using environment variables in the DatabaseConnection. + */ + public DatabaseManager(){ + this.connection = new DatabaseConnection(); + } + + /** + * Connection test for the Database. + * Does a simple SELECT SQL query and returns either true og and Exception if failed + * + * @return true if Sucsedd or SQLExepction if failed + */ + public boolean testConnection() { + try (Connection conn = connection.getMySqlConnection(); + Statement stmt = conn.createStatement()) { + + ResultSet rs = stmt.executeQuery("SELECT 1"); + + if (rs.next()) { + System.out.println("Database connection verified."); + return true; + } + + } catch (SQLException e) { + System.out.println("Database connection failed."); + e.printStackTrace(); + } + + return false; + } + + /** + * Creates the {@code charities} table if it does not already exist. + *

+ * The table structure is based on fields from {@link APICharityData}. + *

+ * @throws RuntimeException if a {@link SQLException} occurs while creating the table + */ + + public void createTables() { + String sql_query1 = + """ + -- ----------------------------------------------------- + -- Table `HelpMeHelp`.`Charities` + -- ----------------------------------------------------- + CREATE TABLE IF NOT EXISTS Charities ( + UUID_charities CHAR(36) PRIMARY KEY, + org_number VARCHAR(255) NOT NULL, + charity_name VARCHAR(255) NOT NULL, + charity_link VARCHAR(255) NOT NULL, + pre_approved TINYINT NOT NULL, + status VARCHAR(255) NOT NULL, + UNIQUE KEY unique_org_number (org_number) + ) ENGINE=InnoDB; + + + """; + String sql_query2 = + """ + -- ----------------------------------------------------- + -- Table `HelpMeHelp`.`Donations` + -- ----------------------------------------------------- + CREATE TABLE IF NOT EXISTS Donations ( + `UUID_Donations` CHAR(36) NOT NULL, + `amount` DECIMAL NOT NULL, + `date` DATE NOT NULL, + `Charities_UUID_charities` CHAR(36) NOT NULL, + PRIMARY KEY (`UUID_Donations`), + INDEX `fk_Donations_Charities_idx` (`Charities_UUID_charities` ASC) VISIBLE, + CONSTRAINT `fk_Donations_Charities` + FOREIGN KEY (`Charities_UUID_charities`) + REFERENCES `apbaluna`.`Charities` (`UUID_charities`) + ON DELETE NO ACTION + ON UPDATE NO ACTION) + ENGINE = InnoDB; + """; + + try (Connection conn = connection.getMySqlConnection(); + Statement s = conn.createStatement()) { + + s.execute(sql_query1); + s.execute(sql_query2); + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("Error creating table."); + } + } + + public void addAPIDataToTable(List charities){ + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + conn.setAutoCommit(false); + String sql_query = """ + INSERT IGNORE INTO Charities (UUID_charities, org_number, charity_name, charity_link, pre_approved, status) + VALUES (?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + charity_name = VALUES(charity_name), + charity_link = VALUES(charity_link), + pre_approved = VALUES(pre_approved), + status = VALUES(status) + """; + + try (PreparedStatement ps = conn.prepareStatement(sql_query)) { + for (Charity charity : charities) { + ps.setString(1, charity.getUUID().toString()); + ps.setString(2, charity.getOrg_number().replaceAll("\\s", "")); + ps.setString(3, charity.getName()); + ps.setString(4, charity.getDescription()); + ps.setBoolean(5, charity.getPreApproved()); // Description is the link + ps.setString(6, charity.getStatus()); + + ps.addBatch(); + } + ps.executeBatch(); + } + conn.commit(); + + } catch (SQLException e) { + if (conn != null) { + try { + conn.rollback(); + } catch (SQLException ex) { + ex.printStackTrace(); + } + } + e.printStackTrace(); + + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } finally { + if (conn != null) { + try { + conn.setAutoCommit(true); + conn.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + } + + } +} \ No newline at end of file diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java index 45c5c26..25f1453 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java @@ -12,7 +12,10 @@ public class Charity { /* UUID for uniquely identifying each charity */ - private UUID id; + private UUID UUID; + + /* Org_number from API, apparently not unique */ + private String org_number; /* Name of the charity */ private String name; @@ -20,11 +23,10 @@ public class Charity { /* Description of the charity's mission and activities */ private String description; - /* Total Donations received */ - private int totalDonations; - /* Is the charity verified? */ - private boolean isVerified; + private String status; + + private boolean is_pre_approved; /* Category for the charity */ private String category; @@ -33,26 +35,66 @@ public class Charity { private List feedbacks; /** - * Konstructor for creating a new charity. The ID is generated automatically using UUID. Total - * donations are initialized to 0. The charity is unverified by default. + * Contructor for creating a new charity. The charity is unverified by default. + * + * @param org_number matches from innsamlingkontrollen + * @param name matches from innsamlingkontrollen + * @param is_pre_approved name matches from innsamlingkontrollen + * @param status name matches from innsamlingkontrollen + * @param description no coreleation to innsamlingskontrollen + * @param category no coreleation to innsamlingskontrollen * - * @param name - * @param description - * @param category + */ - public Charity(String name, String description, String category) { - this.id = UUID.randomUUID(); + public Charity(String org_number, String name, String description, String category, boolean is_pre_approved, String status) { + this.UUID = java.util.UUID.randomUUID(); + this.org_number = org_number == null ? "" : org_number.replaceAll("\\s", ""); this.name = name; this.description = description; - this.totalDonations = 0; - this.isVerified = false; + this.is_pre_approved = is_pre_approved; + this.status = status; this.feedbacks = new ArrayList<>(); this.category = category; } + /** + * Contructor for creating a new charity. Taylored to match data given from Api. + * Other attributes will just be initialized as empty + * + * @param org_number matches from innsamlingkontrollen + * @param name matches from innsamlingkontrollen + * @param is_pre_approved name matches from innsamlingkontrollen + * @param status name matches from innsamlingkontrollen + * + + */ + public Charity(String org_number, String link, String name, boolean is_pre_approved, String status) { + this.UUID = java.util.UUID.randomUUID(); + this.org_number = org_number.replaceAll("\\s", ""); + this.name = name; + this.description = "Les mer her: " + link; + this.is_pre_approved = is_pre_approved; + this.status = status; + this.feedbacks = new ArrayList<>(); + this.category = ""; + } + /** Getters for the charity's attributes. */ - public UUID getId() { - return id; + public UUID getUUID() { + return UUID; + } + public String getOrg_number() { + return org_number; + } + + public String getStatus() { + return status; + } + + public boolean getPreApproved(){return is_pre_approved;} + + public List getFeedbacks() { + return feedbacks; } public String getCategory() { @@ -67,29 +109,14 @@ public String getDescription() { return description; } - public int getTotalDonations() { - return totalDonations; - } - - public boolean isVerified() { - return isVerified; - } - /** Setter for verification status. This one sets the charity as verified. */ public void setVerified() { - this.isVerified = true; + this.status = "Approved"; } /** Setter for verification status. This one sets the charity as unverified. */ public void setUnverified() { - this.isVerified = false; + this.status = "Veto"; } - /** - * Setter for total donations. This method is used to update the total donations when a new - * donation is made. - */ - public void setTotalDonations(int amount) { - this.totalDonations += amount; - } } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/CharityRegistry.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/CharityRegistry.java index 4a270fc..d04e056 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/CharityRegistry.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/CharityRegistry.java @@ -13,11 +13,11 @@ public List getAllCharities() { return Collections.unmodifiableList(charities); } - public Optional findCharityById(UUID charityId) { - if (charityId == null) { + public Optional findCharityByOrgnumber(String org_number) { + if (org_number == null) { throw new IllegalArgumentException("CharityId can not be null."); } - return charities.stream().filter(charity -> charityId.equals(charity.getId())).findFirst(); + return charities.stream().filter(charity -> org_number.equals(charity.getOrg_number())).findFirst(); } public void addCharity(Charity charity) { @@ -27,10 +27,10 @@ public void addCharity(Charity charity) { charities.add(charity); } - public boolean removeCharity(UUID charityId) { - if (charityId == null) { + public boolean removeCharity(String org_number) { + if (org_number == null) { throw new IllegalArgumentException("CharityId can not be null."); } - return charities.removeIf(charity -> charityId.equals(charity.getId())); + return charities.removeIf(charity -> org_number.equals(charity.getOrg_number())); } } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Organization.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Organization.java new file mode 100644 index 0000000..d6ab8bc --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Organization.java @@ -0,0 +1,32 @@ +package ntnu.sytemutvikling.team6.models; + +public class Organization { + private final String name; + private final String telephone; + private final String location; + private final String status; + + public Organization(String name, String telephone, String location, String status) { + this.name = name; + this.telephone = telephone; + this.location = location; + this.status = status; + } + + public String getName() { + return this.name; + } + + public String getTelephone() { + return this.telephone; + } + + public String getLocation() { + return this.location; + } + + public String getStatus() { + return this.status; + } + +} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityData.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityData.java new file mode 100644 index 0000000..1a9bd2b --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityData.java @@ -0,0 +1,94 @@ +package ntnu.sytemutvikling.team6.scraper; + +import ntnu.sytemutvikling.team6.database.DatabaseManager; + +/** + * Represents data parsed from the IK API JSON response. + * Instances are immutable; to update any value, a new object must be created. + *

+ * Receives data directly from {@link APICharityScraper} or {@link IKOrganizationScraper}. + *

+ *

+ * {@code org_number} should be a unique number, as it is used as a primary key + * in {@link DatabaseManager}. + *

+ */ + +public class APICharityData { + private final String org_number; + private final String name; + private final String status; + private final String url; + private final boolean is_pre_approved; + + /** + * Constructs a new APICharityData object. + * @param org_number a unique number that identifies the organization + * @param name the name of the organization + * @param status {@code approved} for approved organizations, + * {@code obs} for non-approved organizations + * @param url the URL for more info about the organization on the IK domain + * @param is_pre_approved whether the organization was pre-approved + */ + + public APICharityData(String org_number, String name, String status, String url, boolean is_pre_approved) { + if (org_number == null || org_number.isBlank()) { + throw new IllegalArgumentException("ERROR: Org number cannot be null or blank"); + } + this.org_number = org_number.replaceAll("\\s", ""); + this.name = name; + this.status = status; + this.url = url; + this.is_pre_approved = is_pre_approved; + } + + /** + * Returns the organization number. Must not be {@code null} or blank. + * + * @return the organization number + */ + + public String getOrg_number() { + return this.org_number; + } + + /** + * Returns the name of the organization. Whitespace removed. + * + * @return the name of the organization + */ + + public String getName() { + return name; + } + + /** + * Returns whether the organization is approved or not + * @return the approved status of the organization + */ + + public String getStatus() { + return status; + } + + /** + * Returns the URL of the organizations information page on IK + * + * @return the URL for more info about the organization + */ + + public String getUrl() { + return url; + } + + /** + * Returns whether the organization was pre-approved. + * + * @return {@code true} if organization was pre-approved
+ * {@code false} otherwise + */ + + public boolean getIs_pre_approved() { + return this.is_pre_approved; + } +} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityScraper.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityScraper.java new file mode 100644 index 0000000..5685cfe --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityScraper.java @@ -0,0 +1,132 @@ +package ntnu.sytemutvikling.team6.scraper; + +import com.google.gson.Gson; +import ntnu.sytemutvikling.team6.database.DatabaseConnection; +import ntnu.sytemutvikling.team6.models.Charity; +import ntnu.sytemutvikling.team6.models.CharityRegistry; + +import java.io.IOException; +import java.net.*; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Fetches JSON information from the IK API and parses the JSON into a list of {@link APICharityData} objects. + * AND can update the database. + */ + +public class APICharityScraper { + private final HttpClient client; + private final HttpRequest request; + private final static String API_url = "https://app.innsamlingskontrollen.no/api/public/v1/all"; + + /** + * Constructs a new APICharityScraper object. + * @param client the client responsible for making the http connection. + * @throws URISyntaxException if the API URL is malformed or violates URI syntax rules + */ + + public APICharityScraper(HttpClient client) throws URISyntaxException { + this.client = client; + this.request = HttpRequest.newBuilder() + .uri(new URI(API_url)) + .GET() + .build(); + } + + /** + * Checks if the http request returns an 'OK' response. + * + * @return {@code true} if connection was successful + * @throws IOException if an I/O error occurs while sending or receiving the HTTP reques + * @throws InterruptedException if the operation is interrupted while waiting for the response + * @throws RuntimeException if the connection is unsuccessful + */ + + public boolean checkConnection() throws IOException, InterruptedException { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == 200) { + return true; + } + throw new RuntimeException("Connection failed: HTTP error code: " + response.statusCode() + + "\nResponse text: " + response.body() + ); + } + + /** + * Fetches the JSON data from the IK API and stores it in a String. + * + * @return a String of the JSON values in IK API + * @throws IOException if an I/O error occurs while sending or receiving the HTTP request + * @throws InterruptedException if the operation is interrupted while waiting for the response + */ + + public String getJSONData() throws IOException, InterruptedException { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + return response.body(); + } + + /** + * Parses the JSON data using gson and creates a list of APICharityData objects. + * + * @param JSONData the {@code String} of JSON data to be parsed + * @return a list of APICharityDaya objects + * @throws com.google.gson.JsonSyntaxException if the provided JSON is not valid + */ + //public List parseJSON(String JSONData) { + public CharityRegistry parseJSON(String JSONData) { + Gson gson = new Gson(); + APICharityData[] charityData = gson.fromJson(JSONData, APICharityData[].class); + + if (charityData == null) { + //return new ArrayList<>(); + return new CharityRegistry(); + } + + // Filters out "obs" status due to non-unique org_number values + /* + return Arrays.stream(charityData) + .filter(c-> !"obs".equalsIgnoreCase(c.getStatus())) + .toList(); + + */ + + // Alternate using predefiend Charity class + CharityRegistry charityRegistry = new CharityRegistry(); + for (APICharityData apiCharityData : charityData){ + Charity charity = new Charity( + apiCharityData.getOrg_number(), + apiCharityData.getUrl(), + apiCharityData.getName(), + apiCharityData.getIs_pre_approved(), + apiCharityData.getStatus() + ); + charityRegistry.addCharity(charity); + } + return charityRegistry; + } + /** + * Synchronizes the {@code charities} table with the provided list of APICharityData. + *

+ * Summary of function: + *

    + *
  • Inserts new records
  • + *
  • Updates existing records using {@code ON DUPLICATE KEY UPDATE}
  • + *
  • Removes database records that are not present in the provided list
  • + *
+ * A temporary table is used to determine which records should be deleted. + *

+ * + * @param charities a list of ApiCharityDaya objects + * @throws RuntimeException if a {@link SQLException} occurs during database synchronization + */ +} diff --git a/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityDataTest.java b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityDataTest.java new file mode 100644 index 0000000..da08823 --- /dev/null +++ b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityDataTest.java @@ -0,0 +1,66 @@ +package ntnu.sytemutvikling.team6.models; + +import ntnu.sytemutvikling.team6.scraper.APICharityData; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class APICharityDataTest { + + @Test + void initialParametersShouldBeCorrect() { + APICharityData charity = new APICharityData( + "938419264", "Misjonsalliansen", "approved", + "https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/", false + ); + + assertEquals("938419264", charity.getOrg_number(), + "Org_number parameter of APICharityData should be correct."); + assertEquals("Misjonsalliansen", charity.getName(), + "Name parameter of APICharityData should be correct."); + assertEquals("approved", charity.getStatus(), + "Status parameter of APICharityData should be correct."); + assertEquals("https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/", + charity.getUrl(), "Url parameter of APICharityData should be correct."); + assertFalse(charity.getIs_pre_approved(), "Is_pre_approved parameter of APICharityData should " + + "be correct."); + + } + + @Test + void org_numberWhiteSpaceShouldBeremoved() { + String org_number = "938 4192 64"; + APICharityData charity = new APICharityData( + org_number, "Misjonsalliansen", "approved", + "https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/", false + ); + + assertEquals(org_number.replaceAll("\\s",""), charity.getOrg_number(), + "Org_number should not contain whitespace."); + } + + @Test + void nullOrg_numberShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> new APICharityData(null, "Misjonsalliansen", + "approved", "https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/", + false), "Null org_number should not be allowed."); + } + + @Test + void blankOrg_numberShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> new APICharityData(" ", "Misjonsalliansen", + "approved", "https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/", + false), "Blank org_number should not be allowed."); + } + + @Test + void emptyOrg_numberShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> new APICharityData("", "Misjonsalliansen", + "approved", "https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/", + false), "Null org_number should not be allowed."); + } + + + + +} \ No newline at end of file diff --git a/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityScraperTest.java b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityScraperTest.java new file mode 100644 index 0000000..0120687 --- /dev/null +++ b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityScraperTest.java @@ -0,0 +1,109 @@ +package ntnu.sytemutvikling.team6.models; + +import ntnu.sytemutvikling.team6.scraper.APICharityData; +import ntnu.sytemutvikling.team6.scraper.APICharityScraper; +import org.junit.jupiter.api.Test; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.List; + +import static org.mockito.Mockito.*; + +import static org.junit.jupiter.api.Assertions.*; + +class APICharityScraperTest { + @Test + void checkConnectionShouldReturnTrueOn200ResponseCode() throws IOException, URISyntaxException, InterruptedException { + HttpClient mockClient = mock(HttpClient.class); + var mockResponse = mock(HttpResponse.class); + + when(mockResponse.statusCode()).thenReturn(200); + when(mockClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandler.class))).thenReturn(mockResponse); + + var con = new APICharityScraper(mockClient); + + assertTrue(con.checkConnection(), "Get response should be 200 (success)"); + } + + @Test + void checkConnectionShouldThrowRuntimeExceptionIfConnectionIsNotASuccess() throws URISyntaxException, IOException, InterruptedException { + var mockClient = mock(HttpClient.class); + HttpResponse mockResponse = mock(HttpResponse.class); + + when(mockResponse.statusCode()).thenReturn(500); + when(mockResponse.body()).thenReturn("Internal server error."); + when(mockClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandler.class))).thenReturn(mockResponse); + + var con = new APICharityScraper(mockClient); + + assertThrows(RuntimeException.class, con::checkConnection, + "The connection is not successful and should throw a RuntimeException."); + } + + @Test + void getJSONDataShouldReturnCorrectJSONString() throws IOException, InterruptedException, URISyntaxException { + String response = "[{\"org_number\":\"938419264\",\"name\":\"Misjonsalliansen\",\"status\":\"approved\"," + + "\"url\":\"https:\\/\\/www.innsamlingskontrollen.no\\/organisasjoner\\/misjonsalliansen\\/\"," + + "\"is_pre_approved\":false}]"; + + var mockClient = mock(HttpClient.class); + var mockResponse = mock(HttpResponse.class); + + when(mockResponse.body()).thenReturn(response); + when(mockClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandler.class))).thenReturn(mockResponse); + + var con = new APICharityScraper(mockClient); + + assertEquals(response, con.getJSONData(), "The extracted JSON data should be the same " + + "as the data from the API."); + } + + + @Test + void parsedJSONShouldHaveCorrectValues() throws URISyntaxException { + APICharityScraper con = new APICharityScraper(HttpClient.newHttpClient()); + + String JSONData = "[{\"org_number\":\"938419264\",\"name\":\"Misjonsalliansen\",\"status\":\"approved\"," + + "\"url\":\"https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/\",\"" + + "is_pre_approved\":false}]"; + CharityRegistry list = con.parseJSON(JSONData); + + Charity d = list.getAllCharities().getFirst(); + + assertEquals("938419264", d.getOrg_number(), "Org_number parameter " + + "should be correct."); + assertEquals("Misjonsalliansen", d.getName(), "Name parameter should be correct."); + assertEquals("approved", d.getStatus(), "Status parameter should be correct."); + /* assertEquals("https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/", d.getUrl(), + "Url parameter should be correct."); + assertFalse(d.getIs_pre_approved(), "Is_pre_approved parameter should be correct."); */ + } + + @Test + void parsedJSONOfNullShouldReturnEmptyList() throws URISyntaxException { + APICharityScraper con = new APICharityScraper(HttpClient.newHttpClient()); + + CharityRegistry list = con.parseJSON(null); + + assertTrue(list.getAllCharities().isEmpty(), "List should be empty due to only parsing " + + "null objects."); + } + + @Test + void shouldRemoveObsStatusEntries() throws URISyntaxException { + APICharityScraper con = new APICharityScraper(HttpClient.newHttpClient()); + + String JSONData = "[{\"org_number\":\"938419264\",\"name\":\"Misjonsalliansen\",\"status\":\"obs\"," + + "\"url\":\"https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/\",\"" + + "is_pre_approved\":false}]"; + CharityRegistry list = con.parseJSON(JSONData); + + assertEquals(0, list.getAllCharities().size(),"Entries containing 'obs' should be ignored, so list should " + + "be empty."); + + } + +} \ No newline at end of file diff --git a/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/DatabaseManagerTest.java b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/DatabaseManagerTest.java new file mode 100644 index 0000000..7dda915 --- /dev/null +++ b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/DatabaseManagerTest.java @@ -0,0 +1,237 @@ +package ntnu.sytemutvikling.team6.models; + +import ntnu.sytemutvikling.team6.scraper.APICharityData; +import ntnu.sytemutvikling.team6.database.DatabaseManager; +import org.junit.jupiter.api.*; +import java.sql.*; +import java.util.ArrayList; +import java.util.List; +import org.h2.jdbc.JdbcSQLNonTransientException; +import static org.junit.jupiter.api.Assertions.*; + +class DatabaseManagerTest { + + private DatabaseManager dbManager; + private String db_url; + private String username; + private String password; + + @BeforeEach + void setUp() throws SQLException{ + db_url = "jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1"; + username = "user"; + password = "123"; + dbManager = new DatabaseManager(db_url, username, password); + + // Removes tables due to quirk with H2 that keeps temporary tables + try (Connection conn = DriverManager.getConnection(db_url, username, password)) { + PreparedStatement endstmt = conn.prepareStatement("DROP TABLE IF EXISTS charities, temp"); + endstmt.execute(); + } + } + + @Test + void constructorShouldThrowIllegalArgumentExceptionIfDatabaseURLIsNull() { + assertThrows(IllegalArgumentException.class, () -> new DatabaseManager(null, username, password), + "DatabaseURL should not be allowed to be null."); + } + + @Test + void constructorShouldThrowIllegalArgumentExceptionIfDatabaseURLIsBlankOrEmpty() { + assertThrows(IllegalArgumentException.class, () -> new DatabaseManager(" ", username, password), + "DatabaseURL should not be allowed to be blank."); + assertThrows(IllegalArgumentException.class, () -> new DatabaseManager("", username, password), + "DatabaseURL should not be allowed to be empty."); + } + + @Test + void constructorShouldThrowIllegalArgumentExceptionIfUsernameIsNull() { + assertThrows(IllegalArgumentException.class, () -> new DatabaseManager(db_url, null, password), + "Database username should not be allowed to be null."); + } + + @Test + void constructorShouldThrowIllegalArgumentExceptionIfUsernameIsBlankOrEmpty() { + assertThrows(IllegalArgumentException.class, () -> new DatabaseManager(db_url, " ", password), + "Username should not be allowed to be blank."); + assertThrows(IllegalArgumentException.class, () -> new DatabaseManager(db_url, "", password), + "Username should not be allowed to be empty."); + } + + @Test + void constructorShouldThrowIllegalArgumentExceptionIfPasswordIsNull() { + assertThrows(IllegalArgumentException.class, () -> new DatabaseManager(db_url, username, null), + "Database password should not be allowed to be null."); + } + + @Test + void constructorShouldThrowIllegalArgumentExceptionIfPasswordIsBlankOrEmpty() { + assertThrows(IllegalArgumentException.class, () -> new DatabaseManager(db_url, username, " "), + "Password should not be allowed to be blank."); + assertThrows(IllegalArgumentException.class, () -> new DatabaseManager(db_url, username, ""), + "Password should not be allowed to be empty."); + } + + @Test + void createCharitiesTableShouldCreateTableSuccessfully() throws SQLException { + dbManager.createCharitiesTable(); + + try (Connection conn = DriverManager.getConnection(db_url, username, password)) { + ResultSet rs = conn.getMetaData().getTables(null, null, + "CHARITIES", null); + + assertTrue(rs.next(), "Connection to the database should be successful."); + } + } + + @Test + void updateCharitiesShouldInsertCorrectData() throws SQLException { + String org_number = "12345"; + String name = "Svindelorg"; + String status = "approved"; + String url = "https://www.svindel.no"; + boolean is_pre_approved = false; + + var charity = new APICharityData( + org_number, + name, + status, + url, + is_pre_approved + ); + + dbManager.createCharitiesTable(); + dbManager.updateCharities(List.of(charity)); + + try (Connection conn = DriverManager.getConnection(db_url, username, password); + PreparedStatement stmt = conn.prepareStatement("SELECT * FROM charities WHERE org_number = ?")) { + + stmt.setString(1, org_number); + ResultSet rs = stmt.executeQuery(); + + assertTrue(rs.next()); + assertEquals(org_number, rs.getString("org_number"), "Organization number should " + + "be correct."); + assertEquals(name, rs.getString("name"), "Name of the organization should be correct."); + assertEquals(status, rs.getString("status"), "Status of the organization should be " + + "correct."); + assertEquals(url, rs.getString("url"), "Url of the organization should be correct."); + assertEquals(is_pre_approved, rs.getBoolean("is_pre_approved"), "Is_pre_approved " + + "value should be correct."); + } + } + + @Test + void updateCharitiesShouldRemoveDataNotInList() throws SQLException { + String org_number = "12345"; + String name = "Svindelorg"; + String status = "approved"; + String url = "https://www.svindel.no"; + boolean is_pre_approved = false; + + var charity1 = new APICharityData( + org_number, + name, + status, + url, + is_pre_approved + ); + + org_number = "23456"; + name = "SvindelKoin"; + status = "approved"; + url = "https://www.svindel.net"; + is_pre_approved = true; + + var charity2 = new APICharityData( + org_number, + name, + status, + url, + is_pre_approved + ); + + org_number = "345672"; + name = "Arme Svindlere"; + status = "approved"; + url = "https://www.armesvindlere.com"; + is_pre_approved = false; + + var charity3 = new APICharityData( + org_number, + name, + status, + url, + is_pre_approved + ); + + var charityListBefore = new ArrayList(); + charityListBefore.add(charity1); + charityListBefore.add(charity2); + charityListBefore.add(charity3); + + var charityListAfter = new ArrayList(); + charityListAfter.add(charity1); + charityListAfter.add(charity3); + + dbManager.createCharitiesTable(); + dbManager.updateCharities(charityListBefore); + + try (Connection conn = DriverManager.getConnection(db_url, username, password)) { + PreparedStatement stmt = conn.prepareStatement("SELECT COUNT(org_number) AS number_b FROM charities"); + + ResultSet rs = stmt.executeQuery(); + + assertTrue(rs.next(), "Charities count row should exist."); + assertEquals(3, rs.getInt("number_b"), "The amount of org_numbers in the table" + + "should be 3."); + PreparedStatement endstmt = conn.prepareStatement("DROP TABLE IF EXISTS temp"); + endstmt.execute(); + } + + dbManager.updateCharities(charityListAfter); + + try (Connection conn = DriverManager.getConnection(db_url, username, password)) { + PreparedStatement stmt = conn.prepareStatement("SELECT COUNT(org_number) AS number_a FROM charities"); + + ResultSet rs = stmt.executeQuery(); + + assertTrue(rs.next(), "Charities count row should exist."); + assertEquals(2, rs.getInt("number_a"), "The amount of org_numbers in the table" + + "should be 2 due to removal of 1 table."); + } + } + + @Test + void tempTableShouldNotExistAfterUpdating() throws SQLException{ + try (Connection conn = DriverManager.getConnection(db_url, username, password)) { + PreparedStatement endstmt = conn.prepareStatement("DROP TABLE IF EXISTS charities, temp"); + endstmt.execute(); + } + String org_number = "12345"; + String name = "Svindelorg"; + String status = "approved"; + String url = "https://www.svindel.no"; + boolean is_pre_approved = false; + + var charity = new APICharityData( + org_number, + name, + status, + url, + is_pre_approved + ); + + dbManager.createCharitiesTable(); + dbManager.updateCharities(List.of(charity)); + + try (Connection conn = DriverManager.getConnection(db_url, username, password)) { + PreparedStatement stmt = conn.prepareStatement("SELECT * FROM temp"); + + ResultSet rs = stmt.executeQuery(); + + assertThrows(JdbcSQLNonTransientException.class, () -> rs.getString("org_number"), + "Exception about table being empty should be thrown."); + } + } +} \ No newline at end of file diff --git a/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/OrganizationTest.java b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/OrganizationTest.java new file mode 100644 index 0000000..2a4d5d6 --- /dev/null +++ b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/OrganizationTest.java @@ -0,0 +1,24 @@ +package ntnu.sytemutvikling.team6.models; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class OrganizationTest { + + @Test + void initialParametersShouldBeCorrect() { + String name = "Hjelpesenter"; + String telephone = "123456789"; + String location = "London"; + String status = "Approved"; + + var org = new Organization(name, telephone, location, status); + + assertEquals(name, org.getName(), "Name parameter should be correct."); + assertEquals(telephone, org.getTelephone(), "Telephone parameter should be correct."); + assertEquals(location, org.getLocation(), "Location parameter should be correct."); + assertEquals(status, org.getStatus(), "Status parameter should be correct."); + } + +} \ No newline at end of file