fix(monitoring): corriger calcul CPU — getProcessCpuLoad au lieu de loadAverage/processors

Le calcul précédent `getSystemLoadAverage() / getAvailableProcessors() * 100`
utilisait :
- getSystemLoadAverage() : charge 1min du NODE Linux hôte entier (12 CPU sur prod VPS)
- getAvailableProcessors() : limite conteneur K8s (1 CPU pour le pod UnionFlow)

Résultat : load 1.5 sur le node → cpuUsage = (1.5 / 1) * 100 = 150% capé à 100%.
Déclenchement constant d'alertes "CPU 100%" (19 faux positifs / 24h sur prod
le 20-21/04/2026) dès que le node fait autre chose que dormir.

Correctif : utilise com.sun.management.OperatingSystemMXBean.getProcessCpuLoad()
qui retourne la charge CPU du process JVM courant (0.0-1.0), conscient du
conteneur. Branche -1 préservée (API non dispo sur certaines JVM).

Tests mis à jour : AlertMonitoringServiceMockStaticCoverageTest injecte
désormais un com.sun.management.OperatingSystemMXBean et mocke
getProcessCpuLoad() (compatible mock-maker-inline déjà configuré).
AlertMonitoringServiceTest conserve sa logique OS-agnostique via des
thresholds extrêmes (-1 toujours / 100 jamais).
This commit is contained in:
dahoud
2026-04-21 13:38:31 +00:00
parent 31330d95e9
commit 8cec38f7b3
3 changed files with 83 additions and 78 deletions

View File

@@ -82,12 +82,15 @@ public class AlertMonitoringService {
*/ */
private void checkCpuThreshold(AlertConfiguration config) { private void checkCpuThreshold(AlertConfiguration config) {
try { try {
OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean(); // getProcessCpuLoad() renvoie la charge CPU de CE process JVM (0.0-1.0),
double loadAvg = osBean.getSystemLoadAverage(); // ce qui est correct en conteneur K8s/Docker.
int processors = osBean.getAvailableProcessors(); // getSystemLoadAverage() renvoie la charge du NODE entier (hôte Linux),
// divisée par availableProcessors() limité par le conteneur (ex: 1),
// Calculer l'utilisation CPU en pourcentage // ce qui produit des faux positifs dès que le node est actif.
double cpuUsage = loadAvg < 0 ? 0.0 : Math.min(100.0, (loadAvg / processors) * 100.0); com.sun.management.OperatingSystemMXBean osBean =
(com.sun.management.OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
double processCpuLoad = osBean.getProcessCpuLoad();
double cpuUsage = processCpuLoad < 0 ? 0.0 : Math.min(100.0, processCpuLoad * 100.0);
lastCpuUsage = cpuUsage; lastCpuUsage = cpuUsage;
int threshold = config.getCpuThresholdPercent(); int threshold = config.getCpuThresholdPercent();

View File

@@ -18,7 +18,6 @@ import dev.lions.unionflow.server.repository.SystemLogRepository;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean; import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage; import java.lang.management.MemoryUsage;
import java.lang.management.OperatingSystemMXBean;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Collections; import java.util.Collections;
@@ -35,11 +34,12 @@ import org.mockito.junit.jupiter.MockitoExtension;
* Tests unitaires purs (sans Quarkus) pour {@link AlertMonitoringService}. * Tests unitaires purs (sans Quarkus) pour {@link AlertMonitoringService}.
* *
* <p>Utilise {@code mockStatic(ManagementFactory.class)} pour injecter des beans MXBean * <p>Utilise {@code mockStatic(ManagementFactory.class)} pour injecter des beans MXBean
* contrôlés et couvrir les branches inaccessibles sur certains systèmes (notamment Windows * contrôlés et couvrir les branches du calcul CPU basé sur
* {@code getSystemLoadAverage()} retourne toujours -1). * {@code com.sun.management.OperatingSystemMXBean.getProcessCpuLoad()} (0.0-1.0, -1 si indispo).
* *
* <p>Note : utilise {@code mock-maker-inline} (configuré via * <p>Note : utilise {@code mock-maker-inline} (configuré via
* {@code src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker}). * {@code src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker})
* nécessaire pour mocker {@code com.sun.management.OperatingSystemMXBean} (type JDK interne).
*/ */
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
@DisplayName("AlertMonitoringService — branches via mockStatic ManagementFactory") @DisplayName("AlertMonitoringService — branches via mockStatic ManagementFactory")
@@ -69,31 +69,31 @@ class AlertMonitoringServiceMockStaticCoverageTest {
} }
// ========================================================================= // =========================================================================
// L85 : branche ternaire loadAvg >= 0 → Math.min(100.0, (loadAvg / processors) * 100.0) // checkCpuThreshold : branche processCpuLoad >= 0
// cpuUsage = Math.min(100.0, processCpuLoad * 100.0)
// ========================================================================= // =========================================================================
/** /**
* Couvre la branche {@code loadAvg >= 0} à la ligne 85 : * Couvre la branche {@code processCpuLoad >= 0} :
* {@code Math.min(100.0, (loadAvg / processors) * 100.0)}. * {@code cpuUsage = Math.min(100.0, processCpuLoad * 100.0)}.
* *
* <p>On injecte un {@code OperatingSystemMXBean} mock qui retourne * <p>On injecte un {@code com.sun.management.OperatingSystemMXBean} mock qui retourne
* {@code getSystemLoadAverage() = 0.5} (valeur positive ≥ 0) et * {@code getProcessCpuLoad() = 0.125} (valeur positive ≥ 0).
* {@code getAvailableProcessors() = 4}. * {@code cpuUsage = Math.min(100.0, 0.125 * 100.0) = 12.5}.
* → {@code cpuUsage = Math.min(100.0, (0.5/4)*100.0) = Math.min(100.0, 12.5) = 12.5}.
* *
* <p>Avec {@code threshold = 0}, {@code 12.5 > 0} → branche cpuUsage > threshold → true * <p>Avec {@code threshold = 0}, {@code 12.5 > 0} → branche cpuUsage > threshold → true
* → {@code lastCpuHighTime} est mis à null initialement → il est settté à now(). * → {@code lastCpuHighTime} est mis à null initialement → il est setté à now().
* Aucune alerte n'est créée (première itération). * Aucune alerte n'est créée (première itération).
*/ */
@Test @Test
@DisplayName("checkCpuThreshold : loadAvg >= 0 → Math.min branch (L85) via OperatingSystemMXBean mock") @DisplayName("checkCpuThreshold : processCpuLoad >= 0 → Math.min branch via OperatingSystemMXBean mock")
void checkCpuThreshold_loadAvgPositive_coversL85MathMinBranch() { void checkCpuThreshold_processCpuLoadPositive_coversMathMinBranch() {
AlertConfiguration config = buildConfig(true, 0, 60, false, 100, false); AlertConfiguration config = buildConfig(true, 0, 60, false, 100, false);
when(alertConfigurationRepository.getConfiguration()).thenReturn(config); when(alertConfigurationRepository.getConfiguration()).thenReturn(config);
OperatingSystemMXBean osMock = mock(OperatingSystemMXBean.class); com.sun.management.OperatingSystemMXBean osMock =
when(osMock.getSystemLoadAverage()).thenReturn(0.5); // >= 0 → branche Math.min à L85 mock(com.sun.management.OperatingSystemMXBean.class);
when(osMock.getAvailableProcessors()).thenReturn(4); when(osMock.getProcessCpuLoad()).thenReturn(0.125); // >= 0 → branche Math.min
try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) { try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) {
mf.when(ManagementFactory::getOperatingSystemMXBean).thenReturn(osMock); mf.when(ManagementFactory::getOperatingSystemMXBean).thenReturn(osMock);
@@ -108,28 +108,28 @@ class AlertMonitoringServiceMockStaticCoverageTest {
} }
/** /**
* Couvre la branche {@code loadAvg >= 0} à L85 avec une valeur élevée déclenchant une alerte. * Couvre la branche {@code processCpuLoad >= 0} avec une valeur élevée déclenchant une alerte.
* *
* <p>On simule un CPU à 200% (loadAvg=8, processors=4 → cpuUsage=200, cappé à 100%). * <p>On simule un CPU à 200% brut (getProcessCpuLoad=2.0 — cas théorique) cappé à 100% via Math.min.
* Après deux appels avec {@code durationMinutes=0}, l'alerte est créée. * Après deux appels avec {@code durationMinutes=0}, l'alerte est créée.
*/ */
@Test @Test
@DisplayName("checkCpuThreshold : loadAvg élevé → cpuUsage 100% → alerte créée en 2 appels (L85 Math.min branch)") @DisplayName("checkCpuThreshold : processCpuLoad élevé → cpuUsage cappé à 100% → alerte créée en 2 appels")
void checkCpuThreshold_loadAvgHighPositive_triggersAlert_coversL85() { void checkCpuThreshold_processCpuLoadHighPositive_triggersAlert() {
// Premier appel : reset lastCpuHighTime via threshold élevé // Premier appel : reset lastCpuHighTime via threshold élevé
AlertConfiguration resetConfig = buildConfig(true, 100, 0, false, 100, false); AlertConfiguration resetConfig = buildConfig(true, 100, 0, false, 100, false);
when(alertConfigurationRepository.getConfiguration()).thenReturn(resetConfig); when(alertConfigurationRepository.getConfiguration()).thenReturn(resetConfig);
OperatingSystemMXBean osMock = mock(OperatingSystemMXBean.class); com.sun.management.OperatingSystemMXBean osMock =
when(osMock.getSystemLoadAverage()).thenReturn(2.0); // >= 0 → Math.min branch mock(com.sun.management.OperatingSystemMXBean.class);
when(osMock.getAvailableProcessors()).thenReturn(4); when(osMock.getProcessCpuLoad()).thenReturn(0.5); // >= 0 → Math.min branch (50%)
try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) { try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) {
mf.when(ManagementFactory::getOperatingSystemMXBean).thenReturn(osMock); mf.when(ManagementFactory::getOperatingSystemMXBean).thenReturn(osMock);
service.monitorSystemMetrics(); // cpuUsage=50%, threshold=100 → below → lastCpuHighTime=null service.monitorSystemMetrics(); // cpuUsage=50%, threshold=100 → below → lastCpuHighTime=null
} }
// Deuxième et troisième appels avec threshold=-1 (0.0 > -1 always), durationMinutes=0 // Deuxième et troisième appels avec threshold=-1 (50.0 > -1 always), durationMinutes=0
AlertConfiguration triggerConfig = buildConfig(true, -1, 0, false, 100, false); AlertConfiguration triggerConfig = buildConfig(true, -1, 0, false, 100, false);
when(alertConfigurationRepository.getConfiguration()).thenReturn(triggerConfig); when(alertConfigurationRepository.getConfiguration()).thenReturn(triggerConfig);
when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class))) when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
@@ -149,28 +149,28 @@ class AlertMonitoringServiceMockStaticCoverageTest {
} }
// ========================================================================= // =========================================================================
// L113-114 : catch(Exception e) dans checkCpuThreshold // catch(Exception e) dans checkCpuThreshold
// ========================================================================= // =========================================================================
/** /**
* Couvre les lignes 113-114 ({@code catch(Exception e)} et {@code log.error(...)}) dans * Couvre le bloc {@code catch(Exception e)} et {@code log.error(...)} dans
* {@code checkCpuThreshold()}. * {@code checkCpuThreshold()}.
* *
* <p>On force {@code ManagementFactory.getOperatingSystemMXBean()} à lancer une * <p>On force {@code ManagementFactory.getOperatingSystemMXBean()} à lancer une
* {@code RuntimeException}. Cette exception est capturée par le catch à L113, * {@code RuntimeException}. Cette exception est capturée par le catch,
* loggée à L114, et la méthode retourne silencieusement. * loggée, et la méthode retourne silencieusement.
*/ */
@Test @Test
@DisplayName("checkCpuThreshold : ManagementFactory.getOperatingSystemMXBean() throw → catch L113-114 couvert") @DisplayName("checkCpuThreshold : ManagementFactory.getOperatingSystemMXBean() throw → catch couvert")
void checkCpuThreshold_managementFactoryThrows_coversCatchL113to114() { void checkCpuThreshold_managementFactoryThrows_coversCatch() {
AlertConfiguration config = buildConfig(true, 80, 5, false, 100, false); AlertConfiguration config = buildConfig(true, 80, 5, false, 100, false);
when(alertConfigurationRepository.getConfiguration()).thenReturn(config); when(alertConfigurationRepository.getConfiguration()).thenReturn(config);
try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) { try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) {
mf.when(ManagementFactory::getOperatingSystemMXBean) mf.when(ManagementFactory::getOperatingSystemMXBean)
.thenThrow(new RuntimeException("Simulated JMX failure — covers catch L113-114")); .thenThrow(new RuntimeException("Simulated JMX failure — covers catch"));
// L113-114 : l'exception est catchée → monitorSystemMetrics ne propage pas // L'exception est catchée → monitorSystemMetrics ne propage pas
assertThatCode(() -> service.monitorSystemMetrics()) assertThatCode(() -> service.monitorSystemMetrics())
.doesNotThrowAnyException(); .doesNotThrowAnyException();
} }
@@ -180,19 +180,19 @@ class AlertMonitoringServiceMockStaticCoverageTest {
} }
/** /**
* Couvre L113-114 via une exception lancée pendant le calcul (NullPointerException * Couvre le catch via une exception lancée pendant le calcul (RuntimeException
* depuis le mock MemoryMXBean non configuré pour OperatingSystemMXBean). * depuis l'appel à {@code getProcessCpuLoad()}).
*/ */
@Test @Test
@DisplayName("checkCpuThreshold : OperatingSystemMXBean.getAvailableProcessors() throw → catch L113-114") @DisplayName("checkCpuThreshold : OperatingSystemMXBean.getProcessCpuLoad() throw → catch couvert")
void checkCpuThreshold_availableProcessorsThrows_coversCatchL113to114() { void checkCpuThreshold_processCpuLoadThrows_coversCatch() {
AlertConfiguration config = buildConfig(true, 80, 5, false, 100, false); AlertConfiguration config = buildConfig(true, 80, 5, false, 100, false);
when(alertConfigurationRepository.getConfiguration()).thenReturn(config); when(alertConfigurationRepository.getConfiguration()).thenReturn(config);
OperatingSystemMXBean osMock = mock(OperatingSystemMXBean.class); com.sun.management.OperatingSystemMXBean osMock =
when(osMock.getSystemLoadAverage()).thenReturn(0.5); mock(com.sun.management.OperatingSystemMXBean.class);
when(osMock.getAvailableProcessors()) when(osMock.getProcessCpuLoad())
.thenThrow(new RuntimeException("Simulated getAvailableProcessors failure")); .thenThrow(new RuntimeException("Simulated getProcessCpuLoad failure"));
try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) { try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) {
mf.when(ManagementFactory::getOperatingSystemMXBean).thenReturn(osMock); mf.when(ManagementFactory::getOperatingSystemMXBean).thenReturn(osMock);
@@ -205,11 +205,11 @@ class AlertMonitoringServiceMockStaticCoverageTest {
} }
// ========================================================================= // =========================================================================
// L127 : branche ternaire maxMemory <= 0 → 0.0 // checkMemoryThreshold : branche ternaire maxMemory <= 0 → 0.0
// ========================================================================= // =========================================================================
/** /**
* Couvre la branche {@code maxMemory <= 0} à la ligne 127 : * Couvre la branche {@code maxMemory <= 0} :
* {@code double memoryUsage = maxMemory > 0 ? ... : 0.0}. * {@code double memoryUsage = maxMemory > 0 ? ... : 0.0}.
* *
* <p>On injecte un {@code MemoryMXBean} mock dont {@code getHeapMemoryUsage().getMax()} * <p>On injecte un {@code MemoryMXBean} mock dont {@code getHeapMemoryUsage().getMax()}
@@ -219,14 +219,14 @@ class AlertMonitoringServiceMockStaticCoverageTest {
* <p>Avec threshold=0 : {@code 0.0 > 0} est false → pas d'alerte. * <p>Avec threshold=0 : {@code 0.0 > 0} est false → pas d'alerte.
*/ */
@Test @Test
@DisplayName("checkMemoryThreshold : maxMemory = -1 (non défini) → memoryUsage = 0.0 → branche false L127") @DisplayName("checkMemoryThreshold : maxMemory = -1 (non défini) → memoryUsage = 0.0 → branche false")
void checkMemoryThreshold_maxMemoryMinusOne_coversL127FalseBranch() { void checkMemoryThreshold_maxMemoryMinusOne_coversFalseBranch() {
AlertConfiguration config = buildConfig(false, 80, 5, true, 0, false); AlertConfiguration config = buildConfig(false, 80, 5, true, 0, false);
when(alertConfigurationRepository.getConfiguration()).thenReturn(config); when(alertConfigurationRepository.getConfiguration()).thenReturn(config);
MemoryMXBean memMock = mock(MemoryMXBean.class); MemoryMXBean memMock = mock(MemoryMXBean.class);
MemoryUsage heapUsageMock = mock(MemoryUsage.class); MemoryUsage heapUsageMock = mock(MemoryUsage.class);
when(heapUsageMock.getMax()).thenReturn(-1L); // maxMemory = -1 → branche false à L127 when(heapUsageMock.getMax()).thenReturn(-1L); // maxMemory = -1 → branche false
when(heapUsageMock.getUsed()).thenReturn(100L); when(heapUsageMock.getUsed()).thenReturn(100L);
when(memMock.getHeapMemoryUsage()).thenReturn(heapUsageMock); when(memMock.getHeapMemoryUsage()).thenReturn(heapUsageMock);
@@ -242,19 +242,19 @@ class AlertMonitoringServiceMockStaticCoverageTest {
} }
/** /**
* Couvre L127 branche false avec maxMemory = 0. * Couvre la branche false du ternaire avec maxMemory = 0.
* *
* <p>La condition {@code maxMemory > 0} est false pour maxMemory = 0 aussi. * <p>La condition {@code maxMemory > 0} est false pour maxMemory = 0 aussi.
*/ */
@Test @Test
@DisplayName("checkMemoryThreshold : maxMemory = 0 → memoryUsage = 0.0 → branche false L127") @DisplayName("checkMemoryThreshold : maxMemory = 0 → memoryUsage = 0.0 → branche false")
void checkMemoryThreshold_maxMemoryZero_coversL127FalseBranch() { void checkMemoryThreshold_maxMemoryZero_coversFalseBranch() {
AlertConfiguration config = buildConfig(false, 80, 5, true, 0, false); AlertConfiguration config = buildConfig(false, 80, 5, true, 0, false);
when(alertConfigurationRepository.getConfiguration()).thenReturn(config); when(alertConfigurationRepository.getConfiguration()).thenReturn(config);
MemoryMXBean memMock = mock(MemoryMXBean.class); MemoryMXBean memMock = mock(MemoryMXBean.class);
MemoryUsage heapUsageMock = mock(MemoryUsage.class); MemoryUsage heapUsageMock = mock(MemoryUsage.class);
when(heapUsageMock.getMax()).thenReturn(0L); // maxMemory = 0 → branche false à L127 when(heapUsageMock.getMax()).thenReturn(0L); // maxMemory = 0 → branche false
when(heapUsageMock.getUsed()).thenReturn(50L); when(heapUsageMock.getUsed()).thenReturn(50L);
when(memMock.getHeapMemoryUsage()).thenReturn(heapUsageMock); when(memMock.getHeapMemoryUsage()).thenReturn(heapUsageMock);
@@ -270,11 +270,11 @@ class AlertMonitoringServiceMockStaticCoverageTest {
} }
/** /**
* Couvre L127 branche true (maxMemory > 0) + alerte mémoire créée. * Couvre la branche true (maxMemory > 0) + alerte mémoire créée.
* Vérifie que le mock MemoryMXBean avec une valeur positive couvre la branche vraie. * Vérifie que le mock MemoryMXBean avec une valeur positive couvre la branche vraie.
*/ */
@Test @Test
@DisplayName("checkMemoryThreshold : maxMemory > 0 + usage > threshold → alerte créée (L127 branche true)") @DisplayName("checkMemoryThreshold : maxMemory > 0 + usage > threshold → alerte créée (branche true)")
void checkMemoryThreshold_maxMemoryPositive_usageAboveThreshold_createsAlert() { void checkMemoryThreshold_maxMemoryPositive_usageAboveThreshold_createsAlert() {
AlertConfiguration config = buildConfig(false, 80, 5, true, 0, false); AlertConfiguration config = buildConfig(false, 80, 5, true, 0, false);
when(alertConfigurationRepository.getConfiguration()).thenReturn(config); when(alertConfigurationRepository.getConfiguration()).thenReturn(config);
@@ -299,25 +299,27 @@ class AlertMonitoringServiceMockStaticCoverageTest {
} }
// ========================================================================= // =========================================================================
// L85 : combinaison de loadAvg >= 0 avec threshold dépassé (couvre Math.min) // checkCpuThreshold : branche processCpuLoad < 0 (API non disponible)
// cpuUsage = 0.0 (ternaire : processCpuLoad < 0 ? 0.0 : ...)
// ========================================================================= // =========================================================================
/** /**
* Couvre explicitement la branche {@code loadAvg < 0} (L85, branche true du ternaire) * Couvre explicitement la branche {@code processCpuLoad < 0} du ternaire
* pour compléter la couverture des deux branches du ternaire à L85. * pour compléter la couverture des deux branches du ternaire.
* *
* <p>On injecte {@code getSystemLoadAverage() = -1.0} (loadAvg < 0 → cpuUsage = 0.0). * <p>On injecte {@code getProcessCpuLoad() = -1.0} → cpuUsage = 0.0.
* Cela simule le comportement Windows où loadAvg = -1. * Cela simule le cas où l'API n'est pas disponible (le contrat JDK dit que
* {@code getProcessCpuLoad()} peut retourner -1 si la valeur n'est pas disponible).
*/ */
@Test @Test
@DisplayName("checkCpuThreshold : loadAvg = -1 (Windows) → cpuUsage = 0.0 → branche true L85 ternaire") @DisplayName("checkCpuThreshold : processCpuLoad = -1 (indispo) → cpuUsage = 0.0 → branche true ternaire")
void checkCpuThreshold_loadAvgNegative_coversL85TrueBranch() { void checkCpuThreshold_processCpuLoadNegative_coversTrueBranch() {
AlertConfiguration config = buildConfig(true, 100, 5, false, 100, false); AlertConfiguration config = buildConfig(true, 100, 5, false, 100, false);
when(alertConfigurationRepository.getConfiguration()).thenReturn(config); when(alertConfigurationRepository.getConfiguration()).thenReturn(config);
OperatingSystemMXBean osMock = mock(OperatingSystemMXBean.class); com.sun.management.OperatingSystemMXBean osMock =
when(osMock.getSystemLoadAverage()).thenReturn(-1.0); // < 0 → cpuUsage = 0.0 mock(com.sun.management.OperatingSystemMXBean.class);
when(osMock.getAvailableProcessors()).thenReturn(4); when(osMock.getProcessCpuLoad()).thenReturn(-1.0); // < 0 → cpuUsage = 0.0
try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) { try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) {
mf.when(ManagementFactory::getOperatingSystemMXBean).thenReturn(osMock); mf.when(ManagementFactory::getOperatingSystemMXBean).thenReturn(osMock);
@@ -331,7 +333,7 @@ class AlertMonitoringServiceMockStaticCoverageTest {
} }
// ========================================================================= // =========================================================================
// L229 : lambda anyMatch dans hasRecentCpuAlert // hasRecentCpuAlert : lambda anyMatch
// Couverture supplémentaire des branches de la lambda // Couverture supplémentaire des branches de la lambda
// ========================================================================= // =========================================================================
@@ -340,11 +342,11 @@ class AlertMonitoringServiceMockStaticCoverageTest {
* {@code source = "CPU"} et {@code acknowledged = false} → {@code anyMatch} retourne true * {@code source = "CPU"} et {@code acknowledged = false} → {@code anyMatch} retourne true
* → {@code hasRecentCpuAlert()} = true → aucune nouvelle alerte créée. * → {@code hasRecentCpuAlert()} = true → aucune nouvelle alerte créée.
* *
* <p>La lambda à L229 : {@code "CPU".equals(alert.getSource()) && !alert.getAcknowledged()} * <p>Lambda : {@code "CPU".equals(alert.getSource()) && !alert.getAcknowledged()}
* — branche : source="CPU" true ET acknowledged=false → true → anyMatch=true. * — branche : source="CPU" true ET acknowledged=false → true → anyMatch=true.
*/ */
@Test @Test
@DisplayName("hasRecentCpuAlert lambda L229 : source=CPU + acknowledged=false → anyMatch true → pas d'alerte") @DisplayName("hasRecentCpuAlert lambda : source=CPU + acknowledged=false → anyMatch true → pas d'alerte")
void hasRecentCpuAlert_lambda_sourceCpu_acknowledgedFalse_anyMatchTrue_noNewAlert() throws Exception { void hasRecentCpuAlert_lambda_sourceCpu_acknowledgedFalse_anyMatchTrue_noNewAlert() throws Exception {
// Préparer : reset lastCpuHighTime à null d'abord // Préparer : reset lastCpuHighTime à null d'abord
injectField(service, "lastCpuHighTime", null); injectField(service, "lastCpuHighTime", null);
@@ -362,9 +364,9 @@ class AlertMonitoringServiceMockStaticCoverageTest {
when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class))) when(systemAlertRepository.findByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
.thenReturn(List.of(existingCpuAlert)); .thenReturn(List.of(existingCpuAlert));
OperatingSystemMXBean osMock = mock(OperatingSystemMXBean.class); com.sun.management.OperatingSystemMXBean osMock =
when(osMock.getSystemLoadAverage()).thenReturn(2.0); // loadAvg >= 0 → Math.min branch L85 mock(com.sun.management.OperatingSystemMXBean.class);
when(osMock.getAvailableProcessors()).thenReturn(4); // cpuUsage = 50% > -1 when(osMock.getProcessCpuLoad()).thenReturn(0.5); // >= 0 → Math.min branch → cpuUsage=50%
try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) { try (MockedStatic<ManagementFactory> mf = mockStatic(ManagementFactory.class)) {
mf.when(ManagementFactory::getOperatingSystemMXBean).thenReturn(osMock); mf.when(ManagementFactory::getOperatingSystemMXBean).thenReturn(osMock);

View File

@@ -215,14 +215,14 @@ class AlertMonitoringServiceTest {
@Test @Test
@DisplayName("monitorSystemMetrics - CPU enabled, duration elapsed, no recent CPU alert => alert is created") @DisplayName("monitorSystemMetrics - CPU enabled, duration elapsed, no recent CPU alert => alert is created")
void monitorSystemMetrics_cpuEnabled_durationElapsed_noRecentAlert_createsCpuAlert() { void monitorSystemMetrics_cpuEnabled_durationElapsed_noRecentAlert_createsCpuAlert() {
// Reset state: threshold=100 forces CPU (0.0 on Windows) to be below threshold → lastCpuHighTime = null // Reset state: threshold=100 forces CPU (always <= 100% via Math.min cap) to be below threshold → lastCpuHighTime = null
AlertConfiguration resetConfig = buildDisabledConfig(); AlertConfiguration resetConfig = buildDisabledConfig();
resetConfig.setCpuHighAlertEnabled(true); resetConfig.setCpuHighAlertEnabled(true);
resetConfig.setCpuThresholdPercent(100); resetConfig.setCpuThresholdPercent(100);
when(alertConfigurationRepository.getConfiguration()).thenReturn(resetConfig); when(alertConfigurationRepository.getConfiguration()).thenReturn(resetConfig);
alertMonitoringService.monitorSystemMetrics(); alertMonitoringService.monitorSystemMetrics();
// threshold=-1: any CPU (even 0.0 on Windows where loadAvg=-1) satisfies 0.0 > -1 // threshold=-1: any CPU (even 0.0 when getProcessCpuLoad() returns -1 or 0 on idle JVM) satisfies 0.0 > -1
AlertConfiguration config = buildDisabledConfig(); AlertConfiguration config = buildDisabledConfig();
config.setCpuHighAlertEnabled(true); config.setCpuHighAlertEnabled(true);
config.setCpuThresholdPercent(-1); // guaranteed: 0.0 > -1 is always true config.setCpuThresholdPercent(-1); // guaranteed: 0.0 > -1 is always true
@@ -488,7 +488,7 @@ class AlertMonitoringServiceTest {
when(alertConfigurationRepository.getConfiguration()).thenReturn(resetConfig); when(alertConfigurationRepository.getConfiguration()).thenReturn(resetConfig);
alertMonitoringService.monitorSystemMetrics(); alertMonitoringService.monitorSystemMetrics();
// threshold=-1: 0.0 > -1 is always true (even on Windows with cpuUsage=0.0) // threshold=-1: 0.0 > -1 is always true (getProcessCpuLoad() returns 0.0..1.0 or -1; cpuUsage clamped to [0, 100])
AlertConfiguration config = buildDisabledConfig(); AlertConfiguration config = buildDisabledConfig();
config.setCpuHighAlertEnabled(true); config.setCpuHighAlertEnabled(true);
config.setCpuThresholdPercent(-1); config.setCpuThresholdPercent(-1);
@@ -556,7 +556,7 @@ class AlertMonitoringServiceTest {
when(alertConfigurationRepository.getConfiguration()).thenReturn(resetConfig); when(alertConfigurationRepository.getConfiguration()).thenReturn(resetConfig);
alertMonitoringService.monitorSystemMetrics(); alertMonitoringService.monitorSystemMetrics();
// threshold=-1: 0.0 > -1 is always true (even on Windows with cpuUsage=0.0) // threshold=-1: 0.0 > -1 is always true (getProcessCpuLoad() returns 0.0..1.0 or -1; cpuUsage clamped to [0, 100])
AlertConfiguration config = buildDisabledConfig(); AlertConfiguration config = buildDisabledConfig();
config.setCpuHighAlertEnabled(true); config.setCpuHighAlertEnabled(true);
config.setCpuThresholdPercent(-1); config.setCpuThresholdPercent(-1);