package dev.lions.unionflow.server.entity; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.lang.reflect.Method; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; @DisplayName("TransactionApproval") class TransactionApprovalTest { private static Organisation newOrganisation() { Organisation o = new Organisation(); o.setId(UUID.randomUUID()); return o; } private static ApproverAction approvedAction() { ApproverAction a = new ApproverAction(); a.setApproverId(UUID.randomUUID()); a.setApproverName("Approver Test"); a.setApproverRole("TRESORIER"); a.setDecision("APPROVED"); return a; } private static ApproverAction pendingAction() { ApproverAction a = new ApproverAction(); a.setApproverId(UUID.randomUUID()); a.setApproverName("Approver Pending"); a.setApproverRole("PRESIDENT"); a.setDecision("PENDING"); return a; } // ------------------------------------------------------------------------- // getRequiredApprovals // ------------------------------------------------------------------------- @Test @DisplayName("getRequiredApprovals: NONE renvoie 0") void getRequiredApprovals_none_returns0() { TransactionApproval ta = new TransactionApproval(); ta.setRequiredLevel("NONE"); assertThat(ta.getRequiredApprovals()).isEqualTo(0); } @Test @DisplayName("getRequiredApprovals: LEVEL1 renvoie 1") void getRequiredApprovals_level1_returns1() { TransactionApproval ta = new TransactionApproval(); ta.setRequiredLevel("LEVEL1"); assertThat(ta.getRequiredApprovals()).isEqualTo(1); } @Test @DisplayName("getRequiredApprovals: LEVEL2 renvoie 2") void getRequiredApprovals_level2_returns2() { TransactionApproval ta = new TransactionApproval(); ta.setRequiredLevel("LEVEL2"); assertThat(ta.getRequiredApprovals()).isEqualTo(2); } @Test @DisplayName("getRequiredApprovals: LEVEL3 renvoie 3") void getRequiredApprovals_level3_returns3() { TransactionApproval ta = new TransactionApproval(); ta.setRequiredLevel("LEVEL3"); assertThat(ta.getRequiredApprovals()).isEqualTo(3); } @Test @DisplayName("getRequiredApprovals: valeur inconnue renvoie 0 (default)") void getRequiredApprovals_default_returns0() { TransactionApproval ta = new TransactionApproval(); ta.setRequiredLevel("INVALID"); assertThat(ta.getRequiredApprovals()).isEqualTo(0); } // ------------------------------------------------------------------------- // countApprovals // ------------------------------------------------------------------------- @Test @DisplayName("countApprovals: aucun approbateur renvoie 0") void countApprovals_noApprovers_returns0() { TransactionApproval ta = TransactionApproval.builder() .transactionId(UUID.randomUUID()) .transactionType("CONTRIBUTION") .amount(new BigDecimal("5000.00")) .requesterId(UUID.randomUUID()) .requesterName("Test Requester") .requiredLevel("LEVEL1") .build(); assertThat(ta.countApprovals()).isEqualTo(0); } @Test @DisplayName("countApprovals: seuls les APPROVED sont comptés") void countApprovals_mixedDecisions_countsApprovedOnly() { TransactionApproval ta = TransactionApproval.builder() .transactionId(UUID.randomUUID()) .transactionType("DEPOSIT") .amount(new BigDecimal("10000.00")) .requesterId(UUID.randomUUID()) .requesterName("Requester") .requiredLevel("LEVEL2") .build(); ta.addApproverAction(approvedAction()); ta.addApproverAction(pendingAction()); ta.addApproverAction(approvedAction()); assertThat(ta.countApprovals()).isEqualTo(2); } // ------------------------------------------------------------------------- // hasAllApprovals // ------------------------------------------------------------------------- @Test @DisplayName("hasAllApprovals: NONE sans approbateur renvoie toujours true") void hasAllApprovals_none_alwaysTrue() { TransactionApproval ta = new TransactionApproval(); ta.setRequiredLevel("NONE"); assertThat(ta.hasAllApprovals()).isTrue(); } @Test @DisplayName("hasAllApprovals: LEVEL1 avec une approbation renvoie true") void hasAllApprovals_level1_oneApproval_true() { TransactionApproval ta = TransactionApproval.builder() .transactionId(UUID.randomUUID()) .transactionType("CONTRIBUTION") .amount(new BigDecimal("1000.00")) .requesterId(UUID.randomUUID()) .requesterName("Requester") .requiredLevel("LEVEL1") .build(); ta.addApproverAction(approvedAction()); assertThat(ta.hasAllApprovals()).isTrue(); } @Test @DisplayName("hasAllApprovals: LEVEL2 avec une seule approbation renvoie false") void hasAllApprovals_level2_onlyOneApproval_false() { TransactionApproval ta = TransactionApproval.builder() .transactionId(UUID.randomUUID()) .transactionType("WITHDRAWAL") .amount(new BigDecimal("50000.00")) .requesterId(UUID.randomUUID()) .requesterName("Requester") .requiredLevel("LEVEL2") .build(); ta.addApproverAction(approvedAction()); assertThat(ta.hasAllApprovals()).isFalse(); } // ------------------------------------------------------------------------- // addApproverAction // ------------------------------------------------------------------------- @Test @DisplayName("addApproverAction: ajoute l'action et positionne le parent") void addApproverAction_setsParentAndAdds() { TransactionApproval ta = TransactionApproval.builder() .transactionId(UUID.randomUUID()) .transactionType("CONTRIBUTION") .amount(new BigDecimal("2000.00")) .requesterId(UUID.randomUUID()) .requesterName("Requester") .requiredLevel("LEVEL1") .build(); ApproverAction action = pendingAction(); ta.addApproverAction(action); assertThat(ta.getApprovers()).hasSize(1); assertThat(action.getApproval()).isSameAs(ta); } // ------------------------------------------------------------------------- // isExpired // ------------------------------------------------------------------------- @Test @DisplayName("isExpired: expiresAt null renvoie false") void isExpired_expiresAtNull_returnsFalse() { TransactionApproval ta = new TransactionApproval(); ta.setExpiresAt(null); assertThat(ta.isExpired()).isFalse(); } @Test @DisplayName("isExpired: expiresAt dans le passé renvoie true") void isExpired_expiredInPast_returnsTrue() { TransactionApproval ta = new TransactionApproval(); ta.setExpiresAt(LocalDateTime.now().minusMinutes(1)); assertThat(ta.isExpired()).isTrue(); } @Test @DisplayName("isExpired: expiresAt dans le futur renvoie false") void isExpired_futureExpiry_returnsFalse() { TransactionApproval ta = new TransactionApproval(); ta.setExpiresAt(LocalDateTime.now().plusDays(1)); assertThat(ta.isExpired()).isFalse(); } // ------------------------------------------------------------------------- // isPending // ------------------------------------------------------------------------- @Test @DisplayName("isPending: statut PENDING renvoie true") void isPending_pending_returnsTrue() { TransactionApproval ta = new TransactionApproval(); ta.setStatus("PENDING"); assertThat(ta.isPending()).isTrue(); } @Test @DisplayName("isPending: statut APPROVED renvoie false") void isPending_approved_returnsFalse() { TransactionApproval ta = new TransactionApproval(); ta.setStatus("APPROVED"); assertThat(ta.isPending()).isFalse(); } // ------------------------------------------------------------------------- // isCompleted // ------------------------------------------------------------------------- @Test @DisplayName("isCompleted: statut VALIDATED renvoie true") void isCompleted_validated_returnsTrue() { TransactionApproval ta = new TransactionApproval(); ta.setStatus("VALIDATED"); assertThat(ta.isCompleted()).isTrue(); } @Test @DisplayName("isCompleted: statut REJECTED renvoie true") void isCompleted_rejected_returnsTrue() { TransactionApproval ta = new TransactionApproval(); ta.setStatus("REJECTED"); assertThat(ta.isCompleted()).isTrue(); } @Test @DisplayName("isCompleted: statut CANCELLED renvoie true") void isCompleted_cancelled_returnsTrue() { TransactionApproval ta = new TransactionApproval(); ta.setStatus("CANCELLED"); assertThat(ta.isCompleted()).isTrue(); } @Test @DisplayName("isCompleted: statut PENDING renvoie false") void isCompleted_pending_returnsFalse() { TransactionApproval ta = new TransactionApproval(); ta.setStatus("PENDING"); assertThat(ta.isCompleted()).isFalse(); } // ------------------------------------------------------------------------- // onCreate (réflexion) // ------------------------------------------------------------------------- @Test @DisplayName("onCreate: initialise createdAt, currency, status, expiresAt si null") void onCreate_initializesNullFields() throws Exception { TransactionApproval ta = new TransactionApproval(); ta.setCreatedAt(null); ta.setCurrency(null); ta.setStatus(null); ta.setExpiresAt(null); Method onCreate = TransactionApproval.class.getDeclaredMethod("onCreate"); onCreate.setAccessible(true); onCreate.invoke(ta); assertThat(ta.getCreatedAt()).isNotNull(); assertThat(ta.getCurrency()).isEqualTo("XOF"); assertThat(ta.getStatus()).isEqualTo("PENDING"); assertThat(ta.getExpiresAt()).isNotNull(); // expiresAt doit être ~7 jours après createdAt assertThat(ta.getExpiresAt()).isAfter(ta.getCreatedAt()); assertThat(ta.getExpiresAt()).isBeforeOrEqualTo(ta.getCreatedAt().plusDays(7).plusSeconds(1)); } @Test @DisplayName("onCreate: ne remplace pas createdAt si déjà renseigné") void onCreate_doesNotOverrideExistingCreatedAt() throws Exception { LocalDateTime existingDate = LocalDateTime.of(2025, 3, 10, 9, 0); LocalDateTime existingExpiry = LocalDateTime.of(2025, 3, 17, 9, 0); TransactionApproval ta = new TransactionApproval(); ta.setCreatedAt(existingDate); ta.setCurrency("EUR"); ta.setStatus("APPROVED"); ta.setExpiresAt(existingExpiry); Method onCreate = TransactionApproval.class.getDeclaredMethod("onCreate"); onCreate.setAccessible(true); onCreate.invoke(ta); assertThat(ta.getCreatedAt()).isEqualTo(existingDate); assertThat(ta.getCurrency()).isEqualTo("EUR"); assertThat(ta.getStatus()).isEqualTo("APPROVED"); assertThat(ta.getExpiresAt()).isEqualTo(existingExpiry); } }