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 :
*
* - {@link SouscriptionOrganisation#isActive()} — selon statut + dateFin
* - {@link SouscriptionOrganisation#isQuotaDepasse()} — selon quotaMax et quotaUtilise
* - {@link SouscriptionOrganisation#getPlacesRestantes()} — calcul des places libres
* - {@link FormuleAbonnement#isIllimitee()} — maxMembres null = illimité
* - {@link FormuleAbonnement#accepteNouveauMembre(int)} — contrôle admission
* - Champs Option C (planCommercial, apiAccess, federationAccess, maxAdmins)
*
*/
@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();
}
}