Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
import org.gridsuite.monitor.commons.PersistedProcessConfig;
import org.gridsuite.monitor.commons.ProcessConfig;
import org.gridsuite.monitor.commons.ProcessType;
import org.gridsuite.monitor.server.dto.ProcessConfigComparison;
import org.gridsuite.monitor.server.services.ProcessConfigService;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
Expand Down Expand Up @@ -99,4 +101,22 @@ public ResponseEntity<List<PersistedProcessConfig>> getProcessConfigs(
List<PersistedProcessConfig> processConfigs = processConfigService.getProcessConfigs(processType);
return ResponseEntity.ok().body(processConfigs);
}

@GetMapping(value = "/compare", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Compare 2 process configs")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Comparison result returned"),
@ApiResponse(responseCode = "404", description = "One or both process configs are not found"),
@ApiResponse(responseCode = "400", description = "Process configs are of different types")})
public ResponseEntity<ProcessConfigComparison> compareProcessConfigs(
@Parameter(description = "First process config UUID") @RequestParam("uuid1") UUID uuid1,
@Parameter(description = "Second process config UUID") @RequestParam("uuid2") UUID uuid2) {

try {
Optional<ProcessConfigComparison> comparison = processConfigService.compareProcessConfigs(uuid1, uuid2);
return comparison.map(c -> ResponseEntity.status(HttpStatus.OK).body(c)).orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND).build());
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().build();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright (c) 2026, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.monitor.server.dto;

import io.swagger.v3.oas.annotations.media.Schema;

import java.util.List;
import java.util.UUID;

/**
* @author Franck Lecuyer <franck.lecuyer at rte-france.com>
*/
@Schema(description = "Process config comparison result")
public record ProcessConfigComparison(
UUID processConfigUuid1,
UUID processConfigUuid2,
boolean identical,
List<ProcessConfigFieldComparison> differences) { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2026, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.monitor.server.dto;

import io.swagger.v3.oas.annotations.media.Schema;

/**
* @author Franck Lecuyer <franck.lecuyer at rte-france.com>
*/
@Schema(description = "Field comparison result")
public record ProcessConfigFieldComparison(
String field,
boolean identical,
Object value1,
Object value2) { }
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@
import org.gridsuite.monitor.commons.ProcessType;
import org.gridsuite.monitor.commons.SecurityAnalysisConfig;
import org.gridsuite.monitor.server.entities.ProcessConfigEntity;
import org.gridsuite.monitor.server.dto.ProcessConfigComparison;
import org.gridsuite.monitor.server.dto.ProcessConfigFieldComparison;
import org.gridsuite.monitor.server.entities.SecurityAnalysisConfigEntity;
import org.gridsuite.monitor.server.mapper.SecurityAnalysisConfigMapper;
import org.gridsuite.monitor.server.repositories.ProcessConfigRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;

Expand Down Expand Up @@ -86,4 +90,63 @@
default -> throw new IllegalArgumentException("Unsupported entity type: " + entity.getProcessType());
}).toList();
}

@Transactional(readOnly = true)
public Optional<ProcessConfigComparison> compareProcessConfigs(UUID uuid1, UUID uuid2) {
Optional<PersistedProcessConfig> config1 = getProcessConfig(uuid1);

Check failure on line 96 in monitor-server/src/main/java/org/gridsuite/monitor/server/services/ProcessConfigService.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Call transactional methods via an injected dependency instead of directly via 'this'.

See more on https://sonarcloud.io/project/issues?id=org.gridsuite%3Amonitor-core&issues=AZyVWM3pDc66HDQ0lRn2&open=AZyVWM3pDc66HDQ0lRn2&pullRequest=49
Optional<PersistedProcessConfig> config2 = getProcessConfig(uuid2);

Check failure on line 97 in monitor-server/src/main/java/org/gridsuite/monitor/server/services/ProcessConfigService.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Call transactional methods via an injected dependency instead of directly via 'this'.

See more on https://sonarcloud.io/project/issues?id=org.gridsuite%3Amonitor-core&issues=AZyVWM3pDc66HDQ0lRn3&open=AZyVWM3pDc66HDQ0lRn3&pullRequest=49

if (config1.isEmpty() || config2.isEmpty()) {
return Optional.empty();
}

ProcessConfig processConfig1 = config1.get().processConfig();
ProcessConfig processConfig2 = config2.get().processConfig();

if (processConfig1.processType() != processConfig2.processType()) {
throw new IllegalArgumentException("Cannot compare different process config types: " + processConfig1.processType() + " vs " + processConfig2.processType());
}

List<ProcessConfigFieldComparison> differences = switch (processConfig1) {
case SecurityAnalysisConfig sac1 -> compareSecurityAnalysisConfigs(sac1, (SecurityAnalysisConfig) processConfig2);
default -> throw new IllegalArgumentException("Unsupported process config type: " + processConfig1.processType());
};

boolean identical = differences.stream().allMatch(ProcessConfigFieldComparison::identical);

return Optional.of(new ProcessConfigComparison(uuid1, uuid2, identical, differences));
}

private List<ProcessConfigFieldComparison> compareSecurityAnalysisConfigs(SecurityAnalysisConfig config1, SecurityAnalysisConfig config2) {
List<ProcessConfigFieldComparison> differences = new ArrayList<>();

// Compare modifications
boolean modificationsIdentical = Objects.equals(config1.modificationUuids(), config2.modificationUuids());
differences.add(new ProcessConfigFieldComparison(
"modifications",
modificationsIdentical,
config1.modificationUuids(),
config2.modificationUuids()
));

// Compare security analysis parameters
boolean securityAnalysisParametersIdentical = Objects.equals(config1.securityAnalysisParametersUuid(), config2.securityAnalysisParametersUuid());
differences.add(new ProcessConfigFieldComparison(
"securityAnalysisParameters",
securityAnalysisParametersIdentical,
config1.securityAnalysisParametersUuid(),
config2.securityAnalysisParametersUuid()
));

// Compare loadflow parameters
boolean loadflowParametersIdentical = Objects.equals(config1.loadflowParametersUuid(), config2.loadflowParametersUuid());
differences.add(new ProcessConfigFieldComparison(
"loadflowParameters",
loadflowParametersIdentical,
config1.loadflowParametersUuid(),
config2.loadflowParametersUuid()
));

return differences;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import org.gridsuite.monitor.commons.ProcessConfig;
import org.gridsuite.monitor.commons.ProcessType;
import org.gridsuite.monitor.commons.SecurityAnalysisConfig;
import org.gridsuite.monitor.server.dto.ProcessConfigComparison;
import org.gridsuite.monitor.server.dto.ProcessConfigFieldComparison;
import org.gridsuite.monitor.server.services.ProcessConfigService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -217,4 +219,122 @@ void getAllSecurityAnalysisConfigsNotFound() throws Exception {

verify(processConfigService).getProcessConfigs(ProcessType.SECURITY_ANALYSIS);
}

@Test
void compareProcessConfigsShouldReturnComparisonResult() throws Exception {
UUID uuid1 = UUID.randomUUID();
UUID uuid2 = UUID.randomUUID();
UUID securityAnalysisParametersUuid = UUID.randomUUID();
UUID loadflowParametersUuid = UUID.randomUUID();
List<UUID> modificationUuids = List.of(UUID.randomUUID());

ProcessConfigComparison comparison = new ProcessConfigComparison(
uuid1,
uuid2,
true,
List.of(
new ProcessConfigFieldComparison("modifications", true, modificationUuids, modificationUuids),
new ProcessConfigFieldComparison("securityAnalysisParameters", true, securityAnalysisParametersUuid, securityAnalysisParametersUuid),
new ProcessConfigFieldComparison("loadflowParameters", true, loadflowParametersUuid, loadflowParametersUuid)
)
);

when(processConfigService.compareProcessConfigs(uuid1, uuid2))
.thenReturn(Optional.of(comparison));

mockMvc.perform(get("/v1/process-configs/compare")
.param("uuid1", uuid1.toString())
.param("uuid2", uuid2.toString())
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.processConfigUuid1").value(uuid1.toString()))
.andExpect(jsonPath("$.processConfigUuid2").value(uuid2.toString()))
.andExpect(jsonPath("$.identical").value(true))
.andExpect(jsonPath("$.differences").isArray())
.andExpect(jsonPath("$.differences.length()").value(3));

verify(processConfigService).compareProcessConfigs(uuid1, uuid2);
}

@Test
void compareProcessConfigsShouldReturnDifferences() throws Exception {
UUID uuid1 = UUID.randomUUID();
UUID uuid2 = UUID.randomUUID();
List<UUID> modificationUuids1 = List.of(UUID.randomUUID());
List<UUID> modificationUuids2 = List.of(UUID.randomUUID());

ProcessConfigComparison comparison = new ProcessConfigComparison(
uuid1,
uuid2,
false,
List.of(
new ProcessConfigFieldComparison("modifications", false, modificationUuids1, modificationUuids2)
)
);

when(processConfigService.compareProcessConfigs(uuid1, uuid2))
.thenReturn(Optional.of(comparison));

mockMvc.perform(get("/v1/process-configs/compare")
.param("uuid1", uuid1.toString())
.param("uuid2", uuid2.toString())
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.identical").value(false))
.andExpect(jsonPath("$.differences[0].field").value("modifications"))
.andExpect(jsonPath("$.differences[0].identical").value(false));

verify(processConfigService).compareProcessConfigs(uuid1, uuid2);
}

@Test
void compareProcessConfigsShouldReturn404WhenConfigNotFound() throws Exception {
UUID uuid1 = UUID.randomUUID();
UUID uuid2 = UUID.randomUUID();

when(processConfigService.compareProcessConfigs(uuid1, uuid2))
.thenReturn(Optional.empty());

mockMvc.perform(get("/v1/process-configs/compare")
.param("uuid1", uuid1.toString())
.param("uuid2", uuid2.toString())
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound());

verify(processConfigService).compareProcessConfigs(uuid1, uuid2);
}

@Test
void compareProcessConfigsShouldReturn400WhenDifferentTypes() throws Exception {
UUID uuid1 = UUID.randomUUID();
UUID uuid2 = UUID.randomUUID();

when(processConfigService.compareProcessConfigs(any(), any()))
.thenThrow(new IllegalArgumentException("Cannot compare different process config types"));

mockMvc.perform(get("/v1/process-configs/compare")
.param("uuid1", uuid1.toString())
.param("uuid2", uuid2.toString())
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest());

verify(processConfigService).compareProcessConfigs(uuid1, uuid2);
}

@Test
void compareProcessConfigsShouldReturn400WhenMissingParameters() throws Exception {
mockMvc.perform(get("/v1/process-configs/compare")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest());
}

@Test
void compareProcessConfigsShouldReturn400WhenInvalidUUID() throws Exception {
mockMvc.perform(get("/v1/process-configs/compare")
.param("uuid1", "invalid-uuid")
.param("uuid2", UUID.randomUUID().toString())
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest());
}
}
Loading
Loading