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
332 lines
14 KiB
Java
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();
|
|
}
|
|
}
|