633 lines
32 KiB
Java
633 lines
32 KiB
Java
package dev.lions.unionflow.server.service;
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
import static org.mockito.ArgumentMatchers.any;
|
|
import static org.mockito.ArgumentMatchers.eq;
|
|
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;
|
|
|
|
@QuarkusTest
|
|
class TrendAnalysisServiceTest {
|
|
|
|
@Inject
|
|
TrendAnalysisService trendService;
|
|
|
|
@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 NOMBRE_MEMBRES_ACTIFS génère des statistiques et prédictions")
|
|
void calculerTendance_nombreMembresActifs_generatesStats() {
|
|
UUID organisationId = UUID.randomUUID();
|
|
|
|
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")))
|
|
.thenReturn(Map.of(TypeMetrique.NOMBRE_MEMBRES_ACTIFS, new BigDecimal("120")));
|
|
|
|
KPITrendResponse response = trendService.calculerTendance(
|
|
TypeMetrique.NOMBRE_MEMBRES_ACTIFS, PeriodeAnalyse.CE_MOIS, organisationId);
|
|
|
|
assertThat(response).isNotNull();
|
|
assertThat(response.getPointsDonnees()).isNotEmpty();
|
|
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();
|
|
}
|
|
}
|