Refactoring - Version stable

This commit is contained in:
dahoud
2026-03-28 14:21:30 +00:00
parent 00b981c510
commit a740c172ef
4402 changed files with 88517 additions and 1555 deletions

View File

@@ -8,12 +8,16 @@ import static org.mockito.Mockito.when;
import dev.lions.unionflow.server.api.dto.analytics.KPITrendResponse;
import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse;
import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import java.math.BigDecimal;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -26,12 +30,32 @@ class TrendAnalysisServiceTest {
@InjectMock
KPICalculatorService kpiCalculatorService;
@InjectMock
OrganisationRepository organisationRepository;
@BeforeEach
void setupMocks() {
// Par défaut, retourner ZERO pour tous les KPI
when(kpiCalculatorService.calculerTousLesKPI(any(), any(), any()))
.thenReturn(Map.of(
TypeMetrique.NOMBRE_MEMBRES_ACTIFS, new BigDecimal("50"),
TypeMetrique.TOTAL_COTISATIONS_COLLECTEES, new BigDecimal("100000"),
TypeMetrique.NOMBRE_EVENEMENTS_ORGANISES, new BigDecimal("5"),
TypeMetrique.NOMBRE_DEMANDES_AIDE, new BigDecimal("10")));
// Par défaut organisationRepository retourne empty
when(organisationRepository.findByIdOptional(any())).thenReturn(Optional.empty());
}
// =========================================================================
// calculerTendance — TypeMetrique.NOMBRE_MEMBRES_ACTIFS
// =========================================================================
@Test
@DisplayName("calculerTendance génère des statistiques et des prédictions")
void calculerTendance_generatesStats() {
@DisplayName("calculerTendance NOMBRE_MEMBRES_ACTIFS génère des statistiques et prédictions")
void calculerTendance_nombreMembresActifs_generatesStats() {
UUID organisationId = UUID.randomUUID();
// Mocking KPI calculator to return fixed values for different points
when(kpiCalculatorService.calculerTousLesKPI(eq(organisationId), any(), any()))
.thenReturn(Map.of(TypeMetrique.NOMBRE_MEMBRES_ACTIFS, new BigDecimal("100")))
.thenReturn(Map.of(TypeMetrique.NOMBRE_MEMBRES_ACTIFS, new BigDecimal("110")))
@@ -45,5 +69,564 @@ class TrendAnalysisServiceTest {
assertThat(response.getValeurMoyenne()).isNotNull();
assertThat(response.getTendanceGenerale()).isNotNull();
assertThat(response.getPredictionProchainePeriode()).isNotNull();
assertThat(response.getTypeMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
assertThat(response.getPeriodeAnalyse()).isEqualTo(PeriodeAnalyse.CE_MOIS);
}
// =========================================================================
// calculerTendance — TypeMetrique.TOTAL_COTISATIONS_COLLECTEES
// =========================================================================
@Test
@DisplayName("calculerTendance TOTAL_COTISATIONS_COLLECTEES retourne une réponse valide")
void calculerTendance_totalCotisations_returnsValidResponse() {
UUID organisationId = UUID.randomUUID();
KPITrendResponse response = trendService.calculerTendance(
TypeMetrique.TOTAL_COTISATIONS_COLLECTEES, PeriodeAnalyse.CETTE_SEMAINE, organisationId);
assertThat(response).isNotNull();
assertThat(response.getTypeMetrique()).isEqualTo(TypeMetrique.TOTAL_COTISATIONS_COLLECTEES);
assertThat(response.getValeurActuelle()).isNotNull();
}
// =========================================================================
// calculerTendance — TypeMetrique.NOMBRE_EVENEMENTS_ORGANISES
// =========================================================================
@Test
@DisplayName("calculerTendance NOMBRE_EVENEMENTS_ORGANISES retourne une réponse valide")
void calculerTendance_nombreEvenements_returnsValidResponse() {
UUID organisationId = UUID.randomUUID();
KPITrendResponse response = trendService.calculerTendance(
TypeMetrique.NOMBRE_EVENEMENTS_ORGANISES, PeriodeAnalyse.TROIS_DERNIERS_MOIS, organisationId);
assertThat(response).isNotNull();
assertThat(response.getTypeMetrique()).isEqualTo(TypeMetrique.NOMBRE_EVENEMENTS_ORGANISES);
}
// =========================================================================
// calculerTendance — TypeMetrique.NOMBRE_DEMANDES_AIDE
// =========================================================================
@Test
@DisplayName("calculerTendance NOMBRE_DEMANDES_AIDE retourne une réponse valide")
void calculerTendance_nombreDemandesAide_returnsValidResponse() {
UUID organisationId = UUID.randomUUID();
KPITrendResponse response = trendService.calculerTendance(
TypeMetrique.NOMBRE_DEMANDES_AIDE, PeriodeAnalyse.SIX_DERNIERS_MOIS, organisationId);
assertThat(response).isNotNull();
assertThat(response.getTypeMetrique()).isEqualTo(TypeMetrique.NOMBRE_DEMANDES_AIDE);
}
// =========================================================================
// calculerTendance — TypeMetrique par défaut (non géré par switch)
// =========================================================================
@Test
@DisplayName("calculerTendance avec TypeMetrique non géré retourne ZERO pour les valeurs")
void calculerTendance_defaultTypeMetrique_returnsZeroValues() {
UUID organisationId = UUID.randomUUID();
// TAUX_CROISSANCE_MEMBRES n'est pas géré explicitement → default → BigDecimal.ZERO
KPITrendResponse response = trendService.calculerTendance(
TypeMetrique.TAUX_CROISSANCE_MEMBRES, PeriodeAnalyse.SEPT_DERNIERS_JOURS, organisationId);
assertThat(response).isNotNull();
assertThat(response.getPointsDonnees()).isNotNull();
}
// =========================================================================
// calculerTendance — avec organisationId null
// =========================================================================
@Test
@DisplayName("calculerTendance avec organisationId null retourne une réponse valide")
void calculerTendance_nullOrganisationId_returnsValidResponse() {
KPITrendResponse response = trendService.calculerTendance(
TypeMetrique.NOMBRE_MEMBRES_ACTIFS, PeriodeAnalyse.CETTE_ANNEE, null);
assertThat(response).isNotNull();
assertThat(response.getOrganisationId()).isNull();
assertThat(response.getNomOrganisation()).isNull();
}
// =========================================================================
// calculerTendance — différentes périodes (branches determinerFrequenceMiseAJour)
// =========================================================================
@Test
@DisplayName("calculerTendance pour AUJOURD_HUI retourne fréquence de mise à jour 15 min")
void calculerTendance_aujourdhui_returns15minFrequency() {
KPITrendResponse response = trendService.calculerTendance(
TypeMetrique.NOMBRE_MEMBRES_ACTIFS, PeriodeAnalyse.AUJOURD_HUI, null);
assertThat(response).isNotNull();
assertThat(response.getFrequenceMiseAJourMinutes()).isEqualTo(15);
}
@Test
@DisplayName("calculerTendance pour CETTE_SEMAINE retourne fréquence de mise à jour 60 min")
void calculerTendance_cetteSemaine_returns60minFrequency() {
KPITrendResponse response = trendService.calculerTendance(
TypeMetrique.NOMBRE_MEMBRES_ACTIFS, PeriodeAnalyse.CETTE_SEMAINE, null);
assertThat(response).isNotNull();
assertThat(response.getFrequenceMiseAJourMinutes()).isEqualTo(60);
}
@Test
@DisplayName("calculerTendance pour CE_MOIS retourne fréquence de mise à jour 240 min")
void calculerTendance_ceMois_returns240minFrequency() {
KPITrendResponse response = trendService.calculerTendance(
TypeMetrique.NOMBRE_MEMBRES_ACTIFS, PeriodeAnalyse.CE_MOIS, null);
assertThat(response).isNotNull();
assertThat(response.getFrequenceMiseAJourMinutes()).isEqualTo(240);
}
@Test
@DisplayName("calculerTendance pour CETTE_ANNEE retourne fréquence de mise à jour 1440 min")
void calculerTendance_cetteAnnee_returns1440minFrequency() {
KPITrendResponse response = trendService.calculerTendance(
TypeMetrique.NOMBRE_MEMBRES_ACTIFS, PeriodeAnalyse.CETTE_ANNEE, null);
assertThat(response).isNotNull();
assertThat(response.getFrequenceMiseAJourMinutes()).isEqualTo(1440);
}
// =========================================================================
// Vérification des champs de tendance et alertes
// =========================================================================
@Test
@DisplayName("calculerTendance retourne les seuils d'alerte et le coefficient de corrélation")
void calculerTendance_returnsAlertsAndCorrelation() {
UUID organisationId = UUID.randomUUID();
// Fournir des valeurs variées pour forcer le calcul des stats réelles
when(kpiCalculatorService.calculerTousLesKPI(eq(organisationId), any(), any()))
.thenReturn(Map.of(TypeMetrique.NOMBRE_MEMBRES_ACTIFS, new BigDecimal("80")))
.thenReturn(Map.of(TypeMetrique.NOMBRE_MEMBRES_ACTIFS, new BigDecimal("100")))
.thenReturn(Map.of(TypeMetrique.NOMBRE_MEMBRES_ACTIFS, new BigDecimal("200")));
KPITrendResponse response = trendService.calculerTendance(
TypeMetrique.NOMBRE_MEMBRES_ACTIFS, PeriodeAnalyse.TROIS_DERNIERS_MOIS, organisationId);
assertThat(response).isNotNull();
assertThat(response.getCoefficientCorrelation()).isNotNull();
assertThat(response.getSeuilAlerteBas()).isNotNull();
assertThat(response.getSeuilAlerteHaut()).isNotNull();
assertThat(response.getAlerteActive()).isNotNull();
assertThat(response.getMargeErreurPrediction()).isNotNull();
}
// =========================================================================
// obtenirNomOrganisation — lambda$1 ligne 367 : .map(org -> org.getNom())
// =========================================================================
@Test
@DisplayName("calculerTendance avec organisation trouvée en DB couvre lambda .map(org -> org.getNom())")
void calculerTendance_withExistingOrganisation_returnsNomOrganisation() {
UUID organisationId = UUID.randomUUID();
Organisation org = new Organisation();
org.setNom("Association Tendance Test");
org.setEmail("tendance@test.com");
org.setTypeOrganisation("ASSOCIATION");
org.setStatut("ACTIVE");
org.setActif(true);
// Surcharger le mock par défaut : l'organisation EST trouvée → .map(org -> org.getNom()) exécuté
when(organisationRepository.findByIdOptional(eq(organisationId)))
.thenReturn(Optional.of(org));
KPITrendResponse response = trendService.calculerTendance(
TypeMetrique.NOMBRE_MEMBRES_ACTIFS, PeriodeAnalyse.CE_MOIS, organisationId);
assertThat(response).isNotNull();
assertThat(response.getNomOrganisation()).isEqualTo("Association Tendance Test");
assertThat(response.getOrganisationId()).isEqualTo(organisationId);
}
// =========================================================================
// StatistiquesDTO — constructeur 6-args (ligne 391)
// déclenché quand les points de données contiennent des valeurs non-nulles variées
// =========================================================================
@Test
@DisplayName("calculerStatistiques avec liste vide déclenche le constructeur no-arg de StatistiquesDTO")
void calculerStatistiques_emptyList_triggersStatistiquesDTONoArgConstructor() throws Exception {
java.lang.reflect.Method method = TrendAnalysisService.class.getDeclaredMethod(
"calculerStatistiques", java.util.List.class);
method.setAccessible(true);
Object result = method.invoke(trendService, java.util.Collections.emptyList());
assertThat(result).isNotNull();
}
@Test
@DisplayName("calculerTendance avec valeurs variées déclenche le constructeur StatistiquesDTO 6-args")
void calculerTendance_withVariedValues_triggersStatistiquesDTOFullConstructor() {
UUID organisationId = UUID.randomUUID();
// Des valeurs très différentes garantissent écartType != 0 → constructeur 6-args de StatistiquesDTO
when(kpiCalculatorService.calculerTousLesKPI(eq(organisationId), any(), any()))
.thenReturn(Map.of(TypeMetrique.NOMBRE_MEMBRES_ACTIFS, new BigDecimal("10")))
.thenReturn(Map.of(TypeMetrique.NOMBRE_MEMBRES_ACTIFS, new BigDecimal("500")))
.thenReturn(Map.of(TypeMetrique.NOMBRE_MEMBRES_ACTIFS, new BigDecimal("250")));
KPITrendResponse response = trendService.calculerTendance(
TypeMetrique.NOMBRE_MEMBRES_ACTIFS, PeriodeAnalyse.TROIS_DERNIERS_MOIS, organisationId);
assertThat(response).isNotNull();
assertThat(response.getEcartType()).isNotNull();
assertThat(response.getValeurMinimale()).isNotNull();
assertThat(response.getValeurMaximale()).isNotNull();
assertThat(response.getValeurMoyenne()).isNotNull();
}
// =========================================================================
// calculerTendanceLineaire — liste < 2 points (via réflexion)
// =========================================================================
@Test
@DisplayName("calculerTendanceLineaire avec liste vide retourne pente=0 et corrélation=0")
void calculerTendanceLineaire_emptyList_returnsZeros() throws Exception {
java.lang.reflect.Method method = TrendAnalysisService.class.getDeclaredMethod(
"calculerTendanceLineaire", java.util.List.class);
method.setAccessible(true);
Object result = method.invoke(trendService, java.util.Collections.emptyList());
assertThat(result).isNotNull();
}
@Test
@DisplayName("calculerTendanceLineaire avec 1 point retourne pente=0 et corrélation=0")
void calculerTendanceLineaire_singlePoint_returnsZeros() throws Exception {
java.lang.reflect.Method method = TrendAnalysisService.class.getDeclaredMethod(
"calculerTendanceLineaire", java.util.List.class);
method.setAccessible(true);
KPITrendResponse.PointDonneeDTO point = KPITrendResponse.PointDonneeDTO.builder()
.date(java.time.LocalDateTime.now())
.valeur(new BigDecimal("100"))
.libelle("T1")
.anomalie(false)
.prediction(false)
.build();
Object result = method.invoke(trendService, java.util.List.of(point));
assertThat(result).isNotNull();
}
// =========================================================================
// calculerTendanceLineaire — branches denominateur = 0 (ligne 212) et denominateurR = 0 (lignes 222-230)
// Branche denominateur = 0 : n*sommeX2 - sommeX^2 = 0 (tous X identiques, e.g. n=1 impossible ici)
// Pour 2 points identiques, sommeX={0,1}, sommeX2={0,1}, n*sommeX2-sommeX^2 = 2*1-1=1 ≠ 0
// Pour atteindre denominateur=0 avec >= 2 points, il faut que les X soient tous égaux, impossible ici
// (X = index 0,1,2,...). Donc on couvre via denominateurR = 0 :
// denominateurR2 = n*sommeY2 - sommeY^2 = 0 quand toutes les Y sont identiques
// =========================================================================
@Test
@DisplayName("calculerTendanceLineaire avec toutes les valeurs Y identiques — denominateurR2=0 → corrélation=0 (branche ligne 222-223)")
void calculerTendanceLineaire_identicalYValues_denominateurRZero_correlationZero() throws Exception {
java.lang.reflect.Method method = TrendAnalysisService.class.getDeclaredMethod(
"calculerTendanceLineaire", java.util.List.class);
method.setAccessible(true);
// 3 points avec Y identique = 100 → denominateurR2 = 3*(100^2+100^2+100^2) - (300)^2 = 0
// → branche: if (denominateurR1 != 0 && denominateurR2 != 0) → FAUX → coefficientCorrelation=0
java.util.List<KPITrendResponse.PointDonneeDTO> points = java.util.List.of(
KPITrendResponse.PointDonneeDTO.builder()
.date(java.time.LocalDateTime.now().minusDays(2))
.valeur(new BigDecimal("100"))
.libelle("T1")
.anomalie(false)
.prediction(false)
.build(),
KPITrendResponse.PointDonneeDTO.builder()
.date(java.time.LocalDateTime.now().minusDays(1))
.valeur(new BigDecimal("100")) // même valeur
.libelle("T2")
.anomalie(false)
.prediction(false)
.build(),
KPITrendResponse.PointDonneeDTO.builder()
.date(java.time.LocalDateTime.now())
.valeur(new BigDecimal("100")) // même valeur
.libelle("T3")
.anomalie(false)
.prediction(false)
.build()
);
Object result = method.invoke(trendService, points);
// Résultat non null, pente = 0 (dénominateur != 0 car X varie), corrélation = 0
assertThat(result).isNotNull();
// Vérifie via réflexion que pente et corrélation sont des BigDecimal
java.lang.reflect.Field penteField = result.getClass().getDeclaredField("pente");
penteField.setAccessible(true);
BigDecimal pente = (BigDecimal) penteField.get(result);
assertThat(pente).isEqualByComparingTo(BigDecimal.ZERO);
java.lang.reflect.Field corrField = result.getClass().getDeclaredField("coefficientCorrelation");
corrField.setAccessible(true);
BigDecimal corr = (BigDecimal) corrField.get(result);
assertThat(corr).isEqualByComparingTo(BigDecimal.ZERO);
}
@Test
@DisplayName("calculerTendanceLineaire avec denominateurR négatif (valeurs négatives) — branches sqrt edge case")
void calculerTendanceLineaire_negativeValues_handlesGracefully() throws Exception {
java.lang.reflect.Method method = TrendAnalysisService.class.getDeclaredMethod(
"calculerTendanceLineaire", java.util.List.class);
method.setAccessible(true);
// Points avec valeurs très différentes → denominateur != 0, denominateurR != 0 → corrélation calculée
// Ce test couvre la branche "if (denominateurR != 0)" ligne 227
java.util.List<KPITrendResponse.PointDonneeDTO> points = java.util.List.of(
KPITrendResponse.PointDonneeDTO.builder()
.date(java.time.LocalDateTime.now().minusDays(2))
.valeur(new BigDecimal("10"))
.libelle("T1")
.anomalie(false)
.prediction(false)
.build(),
KPITrendResponse.PointDonneeDTO.builder()
.date(java.time.LocalDateTime.now().minusDays(1))
.valeur(new BigDecimal("50"))
.libelle("T2")
.anomalie(false)
.prediction(false)
.build(),
KPITrendResponse.PointDonneeDTO.builder()
.date(java.time.LocalDateTime.now())
.valeur(new BigDecimal("90"))
.libelle("T3")
.anomalie(false)
.prediction(false)
.build()
);
Object result = method.invoke(trendService, points);
assertThat(result).isNotNull();
java.lang.reflect.Field penteField = result.getClass().getDeclaredField("pente");
penteField.setAccessible(true);
BigDecimal pente = (BigDecimal) penteField.get(result);
// Tendance croissante → pente > 0
assertThat(pente.compareTo(BigDecimal.ZERO)).isGreaterThan(0);
}
// =========================================================================
// calculerPrediction — liste vide (via réflexion)
// =========================================================================
@Test
@DisplayName("calculerPrediction avec liste vide retourne ZERO")
void calculerPrediction_emptyList_returnsZero() throws Exception {
// Need TendanceDTO — inner class, access via reflection
Class<?> tendanceDTOClass = null;
for (Class<?> inner : TrendAnalysisService.class.getDeclaredClasses()) {
if (inner.getSimpleName().equals("TendanceDTO")) {
tendanceDTOClass = inner;
break;
}
}
assertThat(tendanceDTOClass).isNotNull();
java.lang.reflect.Constructor<?> ctor = tendanceDTOClass.getDeclaredConstructor(
java.math.BigDecimal.class, java.math.BigDecimal.class);
ctor.setAccessible(true);
Object tendance = ctor.newInstance(new BigDecimal("5"), new BigDecimal("0.8"));
java.lang.reflect.Method method = TrendAnalysisService.class.getDeclaredMethod(
"calculerPrediction", java.util.List.class, tendanceDTOClass);
method.setAccessible(true);
Object result = method.invoke(trendService, java.util.Collections.emptyList(), tendance);
assertThat(result).isEqualTo(BigDecimal.ZERO);
}
// =========================================================================
// formaterLibellePoint — branche MONTHS (via réflexion)
// =========================================================================
@Test
@DisplayName("formaterLibellePoint avec ChronoUnit.MONTHS retourne mois + année")
void formaterLibellePoint_months_returnsMonthYear() throws Exception {
java.lang.reflect.Method method = TrendAnalysisService.class.getDeclaredMethod(
"formaterLibellePoint", java.time.LocalDateTime.class, java.time.temporal.ChronoUnit.class);
method.setAccessible(true);
java.time.LocalDateTime date = java.time.LocalDateTime.of(2025, 3, 15, 0, 0);
Object result = method.invoke(trendService, date, java.time.temporal.ChronoUnit.MONTHS);
assertThat(result).isNotNull();
assertThat(result.toString()).contains("2025");
}
@Test
@DisplayName("formaterLibellePoint avec ChronoUnit autre retourne date.toString()")
void formaterLibellePoint_defaultUnit_returnsDateToString() throws Exception {
java.lang.reflect.Method method = TrendAnalysisService.class.getDeclaredMethod(
"formaterLibellePoint", java.time.LocalDateTime.class, java.time.temporal.ChronoUnit.class);
method.setAccessible(true);
java.time.LocalDateTime date = java.time.LocalDateTime.of(2025, 3, 15, 10, 30);
// HOURS is a ChronoUnit that is not DAYS, WEEKS, or MONTHS → default branch
Object result = method.invoke(trendService, date, java.time.temporal.ChronoUnit.HOURS);
assertThat(result).isNotNull();
assertThat(result.toString()).contains("2025");
}
// =========================================================================
// determinerValeurIntervalle — branche > 100 (dureeTotal/15)
// =========================================================================
@Test
@DisplayName("determinerValeurIntervalle avec dureeTotal > 100 retourne dureeTotal/15")
void determinerValeurIntervalle_largeDureeTotal_returnsDivide() throws Exception {
java.lang.reflect.Method method = TrendAnalysisService.class.getDeclaredMethod(
"determinerValeurIntervalle",
java.time.LocalDateTime.class,
java.time.LocalDateTime.class,
java.time.temporal.ChronoUnit.class);
method.setAccessible(true);
// DAYS between these two = 200 days → 200 > 100 → 200/15 = 13
java.time.LocalDateTime debut = java.time.LocalDateTime.of(2024, 1, 1, 0, 0);
java.time.LocalDateTime fin = java.time.LocalDateTime.of(2024, 7, 19, 0, 0); // ~200 days
Object result = method.invoke(trendService, debut, fin, java.time.temporal.ChronoUnit.DAYS);
assertThat(result).isNotNull();
assertThat((Long) result).isGreaterThan(0L);
}
// =========================================================================
// determinerUniteIntervalle — branche > 365 (MONTHS)
// =========================================================================
@Test
@DisplayName("determinerUniteIntervalle avec plus de 365 jours retourne MONTHS")
void determinerUniteIntervalle_moreThan365Days_returnsMonths() throws Exception {
java.lang.reflect.Method method = TrendAnalysisService.class.getDeclaredMethod(
"determinerUniteIntervalle",
java.time.LocalDateTime.class,
java.time.LocalDateTime.class);
method.setAccessible(true);
java.time.LocalDateTime debut = java.time.LocalDateTime.of(2022, 1, 1, 0, 0);
java.time.LocalDateTime fin = java.time.LocalDateTime.of(2024, 6, 1, 0, 0); // ~880 days
Object result = method.invoke(trendService, debut, fin);
assertThat(result).isEqualTo(java.time.temporal.ChronoUnit.MONTHS);
}
// =========================================================================
// verifierAlertes — branche valeur > seuilHaut (via période longue avec valeurs très élevées)
// =========================================================================
@Test
@DisplayName("calculerTendance CETTE_ANNEE couvre formaterLibellePoint MONTHS et determinerUniteIntervalle MONTHS")
void calculerTendance_cetteAnnee_coversMonthsPathInFormatAndDeterminer() {
// CETTE_ANNEE is > 365 days → determinerUniteIntervalle returns MONTHS
// → formaterLibellePoint is called with MONTHS → covers that branch
KPITrendResponse response = trendService.calculerTendance(
TypeMetrique.NOMBRE_MEMBRES_ACTIFS, PeriodeAnalyse.CETTE_ANNEE, null);
assertThat(response).isNotNull();
assertThat(response.getPointsDonnees()).isNotEmpty();
// At least one label should contain the year
assertThat(response.getPointsDonnees().stream()
.anyMatch(p -> p.getLibelle() != null && p.getLibelle().contains("2"))).isTrue();
}
@Test
@DisplayName("verifierAlertes avec valeur actuelle au-dessus du seuil haut retourne true")
void verifierAlertes_valeurAboveSeuilHaut_returnsTrue() throws Exception {
// Build StatistiquesDTO via reflection with a very low valeurActuelle and tight bounds
Class<?> statsDTOClass = null;
for (Class<?> inner : TrendAnalysisService.class.getDeclaredClasses()) {
if (inner.getSimpleName().equals("StatistiquesDTO")) {
statsDTOClass = inner;
break;
}
}
assertThat(statsDTOClass).isNotNull();
java.lang.reflect.Constructor<?> ctor = statsDTOClass.getDeclaredConstructor(
BigDecimal.class, BigDecimal.class, BigDecimal.class,
BigDecimal.class, BigDecimal.class, BigDecimal.class);
ctor.setAccessible(true);
// valeurActuelle=1000, moyenne=100, ecartType=10
// seuilHaut = 100 + 10*1.5 = 115 → valeurActuelle 1000 > 115 → alerteActive=true
Object stats = ctor.newInstance(
new BigDecimal("1000"), // valeurActuelle
new BigDecimal("50"), // valeurMinimale
new BigDecimal("200"), // valeurMaximale
new BigDecimal("100"), // valeurMoyenne
new BigDecimal("10"), // ecartType
new BigDecimal("0.1")); // coefficientVariation
java.lang.reflect.Method method = TrendAnalysisService.class.getDeclaredMethod(
"verifierAlertes", BigDecimal.class, statsDTOClass);
method.setAccessible(true);
Object result = method.invoke(trendService, new BigDecimal("1000"), stats);
assertThat((Boolean) result).isTrue();
}
@Test
@DisplayName("verifierAlertes avec valeur actuelle en dessous du seuil bas retourne true")
void verifierAlertes_valeurBelowSeuilBas_returnsTrue() throws Exception {
Class<?> statsDTOClass = null;
for (Class<?> inner : TrendAnalysisService.class.getDeclaredClasses()) {
if (inner.getSimpleName().equals("StatistiquesDTO")) {
statsDTOClass = inner;
break;
}
}
java.lang.reflect.Constructor<?> ctor = statsDTOClass.getDeclaredConstructor(
BigDecimal.class, BigDecimal.class, BigDecimal.class,
BigDecimal.class, BigDecimal.class, BigDecimal.class);
ctor.setAccessible(true);
// valeurActuelle=5, moyenne=100, ecartType=10
// seuilBas = 100 - 10*1.5 = 85 → valeurActuelle 5 < 85 → alerteActive=true
Object stats = ctor.newInstance(
new BigDecimal("5"), // valeurActuelle
new BigDecimal("5"), // valeurMinimale
new BigDecimal("200"), // valeurMaximale
new BigDecimal("100"), // valeurMoyenne
new BigDecimal("10"), // ecartType
new BigDecimal("0.1")); // coefficientVariation
java.lang.reflect.Method method = TrendAnalysisService.class.getDeclaredMethod(
"verifierAlertes", BigDecimal.class, statsDTOClass);
method.setAccessible(true);
Object result = method.invoke(trendService, new BigDecimal("5"), stats);
assertThat((Boolean) result).isTrue();
}
@Test
@DisplayName("verifierAlertes avec valeur dans les bornes retourne false")
void verifierAlertes_valeurInBounds_returnsFalse() throws Exception {
Class<?> statsDTOClass = null;
for (Class<?> inner : TrendAnalysisService.class.getDeclaredClasses()) {
if (inner.getSimpleName().equals("StatistiquesDTO")) {
statsDTOClass = inner;
break;
}
}
java.lang.reflect.Constructor<?> ctor = statsDTOClass.getDeclaredConstructor(
BigDecimal.class, BigDecimal.class, BigDecimal.class,
BigDecimal.class, BigDecimal.class, BigDecimal.class);
ctor.setAccessible(true);
// valeurActuelle=100, moyenne=100, ecartType=10
// seuilBas=85, seuilHaut=115 → valeurActuelle=100 is between → alerteActive=false
Object stats = ctor.newInstance(
new BigDecimal("100"), // valeurActuelle
new BigDecimal("80"), // valeurMinimale
new BigDecimal("120"), // valeurMaximale
new BigDecimal("100"), // valeurMoyenne
new BigDecimal("10"), // ecartType
new BigDecimal("0.1")); // coefficientVariation
java.lang.reflect.Method method = TrendAnalysisService.class.getDeclaredMethod(
"verifierAlertes", BigDecimal.class, statsDTOClass);
method.setAccessible(true);
Object result = method.invoke(trendService, new BigDecimal("100"), stats);
assertThat((Boolean) result).isFalse();
}
}