package dev.lions.unionflow.server.entity; import dev.lions.unionflow.server.api.enums.abonnement.PlageMembres; import dev.lions.unionflow.server.api.enums.abonnement.StatutSouscription; import dev.lions.unionflow.server.api.enums.abonnement.TypeFormule; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.time.LocalDate; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; /** * Tests unitaires pour la logique de quota (Option C) et les champs de plan commercial. * *

Couvre : *

*/ @DisplayName("SouscriptionOrganisation + FormuleAbonnement — Quota & Option C") class SouscriptionQuotaOptionCTest { // ─── Builders ───────────────────────────────────────────────────────────── private static Organisation newOrganisation() { Organisation o = new Organisation(); o.setId(UUID.randomUUID()); o.setNom("Association Test"); o.setTypeOrganisation("ASSOCIATION"); o.setStatut("ACTIVE"); o.setEmail("test@asso.sn"); return o; } private static FormuleAbonnement newFormule(int maxMembres) { FormuleAbonnement f = new FormuleAbonnement(); f.setId(UUID.randomUUID()); f.setCode(TypeFormule.STANDARD); f.setPlage(PlageMembres.PETITE); f.setLibelle("Standard Petite"); f.setMaxMembres(maxMembres); f.setPrixMensuel(BigDecimal.valueOf(7500)); f.setPrixAnnuel(BigDecimal.valueOf(75000)); return f; } private static FormuleAbonnement newFormuleIllimitee() { FormuleAbonnement f = new FormuleAbonnement(); f.setId(UUID.randomUUID()); f.setCode(TypeFormule.PREMIUM); f.setPlage(PlageMembres.GRANDE); f.setLibelle("Enterprise"); f.setMaxMembres(null); // illimité f.setPrixMensuel(BigDecimal.valueOf(30000)); f.setPrixAnnuel(BigDecimal.valueOf(300000)); return f; } private static SouscriptionOrganisation newSouscription( Organisation org, FormuleAbonnement formule, StatutSouscription statut, LocalDate dateFin, int quotaMax, int quotaUtilise) { SouscriptionOrganisation s = new SouscriptionOrganisation(); s.setId(UUID.randomUUID()); s.setOrganisation(org); s.setFormule(formule); s.setStatut(statut); s.setDateDebut(LocalDate.now().minusMonths(1)); s.setDateFin(dateFin); s.setQuotaMax(quotaMax); s.setQuotaUtilise(quotaUtilise); return s; } // ═════════════════════════════════════════════════════════════════════════ // isActive() // ═════════════════════════════════════════════════════════════════════════ @Test @DisplayName("isActive: statut ACTIVE et dateFin future → true") void isActive_activePlusFutureDateFin_returnsTrue() { var s = newSouscription(newOrganisation(), newFormule(100), StatutSouscription.ACTIVE, LocalDate.now().plusMonths(6), 100, 0); assertThat(s.isActive()).isTrue(); } @Test @DisplayName("isActive: statut ACTIVE mais dateFin hier → false (expirée)") void isActive_activePlusExpiredDate_returnsFalse() { var s = newSouscription(newOrganisation(), newFormule(100), StatutSouscription.ACTIVE, LocalDate.now().minusDays(1), 100, 0); assertThat(s.isActive()).isFalse(); } @Test @DisplayName("isActive: statut SUSPENDUE → false") void isActive_suspendue_returnsFalse() { var s = newSouscription(newOrganisation(), newFormule(100), StatutSouscription.SUSPENDUE, LocalDate.now().plusMonths(1), 100, 0); assertThat(s.isActive()).isFalse(); } @Test @DisplayName("isActive: statut EXPIREE → false") void isActive_expiree_returnsFalse() { var s = newSouscription(newOrganisation(), newFormule(100), StatutSouscription.EXPIREE, LocalDate.now().plusMonths(1), 100, 0); assertThat(s.isActive()).isFalse(); } @Test @DisplayName("isActive: dateFin aujourd'hui → true (borne inclusive)") void isActive_dateFin_today_returnsTrue() { var s = newSouscription(newOrganisation(), newFormule(100), StatutSouscription.ACTIVE, LocalDate.now(), 100, 0); assertThat(s.isActive()).isTrue(); } // ═════════════════════════════════════════════════════════════════════════ // isQuotaDepasse() // ═════════════════════════════════════════════════════════════════════════ @Test @DisplayName("isQuotaDepasse: quotaUtilise < quotaMax → false") void isQuotaDepasse_utiliseInferieur_returnsFalse() { var s = newSouscription(newOrganisation(), newFormule(100), StatutSouscription.ACTIVE, LocalDate.now().plusMonths(1), 100, 50); assertThat(s.isQuotaDepasse()).isFalse(); } @Test @DisplayName("isQuotaDepasse: quotaUtilise == quotaMax → true (atteint)") void isQuotaDepasse_utiliseEgalMax_returnsTrue() { var s = newSouscription(newOrganisation(), newFormule(100), StatutSouscription.ACTIVE, LocalDate.now().plusMonths(1), 100, 100); assertThat(s.isQuotaDepasse()).isTrue(); } @Test @DisplayName("isQuotaDepasse: quotaUtilise > quotaMax → true (dépassé)") void isQuotaDepasse_utiliseSuperieur_returnsTrue() { var s = newSouscription(newOrganisation(), newFormule(100), StatutSouscription.ACTIVE, LocalDate.now().plusMonths(1), 100, 110); assertThat(s.isQuotaDepasse()).isTrue(); } @Test @DisplayName("isQuotaDepasse: quotaMax null (illimité) → false") void isQuotaDepasse_quotaMaxNull_returnsFalse() { var s = newSouscription(newOrganisation(), newFormuleIllimitee(), StatutSouscription.ACTIVE, LocalDate.now().plusYears(1), 0, 500); s.setQuotaMax(null); // explicitement illimité assertThat(s.isQuotaDepasse()).isFalse(); } // ═════════════════════════════════════════════════════════════════════════ // getPlacesRestantes() // ═════════════════════════════════════════════════════════════════════════ @Test @DisplayName("getPlacesRestantes: 100 max - 30 utilisés = 70 restantes") void getPlacesRestantes_partialUsage_returnsCorrectValue() { var s = newSouscription(newOrganisation(), newFormule(100), StatutSouscription.ACTIVE, LocalDate.now().plusMonths(1), 100, 30); assertThat(s.getPlacesRestantes()).isEqualTo(70); } @Test @DisplayName("getPlacesRestantes: quota plein → 0 (jamais négatif)") void getPlacesRestantes_quotaFull_returnsZero() { var s = newSouscription(newOrganisation(), newFormule(100), StatutSouscription.ACTIVE, LocalDate.now().plusMonths(1), 100, 100); assertThat(s.getPlacesRestantes()).isZero(); } @Test @DisplayName("getPlacesRestantes: quotaMax null (illimité) → Integer.MAX_VALUE") void getPlacesRestantes_illimite_returnsMaxValue() { var s = newSouscription(newOrganisation(), newFormuleIllimitee(), StatutSouscription.ACTIVE, LocalDate.now().plusYears(1), 0, 0); s.setQuotaMax(null); assertThat(s.getPlacesRestantes()).isEqualTo(Integer.MAX_VALUE); } // ═════════════════════════════════════════════════════════════════════════ // FormuleAbonnement — isIllimitee() + accepteNouveauMembre() // ═════════════════════════════════════════════════════════════════════════ @Test @DisplayName("FormuleAbonnement.isIllimitee: maxMembres null → true") void formule_maxMembresNull_isIllimitee() { var f = newFormuleIllimitee(); assertThat(f.isIllimitee()).isTrue(); } @Test @DisplayName("FormuleAbonnement.isIllimitee: maxMembres défini → false") void formule_maxMembresDefined_notIllimitee() { var f = newFormule(200); assertThat(f.isIllimitee()).isFalse(); } @Test @DisplayName("FormuleAbonnement.accepteNouveauMembre: quota non atteint → true") void formule_accepteNouveauMembre_quotaNotReached_returnsTrue() { var f = newFormule(100); assertThat(f.accepteNouveauMembre(99)).isTrue(); } @Test @DisplayName("FormuleAbonnement.accepteNouveauMembre: quota atteint → false") void formule_accepteNouveauMembre_quotaReached_returnsFalse() { var f = newFormule(100); assertThat(f.accepteNouveauMembre(100)).isFalse(); } @Test @DisplayName("FormuleAbonnement.accepteNouveauMembre: formule illimitée → toujours true") void formule_accepteNouveauMembre_illimitee_alwaysTrue() { var f = newFormuleIllimitee(); assertThat(f.accepteNouveauMembre(10000)).isTrue(); } // ═════════════════════════════════════════════════════════════════════════ // Champs Option C sur FormuleAbonnement // ═════════════════════════════════════════════════════════════════════════ @Test @DisplayName("FormuleAbonnement Option C: plan ENTERPRISE — tous les droits activés") void formule_enterprisePlan_allFeaturesEnabled() { FormuleAbonnement f = new FormuleAbonnement(); f.setCode(TypeFormule.PREMIUM); f.setPlage(PlageMembres.GRANDE); f.setLibelle("Enterprise"); f.setMaxMembres(null); f.setPrixMensuel(BigDecimal.valueOf(50000)); f.setPrixAnnuel(BigDecimal.valueOf(500000)); f.setPlanCommercial("ENTERPRISE"); f.setNiveauReporting("AVANCE"); f.setApiAccess(true); f.setFederationAccess(true); f.setSupportPrioritaire(true); f.setSlaGaranti("99.9%"); f.setMaxAdmins(null); // illimité assertThat(f.getPlanCommercial()).isEqualTo("ENTERPRISE"); assertThat(f.getNiveauReporting()).isEqualTo("AVANCE"); assertThat(f.getApiAccess()).isTrue(); assertThat(f.getFederationAccess()).isTrue(); assertThat(f.getSupportPrioritaire()).isTrue(); assertThat(f.getSlaGaranti()).isEqualTo("99.9%"); assertThat(f.getMaxAdmins()).isNull(); } @Test @DisplayName("FormuleAbonnement Option C: plan BASIC — accès limités") void formule_basicPlan_limitedFeatures() { FormuleAbonnement f = newFormule(50); f.setPlanCommercial("MICRO"); f.setNiveauReporting("BASIQUE"); f.setApiAccess(false); f.setFederationAccess(false); f.setSupportPrioritaire(false); f.setSlaGaranti("99.0%"); f.setMaxAdmins(1); assertThat(f.getApiAccess()).isFalse(); assertThat(f.getFederationAccess()).isFalse(); assertThat(f.getSupportPrioritaire()).isFalse(); assertThat(f.getMaxAdmins()).isEqualTo(1); assertThat(f.getNiveauReporting()).isEqualTo("BASIQUE"); } // ═════════════════════════════════════════════════════════════════════════ // incrementerQuota() + decrementerQuota() // ═════════════════════════════════════════════════════════════════════════ @Test @DisplayName("incrementerQuota: 30 utilisés → 31 après incrémentation") void incrementerQuota_increment() { var s = newSouscription(newOrganisation(), newFormule(100), StatutSouscription.ACTIVE, LocalDate.now().plusMonths(1), 100, 30); s.incrementerQuota(); assertThat(s.getQuotaUtilise()).isEqualTo(31); } @Test @DisplayName("decrementerQuota: 30 utilisés → 29 après décrémentation") void decrementerQuota_decrement() { var s = newSouscription(newOrganisation(), newFormule(100), StatutSouscription.ACTIVE, LocalDate.now().plusMonths(1), 100, 30); s.decrementerQuota(); assertThat(s.getQuotaUtilise()).isEqualTo(29); } @Test @DisplayName("decrementerQuota: 0 utilisés → reste à 0 (pas négatif)") void decrementerQuota_atZero_staysZero() { var s = newSouscription(newOrganisation(), newFormule(100), StatutSouscription.ACTIVE, LocalDate.now().plusMonths(1), 100, 0); s.decrementerQuota(); assertThat(s.getQuotaUtilise()).isZero(); } }