diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedElementInfos.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedElementInfos.java new file mode 100644 index 0000000000..96356e9994 --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedElementInfos.java @@ -0,0 +1,16 @@ +/** + * 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.study.server.dto.studyexport.parameters; + +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.UUID; + +/** + * @author Ghazwa Rehili + */ +public record ExportedElementInfos(UUID uuid, String name, JsonNode content) { } diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParameters.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParameters.java new file mode 100644 index 0000000000..3cbddefb37 --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParameters.java @@ -0,0 +1,36 @@ +/** + * 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.study.server.dto.studyexport.parameters; + +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Ghazwa Rehili + */ +public interface ExportedParameters { + + default Set getFilterUuids() { + return Set.of(); + } + + default Set getContingencyListUuids() { + return Set.of(); + } + + static Stream nullSafe(List list) { + return list == null ? Stream.empty() : list.stream(); + } + + static Set toUuidSet(Stream uuids) { + return uuids.filter(Objects::nonNull).collect(Collectors.toSet()); + } +} diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/PccMinExportedParameters.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/PccMinExportedParameters.java new file mode 100644 index 0000000000..e129998f99 --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/PccMinExportedParameters.java @@ -0,0 +1,28 @@ +/** + * 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.study.server.dto.studyexport.parameters; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParameters.nullSafe; +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParameters.toUuidSet; + +/** + * @author Ghazwa Rehili + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record PccMinExportedParameters(List filters) implements ExportedParameters { + + @Override + public Set getFilterUuids() { + return toUuidSet(nullSafe(filters)); + } +} diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SecurityAnalysisExportedParameters.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SecurityAnalysisExportedParameters.java new file mode 100644 index 0000000000..aad2268a09 --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SecurityAnalysisExportedParameters.java @@ -0,0 +1,31 @@ +/** + * 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.study.server.dto.studyexport.parameters; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParameters.nullSafe; +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParameters.toUuidSet; + +/** + * @author Ghazwa Rehili + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record SecurityAnalysisExportedParameters(List contingencyListsInfos) implements ExportedParameters { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record ContingencyListsInfos(List contingencyLists) { } + + @Override + public Set getContingencyListUuids() { + return toUuidSet(nullSafe(contingencyListsInfos).flatMap(infos -> nullSafe(infos.contingencyLists()))); + } +} diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SensitivityAnalysisExportedParameters.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SensitivityAnalysisExportedParameters.java new file mode 100644 index 0000000000..0758cd1571 --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/SensitivityAnalysisExportedParameters.java @@ -0,0 +1,74 @@ +/** + * 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.study.server.dto.studyexport.parameters; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Stream; + +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParameters.nullSafe; +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParameters.toUuidSet; + +/** + * @author Ghazwa Rehili + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record SensitivityAnalysisExportedParameters( + List sensitivityInjectionsSet, + List sensitivityInjection, + List sensitivityHVDC, + List sensitivityPST, + List sensitivityNodes +) implements ExportedParameters { + @JsonIgnoreProperties(ignoreUnknown = true) + public record SensitivityFactor( + List monitoredBranches, + List injections, + List hvdcs, + List psts, + List contingencies + ) { + Stream filterUuids() { + return Stream.of(monitoredBranches, injections, hvdcs, psts) + .flatMap(ExportedParameters::nullSafe); + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record SensitivityNodes( + List monitoredVoltageLevels, + List equipmentsInVoltageRegulation, + List contingencies + ) { + Stream filterUuids() { + return Stream.of(monitoredVoltageLevels, equipmentsInVoltageRegulation) + .flatMap(ExportedParameters::nullSafe); + } + } + + private Stream allFactors() { + return Stream.of(sensitivityInjectionsSet, sensitivityInjection, sensitivityHVDC, sensitivityPST) + .flatMap(ExportedParameters::nullSafe); + } + + @Override + public Set getFilterUuids() { + return toUuidSet(Stream.concat( + allFactors().flatMap(SensitivityFactor::filterUuids), + nullSafe(sensitivityNodes).flatMap(SensitivityNodes::filterUuids))); + } + + @Override + public Set getContingencyListUuids() { + return toUuidSet(Stream.concat( + allFactors().flatMap(factor -> nullSafe(factor.contingencies())), + nullSafe(sensitivityNodes).flatMap(nodes -> nullSafe(nodes.contingencies())))); + } +} diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ShortCircuitExportedParameters.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ShortCircuitExportedParameters.java new file mode 100644 index 0000000000..b2cc584635 --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/ShortCircuitExportedParameters.java @@ -0,0 +1,64 @@ +/** + * 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.study.server.dto.studyexport.parameters; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; + +import java.io.UncheckedIOException; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Stream; + +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParameters.nullSafe; +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParameters.toUuidSet; + +/** + * @author Ghazwa Rehili + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record ShortCircuitExportedParameters(Map> specificParametersPerProvider) implements ExportedParameters { + + public static final String POWER_ELECTRONICS_CLUSTERS = "powerElectronicsClusters"; + public static final String NODE_CLUSTER_FILTER_IDS = "nodeClusterFilterIds"; + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + @JsonIgnoreProperties(ignoreUnknown = true) + public record FilterElements(UUID filterId) { } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record PowerElectronicsCluster(List filters) { } + + @Override + public Set getFilterUuids() { + Stream> specificParameters = specificParametersPerProvider == null ? Stream.empty() + : specificParametersPerProvider.values().stream().filter(Objects::nonNull); + return toUuidSet(specificParameters.flatMap(parameters -> Stream.concat( + parseList(parameters.get(POWER_ELECTRONICS_CLUSTERS), new TypeReference>() { }) + .flatMap(cluster -> nullSafe(cluster.filters())), + parseList(parameters.get(NODE_CLUSTER_FILTER_IDS), new TypeReference>() { }))) + .map(FilterElements::filterId)); + } + + private static Stream parseList(String json, TypeReference> type) { + if (StringUtils.isBlank(json)) { + return Stream.empty(); + } + try { + return nullSafe(OBJECT_MAPPER.readValue(json, type)).filter(Objects::nonNull); + } catch (JsonProcessingException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/VoltageInitExportedParameters.java b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/VoltageInitExportedParameters.java new file mode 100644 index 0000000000..7de9ca7b7f --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/dto/studyexport/parameters/VoltageInitExportedParameters.java @@ -0,0 +1,45 @@ +/** + * 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.study.server.dto.studyexport.parameters; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Stream; + +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParameters.nullSafe; +import static org.gridsuite.study.server.dto.studyexport.parameters.ExportedParameters.toUuidSet; + +/** + * @author Ghazwa Rehili + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record VoltageInitExportedParameters( + List voltageLimitsModification, + List voltageLimitsDefault, + List variableQGenerators, + List variableTwoWindingsTransformers, + List variableShuntCompensators +) implements ExportedParameters { + + @JsonIgnoreProperties(ignoreUnknown = true) + public record VoltageLimit(List filters) { } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record FilterEquipments(UUID filterId) { } + + @Override + public Set getFilterUuids() { + Stream limitFilters = Stream.concat(nullSafe(voltageLimitsModification), nullSafe(voltageLimitsDefault)) + .flatMap(voltageLimit -> nullSafe(voltageLimit.filters())); + Stream variableFilters = Stream.of(variableQGenerators, variableTwoWindingsTransformers, variableShuntCompensators) + .flatMap(ExportedParameters::nullSafe); + return toUuidSet(Stream.concat(limitFilters, variableFilters).map(FilterEquipments::filterId)); + } +} diff --git a/src/main/java/org/gridsuite/study/server/service/ActionsService.java b/src/main/java/org/gridsuite/study/server/service/ActionsService.java index f863d1ee26..08d4608c3a 100644 --- a/src/main/java/org/gridsuite/study/server/service/ActionsService.java +++ b/src/main/java/org/gridsuite/study/server/service/ActionsService.java @@ -11,14 +11,17 @@ * @author Kevin Le Saulnier */ +import lombok.Setter; import org.apache.commons.lang3.StringUtils; import org.gridsuite.study.server.RemoteServicesProperties; import org.gridsuite.study.server.dto.ContingencyCount; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.UUID; @@ -32,9 +35,11 @@ public class ActionsService { private static final String NETWORK_UUID = "networkUuid"; private static final String CONTINGENCY_LIST_IDS = "ids"; + private static final String EMPTY_JSON_ARRAY = "[]"; public static final ContingencyCount EMPTY_CONTINGENCY_COUNT = new ContingencyCount(Map.of()); + @Setter private String actionsServerBaseUri; public ActionsService(RemoteServicesProperties remoteServicesProperties, RestTemplate restTemplate) { @@ -58,7 +63,17 @@ public ContingencyCount getContingencyCount(UUID networkUuid, String variantId, ).getBody(); } - public void setActionsServerBaseUri(String actionsServerBaseUri) { - this.actionsServerBaseUri = actionsServerBaseUri; + public List getReferencedFilterUuids(Collection contingencyListUuids) { + String path = UriComponentsBuilder.fromPath(DELIMITER + ACTIONS_API_VERSION + "/contingency-lists/filter-uuids") + .queryParam(CONTINGENCY_LIST_IDS, contingencyListUuids) + .toUriString(); + return restTemplate.exchange(actionsServerBaseUri + path, HttpMethod.GET, null, new ParameterizedTypeReference>() { }).getBody(); + } + + public String getContingencyLists(Collection ids) { + if (ids.isEmpty()) { + return EMPTY_JSON_ARRAY; + } + return restTemplate.postForObject(actionsServerBaseUri + DELIMITER + ACTIONS_API_VERSION + "/contingency-lists", ids, String.class); } } diff --git a/src/main/java/org/gridsuite/study/server/service/FilterService.java b/src/main/java/org/gridsuite/study/server/service/FilterService.java index 1f643854ec..46ff42d1f1 100644 --- a/src/main/java/org/gridsuite/study/server/service/FilterService.java +++ b/src/main/java/org/gridsuite/study/server/service/FilterService.java @@ -22,6 +22,7 @@ import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.UUID; @@ -43,6 +44,7 @@ public class FilterService { public static final String FILTER_END_POINT_EVALUATE_IDS = "/filters/evaluate/identifiables"; public static final String FILTER_END_POINT_EXPORT = "/filters/{id}/export"; public static final String FILTERS_END_POINT_EXPORT = "/filters/export"; + private static final String EMPTY_JSON_ARRAY = "[]"; private final RestTemplate restTemplate; @@ -158,4 +160,21 @@ public String evaluateFilters(UUID networkUuid, String filters) { return restTemplate.postForObject(uriComponent.toUriString(), request, String.class); } + + public List getReferencedFilterUuids(Collection filterUuids) { + String path = UriComponentsBuilder.fromPath(DELIMITER + FILTER_API_VERSION + "/filters/referenced-filter-uuids") + .queryParam(IDS, filterUuids) + .toUriString(); + return restTemplate.exchange(getBaseUri() + path, HttpMethod.GET, null, new ParameterizedTypeReference>() { }).getBody(); + } + + public String getFilters(Collection filterUuids) { + if (filterUuids.isEmpty()) { + return EMPTY_JSON_ARRAY; + } + String path = UriComponentsBuilder.fromPath(DELIMITER + FILTER_API_VERSION + "/filters/metadata") + .queryParam(IDS, filterUuids) + .toUriString(); + return restTemplate.getForObject(getBaseUri() + path, String.class); + } } diff --git a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java index 39785ae8b6..953c58d588 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyExportService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyExportService.java @@ -6,11 +6,15 @@ */ package org.gridsuite.study.server.service; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.gridsuite.study.server.dto.ComputationType; import org.gridsuite.study.server.dto.networkexport.PermissionType; import org.gridsuite.study.server.dto.studyexport.RootNetworkExportInfos; import org.gridsuite.study.server.dto.studyexport.TreeExportInfos; +import org.gridsuite.study.server.dto.studyexport.parameters.*; import org.gridsuite.study.server.error.StudyException; +import org.gridsuite.study.server.service.common.ComputationParametersService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.InputStreamResource; @@ -25,10 +29,14 @@ import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; +import java.util.ArrayList; import java.util.Comparator; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; @@ -44,17 +52,30 @@ public class StudyExportService { private static final Logger LOGGER = LoggerFactory.getLogger(StudyExportService.class); public static final String TREE_JSON_FILE_NAME = "tree.json"; public static final String CASES_FOLDER = "cases"; + public static final String PARAMETERS_FOLDER = "computationParameters"; + public static final String CONTINGENCY_LIST_JSON = "contingencyList.json"; + public static final String FILTERS_JSON = "filters.json"; + public static final String ID = "id"; + public static final String JSON = ".json"; private final StudyService studyService; private final CaseService caseService; private final DirectoryService directoryService; private final ObjectMapper objectMapper; + private final ComputationParametersService computationParametersService; + private final FilterService filterService; + private final ActionsService actionsService; - public StudyExportService(StudyService studyService, CaseService caseService, DirectoryService directoryService, ObjectMapper objectMapper) { + public StudyExportService(StudyService studyService, CaseService caseService, DirectoryService directoryService, + ObjectMapper objectMapper, ComputationParametersService computationParametersService, + FilterService filterService, ActionsService actionsService) { this.studyService = studyService; this.caseService = caseService; this.directoryService = directoryService; this.objectMapper = objectMapper; + this.computationParametersService = computationParametersService; + this.filterService = filterService; + this.actionsService = actionsService; } /** @@ -103,6 +124,7 @@ private Path compressStudyToZip(UUID studyUuid, Path tempDir) throws IOException String caseName = rootNetworkInfos.caseInfos().getCaseName(); exportCaseFile(caseUuid, caseName, casesDir); } + exportParameters(studyUuid, tempDir); Path zipFile = createTempExportFile(studyUuid); try (OutputStream fos = Files.newOutputStream(zipFile); ZipOutputStream zipOut = new ZipOutputStream(fos)) { @@ -111,6 +133,51 @@ private Path compressStudyToZip(UUID studyUuid, Path tempDir) throws IOException return zipFile; } + private void exportParameters(UUID studyUuid, Path tempDir) throws IOException { + Path parametersDir = Files.createDirectories(tempDir.resolve(PARAMETERS_FOLDER)); + Set filterUuids = new HashSet<>(); + Set contingencyListUuids = new HashSet<>(); + for (Map.Entry parameters : computationParametersService.exportParameters(studyService.getStudy(studyUuid)).entrySet()) { + Files.writeString(parametersDir.resolve(parameters.getKey().name() + JSON), parameters.getValue()); + ExportedParameters exportedParameters = readParametersReferences(parameters.getKey(), parameters.getValue()); + filterUuids.addAll(exportedParameters.getFilterUuids()); + contingencyListUuids.addAll(exportedParameters.getContingencyListUuids()); + } + if (!contingencyListUuids.isEmpty()) { + filterUuids.addAll(actionsService.getReferencedFilterUuids(contingencyListUuids)); + } + if (!filterUuids.isEmpty()) { + filterUuids.addAll(filterService.getReferencedFilterUuids(filterUuids)); + } + Map names = directoryService.getElementNames(Stream.concat(filterUuids.stream(), contingencyListUuids.stream()).collect(Collectors.toSet())); + List contingencyLists = toExportedElements(actionsService.getContingencyLists(contingencyListUuids), names); + List filters = toExportedElements(filterService.getFilters(filterUuids), names); + objectMapper.writeValue(parametersDir.resolve(CONTINGENCY_LIST_JSON).toFile(), contingencyLists); + objectMapper.writeValue(parametersDir.resolve(FILTERS_JSON).toFile(), filters); + } + + private List toExportedElements(String content, Map names) throws IOException { + List elements = new ArrayList<>(); + for (JsonNode jsonNodeContent : objectMapper.readTree(content)) { + UUID uuid = UUID.fromString(jsonNodeContent.get(ID).asText()); + elements.add(new ExportedElementInfos(uuid, names.get(uuid), jsonNodeContent)); + } + return elements; + } + + private ExportedParameters readParametersReferences(ComputationType type, String parametersJson) throws IOException { + Class referencesClass = switch (type) { + case SECURITY_ANALYSIS -> SecurityAnalysisExportedParameters.class; + case SHORT_CIRCUIT -> ShortCircuitExportedParameters.class; + case SENSITIVITY_ANALYSIS -> SensitivityAnalysisExportedParameters.class; + case VOLTAGE_INITIALIZATION -> VoltageInitExportedParameters.class; + case PCC_MIN -> PccMinExportedParameters.class; + // LOAD_FLOW: the load flow parameters do not reference any filter or contingency list + default -> null; + }; + return referencesClass == null ? new ExportedParameters() { } : objectMapper.readValue(parametersJson, referencesClass); + } + private Path createTempWorkDir(UUID studyUuid) { return createTempPath(studyUuid, "temp directory", "rwx------", attr -> Files.createTempDirectory("study-export-" + studyUuid, attr)); diff --git a/src/main/java/org/gridsuite/study/server/service/client/RemoteServiceName.java b/src/main/java/org/gridsuite/study/server/service/client/RemoteServiceName.java index fc63d66a9c..80906d2117 100644 --- a/src/main/java/org/gridsuite/study/server/service/client/RemoteServiceName.java +++ b/src/main/java/org/gridsuite/study/server/service/client/RemoteServiceName.java @@ -28,6 +28,7 @@ public enum RemoteServiceName { DYNAMIC_MAPPING_SERVER, DYNAMIC_SECURITY_ANALYSIS_SERVER, DYNAMIC_SIMULATION_SERVER, + DYNAMIC_MARGIN_CALCULATION_SERVER, EXPLORE_SERVER, FILTER_SERVER, GATEWAY, @@ -49,7 +50,8 @@ public enum RemoteServiceName { TIMESERIES_SERVER, USER_ADMIN_SERVER, USER_IDENTITY_SERVER, - VOLTAGE_INIT_SERVER; + VOLTAGE_INIT_SERVER, + STATE_ESTIMATION_SERVER; /** * Service name in the format found in {@link RemoteServicesProperties.Service#getName() Service.name} diff --git a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java index 737d758a87..d246d509cf 100644 --- a/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java +++ b/src/main/java/org/gridsuite/study/server/service/common/ComputationParametersService.java @@ -6,11 +6,16 @@ */ package org.gridsuite.study.server.service.common; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.gridsuite.study.server.dto.ComputationType; +import org.gridsuite.study.server.dto.ServiceStatusInfos; +import org.gridsuite.study.server.dto.ServiceStatusInfos.ServiceStatus; import org.gridsuite.study.server.dto.UserProfileInfos; import org.gridsuite.study.server.dto.computation.ComputationParameterUUIDs; import org.gridsuite.study.server.repository.StudyEntity; import org.gridsuite.study.server.service.*; +import org.gridsuite.study.server.service.client.RemoteServiceName; import org.gridsuite.study.server.service.dynamicmargincalculation.DynamicMarginCalculationRestService; import org.gridsuite.study.server.service.dynamicsecurityanalysis.DynamicSecurityAnalysisRestService; import org.gridsuite.study.server.service.dynamicsimulation.DynamicSimulationRestService; @@ -25,10 +30,14 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; +import java.util.EnumMap; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.function.BiConsumer; import java.util.function.Function; +import java.util.stream.Collectors; /** * @author Abdelsalem HEDHILI @@ -40,15 +49,20 @@ public class ComputationParametersService { private static final Logger LOGGER = LoggerFactory.getLogger(ComputationParametersService.class); private final UserAdminService userAdminService; + private final RemoteServicesInspector remoteServicesInspector; + private final ObjectMapper objectMapper; private final List computationParametersDefinitions; - // this is useful to avoid repetitive calls when doing operation on all computation types (duplicate, delete) + // this is useful to avoid repetitive calls when doing operation on all computation types (duplicate, delete, export) private record ComputationParametersDefinition( ComputationType type, + RemoteServiceName serviceName, Function studyParameterGetter, Function profileParameterGetter, ComputationParameters service, - BiConsumer parametersSetter + BiConsumer parametersSetter, + // null for the computations whose parameters are not exported + Function parametersFetcher ) { } @@ -62,70 +76,94 @@ public ComputationParametersService(SecurityAnalysisRestService securityAnalysis DynamicMarginCalculationRestService dynamicMarginCalculationRestService, StateEstimationRestService stateEstimationService, PccMinRestService pccMinService, - UserAdminService userAdminService) { + UserAdminService userAdminService, + RemoteServicesInspector remoteServicesInspector, + ObjectMapper objectMapper) { this.userAdminService = userAdminService; + this.remoteServicesInspector = remoteServicesInspector; + this.objectMapper = objectMapper; this.computationParametersDefinitions = List.of( new ComputationParametersDefinition( ComputationType.LOAD_FLOW, + RemoteServiceName.LOADFLOW_SERVER, StudyEntity::getLoadFlowParametersUuid, UserProfileInfos::getLoadFlowParameterId, loadFlowRestService, - ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::loadFlowParametersUuid), + ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::loadFlowParametersUuid, + loadFlowRestService::getParameters), new ComputationParametersDefinition( ComputationType.SHORT_CIRCUIT, + RemoteServiceName.SHORTCIRCUIT_SERVER, StudyEntity::getShortCircuitParametersUuid, UserProfileInfos::getShortcircuitParameterId, shortCircuitService, - ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::shortCircuitParametersUuid), + ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::shortCircuitParametersUuid, + shortCircuitService::getParameters), new ComputationParametersDefinition( ComputationType.DYNAMIC_SIMULATION, + RemoteServiceName.DYNAMIC_SIMULATION_SERVER, StudyEntity::getDynamicSimulationParametersUuid, UserProfileInfos::getDynamicSimulationParameterId, dynamicSimulationRestService, - ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::dynamicSimulationParametersUuid), + ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::dynamicSimulationParametersUuid, + null), new ComputationParametersDefinition( ComputationType.VOLTAGE_INITIALIZATION, + RemoteServiceName.VOLTAGE_INIT_SERVER, StudyEntity::getVoltageInitParametersUuid, UserProfileInfos::getVoltageInitParameterId, voltageInitService, - ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::voltageInitParametersUuid), + ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::voltageInitParametersUuid, + voltageInitService::getParameters), new ComputationParametersDefinition( ComputationType.SECURITY_ANALYSIS, + RemoteServiceName.SECURITY_ANALYSIS_SERVER, StudyEntity::getSecurityAnalysisParametersUuid, UserProfileInfos::getSecurityAnalysisParameterId, securityAnalysisService, - ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::securityAnalysisParametersUuid), + ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::securityAnalysisParametersUuid, + securityAnalysisService::getParameters), new ComputationParametersDefinition( ComputationType.SENSITIVITY_ANALYSIS, + RemoteServiceName.SENSITIVITY_ANALYSIS_SERVER, StudyEntity::getSensitivityAnalysisParametersUuid, UserProfileInfos::getSensitivityAnalysisParameterId, sensitivityAnalysisService, - ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::sensitivityAnalysisParametersUuid), + ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::sensitivityAnalysisParametersUuid, + sensitivityAnalysisService::getParameters), new ComputationParametersDefinition( ComputationType.DYNAMIC_SECURITY_ANALYSIS, + RemoteServiceName.DYNAMIC_SECURITY_ANALYSIS_SERVER, StudyEntity::getDynamicSecurityAnalysisParametersUuid, UserProfileInfos::getDynamicSecurityAnalysisParameterId, dynamicSecurityAnalysisRestService, - ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::dynamicSecurityAnalysisParametersUuid), + ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::dynamicSecurityAnalysisParametersUuid, + null), new ComputationParametersDefinition( ComputationType.DYNAMIC_MARGIN_CALCULATION, + RemoteServiceName.DYNAMIC_MARGIN_CALCULATION_SERVER, StudyEntity::getDynamicMarginCalculationParametersUuid, UserProfileInfos::getDynamicMarginCalculationParameterId, dynamicMarginCalculationRestService, - ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::dynamicMarginCalculationParametersUuid), + ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::dynamicMarginCalculationParametersUuid, + null), new ComputationParametersDefinition( ComputationType.STATE_ESTIMATION, + RemoteServiceName.STATE_ESTIMATION_SERVER, StudyEntity::getStateEstimationParametersUuid, userProfileInfos -> null, stateEstimationService, - ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::stateEstimationParametersUuid), + ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::stateEstimationParametersUuid, + null), new ComputationParametersDefinition( ComputationType.PCC_MIN, + RemoteServiceName.PCC_MIN_SERVER, StudyEntity::getPccMinParametersUuid, UserProfileInfos::getPccMinParameterId, pccMinService, - ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::pccMinParametersUuid) + ComputationParameterUUIDs.ComputationParameterUUIDsBuilder::pccMinParametersUuid, + pccMinService::getParameters) ); } @@ -220,4 +258,22 @@ public void createOrUpdateParameters( } } + public Map exportParameters(StudyEntity studyEntity) throws JsonProcessingException { + Map parametersByType = new EnumMap<>(ComputationType.class); + Set downServices = remoteServicesInspector.getOptionalServices().stream() + .filter(serviceStatus -> serviceStatus.status() == ServiceStatus.DOWN) + .map(ServiceStatusInfos::name) + .collect(Collectors.toSet()); + for (ComputationParametersDefinition definition : computationParametersDefinitions) { + if (definition.parametersFetcher() == null || downServices.contains(definition.serviceName().serviceName())) { + continue; + } + UUID parametersUuid = definition.studyParameterGetter().apply(studyEntity); + if (parametersUuid != null) { + Object parameters = definition.parametersFetcher().apply(parametersUuid); + parametersByType.put(definition.type(), parameters instanceof String parametersJson ? parametersJson : objectMapper.writeValueAsString(parameters)); + } + } + return parametersByType; + } } diff --git a/src/test/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersTest.java b/src/test/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersTest.java new file mode 100644 index 0000000000..39c0577262 --- /dev/null +++ b/src/test/java/org/gridsuite/study/server/dto/studyexport/parameters/ExportedParametersTest.java @@ -0,0 +1,97 @@ +/** + * 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.study.server.dto.studyexport.parameters; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Ghazwa Rehili + */ +class ExportedParametersTest { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + private static final UUID ID_1 = UUID.randomUUID(); + private static final UUID ID_2 = UUID.randomUUID(); + private static final UUID ID_3 = UUID.randomUUID(); + private static final UUID ID_4 = UUID.randomUUID(); + private static final UUID ID_5 = UUID.randomUUID(); + + @Test + void testSecurityAnalysisReferences() throws Exception { + String json = "{\"provider\":\"OpenLoadFlow\",\"contingencyListsInfos\":[" + + "{\"contingencyLists\":[\"" + ID_1 + "\",\"" + ID_2 + "\"],\"description\":\"d\",\"activated\":true}," + + "{\"contingencyLists\":[\"" + ID_2 + "\"],\"activated\":false},{\"contingencyLists\":null}]}"; + SecurityAnalysisExportedParameters parameters = objectMapper.readValue(json, SecurityAnalysisExportedParameters.class); + assertEquals(Set.of(ID_1, ID_2), parameters.getContingencyListUuids()); + assertEquals(Set.of(), parameters.getFilterUuids()); + assertEquals(Set.of(), objectMapper.readValue("{}", SecurityAnalysisExportedParameters.class).getContingencyListUuids()); + } + + @Test + void testSensitivityAnalysisReferences() throws Exception { + String json = "{\"provider\":\"OpenLoadFlow\"," + + "\"sensitivityInjectionsSet\":[{\"monitoredBranches\":[\"" + ID_1 + "\"],\"injections\":[\"" + ID_2 + "\"],\"contingencies\":[\"" + ID_3 + "\"],\"activated\":true}]," + + "\"sensitivityInjection\":[{\"monitoredBranches\":[\"" + ID_1 + "\"],\"injections\":[],\"contingencies\":[]}]," + + "\"sensitivityHVDC\":[{\"monitoredBranches\":[],\"hvdcs\":[\"" + ID_4 + "\"],\"contingencies\":[\"" + ID_5 + "\"]}]," + + "\"sensitivityPST\":[{\"monitoredBranches\":[],\"psts\":[\"" + ID_2 + "\"],\"contingencies\":[]}]," + + "\"sensitivityNodes\":[{\"monitoredVoltageLevels\":[\"" + ID_5 + "\"],\"equipmentsInVoltageRegulation\":[\"" + ID_3 + "\"],\"contingencies\":null}]}"; + SensitivityAnalysisExportedParameters parameters = objectMapper.readValue(json, SensitivityAnalysisExportedParameters.class); + assertEquals(Set.of(ID_1, ID_2, ID_3, ID_4, ID_5), parameters.getFilterUuids()); + assertEquals(Set.of(ID_3, ID_5), parameters.getContingencyListUuids()); + } + + @Test + void testSensitivityAnalysisNodesReferences() throws Exception { + String json = "{\"sensitivityNodes\":[{\"monitoredVoltageLevels\":[\"" + ID_1 + "\"],\"equipmentsInVoltageRegulation\":[\"" + ID_2 + "\"]," + + "\"contingencies\":[\"" + ID_3 + "\"],\"activated\":true},{\"monitoredVoltageLevels\":null}]}"; + SensitivityAnalysisExportedParameters parameters = objectMapper.readValue(json, SensitivityAnalysisExportedParameters.class); + assertEquals(Set.of(ID_1, ID_2), parameters.getFilterUuids()); + assertEquals(Set.of(ID_3), parameters.getContingencyListUuids()); + } + + @Test + void testShortCircuitReferences() throws Exception { + String clusters = "[{\"alpha\":0.9,\"filters\":[{\"filterId\":\"" + ID_1 + "\",\"filterName\":\"f1\"},{\"filterId\":\"" + ID_2 + "\"}],\"active\":true}," + + "{\"filters\":null}]"; + String nodeCluster = "[{\"filterId\":\"" + ID_3 + "\",\"filterName\":\"f3\"}]"; + String json = objectMapper.writeValueAsString(Map.of("provider", "Courcirc", "specificParametersPerProvider", Map.of( + "Courcirc", Map.of("powerElectronicsClusters", clusters, "nodeClusterFilterIds", nodeCluster, "other", "value"), + "OtherProvider", Map.of("nodeClusterFilterIds", "")))); + ShortCircuitExportedParameters parameters = objectMapper.readValue(json, ShortCircuitExportedParameters.class); + assertEquals(Set.of(ID_1, ID_2, ID_3), parameters.getFilterUuids()); + assertEquals(Set.of(), parameters.getContingencyListUuids()); + assertEquals(Set.of(), objectMapper.readValue("{}", ShortCircuitExportedParameters.class).getFilterUuids()); + } + + @Test + void testVoltageInitReferences() throws Exception { + String json = "{\"voltageLimitsModification\":[{\"priority\":0,\"filters\":[{\"filterId\":\"" + ID_1 + "\",\"filterName\":\"f1\"}]}]," + + "\"voltageLimitsDefault\":[{\"priority\":0,\"filters\":[{\"filterId\":\"" + ID_2 + "\"}]}]," + + "\"variableQGenerators\":[{\"filterId\":\"" + ID_3 + "\"}]," + + "\"variableTwoWindingsTransformers\":[{\"filterId\":\"" + ID_4 + "\"}]," + + "\"variableShuntCompensators\":[{\"filterId\":null}],\"updateBusVoltage\":true}"; + VoltageInitExportedParameters parameters = objectMapper.readValue(json, VoltageInitExportedParameters.class); + assertEquals(Set.of(ID_1, ID_2, ID_3, ID_4), parameters.getFilterUuids()); + assertEquals(Set.of(), parameters.getContingencyListUuids()); + } + + @Test + void testPccMinReferences() throws Exception { + String json = "{\"uuid\":\"" + ID_5 + "\",\"filters\":[\"" + ID_1 + "\"]}"; + PccMinExportedParameters parameters = objectMapper.readValue(json, PccMinExportedParameters.class); + assertEquals(Set.of(ID_1), parameters.getFilterUuids()); + assertEquals(Set.of(), objectMapper.readValue("{}", PccMinExportedParameters.class).getFilterUuids()); + } +} diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java b/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java index b7e70195c4..e75fbffc1b 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/TreeExportTest.java @@ -6,10 +6,13 @@ */ package org.gridsuite.study.server.studycontroller; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.tomakehurst.wiremock.client.WireMock; import org.gridsuite.study.server.dto.networkexport.PermissionType; import org.gridsuite.study.server.dto.studyexport.TreeExportInfos; +import org.gridsuite.study.server.service.ActionsService; +import org.gridsuite.study.server.service.FilterService; import org.gridsuite.study.server.utils.wiremock.WireMockUtilsCriteria; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatcher; @@ -25,8 +28,11 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import java.util.zip.ZipEntry; @@ -51,6 +57,12 @@ class TreeExportTest extends StudyTestBase { @Autowired private ObjectMapper objectMapper; + @Autowired + private FilterService filterService; + + @Autowired + private ActionsService actionsService; + @Test void testExportStudy() throws Exception { // Create a study @@ -63,6 +75,8 @@ void testExportStudy() throws Exception { .willReturn(WireMock.aResponse().withStatus(200) .withHeader("Content-Type", "application/octet-stream") .withBody("dummy case content".getBytes()))); + // Stub the computation parameters fetches + computationServerStubs.stubGetParametersAny("{}"); // Export as zip MvcResult result = mockMvc.perform(get("/v1/studies/{studyUuid}/export/{studyName}", studyUuid, "studyName").header(HEADER_USER_ID, "testUser")) .andExpect(status().isOk()) @@ -100,9 +114,91 @@ void testExportStudy() throws Exception { String rootNetworkCaseName = exportInfos.rootNetworks().getFirst().caseInfos().getCaseName(); String expectedCaseEntry = "cases/" + rootNetworkCaseUuid + "/" + rootNetworkCaseName; assertEquals(List.of(expectedCaseEntry), zipEntryNames.stream().filter(name -> name.startsWith("cases/")).toList()); + assertTrue(zipEntryNames.containsAll(List.of("computationParameters/filters.json", "computationParameters/contingencyList.json"))); // Verify the case content download call WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/cases/" + CASE_UUID, false, Map.of(), 1); wireMockStubs.directoryServer.verifyCheckPermission(List.of(studyUuid), null, PermissionType.READ, false); + // Verify the computation parameters fetches + computationServerStubs.verifyParametersGetAny(6); + } + + @Test + void testExportStudyWithFilterAndContingencyList() throws Exception { + UUID studyUuid = createStudyWithStubs("testUser", CASE_UUID); + ReflectionTestUtils.setField(caseService, "caseServerBaseUri", wireMockServer.baseUrl()); + filterService.setBaseUri(wireMockServer.baseUrl()); + actionsService.setActionsServerBaseUri(wireMockServer.baseUrl()); + UUID filterA = UUID.randomUUID(); + UUID filterB = UUID.randomUUID(); + UUID filterC = UUID.randomUUID(); + UUID deletedFilter = UUID.randomUUID(); + UUID identifierList = UUID.randomUUID(); + UUID filterBasedList = UUID.randomUUID(); + wireMockStubs.directoryServer.stubCheckPermission(List.of(studyUuid), null, "testUser", PermissionType.READ, false, HttpStatus.OK.value()); + wireMockServer.stubFor(WireMock.get(WireMock.urlPathEqualTo("/v1/cases/" + CASE_UUID)) + .willReturn(WireMock.aResponse().withStatus(200).withHeader("Content-Type", "application/octet-stream").withBody("dummy case content".getBytes()))); + computationServerStubs.stubGetParametersAny("{" + + "\"contingencyListsInfos\":[{\"contingencyLists\":[\"" + identifierList + "\"],\"activated\":true}]," + + "\"sensitivityInjectionsSet\":[{\"monitoredBranches\":[\"" + filterA + "\"],\"injections\":[\"" + deletedFilter + "\"]," + + "\"contingencies\":[\"" + filterBasedList + "\"]}]," + + "\"variableQGenerators\":[{\"filterId\":\"" + filterA + "\",\"filterName\":\"nameA\"}]," + + "\"filters\":[\"" + filterA + "\"]}"); + String filterAJson = "{\"id\":\"" + filterA + "\",\"type\":\"EXPERT\",\"rules\":{\"dataType\":\"COMBINATOR\",\"rules\":[" + + "{\"dataType\":\"FILTER_UUID\",\"field\":\"ID\",\"operator\":\"IS_PART_OF\",\"values\":[\"" + filterC + "\"]}]}}"; + String filterBJson = "{\"id\":\"" + filterB + "\",\"type\":\"IDENTIFIER_LIST\"}"; + String filterCJson = "{\"id\":\"" + filterC + "\",\"type\":\"IDENTIFIER_LIST\"}"; + // filterB is referenced by the filter based contingency list, filterC by filterA, deletedFilter does not exist anymore + stubJsonGet("/v1/contingency-lists/filter-uuids", "[\"" + filterB + "\"]"); + stubJsonGet("/v1/filters/referenced-filter-uuids", "[\"" + filterC + "\"]"); + stubJsonGet("/v1/filters/metadata", "[" + filterAJson + "," + filterBJson + "," + filterCJson + "]"); + String identifierListJson = "{\"id\":\"" + identifierList + "\",\"type\":\"IDENTIFIERS\",\"identifiersListByNetwork\":[]}"; + String filterBasedListJson = "{\"id\":\"" + filterBasedList + "\",\"type\":\"FILTERS\",\"filters\":[{\"id\":\"" + filterB + "\"}]," + + "\"selectedEquipmentTypesByFilter\":[{\"filterId\":\"" + filterB + "\",\"equipmentTypes\":[\"LINE\"]}]}"; + wireMockServer.stubFor(WireMock.post(WireMock.urlPathEqualTo("/v1/contingency-lists")) + .willReturn(WireMock.okJson("[" + identifierListJson + "," + filterBasedListJson + "]"))); + Map names = Map.of(filterA, "nameA", filterB, "nameB", filterC, "nameC", identifierList, "nameI", filterBasedList, "nameF"); + wireMockStubs.directoryServer.stubGetElementNames(objectMapper.writeValueAsString(names)); + + MvcResult result = mockMvc.perform(get("/v1/studies/{studyUuid}/export/{studyName}", studyUuid, "studyName").header(HEADER_USER_ID, "testUser")) + .andExpect(status().isOk()) + .andReturn(); + + Map zipContents = new HashMap<>(); + try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(result.getResponse().getContentAsByteArray()))) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + zipContents.put(entry.getName(), new String(zis.readAllBytes())); + } + } + Set expectedFilters = Set.of( + objectMapper.readTree("{\"uuid\":\"" + filterA + "\",\"name\":\"nameA\",\"content\":" + filterAJson + "}"), + objectMapper.readTree("{\"uuid\":\"" + filterB + "\",\"name\":\"nameB\",\"content\":" + filterBJson + "}"), + objectMapper.readTree("{\"uuid\":\"" + filterC + "\",\"name\":\"nameC\",\"content\":" + filterCJson + "}")); + Set expectedContingencyLists = Set.of( + objectMapper.readTree("{\"uuid\":\"" + identifierList + "\",\"name\":\"nameI\",\"content\":" + identifierListJson + "}"), + objectMapper.readTree("{\"uuid\":\"" + filterBasedList + "\",\"name\":\"nameF\",\"content\":" + filterBasedListJson + "}")); + assertEquals(expectedFilters, readJson(zipContents.get("computationParameters/filters.json"))); + assertEquals(expectedContingencyLists, readJson(zipContents.get("computationParameters/contingencyList.json"))); + + WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/cases/" + CASE_UUID, false, Map.of(), 1); + wireMockStubs.directoryServer.verifyCheckPermission(List.of(studyUuid), null, PermissionType.READ, false); + computationServerStubs.verifyParametersGetAny(6); + WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/contingency-lists/filter-uuids", false, Map.of("ids", WireMock.matching(".*")), 1); + WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/filters/referenced-filter-uuids", false, Map.of("ids", WireMock.matching(".*")), 1); + WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/filters/metadata", false, Map.of("ids", WireMock.matching(".*")), 1); + WireMockUtilsCriteria.verifyPostRequest(wireMockServer, "/v1/contingency-lists", Map.of(), 1); + WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/elements/names", false, Map.of("ids", WireMock.matching(".*"), "strictMode", WireMock.equalTo("false")), 1); + } + + private void stubJsonGet(String urlRegex, String body) { + wireMockServer.stubFor(WireMock.get(WireMock.urlPathMatching(urlRegex)) + .willReturn(WireMock.ok().withHeader("Content-Type", "application/json").withBody(body))); + } + + private Set readJson(String json) throws IOException { + Set jsonNodes = new HashSet<>(); + objectMapper.readTree(json).forEach(jsonNodes::add); + return jsonNodes; } @Test @@ -125,6 +221,7 @@ void testExportStudyFailToDeleteTempZipFile() throws Exception { wireMockServer.stubFor(WireMock.get(WireMock.urlPathEqualTo("/v1/cases/" + CASE_UUID)) .willReturn(WireMock.aResponse().withStatus(200).withHeader("Content-Type", "application/octet-stream") .withBody("dummy case content".getBytes()))); + computationServerStubs.stubGetParametersAny("{}"); // Capture the real zip file path as it is matched, so the test can clean it up itself: // the service's own Files.deleteIfExists call on this path is mocked to fail below. AtomicReference capturedZipFile = new AtomicReference<>(); @@ -156,5 +253,6 @@ void testExportStudyFailToDeleteTempZipFile() throws Exception { } WireMockUtilsCriteria.verifyGetRequest(wireMockServer, "/v1/cases/" + CASE_UUID, false, Map.of(), 1); wireMockStubs.directoryServer.verifyCheckPermission(List.of(studyUuid), null, PermissionType.READ, false); + computationServerStubs.verifyParametersGetAny(6); } } diff --git a/src/test/java/org/gridsuite/study/server/utils/wiremock/ComputationServerStubs.java b/src/test/java/org/gridsuite/study/server/utils/wiremock/ComputationServerStubs.java index d74cbb6a13..e0291cbfc6 100644 --- a/src/test/java/org/gridsuite/study/server/utils/wiremock/ComputationServerStubs.java +++ b/src/test/java/org/gridsuite/study/server/utils/wiremock/ComputationServerStubs.java @@ -202,6 +202,15 @@ public void verifyParametersGet(String paramUuid, String prefix) { WireMockUtilsCriteria.verifyGetRequest(wireMock, "/v1/" + prefix + "parameters/" + paramUuid, Map.of()); } + public void stubGetParametersAny(String responseBody) { + wireMock.stubFor(WireMock.get(WireMock.urlPathMatching("/v1/parameters/[^/]+")) + .willReturn(WireMock.ok().withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).withBody(responseBody))); + } + + public void verifyParametersGetAny(int nbRequests) { + WireMockUtilsCriteria.verifyGetRequest(wireMock, "/v1/parameters/[^/]+", true, Map.of(), nbRequests); + } + public void stubCreateParameter(String responseBody) { wireMock.stubFor(WireMock.post(WireMock.urlEqualTo("/v1/parameters")) .atPriority(10)