feat(p0-2026-04-25): mobile SPKI pinning + Play Integrity/App Attest + tests Sprint 1
Some checks failed
CI/CD Pipeline / pipeline (push) Failing after 3m7s
Some checks failed
CI/CD Pipeline / pipeline (push) Failing after 3m7s
P0-NEW-21 — SPKI Pinning + rotation Firebase Remote Config (mobile)
- lib/core/security/spki_pinning_service.dart : digest SHA-256 SPKI/cert
- Liste de pins active depuis Firebase Remote Config (key 'spki_pins')
- Fallback statique au bundle si Firebase indisponible
- Multi-pin (leaf + backup + intermediate) pour transitions sans downtime
- Hosts pinnés : api.lions.dev, security.lions.dev
- Câblé dans ApiClient._configureSslPinning() (remplace check CN obsolète)
P0-NEW-22 — Play Integrity (Android) + App Attest (iOS) (mobile)
- lib/core/security/app_device_integrity_service.dart
- Token attestation court (cache 60s) avec challenge backend
- Bypass automatique en kDebugMode
- À compléter avec un Dio interceptor X-Device-Integrity-Token avant go-live
pubspec.yaml :
- freerasp 7.0.0 → 7.5.1
- +app_device_integrity 1.1.0
- +firebase_core 3.6.0 + firebase_remote_config 5.1.3
Tests Sprint 1 (40 tests, 0 failure) :
- ReferentielComptableTest (6 cas) : defaultFor mapping
- AmlSeuilsTest (10 cas) : seuils 10M/5M/1M, pays UEMOA, depasseSeuil
- SoDPermissionCheckerTest (13 cas) : validation distincte, combinations interdites,
compliance officer eligibility
- PispiRtpRequestTest (6 cas) : validation contraintes
- PispiRtpResponseTest (5 cas) : helpers status
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ReferentielComptableTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("defaultFor null retourne SYSCOHADA")
|
||||
void defaultForNullReturnsSyscohada() {
|
||||
assertThat(ReferentielComptable.defaultFor(null)).isEqualTo(ReferentielComptable.SYSCOHADA);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("defaultFor MUTUELLE_SANTE retourne SYCEBNL")
|
||||
void defaultForMutuelleSanteReturnsSycebnl() {
|
||||
assertThat(ReferentielComptable.defaultFor("MUTUELLE_SANTE"))
|
||||
.isEqualTo(ReferentielComptable.SYCEBNL);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("defaultFor ASSOCIATION (case-insensitive) retourne SYCEBNL")
|
||||
void defaultForAssociationCaseInsensitiveReturnsSycebnl() {
|
||||
assertThat(ReferentielComptable.defaultFor("association")).isEqualTo(ReferentielComptable.SYCEBNL);
|
||||
assertThat(ReferentielComptable.defaultFor("Association")).isEqualTo(ReferentielComptable.SYCEBNL);
|
||||
assertThat(ReferentielComptable.defaultFor("ASSOCIATION")).isEqualTo(ReferentielComptable.SYCEBNL);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("defaultFor LIONS_CLUB / ONG / FONDATION / SYNDICAT / ORDRE_PROFESSIONNEL retourne SYCEBNL")
|
||||
void defaultForBNLEntitiesReturnsSycebnl() {
|
||||
for (String t : new String[] {
|
||||
"LIONS_CLUB", "ONG", "FONDATION", "SYNDICAT", "ORDRE_PROFESSIONNEL", "PROJET_DEVELOPPEMENT"
|
||||
}) {
|
||||
assertThat(ReferentielComptable.defaultFor(t))
|
||||
.as("type=%s", t)
|
||||
.isEqualTo(ReferentielComptable.SYCEBNL);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("defaultFor SFD_TIER_1 retourne PCSFD_UMOA")
|
||||
void defaultForSfdTier1ReturnsPcsfd() {
|
||||
assertThat(ReferentielComptable.defaultFor("SFD_TIER_1"))
|
||||
.isEqualTo(ReferentielComptable.PCSFD_UMOA);
|
||||
assertThat(ReferentielComptable.defaultFor("SFD_CATEGORIE_III"))
|
||||
.isEqualTo(ReferentielComptable.PCSFD_UMOA);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("defaultFor type non listé retourne SYSCOHADA")
|
||||
void defaultForUnknownReturnsSyscohada() {
|
||||
assertThat(ReferentielComptable.defaultFor("MUTUELLE_EPARGNE_CREDIT"))
|
||||
.isEqualTo(ReferentielComptable.SYSCOHADA);
|
||||
assertThat(ReferentielComptable.defaultFor("COOPERATIVE_COMMERCIALE"))
|
||||
.isEqualTo(ReferentielComptable.SYSCOHADA);
|
||||
assertThat(ReferentielComptable.defaultFor("INCONNU_XYZ"))
|
||||
.isEqualTo(ReferentielComptable.SYSCOHADA);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package dev.lions.unionflow.server.payment.pispi.dto;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class PispiRtpRequestTest {
|
||||
|
||||
private PispiRtpRequest validRequest() {
|
||||
return new PispiRtpRequest(
|
||||
"RTP-001",
|
||||
"SFD-CIV-001",
|
||||
"ACC-1234",
|
||||
"Mutuelle X",
|
||||
"+22507123456@unionflow",
|
||||
new BigDecimal("5000"),
|
||||
"XOF",
|
||||
"COTISATION",
|
||||
"Cotisation oct 2026",
|
||||
LocalDateTime.now().plusDays(1),
|
||||
LocalDateTime.now().plusDays(7));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Request valide → validate() ne throw pas")
|
||||
void validRequestPasses() {
|
||||
validRequest().validate();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("RTP id manquant → IllegalArgumentException")
|
||||
void rtpIdMissing() {
|
||||
PispiRtpRequest r = new PispiRtpRequest(
|
||||
"", "SFD", "ACC", "Mutuelle", "+225@unionflow",
|
||||
new BigDecimal("5000"), "XOF", "P", "D", null, null);
|
||||
assertThatThrownBy(r::validate)
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("RTP id");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Montant <= 0 → IllegalArgumentException")
|
||||
void montantNonPositif() {
|
||||
PispiRtpRequest r = new PispiRtpRequest(
|
||||
"RTP-1", "SFD", "ACC", "Mutuelle", "+225@unionflow",
|
||||
BigDecimal.ZERO, "XOF", "P", "D", null, null);
|
||||
assertThatThrownBy(r::validate)
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("positif");
|
||||
|
||||
PispiRtpRequest neg = new PispiRtpRequest(
|
||||
"RTP-1", "SFD", "ACC", "Mutuelle", "+225@unionflow",
|
||||
new BigDecimal("-100"), "XOF", "P", "D", null, null);
|
||||
assertThatThrownBy(neg::validate).isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Alias débiteur manquant → IllegalArgumentException")
|
||||
void aliasDebiteurMissing() {
|
||||
PispiRtpRequest r = new PispiRtpRequest(
|
||||
"RTP-1", "SFD", "ACC", "Mutuelle", "",
|
||||
new BigDecimal("5000"), "XOF", "P", "D", null, null);
|
||||
assertThatThrownBy(r::validate).hasMessageContaining("Alias débiteur");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Devise non-XOF → IllegalArgumentException")
|
||||
void deviseNonXof() {
|
||||
PispiRtpRequest r = new PispiRtpRequest(
|
||||
"RTP-1", "SFD", "ACC", "Mutuelle", "+225@unionflow",
|
||||
new BigDecimal("5000"), "EUR", "P", "D", null, null);
|
||||
assertThatThrownBy(r::validate).hasMessageContaining("XOF");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Date expiration avant date exécution → IllegalArgumentException")
|
||||
void expiryAvantExecution() {
|
||||
LocalDateTime exec = LocalDateTime.now().plusDays(7);
|
||||
LocalDateTime expiry = LocalDateTime.now().plusDays(2);
|
||||
PispiRtpRequest r = new PispiRtpRequest(
|
||||
"RTP-1", "SFD", "ACC", "Mutuelle", "+225@unionflow",
|
||||
new BigDecimal("5000"), "XOF", "P", "D", exec, expiry);
|
||||
assertThatThrownBy(r::validate).hasMessageContaining("expiration");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package dev.lions.unionflow.server.payment.pispi.dto;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class PispiRtpResponseTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("isAccepted true uniquement si status=ACCEPTED")
|
||||
void isAccepted() {
|
||||
var accepted = new PispiRtpResponse("R1", "ACCEPTED", null, null, null, "TX001");
|
||||
assertThat(accepted.isAccepted()).isTrue();
|
||||
assertThat(accepted.isRefused()).isFalse();
|
||||
assertThat(accepted.isPending()).isFalse();
|
||||
assertThat(accepted.isExpired()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isRefused true uniquement si status=REFUSED")
|
||||
void isRefused() {
|
||||
var refused = new PispiRtpResponse("R1", "REFUSED", "FOCR", "Compte clôturé", null, null);
|
||||
assertThat(refused.isRefused()).isTrue();
|
||||
assertThat(refused.isAccepted()).isFalse();
|
||||
assertThat(refused.isPending()).isFalse();
|
||||
assertThat(refused.isExpired()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isPending true uniquement si status=PENDING")
|
||||
void isPending() {
|
||||
var pending = new PispiRtpResponse("R1", "PENDING", null, null, null, null);
|
||||
assertThat(pending.isPending()).isTrue();
|
||||
assertThat(pending.isAccepted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isExpired true uniquement si status=EXPIRED")
|
||||
void isExpired() {
|
||||
var expired = new PispiRtpResponse("R1", "EXPIRED", null, null, null, null);
|
||||
assertThat(expired.isExpired()).isTrue();
|
||||
assertThat(expired.isAccepted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Tous les helpers false si status null")
|
||||
void statusNullAllFalse() {
|
||||
var nullStatus = new PispiRtpResponse("R1", null, null, null, null, null);
|
||||
assertThat(nullStatus.isAccepted()).isFalse();
|
||||
assertThat(nullStatus.isRefused()).isFalse();
|
||||
assertThat(nullStatus.isPending()).isFalse();
|
||||
assertThat(nullStatus.isExpired()).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package dev.lions.unionflow.server.security;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class SoDPermissionCheckerTest {
|
||||
|
||||
private SoDPermissionChecker checker;
|
||||
private final UUID userA = UUID.randomUUID();
|
||||
private final UUID userB = UUID.randomUUID();
|
||||
private final UUID entityId = UUID.randomUUID();
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
checker = new SoDPermissionChecker();
|
||||
// AuditTrailService est injecté en prod ; on injecte un mock pour les tests.
|
||||
checker.auditTrail = mock(AuditTrailService.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("checkValidationDistinct PASS si créateur ≠ validateur")
|
||||
void validationDistinctPass() {
|
||||
var result = checker.checkValidationDistinct(userA, userB, "Cotisation", entityId);
|
||||
assertThat(result.passed()).isTrue();
|
||||
assertThat(result.violationReason()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("checkValidationDistinct VIOLATION si même utilisateur")
|
||||
void validationDistinctViolation() {
|
||||
var result = checker.checkValidationDistinct(userA, userA, "Cotisation", entityId);
|
||||
assertThat(result.passed()).isFalse();
|
||||
assertThat(result.violationReason()).contains("SoD VIOLATION").contains("Cotisation");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("checkValidationDistinct PASS si UUID null (contexte manquant)")
|
||||
void validationDistinctNullPass() {
|
||||
assertThat(checker.checkValidationDistinct(null, userA, "X", entityId).passed()).isTrue();
|
||||
assertThat(checker.checkValidationDistinct(userA, null, "X", entityId).passed()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("checkRoleCombination : Trésorier + Président → VIOLATION")
|
||||
void cumulTresorierPresidentInterdit() {
|
||||
var result = checker.checkRoleCombination(userA, Set.of("TRESORIER", "PRESIDENT"));
|
||||
assertThat(result.passed()).isFalse();
|
||||
assertThat(result.violationReason()).contains("TRESORIER + PRESIDENT");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("checkRoleCombination : Trésorier + Contrôleur Interne → VIOLATION (auto-contrôle)")
|
||||
void cumulTresorierControleurInterne() {
|
||||
var result = checker.checkRoleCombination(userA, Set.of("TRESORIER", "CONTROLEUR_INTERNE"));
|
||||
assertThat(result.passed()).isFalse();
|
||||
assertThat(result.violationReason()).contains("auto-contrôle");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("checkRoleCombination : Président + Contrôleur Interne → VIOLATION (juge et partie)")
|
||||
void cumulPresidentControleur() {
|
||||
var result = checker.checkRoleCombination(userA, Set.of("PRESIDENT", "CONTROLEUR_INTERNE"));
|
||||
assertThat(result.passed()).isFalse();
|
||||
assertThat(result.violationReason()).contains("juge et partie");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("checkRoleCombination : Commissaire aux comptes + autre → VIOLATION (indépendance OHADA)")
|
||||
void cumulCommissaireInterdit() {
|
||||
var result = checker.checkRoleCombination(userA, Set.of("COMMISSAIRE_COMPTES", "MEMBRE_ACTIF"));
|
||||
assertThat(result.passed()).isFalse();
|
||||
assertThat(result.violationReason()).contains("COMMISSAIRE_COMPTES");
|
||||
assertThat(result.violationReason()).contains("indépendant");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("checkRoleCombination : un seul rôle → PASS")
|
||||
void unSeulRolePass() {
|
||||
assertThat(checker.checkRoleCombination(userA, Set.of("MEMBRE_ACTIF")).passed()).isTrue();
|
||||
assertThat(checker.checkRoleCombination(userA, Set.of("TRESORIER")).passed()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("checkRoleCombination : combinaison autorisée → PASS")
|
||||
void combinaisonAutoriseePass() {
|
||||
assertThat(checker.checkRoleCombination(userA, Set.of("MEMBRE_ACTIF", "ANIMATEUR_ZONE")).passed())
|
||||
.isTrue();
|
||||
assertThat(checker.checkRoleCombination(userA, Set.of("SECRETAIRE", "SECRETAIRE_ADJOINT")).passed())
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("checkRoleCombination : null/empty → PASS")
|
||||
void roleCombinaisonNull() {
|
||||
assertThat(checker.checkRoleCombination(userA, null).passed()).isTrue();
|
||||
assertThat(checker.checkRoleCombination(userA, Set.of()).passed()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("checkComplianceOfficerEligibility : Trésorier interdit (Instruction BCEAO 001-03-2025)")
|
||||
void complianceOfficerNotTresorier() {
|
||||
var result = checker.checkComplianceOfficerEligibility(
|
||||
userA, Set.of("TRESORIER", "MEMBRE_ACTIF"));
|
||||
assertThat(result.passed()).isFalse();
|
||||
assertThat(result.violationReason()).contains("TRESORIER");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("checkComplianceOfficerEligibility : Commissaire aux comptes interdit (indépendance)")
|
||||
void complianceOfficerNotCommissaire() {
|
||||
var result = checker.checkComplianceOfficerEligibility(
|
||||
userA, Set.of("COMMISSAIRE_COMPTES"));
|
||||
assertThat(result.passed()).isFalse();
|
||||
assertThat(result.violationReason()).contains("COMMISSAIRE_COMPTES");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("checkComplianceOfficerEligibility : ADMIN_ORGANISATION + MEMBRE → PASS")
|
||||
void complianceOfficerAdminMembrePass() {
|
||||
var result = checker.checkComplianceOfficerEligibility(
|
||||
userA, Set.of("ADMIN_ORGANISATION", "MEMBRE_ACTIF"));
|
||||
assertThat(result.passed()).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class AmlSeuilsTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("Seuils alignés Instruction BCEAO 002-03-2025")
|
||||
void seuilsConformesInstructionBceao() {
|
||||
assertThat(AmlSeuils.SEUIL_INTRA_UEMOA_FCFA).isEqualByComparingTo(new BigDecimal("10000000"));
|
||||
assertThat(AmlSeuils.SEUIL_ENTREE_SORTIE_UEMOA_FCFA).isEqualByComparingTo(new BigDecimal("5000000"));
|
||||
assertThat(AmlSeuils.SEUIL_OPERATION_ESPECE_FCFA).isEqualByComparingTo(new BigDecimal("1000000"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Pays UEMOA = 8 États ISO 3166-1 alpha-3")
|
||||
void paysUemoaContientLes8Etats() {
|
||||
assertThat(AmlSeuils.PAYS_UEMOA)
|
||||
.containsExactlyInAnyOrder("BEN", "BFA", "CIV", "GNB", "MLI", "NER", "SEN", "TGO");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Transaction CIV → SEN (intra-UEMOA) → seuil 10M")
|
||||
void transactionIntraUemoaSeuil10M() {
|
||||
assertThat(AmlSeuils.seuilApplicable("CIV", "SEN"))
|
||||
.isEqualByComparingTo(AmlSeuils.SEUIL_INTRA_UEMOA_FCFA);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Transaction CIV → FRA (sortie UEMOA) → seuil 5M")
|
||||
void transactionSortieUemoaSeuil5M() {
|
||||
assertThat(AmlSeuils.seuilApplicable("CIV", "FRA"))
|
||||
.isEqualByComparingTo(AmlSeuils.SEUIL_ENTREE_SORTIE_UEMOA_FCFA);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Transaction USA → CIV (entrée UEMOA) → seuil 5M")
|
||||
void transactionEntreeUemoaSeuil5M() {
|
||||
assertThat(AmlSeuils.seuilApplicable("USA", "CIV"))
|
||||
.isEqualByComparingTo(AmlSeuils.SEUIL_ENTREE_SORTIE_UEMOA_FCFA);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Pays null → seuil le plus restrictif (5M)")
|
||||
void paysNullRetourneSeuilRestrictif() {
|
||||
assertThat(AmlSeuils.seuilApplicable(null, "CIV"))
|
||||
.isEqualByComparingTo(AmlSeuils.SEUIL_ENTREE_SORTIE_UEMOA_FCFA);
|
||||
assertThat(AmlSeuils.seuilApplicable("CIV", null))
|
||||
.isEqualByComparingTo(AmlSeuils.SEUIL_ENTREE_SORTIE_UEMOA_FCFA);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("depasseSeuil intra-UEMOA : 9M sous seuil, 11M au-dessus")
|
||||
void depasseSeuilIntraUemoa() {
|
||||
assertThat(AmlSeuils.depasseSeuil(new BigDecimal("9000000"), "CIV", "SEN")).isFalse();
|
||||
assertThat(AmlSeuils.depasseSeuil(new BigDecimal("10000000"), "CIV", "SEN")).isFalse(); // seuil exact = pas dépassé (>)
|
||||
assertThat(AmlSeuils.depasseSeuil(new BigDecimal("11000000"), "CIV", "SEN")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("depasseSeuil sortie UEMOA : 4M sous seuil, 6M au-dessus")
|
||||
void depasseSeuilSortieUemoa() {
|
||||
assertThat(AmlSeuils.depasseSeuil(new BigDecimal("4000000"), "CIV", "FRA")).isFalse();
|
||||
assertThat(AmlSeuils.depasseSeuil(new BigDecimal("6000000"), "CIV", "FRA")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("depasseSeuilEspece : 999K sous seuil, 1M001 au-dessus")
|
||||
void depasseSeuilEspece() {
|
||||
assertThat(AmlSeuils.depasseSeuilEspece(new BigDecimal("999999"))).isFalse();
|
||||
assertThat(AmlSeuils.depasseSeuilEspece(new BigDecimal("1000001"))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Montant null jamais dépasse")
|
||||
void montantNullJamaisDepasse() {
|
||||
assertThat(AmlSeuils.depasseSeuil(null, "CIV", "SEN")).isFalse();
|
||||
assertThat(AmlSeuils.depasseSeuilEspece(null)).isFalse();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user