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:
+ *
+ * - {@code HMH_DB_URL}
+ * - {@code HMH_DB_USERNAME}
+ * - {@code HMH_DB_PASSWORD}
+ *
+ *
+ *
+ * @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:
+ *
+ * - 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
+ */
+
+ 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