diff --git a/src/main/java/edu/group5/app/model/DBRepository.java b/src/main/java/edu/group5/app/model/DBRepository.java
new file mode 100644
index 0000000..1196eae
--- /dev/null
+++ b/src/main/java/edu/group5/app/model/DBRepository.java
@@ -0,0 +1,25 @@
+package edu.group5.app.model;
+
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Abstract base class for repositories that store their data
+ * in a database-related structure.
+ *
+ *
+ * Extends {@link Repository} and specifies that the content
+ * is stored as a {@link Map}.
+ *
+ */
+public abstract class DBRepository extends Repository {
+ /**
+ * Constructs a DBRepository with the given content.
+ *
+ * @param content the HashMap used to store repository entities
+ */
+ protected DBRepository(Map content) {
+ super(content);
+ }
+}
diff --git a/src/main/java/edu/group5/app/model/Repository.java b/src/main/java/edu/group5/app/model/Repository.java
index 6850bfe..5396b2f 100644
--- a/src/main/java/edu/group5/app/model/Repository.java
+++ b/src/main/java/edu/group5/app/model/Repository.java
@@ -3,11 +3,15 @@
import java.util.Map;
/**
- * Represents a repository
+ * Represents a repository.
*/
public abstract class Repository {
protected final Map content;
-
+ /**
+ * Constructs a new Repository with the specified content.
+ *
+ * @param content the underlying data structure used to store entities
+ */
public Repository(Map content) {
this.content = content;
}
diff --git a/src/main/java/edu/group5/app/model/donation/Donation.java b/src/main/java/edu/group5/app/model/donation/Donation.java
index a127439..92adfbe 100644
--- a/src/main/java/edu/group5/app/model/donation/Donation.java
+++ b/src/main/java/edu/group5/app/model/donation/Donation.java
@@ -1,5 +1,53 @@
package edu.group5.app.model.donation;
-public class Donation {
-
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+
+/**
+ * Represents a verified donation made by a user to an organization.
+ * @param donationId - the unique ID of this donation
+ * @param userId - the ID of the user making the donation
+ * @param organizationId - the ID of the organization receiving the donation
+ * @param amount - the donation amount
+ * @param date - the timestamp when the donation was made
+ * @param paymentMethod - the payment method used
+ */
+public record Donation(
+ int donationId,
+ int userId,
+ int organizationId,
+ BigDecimal amount,
+ Timestamp date,
+ String paymentMethod) {
+
+ /**
+ * Constructor with throws.
+ *
+ * @param donationId - throws if donationID is negative or 0.
+ * @param userId - throws if userID is negative or 0.
+ * @param organizationId - throws if organizationID is negative or 0.
+ * @param amount - throws if amount is negative or null.
+ * @param date - throws if date is null.
+ * @param paymentMethod - throws if payment is null or blank.
+ */
+ public Donation {
+ if (donationId <= 0) {
+ throw new IllegalArgumentException("Donation ID must be positive");
+ }
+ if (userId <= 0) {
+ throw new IllegalArgumentException("User ID must be positive");
+ }
+ if (organizationId <= 0) {
+ throw new IllegalArgumentException("Organization ID must be positive");
+ }
+ if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
+ throw new IllegalArgumentException("Amount must be positive and not null");
+ }
+ if (date == null) {
+ throw new IllegalArgumentException("Date must not be null");
+ }
+ if (paymentMethod == null || paymentMethod.isBlank()) {
+ throw new IllegalArgumentException("Payment method must not be empty");
+ }
+ }
}
diff --git a/src/main/java/edu/group5/app/model/donation/DonationRepository.java b/src/main/java/edu/group5/app/model/donation/DonationRepository.java
new file mode 100644
index 0000000..339e7ca
--- /dev/null
+++ b/src/main/java/edu/group5/app/model/donation/DonationRepository.java
@@ -0,0 +1,119 @@
+package edu.group5.app.model.donation;
+
+import edu.group5.app.model.DBRepository;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Repository class for Donation.
+ *
+ *
+ * Extends {@link DBRepository} and manages Donation entities.
+ *
+ */
+public class DonationRepository extends DBRepository {
+ private final HashMap content;
+
+ /**
+ * Constructs DonationRepository using Hashmap,
+ * and extends the content from DBRepository.
+ * @param content the underlying map used to store donations,
+ * where the key represents the donation ID
+ */
+ public DonationRepository(HashMap content){
+ if (content == null) {
+ throw new IllegalArgumentException("Content cannot be null");
+ }
+ super(content);
+ this.content = content;
+ }
+
+ /**
+ * Adds a new donation to the repository
+ *
+ * The donation is stored using its {@code donationId} as the key.
+ * If a donation with the same ID already exists, the donation
+ * will not be added.
+ *
+ *
+ * @param donation the donation to add
+ * @return {@code true} if the donation was successfully added, and
+ * {@code false} if a donation with the same ID already exists
+ */
+ public boolean addDonation(Donation donation) {
+ if (donation == null) {
+ throw new IllegalArgumentException("Donation cannot be null");
+ }
+ if (content.containsKey(donation.donationId())){
+ return false;
+ }
+ content.put(donation.donationId(), donation);
+ return true;
+ }
+
+ /**
+ * Returns all donations sorted by date (ascending).
+ *
+ *
+ * The returned map preserves the sorted order.
+ *
+ *
+ * @return a new {@link HashMap} containing the donations sorted by date
+ */
+ public HashMap sortByDate(){
+ return content.entrySet().stream()
+ .sorted(Map.Entry.comparingByValue(
+ Comparator.comparing(Donation::date)))
+ .collect(Collectors.toMap(
+ Map.Entry::getKey,
+ Map.Entry::getValue,
+ (e1, e2) -> e1,
+ LinkedHashMap::new));
+ }
+
+ /**
+ * Returns all donations sorted by amount (ascending).
+ *
+ *
+ * The returned map preserves the sorted order from lowest to highest amount.
+ *
+ *
+ * @return a new {@link HashMap} containing the donations sorted by amount.
+ */
+ public HashMap sortByAmount() {
+ return content.entrySet().stream()
+ .sorted(Map.Entry.comparingByValue(
+ Comparator.comparing(Donation::amount)))
+ .collect(Collectors.toMap(
+ Map.Entry::getKey,
+ Map.Entry::getValue,
+ (e1, e2) -> e1,
+ LinkedHashMap::new
+ ));
+ }
+
+ /**
+ * Returns all donations associated with a specific organization.
+ *
+ * @param orgNumber the organization ID to filter by
+ * @return a map containing all donations that belong to the given organization
+ */
+ public HashMap filterByOrganization(int orgNumber) {
+ if (orgNumber <= 0) {
+ throw new IllegalArgumentException("Organization number must be positive");
+ }
+ return content.entrySet()
+ .stream()
+ .filter(entry -> entry.getValue().organizationId() == orgNumber)
+ .collect(Collectors.toMap(
+ Map.Entry::getKey,
+ Map.Entry::getValue,
+ (e1, e2) -> e1,
+ LinkedHashMap::new
+ ));
+ }
+}
diff --git a/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java b/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java
new file mode 100644
index 0000000..b55ef57
--- /dev/null
+++ b/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java
@@ -0,0 +1,188 @@
+package edu.group5.app.model.donation;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+import java.util.HashMap;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class DonationRepositoryTest {
+ private DonationRepository repo;
+ private Timestamp now;
+ private Donation donation1;
+ private Donation donation2;
+ private Donation donation3;
+ private Donation donation4;
+
+ @BeforeEach
+ void setUp() {
+ repo = new DonationRepository(new HashMap<>());
+
+ Timestamp ts1 = Timestamp.valueOf("2025-01-01 10:00:00");
+ Timestamp ts2 = Timestamp.valueOf("2025-01-01 10:00:01");
+ now = new Timestamp(System.currentTimeMillis());
+
+ donation1 = new Donation(1, 102, 202, new BigDecimal("500.0"), ts1, "Card");
+ donation2 = new Donation(2, 102, 202, new BigDecimal("500.0"), ts2, "PayPal");
+ donation3 = new Donation(3, 103, 203, new BigDecimal("200.0"), now, "PayPal");
+ donation4 = new Donation(1, 101, 201, new BigDecimal("500.0"), now, "Card");
+
+
+ }
+ @Test
+ void testThrowsExceptionIfContentIsNull() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+ () -> new DonationRepository(null));
+ assertEquals("Content cannot be null", thrown.getMessage());
+ }
+
+ @Test
+ void testAddDonation() {
+ assertTrue(repo.addDonation(donation1));
+ assertEquals(1, repo.getContent().size());
+
+ assertTrue(repo.addDonation(donation3));
+ assertEquals(2, repo.getContent().size());
+ }
+ @Test
+ void testAddDonationWithDuplicateId() {
+ assertTrue(repo.addDonation(donation1));
+ assertFalse(repo.addDonation(donation4));
+ assertEquals(1, repo.getContent().size());
+ }
+ @Test
+ void testAddNullDonation() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+ () -> repo.addDonation(null));
+ assertEquals("Donation cannot be null",thrown.getMessage());
+ }
+
+ @Test
+ void testSortByDate() {
+ repo.addDonation(donation3);
+ repo.addDonation(donation1);
+ repo.addDonation(donation2);
+
+ HashMap sorted = repo.sortByDate();
+
+ Integer[] keys = sorted.keySet().toArray(new Integer[0]);
+ assertEquals(1, keys[0]);
+ assertEquals(2, keys[1]);
+ assertEquals(3, keys[2]);
+
+ }
+ @Test
+ void testSortByDateWithSameDate() {
+
+ repo.addDonation(donation3);
+ repo.addDonation(donation4);
+
+ HashMap sorted = repo.sortByDate();
+
+ assertEquals(2, sorted.size());
+ assertTrue(sorted.containsKey(1));
+ assertTrue(sorted.containsKey(3));
+ }
+ @Test
+ void testSortByDateWithSameDonation() {
+ Timestamp sameDate = Timestamp.valueOf("2025-01-01 10:00:00");
+
+ Donation d1 = new Donation(1, 101, 201,
+ new BigDecimal("500.0"), sameDate, "Card");
+
+ repo.addDonation(d1);
+ repo.addDonation(donation4);
+
+ HashMap sorted = repo.sortByDate();
+
+ assertEquals(1, sorted.size());
+ assertTrue(sorted.containsKey(1));
+ assertFalse(sorted.containsKey(2));
+ }
+ @Test
+ void TestSortByAmountTest() {
+ repo.addDonation(donation2);
+ repo.addDonation(donation3);
+
+ HashMap sorted = repo.sortByAmount();
+ Integer[] keys = sorted.keySet().toArray(new Integer[0]);
+
+ assertEquals(3, keys[0]);
+ assertEquals(2, keys[1]);
+
+
+ }
+ @Test
+ void testSortByAmountWithSameAmount() {
+ repo.addDonation(donation1);
+ repo.addDonation(donation2);
+
+ HashMap sorted = repo.sortByAmount();
+
+ assertEquals(2, sorted.size());
+ assertTrue(sorted.containsKey(1));
+ assertTrue(sorted.containsKey(2));
+ }
+ @Test
+ void testSortByAmountWithSameDonation() {
+
+ Donation d1 = new Donation(1, 101, 201,
+ new BigDecimal("500.0"), now, "Card");
+
+ repo.addDonation(d1);
+ repo.addDonation(donation4);
+
+ HashMap sorted = repo.sortByAmount();
+
+ assertEquals(1, sorted.size());
+ assertTrue(sorted.containsKey(1));
+ assertFalse(sorted.containsKey(2));
+ }
+ @Test
+ void testFilterByOrganization() {
+ repo.addDonation(donation1);
+ repo.addDonation(donation2);
+
+ HashMap filtered = repo.filterByOrganization(202);
+
+ assertEquals(2, filtered.size());
+ assertTrue(filtered.containsKey(1));
+ assertTrue(filtered.containsKey(2));
+ assertFalse(filtered.containsKey(3));
+ }
+ @Test
+ void testFilterByOrganizationNoMatch() {
+ repo.addDonation(donation1);
+ repo.addDonation(donation2);
+
+ HashMap filtered = repo.filterByOrganization(999);
+
+ assertTrue(filtered.isEmpty());
+ }
+ @Test
+ void testThrowsExceptionWhenOrgNumberIsNegative() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+ () -> repo.filterByOrganization(-1));
+ assertEquals("Organization number must be positive",thrown.getMessage());
+ }
+ @Test
+ void testFilterByOrganizationWithSameDonation() {
+ Timestamp sameDate = Timestamp.valueOf("2025-01-01 10:00:00");
+
+ Donation d1 = new Donation(1, 101, 201,
+ new BigDecimal("500.0"), sameDate, "Card");
+
+ repo.addDonation(d1);
+ repo.addDonation(donation4);
+
+ HashMap sorted = repo.filterByOrganization(201);
+
+ assertEquals(1, sorted.size());
+ assertTrue(sorted.containsKey(1));
+ assertFalse(sorted.containsKey(2));
+ }
+
+}
diff --git a/src/test/java/edu/group5/app/model/donation/DonationTest.java b/src/test/java/edu/group5/app/model/donation/DonationTest.java
index a905209..f8f9069 100644
--- a/src/test/java/edu/group5/app/model/donation/DonationTest.java
+++ b/src/test/java/edu/group5/app/model/donation/DonationTest.java
@@ -1,5 +1,99 @@
package edu.group5.app.model.donation;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
public class DonationTest {
-
+
+ private String expectedMessage;
+ private int donationId1;
+ private int userId1;
+ private int organizationId1;
+ private BigDecimal amount1;
+ private Timestamp date1;
+ private String paymentMethod1;
+ private Donation donation;
+
+ @BeforeEach
+ void setUp(){
+ donationId1 = 1;
+ userId1 = 101;
+ organizationId1 = 202;
+ amount1 = new BigDecimal("500.0");
+ date1 = new Timestamp(System.currentTimeMillis());
+ paymentMethod1 = "Card";
+ donation = new Donation(donationId1, userId1,
+ organizationId1, amount1, date1, paymentMethod1);
+
+ }
+
+ private void exceptionTest(int donationId, int userId,
+ int organizationId, BigDecimal amount,
+ Timestamp date, String paymentMethod) {
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class, () -> new Donation(
+ donationId, userId, organizationId, amount, date, paymentMethod)
+ );
+ assertEquals(expectedMessage, exception.getMessage());
+ }
+
+ @Test
+ void testIfDonationGetsDonationValues() {
+
+ assertEquals(1, donation.donationId());
+ assertEquals(101, donation.userId());
+ assertEquals(202, donation.organizationId());
+ assertEquals(new BigDecimal("500.0"), donation.amount());
+ assertEquals(date1, donation.date());
+ assertEquals("Card", donation.paymentMethod());
+ }
+
+ @Test
+ void testIfThrowsExceptionWhenDonationIdIsNotPositive() {
+ expectedMessage = "Donation ID must be positive";
+ exceptionTest(0, userId1, organizationId1,
+ amount1, date1, paymentMethod1);
+ }
+
+ @Test
+ void testIfThrowsExceptionWhenUserIdIsNotPositive() {
+ expectedMessage = "User ID must be positive";
+ exceptionTest(donationId1, -1, organizationId1,
+ amount1, date1, paymentMethod1);
+ }
+ @Test
+ void testIfThrowsExceptionWhenOrganizationIdIsNotPositive() {
+ expectedMessage = "Organization ID must be positive";
+ exceptionTest(donationId1, userId1, 0,
+ amount1, date1, paymentMethod1);
+ }
+ @Test
+ void testIfThrowsExceptionWhenAmountIsNotPositive() {
+ expectedMessage = "Amount must be positive and not null";
+ exceptionTest(donationId1, userId1, organizationId1,
+ new BigDecimal("0.00"), date1, paymentMethod1);
+ }
+ @Test
+ void testIfThrowsExceptionWhenDateIsNull() {
+ expectedMessage = "Date must not be null";
+ exceptionTest(donationId1, userId1, organizationId1,
+ amount1, null, paymentMethod1);
+ }
+ @Test
+ void testIfThrowsExceptionWhenPaymentMethodIsNullOrEmpty() {
+ expectedMessage = "Payment method must not be empty";
+ exceptionTest(donationId1, userId1, organizationId1,
+ amount1, date1, null);
+ exceptionTest(donationId1, userId1, organizationId1,
+ amount1, date1, " ");
+
+ }
+
+
}