Files
unionflow-server-impl-quarkus/src/test/java/dev/lions/unionflow/server/entity/SouscriptionQuotaOptionCTest.java
dahoud a2dfae9a0b fix(security): audit RBAC complet v3.0 — rôles normalisés, lifecycle, changement mdp mobile
RBAC:
- HealthResource: @PermitAll
- RoleResource: @RolesAllowed ADMIN/SUPER_ADMIN/ADMIN_ORGANISATION class-level
- PropositionAideResource: @RolesAllowed MEMBRE/USER class-level
- AuthCallbackResource: @PermitAll
- EvenementResource: @PermitAll /publics et /test, count restreint
- BackupResource/LogsMonitoringResource/SystemResource: MODERATOR → MODERATEUR
- AnalyticsResource: MANAGER/MEMBER → ADMIN_ORGANISATION/MEMBRE
- RoleConstant.java: constantes de rôles centralisées

Cycle de vie membres:
- MemberLifecycleService: ajouterMembre()/retirerMembre() sur activation/radiation/archivage
- MembreResource: endpoint GET /numero/{numeroMembre}
- MembreService: méthode trouverParNumeroMembre()

Changement mot de passe:
- CompteAdherentResource: endpoint POST /auth/change-password (mobile)
- MembreKeycloakSyncService: changerMotDePasseDirectKeycloak() via API Admin Keycloak directe
- Fallback automatique si lions-user-manager indisponible

Workflow:
- Flyway V17-V23: rôles, types org, formules Option C, lifecycle columns, bareme cotisation
- Nouvelles classes: MemberLifecycleService, OrganisationModuleService, scheduler
- Security: OrganisationContextFilter, OrganisationContextHolder, ModuleAccessFilter
2026-04-07 20:52:26 +00:00

332 lines
14 KiB
Java

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