From 8894fcb1214dfb3b9868636a86a1192cb395ec6c Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Mon, 29 Dec 2025 09:25:30 +0100 Subject: [PATCH 01/26] feat: when running loadflow on security node, all its children are built automatically Signed-off-by: LE SAULNIER Kevin --- .../study/server/service/ConsumerService.java | 4 ++ .../study/server/service/StudyService.java | 40 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/main/java/org/gridsuite/study/server/service/ConsumerService.java b/src/main/java/org/gridsuite/study/server/service/ConsumerService.java index 76410460f0..8ddec3b425 100644 --- a/src/main/java/org/gridsuite/study/server/service/ConsumerService.java +++ b/src/main/java/org/gridsuite/study/server/service/ConsumerService.java @@ -641,6 +641,10 @@ public void consumeCalculationResult(Message msg, ComputationType comput // send notifications UUID studyUuid = networkModificationTreeService.getStudyUuidForNodeId(receiverObj.getNodeUuid()); + if (computationType == ComputationType.LOAD_FLOW && networkModificationTreeService.isSecurityNode(receiverObj.getNodeUuid())) { + String userId = (String) msg.getHeaders().get(HEADER_USER_ID); + studyService.buildSubtree(studyUuid, receiverObj.getNodeUuid(), receiverObj.getRootNetworkUuid(), userId); + } notificationService.emitStudyChanged(studyUuid, receiverObj.getNodeUuid(), receiverObj.getRootNetworkUuid(), computationType.getUpdateStatusType()); notificationService.emitStudyChanged(studyUuid, receiverObj.getNodeUuid(), receiverObj.getRootNetworkUuid(), computationType.getUpdateResultType()); })); diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index 792a250fe2..c034f14017 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -1908,6 +1908,46 @@ private void buildNode(@NonNull UUID studyUuid, @NonNull UUID nodeUuid, @NonNull } } + @Transactional + public void buildSubtree(@NonNull UUID studyUuid, @NonNull UUID parentNodeUuid, @NonNull UUID rootNetworkUuid, @NonNull String userId) { + AbstractNode studySubTree = networkModificationTreeService.getStudySubtree(studyUuid, parentNodeUuid, rootNetworkUuid); + studySubTree.getChildren().forEach(child -> + buildStudySubTree( + child, + studyUuid, + rootNetworkUuid, + userId + ) + ); + } + + private void buildStudySubTree( + @NonNull AbstractNode nodeToBuild, + @NonNull UUID studyUuid, + @NonNull UUID rootNetworkUuid, + @NonNull String userId + ) { + if (!NodeType.ROOT.equals(nodeToBuild.getType())) { + buildNode( + studyUuid, + nodeToBuild.getId(), + rootNetworkUuid, + userId, + null + ); + } + + nodeToBuild.getChildren() + .forEach(child -> + buildStudySubTree( + child, + studyUuid, + rootNetworkUuid, + userId + ) + ); + } + public void handleBuildSuccess(UUID studyUuid, UUID nodeUuid, UUID rootNetworkUuid, NetworkModificationResult networkModificationResult) { LOGGER.info("Build completed for node '{}'", nodeUuid); From 05e93501c6a29e733f9a5a89c1795475d2458b57 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Mon, 29 Dec 2025 10:41:38 +0100 Subject: [PATCH 02/26] feat: on security node creation, with construction node as parent, build first one automatically Signed-off-by: LE SAULNIER Kevin --- .../NetworkModificationTreeService.java | 6 ++++- .../study/server/service/StudyService.java | 24 +++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java b/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java index 19bbdf9d71..9084aa45f7 100644 --- a/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java +++ b/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java @@ -598,11 +598,15 @@ private void assertNodeNameNotExist(UUID studyUuid, String nodeName) { } public void assertIsRootOrConstructionNode(UUID nodeUuid) { - if (!self.getNode(nodeUuid, null).getType().equals(NodeType.ROOT) && !isConstructionNode(nodeUuid)) { + if (!isRootOrConstructionNode(nodeUuid)) { throw new StudyException(NOT_ALLOWED); } } + public boolean isRootOrConstructionNode(UUID nodeUuid) { + return self.getNode(nodeUuid, null).getType().equals(NodeType.ROOT) || isConstructionNode(nodeUuid); + } + private void assertInsertNode( UUID parentNodeId, NetworkModificationNodeType newNodeType, diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index c034f14017..928bdf4c87 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -40,10 +40,7 @@ import org.gridsuite.study.server.elasticsearch.EquipmentInfosService; import org.gridsuite.study.server.elasticsearch.StudyInfosService; import org.gridsuite.study.server.networkmodificationtree.dto.*; -import org.gridsuite.study.server.networkmodificationtree.entities.NetworkModificationNodeInfoEntity; -import org.gridsuite.study.server.networkmodificationtree.entities.NodeEntity; -import org.gridsuite.study.server.networkmodificationtree.entities.NodeType; -import org.gridsuite.study.server.networkmodificationtree.entities.RootNetworkNodeInfoEntity; +import org.gridsuite.study.server.networkmodificationtree.entities.*; import org.gridsuite.study.server.notification.NotificationService; import org.gridsuite.study.server.notification.dto.NetworkImpactsInfos; import org.gridsuite.study.server.repository.*; @@ -1893,6 +1890,10 @@ public void buildNode(@NonNull UUID studyUuid, @NonNull UUID nodeUuid, @NonNull buildNode(studyUuid, nodeUuid, rootNetworkUuid, userId, null); } + private void buildNode(@NonNull UUID studyUuid, @NonNull UUID nodeUuid, @NonNull String userId) { + getStudyRootNetworks(studyUuid).forEach(rn -> buildNode(studyUuid, nodeUuid, rn.getId(), userId, null)); + } + private void buildNode(@NonNull UUID studyUuid, @NonNull UUID nodeUuid, @NonNull UUID rootNetworkUuid, @NonNull String userId, AbstractWorkflowInfos workflowInfos) { assertCanBuildNode(studyUuid, rootNetworkUuid, userId); BuildInfos buildInfos = networkModificationTreeService.getBuildInfos(nodeUuid, rootNetworkUuid); @@ -3530,6 +3531,8 @@ public NetworkModificationNode createNode(UUID studyUuid, UUID nodeId, NetworkMo networkModificationTreeService.assertCreateNode(nodeId, nodeInfo.getNodeType(), insertMode); NetworkModificationNode newNode = networkModificationTreeService.createNode(study, nodeId, nodeInfo, insertMode, userId); + createNodePostAction(studyUuid, nodeId, newNode, userId); + UUID parentUuid = networkModificationTreeService.getParentNodeUuid(newNode.getId()).orElse(null); notificationService.emitNodeInserted(study.getId(), parentUuid, newNode.getId(), insertMode, nodeId); // userId is null when creating initial nodes, we don't need to send element update notifications in this case @@ -3539,12 +3542,19 @@ public NetworkModificationNode createNode(UUID studyUuid, UUID nodeId, NetworkMo return newNode; } + private void createNodePostAction(UUID studyUuid, UUID parentNodeUuid, NetworkModificationNode newNode, String userId) { + if (newNode.isSecurityNode() && networkModificationTreeService.isRootOrConstructionNode(parentNodeUuid)) { + buildNode(studyUuid, newNode.getId(), userId); + } + } + @Transactional public NetworkModificationNode createSequence(UUID studyUuid, UUID parentNodeUuid, NodeSequenceType nodeSequenceType, String userId) { StudyEntity study = getStudy(studyUuid); networkModificationTreeService.assertIsRootOrConstructionNode(parentNodeUuid); NetworkModificationNode newParentNode = networkModificationTreeService.createTreeNodeFromNodeSequence(study, parentNodeUuid, nodeSequenceType); + createSequencePostAction(studyUuid, newParentNode.getId(), nodeSequenceType, userId); notificationService.emitSubtreeInserted(study.getId(), newParentNode.getId(), parentNodeUuid); // userId is null when creating initial nodes, we don't need to send element update notifications in this case @@ -3554,6 +3564,12 @@ public NetworkModificationNode createSequence(UUID studyUuid, UUID parentNodeUui return newParentNode; } + private void createSequencePostAction(UUID studyUuid, UUID sequenceParentNode, NodeSequenceType nodeSequenceType, String userId) { + if (nodeSequenceType == NodeSequenceType.SECURITY_SEQUENCE) { + buildNode(studyUuid, sequenceParentNode, userId); + } + } + private List getStudyRootNetworks(UUID studyUuid) { StudyEntity studyEntity = getStudy(studyUuid); return studyEntity.getRootNetworks(); From 2ded25a1abf5fdbe862238a2ebdd8790a3499982 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Tue, 30 Dec 2025 09:23:10 +0100 Subject: [PATCH 03/26] feat: add auto rebuild on multiple operations Signed-off-by: LE SAULNIER Kevin --- .../RebuildNodeIfPreviouslyBuilt.java | 11 ++++ .../server/annotation/RebuildNodeUuid.java | 12 ++++ .../server/annotation/RebuildStudyUuid.java | 11 ++++ .../server/annotation/RebuildUserId.java | 11 ++++ .../aspect/AnnotatedParameterExtractor.java | 51 ++++++++++++++++ .../RebuildNodeIfPreviouslyBuiltAspect.java | 60 +++++++++++++++++++ .../server/controller/StudyController.java | 11 ++++ .../study/server/service/StudyService.java | 48 +++++++++++++-- 8 files changed, 209 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/gridsuite/study/server/annotation/RebuildNodeIfPreviouslyBuilt.java create mode 100644 src/main/java/org/gridsuite/study/server/annotation/RebuildNodeUuid.java create mode 100644 src/main/java/org/gridsuite/study/server/annotation/RebuildStudyUuid.java create mode 100644 src/main/java/org/gridsuite/study/server/annotation/RebuildUserId.java create mode 100644 src/main/java/org/gridsuite/study/server/aspect/AnnotatedParameterExtractor.java create mode 100644 src/main/java/org/gridsuite/study/server/aspect/RebuildNodeIfPreviouslyBuiltAspect.java diff --git a/src/main/java/org/gridsuite/study/server/annotation/RebuildNodeIfPreviouslyBuilt.java b/src/main/java/org/gridsuite/study/server/annotation/RebuildNodeIfPreviouslyBuilt.java new file mode 100644 index 0000000000..a2195ae3c8 --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/annotation/RebuildNodeIfPreviouslyBuilt.java @@ -0,0 +1,11 @@ +package org.gridsuite.study.server.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface RebuildNodeIfPreviouslyBuilt { +} diff --git a/src/main/java/org/gridsuite/study/server/annotation/RebuildNodeUuid.java b/src/main/java/org/gridsuite/study/server/annotation/RebuildNodeUuid.java new file mode 100644 index 0000000000..1da63bda0e --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/annotation/RebuildNodeUuid.java @@ -0,0 +1,12 @@ +package org.gridsuite.study.server.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface RebuildNodeUuid { +} + diff --git a/src/main/java/org/gridsuite/study/server/annotation/RebuildStudyUuid.java b/src/main/java/org/gridsuite/study/server/annotation/RebuildStudyUuid.java new file mode 100644 index 0000000000..e099785a46 --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/annotation/RebuildStudyUuid.java @@ -0,0 +1,11 @@ +package org.gridsuite.study.server.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface RebuildStudyUuid { +} \ No newline at end of file diff --git a/src/main/java/org/gridsuite/study/server/annotation/RebuildUserId.java b/src/main/java/org/gridsuite/study/server/annotation/RebuildUserId.java new file mode 100644 index 0000000000..13b7839f19 --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/annotation/RebuildUserId.java @@ -0,0 +1,11 @@ +package org.gridsuite.study.server.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface RebuildUserId { +} \ No newline at end of file diff --git a/src/main/java/org/gridsuite/study/server/aspect/AnnotatedParameterExtractor.java b/src/main/java/org/gridsuite/study/server/aspect/AnnotatedParameterExtractor.java new file mode 100644 index 0000000000..f54e8fa8ab --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/aspect/AnnotatedParameterExtractor.java @@ -0,0 +1,51 @@ +package org.gridsuite.study.server.aspect; + +import org.springframework.core.MethodParameter; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +public class AnnotatedParameterExtractor { //TODO: improve throws + public static T extractRequiredParameter( + Method method, + Object[] args, + Class annotation, + Class expectedType + ) { + T result = null; + + for (int i = 0; i < args.length; i++) { + MethodParameter param = new MethodParameter(method, i); + + if (param.hasParameterAnnotation(annotation)) { + if (result != null) { + throw new IllegalStateException( + "Multiple parameters annotated with @" + + annotation.getSimpleName() + ); + } + + Object value = args[i]; + if (!expectedType.isInstance(value)) { + throw new IllegalStateException( + "Parameter annotated with @" + + annotation.getSimpleName() + + " must be of type " + + expectedType.getSimpleName() + ); + } + + result = expectedType.cast(value); + } + } + + if (result == null) { + throw new IllegalStateException( + "Missing parameter annotated with @" + + annotation.getSimpleName() + ); + } + + return result; + } +} diff --git a/src/main/java/org/gridsuite/study/server/aspect/RebuildNodeIfPreviouslyBuiltAspect.java b/src/main/java/org/gridsuite/study/server/aspect/RebuildNodeIfPreviouslyBuiltAspect.java new file mode 100644 index 0000000000..9a98965f67 --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/aspect/RebuildNodeIfPreviouslyBuiltAspect.java @@ -0,0 +1,60 @@ +package org.gridsuite.study.server.aspect; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.gridsuite.study.server.annotation.RebuildNodeUuid; +import org.gridsuite.study.server.annotation.RebuildStudyUuid; +import org.gridsuite.study.server.annotation.RebuildUserId; +import org.gridsuite.study.server.networkmodificationtree.dto.NodeBuildStatus; +import org.gridsuite.study.server.networkmodificationtree.entities.NodeBuildStatusEmbeddable; +import org.gridsuite.study.server.service.NetworkModificationTreeService; +import org.gridsuite.study.server.service.RootNetworkNodeInfoService; +import org.gridsuite.study.server.service.StudyService; +import org.springframework.stereotype.Component; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.UUID; + +@Aspect +@Component +public class RebuildNodeIfPreviouslyBuiltAspect { + + private final StudyService studyService; + private final NetworkModificationTreeService networkModificationTreeService; + + public RebuildNodeIfPreviouslyBuiltAspect(StudyService studyService, NetworkModificationTreeService networkModificationTreeService) { + this.studyService = studyService; + this.networkModificationTreeService = networkModificationTreeService; + } + + @Around("@annotation(org.gridsuite.study.server.annotation.RebuildNodeIfPreviouslyBuilt)") + public Object around(ProceedingJoinPoint pjp) throws Throwable { + MethodSignature signature = (MethodSignature) pjp.getSignature(); + Method method = signature.getMethod(); + Object[] args = pjp.getArgs(); + + UUID nodeUuid = AnnotatedParameterExtractor.extractRequiredParameter(method, args, RebuildNodeUuid.class, UUID.class); + + if (networkModificationTreeService.isRootOrConstructionNode(nodeUuid)) { + // This aspect only rebuilds security nodes + return pjp.proceed(); + } + + UUID studyUuid = AnnotatedParameterExtractor.extractRequiredParameter(method, args, RebuildStudyUuid.class, UUID.class); + String userId = AnnotatedParameterExtractor.extractRequiredParameter(method, args, RebuildUserId.class, String.class); + + Map buildStatusByRootNetworkUuid = studyService.getNodeBuildStatusByRootNetworkUuid(studyUuid, nodeUuid); + + Object result = pjp.proceed(); + + // No try catch -> do not rebuild node if operation has failed + buildStatusByRootNetworkUuid.entrySet().stream() + .filter(entry -> entry.getValue().isBuilt()) + .forEach(entry -> studyService.buildNode(studyUuid, nodeUuid, entry.getKey(), userId)); + return result; + } +} diff --git a/src/main/java/org/gridsuite/study/server/controller/StudyController.java b/src/main/java/org/gridsuite/study/server/controller/StudyController.java index 9a448350bf..efc1be87b3 100644 --- a/src/main/java/org/gridsuite/study/server/controller/StudyController.java +++ b/src/main/java/org/gridsuite/study/server/controller/StudyController.java @@ -43,6 +43,7 @@ import org.gridsuite.study.server.elasticsearch.EquipmentInfosService; import org.gridsuite.study.server.exception.PartialResultException; import org.gridsuite.study.server.networkmodificationtree.dto.*; +import org.gridsuite.study.server.networkmodificationtree.entities.NodeBuildStatusEmbeddable; import org.gridsuite.study.server.service.*; import org.gridsuite.study.server.service.securityanalysis.SecurityAnalysisResultType; import org.gridsuite.study.server.service.shortcircuit.FaultResultsMode; @@ -644,11 +645,15 @@ public ResponseEntity moveModification(@PathVariable("studyUuid") UUID stu private void handleMoveNetworkModification(UUID studyUuid, UUID nodeUuid, UUID modificationUuid, UUID beforeUuid, String userId) { studyService.assertNoBlockedNodeInStudy(studyUuid, nodeUuid); + Map buildStatusByRootNetworkUuid = studyService.getNodeBuildStatusByRootNetworkUuid(studyUuid, nodeUuid); studyService.invalidateNodeTreeWhenMoveModification(studyUuid, nodeUuid); try { studyService.moveNetworkModifications(studyUuid, nodeUuid, nodeUuid, List.of(modificationUuid), beforeUuid, false, userId); } finally { studyService.unblockNodeTree(studyUuid, nodeUuid); + buildStatusByRootNetworkUuid.entrySet().stream() + .filter(entry -> entry.getValue().isBuilt()) + .forEach(entry -> studyService.buildNode(studyUuid, nodeUuid, entry.getKey(), userId)); } } @@ -693,6 +698,9 @@ private void handleDuplicateOrInsertNetworkModifications(UUID targetStudyUuid, U private void handleMoveNetworkModifications(UUID studyUuid, UUID targetNodeUuid, UUID originNodeUuid, List modificationsToCopyUuidList, String userId) { studyService.assertNoBlockedNodeInStudy(studyUuid, originNodeUuid); studyService.assertNoBlockedNodeInStudy(studyUuid, targetNodeUuid); + + Map buildStatusByRootNetworkUuid = studyService.getNodeBuildStatusByRootNetworkUuid(studyUuid, originNodeUuid); + boolean isTargetInDifferentNodeTree = studyService.invalidateNodeTreeWhenMoveModifications(studyUuid, targetNodeUuid, originNodeUuid); try { studyService.moveNetworkModifications(studyUuid, targetNodeUuid, originNodeUuid, modificationsToCopyUuidList, null, isTargetInDifferentNodeTree, userId); @@ -701,6 +709,9 @@ private void handleMoveNetworkModifications(UUID studyUuid, UUID targetNodeUuid, if (isTargetInDifferentNodeTree) { studyService.unblockNodeTree(studyUuid, targetNodeUuid); } + buildStatusByRootNetworkUuid.entrySet().stream() + .filter(entry -> entry.getValue().isBuilt()) + .forEach(entry -> studyService.buildNode(studyUuid, originNodeUuid, entry.getKey(), userId)); } } diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index 928bdf4c87..2d47298a47 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -16,6 +16,10 @@ import org.gridsuite.filter.globalfilter.GlobalFilter; import org.gridsuite.filter.utils.EquipmentType; import org.gridsuite.study.server.StudyConstants; +import org.gridsuite.study.server.annotation.RebuildNodeIfPreviouslyBuilt; +import org.gridsuite.study.server.annotation.RebuildNodeUuid; +import org.gridsuite.study.server.annotation.RebuildStudyUuid; +import org.gridsuite.study.server.annotation.RebuildUserId; import org.gridsuite.study.server.dto.modification.*; import org.gridsuite.study.server.dto.voltageinit.ContextInfos; import org.gridsuite.study.server.error.StudyException; @@ -68,6 +72,9 @@ import org.springframework.web.util.UriUtils; import java.io.UncheckedIOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.*; @@ -1835,8 +1842,9 @@ public void createNetworkModification(UUID studyUuid, UUID nodeUuid, String crea notificationService.emitElementUpdated(studyUuid, userId); } + @RebuildNodeIfPreviouslyBuilt @Transactional - public void updateNetworkModification(UUID studyUuid, String updateModificationAttributes, UUID nodeUuid, UUID modificationUuid, String userId) { + public void updateNetworkModification(@RebuildStudyUuid UUID studyUuid, String updateModificationAttributes, @RebuildNodeUuid UUID nodeUuid, UUID modificationUuid, @RebuildUserId String userId) { List childrenUuids = networkModificationTreeService.getChildrenUuids(nodeUuid); notificationService.emitStartModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids, NotificationService.MODIFICATIONS_UPDATING_IN_PROGRESS); try { @@ -2222,7 +2230,8 @@ public void deleteNetworkModifications(UUID studyUuid, UUID nodeUuid, List } @Transactional - public void stashNetworkModifications(UUID studyUuid, UUID nodeUuid, List modificationsUuids, String userId) { + @RebuildNodeIfPreviouslyBuilt + public void stashNetworkModifications(@RebuildStudyUuid UUID studyUuid, @RebuildNodeUuid UUID nodeUuid, List modificationsUuids, @RebuildUserId String userId) { List childrenUuids = networkModificationTreeService.getChildrenUuids(nodeUuid); notificationService.emitStartModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids, NotificationService.MODIFICATIONS_STASHING_IN_PROGRESS); try { @@ -2239,7 +2248,12 @@ public void stashNetworkModifications(UUID studyUuid, UUID nodeUuid, List } @Transactional - public void updateNetworkModificationsMetadata(UUID studyUuid, UUID nodeUuid, List modificationsUuids, String userId, NetworkModificationMetadata metadata) { + @RebuildNodeIfPreviouslyBuilt + public void updateNetworkModificationsMetadata(@RebuildStudyUuid UUID studyUuid, + @RebuildNodeUuid UUID nodeUuid, + List modificationsUuids, + @RebuildUserId String userId, + NetworkModificationMetadata metadata) { List childrenUuids = networkModificationTreeService.getChildrenUuids(nodeUuid); notificationService.emitStartModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids, NotificationService.MODIFICATIONS_UPDATING_IN_PROGRESS); try { @@ -2258,7 +2272,14 @@ public void updateNetworkModificationsMetadata(UUID studyUuid, UUID nodeUuid, Li } @Transactional - public void updateNetworkModificationsActivationInRootNetwork(UUID studyUuid, UUID nodeUuid, UUID rootNetworkUuid, Set modificationsUuids, String userId, boolean activated) { + @RebuildNodeIfPreviouslyBuilt + public void updateNetworkModificationsActivationInRootNetwork( + @RebuildStudyUuid UUID studyUuid, + @RebuildNodeUuid UUID nodeUuid, + UUID rootNetworkUuid, + Set modificationsUuids, + @RebuildUserId String userId, + boolean activated) { List childrenUuids = networkModificationTreeService.getChildrenUuids(nodeUuid); networkModificationService.verifyModifications(networkModificationTreeService.getModificationGroupUuid(nodeUuid), modificationsUuids); notificationService.emitStartModificationEquipmentNotification(studyUuid, nodeUuid, Optional.of(rootNetworkUuid), childrenUuids, NotificationService.MODIFICATIONS_UPDATING_IN_PROGRESS); @@ -2275,7 +2296,8 @@ public void updateNetworkModificationsActivationInRootNetwork(UUID studyUuid, UU } @Transactional - public void restoreNetworkModifications(UUID studyUuid, UUID nodeUuid, List modificationsUuids, String userId) { + @RebuildNodeIfPreviouslyBuilt + public void restoreNetworkModifications(@RebuildStudyUuid UUID studyUuid, @RebuildNodeUuid UUID nodeUuid, List modificationsUuids, @RebuildUserId String userId) { List childrenUuids = networkModificationTreeService.getChildrenUuids(nodeUuid); notificationService.emitStartModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids, NotificationService.MODIFICATIONS_RESTORING_IN_PROGRESS); try { @@ -2441,6 +2463,14 @@ private StudyEntity getStudy(UUID studyUuid) { return studyRepository.findById(studyUuid).orElseThrow(() -> new StudyException(NOT_FOUND, "Study not found")); } + @Transactional + public Map getNodeBuildStatusByRootNetworkUuid(UUID studyUuid, UUID nodeUuid) { + return getStudyRootNetworks(studyUuid).stream().collect(Collectors.toMap( + RootNetworkEntity::getId, + rn -> rootNetworkNodeInfoService.getRootNetworkNodeInfo(nodeUuid, rn.getId()).map(rni -> rni.getNodeBuildStatus().toDto()).orElseThrow(() -> new StudyException(NOT_FOUND, "Root network not found")) + )); + } + @Transactional public RootNetworkIndexationStatus getRootNetworkIndexationStatus(UUID studyUuid, UUID rootNetworkUuid) { StudyEntity study = getStudy(studyUuid); @@ -2453,7 +2483,13 @@ public RootNetworkIndexationStatus getRootNetworkIndexationStatus(UUID studyUuid } @Transactional - public void moveNetworkModifications(@NonNull UUID studyUuid, UUID targetNodeUuid, @NonNull UUID originNodeUuid, List modificationUuidList, UUID beforeUuid, boolean isTargetInDifferentNodeTree, String userId) { + public void moveNetworkModifications(@NonNull UUID studyUuid, + UUID targetNodeUuid, + @NonNull UUID originNodeUuid, + List modificationUuidList, + UUID beforeUuid, + boolean isTargetInDifferentNodeTree, + String userId) { boolean isTargetDifferentNode = !targetNodeUuid.equals(originNodeUuid); List childrenUuids = networkModificationTreeService.getChildrenUuids(targetNodeUuid); From e34cfc5c84650521407a1da8f106c5724ac78ac9 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Tue, 30 Dec 2025 09:40:26 +0100 Subject: [PATCH 04/26] fix: checkstyle and copyright Signed-off-by: LE SAULNIER Kevin --- .../annotation/RebuildNodeIfPreviouslyBuilt.java | 9 +++++++++ .../study/server/annotation/RebuildNodeUuid.java | 10 ++++++++++ .../server/annotation/RebuildStudyUuid.java | 11 ++++++++++- .../study/server/annotation/RebuildUserId.java | 11 ++++++++++- .../aspect/AnnotatedParameterExtractor.java | 16 +++++++++++++++- .../RebuildNodeIfPreviouslyBuiltAspect.java | 12 +++++++++--- .../study/server/controller/StudyController.java | 1 - .../study/server/service/StudyService.java | 5 +---- 8 files changed, 64 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/annotation/RebuildNodeIfPreviouslyBuilt.java b/src/main/java/org/gridsuite/study/server/annotation/RebuildNodeIfPreviouslyBuilt.java index a2195ae3c8..45cef9ee2e 100644 --- a/src/main/java/org/gridsuite/study/server/annotation/RebuildNodeIfPreviouslyBuilt.java +++ b/src/main/java/org/gridsuite/study/server/annotation/RebuildNodeIfPreviouslyBuilt.java @@ -1,3 +1,9 @@ +/** + * Copyright (c) 2025, 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.annotation; import java.lang.annotation.ElementType; @@ -5,6 +11,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * @author Kevin Le Saulnier + */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RebuildNodeIfPreviouslyBuilt { diff --git a/src/main/java/org/gridsuite/study/server/annotation/RebuildNodeUuid.java b/src/main/java/org/gridsuite/study/server/annotation/RebuildNodeUuid.java index 1da63bda0e..dab4c005f1 100644 --- a/src/main/java/org/gridsuite/study/server/annotation/RebuildNodeUuid.java +++ b/src/main/java/org/gridsuite/study/server/annotation/RebuildNodeUuid.java @@ -1,3 +1,9 @@ +/** + * Copyright (c) 2025, 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.annotation; import java.lang.annotation.ElementType; @@ -5,8 +11,12 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * @author Kevin Le Saulnier + */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface RebuildNodeUuid { } + diff --git a/src/main/java/org/gridsuite/study/server/annotation/RebuildStudyUuid.java b/src/main/java/org/gridsuite/study/server/annotation/RebuildStudyUuid.java index e099785a46..5e2cd870cb 100644 --- a/src/main/java/org/gridsuite/study/server/annotation/RebuildStudyUuid.java +++ b/src/main/java/org/gridsuite/study/server/annotation/RebuildStudyUuid.java @@ -1,3 +1,9 @@ +/** + * Copyright (c) 2025, 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.annotation; import java.lang.annotation.ElementType; @@ -5,7 +11,10 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * @author Kevin Le Saulnier + */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface RebuildStudyUuid { -} \ No newline at end of file +} diff --git a/src/main/java/org/gridsuite/study/server/annotation/RebuildUserId.java b/src/main/java/org/gridsuite/study/server/annotation/RebuildUserId.java index 13b7839f19..f43f1d8c9a 100644 --- a/src/main/java/org/gridsuite/study/server/annotation/RebuildUserId.java +++ b/src/main/java/org/gridsuite/study/server/annotation/RebuildUserId.java @@ -1,3 +1,9 @@ +/** + * Copyright (c) 2025, 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.annotation; import java.lang.annotation.ElementType; @@ -5,7 +11,10 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * @author Kevin Le Saulnier + */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface RebuildUserId { -} \ No newline at end of file +} diff --git a/src/main/java/org/gridsuite/study/server/aspect/AnnotatedParameterExtractor.java b/src/main/java/org/gridsuite/study/server/aspect/AnnotatedParameterExtractor.java index f54e8fa8ab..c0b8095c89 100644 --- a/src/main/java/org/gridsuite/study/server/aspect/AnnotatedParameterExtractor.java +++ b/src/main/java/org/gridsuite/study/server/aspect/AnnotatedParameterExtractor.java @@ -1,3 +1,9 @@ +/** + * Copyright (c) 2025, 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.aspect; import org.springframework.core.MethodParameter; @@ -5,7 +11,15 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; -public class AnnotatedParameterExtractor { //TODO: improve throws +/** + * @author Kevin Le Saulnier + */ +public final class AnnotatedParameterExtractor { + private AnnotatedParameterExtractor() { + throw new AssertionError("Utility class should not be instantiated"); + } + + // extract annotated parameter with checked type - if not found, found multiple times, or found with wrong type, this throw an exception public static T extractRequiredParameter( Method method, Object[] args, diff --git a/src/main/java/org/gridsuite/study/server/aspect/RebuildNodeIfPreviouslyBuiltAspect.java b/src/main/java/org/gridsuite/study/server/aspect/RebuildNodeIfPreviouslyBuiltAspect.java index 9a98965f67..de98e92af1 100644 --- a/src/main/java/org/gridsuite/study/server/aspect/RebuildNodeIfPreviouslyBuiltAspect.java +++ b/src/main/java/org/gridsuite/study/server/aspect/RebuildNodeIfPreviouslyBuiltAspect.java @@ -1,3 +1,9 @@ +/** + * Copyright (c) 2025, 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.aspect; import org.aspectj.lang.ProceedingJoinPoint; @@ -8,17 +14,17 @@ import org.gridsuite.study.server.annotation.RebuildStudyUuid; import org.gridsuite.study.server.annotation.RebuildUserId; import org.gridsuite.study.server.networkmodificationtree.dto.NodeBuildStatus; -import org.gridsuite.study.server.networkmodificationtree.entities.NodeBuildStatusEmbeddable; import org.gridsuite.study.server.service.NetworkModificationTreeService; -import org.gridsuite.study.server.service.RootNetworkNodeInfoService; import org.gridsuite.study.server.service.StudyService; import org.springframework.stereotype.Component; -import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Map; import java.util.UUID; +/** + * @author Kevin Le Saulnier + */ @Aspect @Component public class RebuildNodeIfPreviouslyBuiltAspect { diff --git a/src/main/java/org/gridsuite/study/server/controller/StudyController.java b/src/main/java/org/gridsuite/study/server/controller/StudyController.java index 2d8ec3122f..411ada8b92 100644 --- a/src/main/java/org/gridsuite/study/server/controller/StudyController.java +++ b/src/main/java/org/gridsuite/study/server/controller/StudyController.java @@ -42,7 +42,6 @@ import org.gridsuite.study.server.elasticsearch.EquipmentInfosService; import org.gridsuite.study.server.exception.PartialResultException; import org.gridsuite.study.server.networkmodificationtree.dto.*; -import org.gridsuite.study.server.networkmodificationtree.entities.NodeBuildStatusEmbeddable; import org.gridsuite.study.server.service.*; import org.gridsuite.study.server.service.securityanalysis.SecurityAnalysisResultType; import org.gridsuite.study.server.service.shortcircuit.FaultResultsMode; diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index 2d47298a47..94b862a307 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -72,9 +72,6 @@ import org.springframework.web.util.UriUtils; import java.io.UncheckedIOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.*; @@ -2231,7 +2228,7 @@ public void deleteNetworkModifications(UUID studyUuid, UUID nodeUuid, List @Transactional @RebuildNodeIfPreviouslyBuilt - public void stashNetworkModifications(@RebuildStudyUuid UUID studyUuid, @RebuildNodeUuid UUID nodeUuid, List modificationsUuids, @RebuildUserId String userId) { + public void stashNetworkModifications(@RebuildStudyUuid UUID studyUuid, @RebuildNodeUuid UUID nodeUuid, List modificationsUuids, @RebuildUserId String userId) { List childrenUuids = networkModificationTreeService.getChildrenUuids(nodeUuid); notificationService.emitStartModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids, NotificationService.MODIFICATIONS_STASHING_IN_PROGRESS); try { From e1da708f82d7ab546b921d30859820e1ada3c82e Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Wed, 31 Dec 2025 08:39:35 +0100 Subject: [PATCH 05/26] fix: replace aspect with handler Signed-off-by: LE SAULNIER Kevin --- .../study/server/RebuildNodeService.java | 52 +++++++++++++ .../RebuildNodeIfPreviouslyBuilt.java | 20 ----- .../server/annotation/RebuildNodeUuid.java | 22 ------ .../server/annotation/RebuildStudyUuid.java | 20 ----- .../server/annotation/RebuildUserId.java | 20 ----- .../aspect/AnnotatedParameterExtractor.java | 65 ----------------- .../RebuildNodeIfPreviouslyBuiltAspect.java | 66 ----------------- .../server/controller/StudyController.java | 36 ++++----- .../RebuildPreviouslyBuiltNodeHandler.java | 73 +++++++++++++++++++ .../study/server/service/StudyService.java | 29 ++------ 10 files changed, 148 insertions(+), 255 deletions(-) create mode 100644 src/main/java/org/gridsuite/study/server/RebuildNodeService.java delete mode 100644 src/main/java/org/gridsuite/study/server/annotation/RebuildNodeIfPreviouslyBuilt.java delete mode 100644 src/main/java/org/gridsuite/study/server/annotation/RebuildNodeUuid.java delete mode 100644 src/main/java/org/gridsuite/study/server/annotation/RebuildStudyUuid.java delete mode 100644 src/main/java/org/gridsuite/study/server/annotation/RebuildUserId.java delete mode 100644 src/main/java/org/gridsuite/study/server/aspect/AnnotatedParameterExtractor.java delete mode 100644 src/main/java/org/gridsuite/study/server/aspect/RebuildNodeIfPreviouslyBuiltAspect.java create mode 100644 src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java diff --git a/src/main/java/org/gridsuite/study/server/RebuildNodeService.java b/src/main/java/org/gridsuite/study/server/RebuildNodeService.java new file mode 100644 index 0000000000..6d1d016cf0 --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/RebuildNodeService.java @@ -0,0 +1,52 @@ +package org.gridsuite.study.server; + +import org.gridsuite.study.server.dto.modification.NetworkModificationMetadata; +import org.gridsuite.study.server.handler.RebuildPreviouslyBuiltNodeHandler; +import org.gridsuite.study.server.service.StudyService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +@Service +public class RebuildNodeService { + private final RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler; + private final StudyService studyService; + + public RebuildNodeService(RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler, StudyService studyService) { + this.rebuildPreviouslyBuiltNodeHandler = rebuildPreviouslyBuiltNodeHandler; + this.studyService = studyService; + } + + @Transactional + public void updateNetworkModification(UUID studyUuid, String updateModificationAttributes, UUID nodeUuid, UUID modificationUuid, String userId) { + rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, + () -> studyService.updateNetworkModification(studyUuid, updateModificationAttributes, nodeUuid, modificationUuid, userId)); + } + + @Transactional + public void stashNetworkModifications(UUID studyUuid, UUID nodeUuid, List modificationsUuids, String userId) { + rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, + () -> studyService.stashNetworkModifications(studyUuid, nodeUuid, modificationsUuids, userId)); + } + + @Transactional + public void updateNetworkModificationsMetadata(UUID studyUuid, UUID nodeUuid, List modificationsUuids, String userId, NetworkModificationMetadata metadata) { + rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, + () -> studyService.updateNetworkModificationsMetadata(studyUuid, nodeUuid, modificationsUuids, userId, metadata)); + } + + @Transactional + public void updateNetworkModificationsActivationInRootNetwork(UUID studyUuid, UUID nodeUuid, UUID rootNetworkUuid, Set modificationsUuids, String userId, boolean activated) { + rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, + () -> studyService.updateNetworkModificationsActivationInRootNetwork(studyUuid, nodeUuid, rootNetworkUuid, modificationsUuids, userId, activated)); + } + + @Transactional + public void restoreNetworkModifications(UUID studyUuid, UUID nodeUuid, List modificationsUuids, String userId) { + rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, + () -> studyService.restoreNetworkModifications(studyUuid, nodeUuid, modificationsUuids, userId)); + } +} diff --git a/src/main/java/org/gridsuite/study/server/annotation/RebuildNodeIfPreviouslyBuilt.java b/src/main/java/org/gridsuite/study/server/annotation/RebuildNodeIfPreviouslyBuilt.java deleted file mode 100644 index 45cef9ee2e..0000000000 --- a/src/main/java/org/gridsuite/study/server/annotation/RebuildNodeIfPreviouslyBuilt.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) 2025, 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.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @author Kevin Le Saulnier - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface RebuildNodeIfPreviouslyBuilt { -} diff --git a/src/main/java/org/gridsuite/study/server/annotation/RebuildNodeUuid.java b/src/main/java/org/gridsuite/study/server/annotation/RebuildNodeUuid.java deleted file mode 100644 index dab4c005f1..0000000000 --- a/src/main/java/org/gridsuite/study/server/annotation/RebuildNodeUuid.java +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) 2025, 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.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @author Kevin Le Saulnier - */ -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -public @interface RebuildNodeUuid { -} - - diff --git a/src/main/java/org/gridsuite/study/server/annotation/RebuildStudyUuid.java b/src/main/java/org/gridsuite/study/server/annotation/RebuildStudyUuid.java deleted file mode 100644 index 5e2cd870cb..0000000000 --- a/src/main/java/org/gridsuite/study/server/annotation/RebuildStudyUuid.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) 2025, 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.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @author Kevin Le Saulnier - */ -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -public @interface RebuildStudyUuid { -} diff --git a/src/main/java/org/gridsuite/study/server/annotation/RebuildUserId.java b/src/main/java/org/gridsuite/study/server/annotation/RebuildUserId.java deleted file mode 100644 index f43f1d8c9a..0000000000 --- a/src/main/java/org/gridsuite/study/server/annotation/RebuildUserId.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) 2025, 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.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @author Kevin Le Saulnier - */ -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -public @interface RebuildUserId { -} diff --git a/src/main/java/org/gridsuite/study/server/aspect/AnnotatedParameterExtractor.java b/src/main/java/org/gridsuite/study/server/aspect/AnnotatedParameterExtractor.java deleted file mode 100644 index c0b8095c89..0000000000 --- a/src/main/java/org/gridsuite/study/server/aspect/AnnotatedParameterExtractor.java +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright (c) 2025, 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.aspect; - -import org.springframework.core.MethodParameter; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; - -/** - * @author Kevin Le Saulnier - */ -public final class AnnotatedParameterExtractor { - private AnnotatedParameterExtractor() { - throw new AssertionError("Utility class should not be instantiated"); - } - - // extract annotated parameter with checked type - if not found, found multiple times, or found with wrong type, this throw an exception - public static T extractRequiredParameter( - Method method, - Object[] args, - Class annotation, - Class expectedType - ) { - T result = null; - - for (int i = 0; i < args.length; i++) { - MethodParameter param = new MethodParameter(method, i); - - if (param.hasParameterAnnotation(annotation)) { - if (result != null) { - throw new IllegalStateException( - "Multiple parameters annotated with @" - + annotation.getSimpleName() - ); - } - - Object value = args[i]; - if (!expectedType.isInstance(value)) { - throw new IllegalStateException( - "Parameter annotated with @" - + annotation.getSimpleName() - + " must be of type " - + expectedType.getSimpleName() - ); - } - - result = expectedType.cast(value); - } - } - - if (result == null) { - throw new IllegalStateException( - "Missing parameter annotated with @" - + annotation.getSimpleName() - ); - } - - return result; - } -} diff --git a/src/main/java/org/gridsuite/study/server/aspect/RebuildNodeIfPreviouslyBuiltAspect.java b/src/main/java/org/gridsuite/study/server/aspect/RebuildNodeIfPreviouslyBuiltAspect.java deleted file mode 100644 index de98e92af1..0000000000 --- a/src/main/java/org/gridsuite/study/server/aspect/RebuildNodeIfPreviouslyBuiltAspect.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (c) 2025, 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.aspect; - -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.reflect.MethodSignature; -import org.gridsuite.study.server.annotation.RebuildNodeUuid; -import org.gridsuite.study.server.annotation.RebuildStudyUuid; -import org.gridsuite.study.server.annotation.RebuildUserId; -import org.gridsuite.study.server.networkmodificationtree.dto.NodeBuildStatus; -import org.gridsuite.study.server.service.NetworkModificationTreeService; -import org.gridsuite.study.server.service.StudyService; -import org.springframework.stereotype.Component; - -import java.lang.reflect.Method; -import java.util.Map; -import java.util.UUID; - -/** - * @author Kevin Le Saulnier - */ -@Aspect -@Component -public class RebuildNodeIfPreviouslyBuiltAspect { - - private final StudyService studyService; - private final NetworkModificationTreeService networkModificationTreeService; - - public RebuildNodeIfPreviouslyBuiltAspect(StudyService studyService, NetworkModificationTreeService networkModificationTreeService) { - this.studyService = studyService; - this.networkModificationTreeService = networkModificationTreeService; - } - - @Around("@annotation(org.gridsuite.study.server.annotation.RebuildNodeIfPreviouslyBuilt)") - public Object around(ProceedingJoinPoint pjp) throws Throwable { - MethodSignature signature = (MethodSignature) pjp.getSignature(); - Method method = signature.getMethod(); - Object[] args = pjp.getArgs(); - - UUID nodeUuid = AnnotatedParameterExtractor.extractRequiredParameter(method, args, RebuildNodeUuid.class, UUID.class); - - if (networkModificationTreeService.isRootOrConstructionNode(nodeUuid)) { - // This aspect only rebuilds security nodes - return pjp.proceed(); - } - - UUID studyUuid = AnnotatedParameterExtractor.extractRequiredParameter(method, args, RebuildStudyUuid.class, UUID.class); - String userId = AnnotatedParameterExtractor.extractRequiredParameter(method, args, RebuildUserId.class, String.class); - - Map buildStatusByRootNetworkUuid = studyService.getNodeBuildStatusByRootNetworkUuid(studyUuid, nodeUuid); - - Object result = pjp.proceed(); - - // No try catch -> do not rebuild node if operation has failed - buildStatusByRootNetworkUuid.entrySet().stream() - .filter(entry -> entry.getValue().isBuilt()) - .forEach(entry -> studyService.buildNode(studyUuid, nodeUuid, entry.getKey(), userId)); - return result; - } -} diff --git a/src/main/java/org/gridsuite/study/server/controller/StudyController.java b/src/main/java/org/gridsuite/study/server/controller/StudyController.java index 411ada8b92..c6b3280544 100644 --- a/src/main/java/org/gridsuite/study/server/controller/StudyController.java +++ b/src/main/java/org/gridsuite/study/server/controller/StudyController.java @@ -17,6 +17,7 @@ import org.apache.commons.lang3.StringUtils; import org.gridsuite.filter.globalfilter.GlobalFilter; import org.gridsuite.filter.utils.EquipmentType; +import org.gridsuite.study.server.RebuildNodeService; import org.gridsuite.study.server.StudyApi; import org.gridsuite.study.server.StudyConstants.ModificationsActionType; import org.gridsuite.study.server.dto.modification.NetworkModificationMetadata; @@ -41,6 +42,7 @@ import org.gridsuite.study.server.dto.voltageinit.parameters.StudyVoltageInitParameters; import org.gridsuite.study.server.elasticsearch.EquipmentInfosService; import org.gridsuite.study.server.exception.PartialResultException; +import org.gridsuite.study.server.handler.RebuildPreviouslyBuiltNodeHandler; import org.gridsuite.study.server.networkmodificationtree.dto.*; import org.gridsuite.study.server.service.*; import org.gridsuite.study.server.service.securityanalysis.SecurityAnalysisResultType; @@ -86,6 +88,8 @@ public class StudyController { private final RootNetworkService rootNetworkService; private final RootNetworkNodeInfoService rootNetworkNodeInfoService; private final SensitivityAnalysisService sensitivityAnalysisService; + private final RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler; + private final RebuildNodeService rebuildNodeService; public StudyController(StudyService studyService, NetworkService networkStoreService, @@ -95,7 +99,9 @@ public StudyController(StudyService studyService, CaseService caseService, RemoteServicesInspector remoteServicesInspector, RootNetworkService rootNetworkService, - RootNetworkNodeInfoService rootNetworkNodeInfoService, SensitivityAnalysisService sensitivityAnalysisService) { + RootNetworkNodeInfoService rootNetworkNodeInfoService, + SensitivityAnalysisService sensitivityAnalysisService, + RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler, RebuildNodeService rebuildNodeService) { this.studyService = studyService; this.networkModificationTreeService = networkModificationTreeService; this.networkStoreService = networkStoreService; @@ -106,6 +112,8 @@ public StudyController(StudyService studyService, this.rootNetworkService = rootNetworkService; this.rootNetworkNodeInfoService = rootNetworkNodeInfoService; this.sensitivityAnalysisService = sensitivityAnalysisService; + this.rebuildPreviouslyBuiltNodeHandler = rebuildPreviouslyBuiltNodeHandler; + this.rebuildNodeService = rebuildNodeService; } @InitBinder @@ -637,21 +645,18 @@ public ResponseEntity moveModification(@PathVariable("studyUuid") UUID stu @Nullable @Parameter(description = "move before, if no value move to end") @RequestParam(value = "beforeUuid") UUID beforeUuid, @RequestHeader(HEADER_USER_ID) String userId) { studyService.assertCanUpdateModifications(studyUuid, nodeUuid); - handleMoveNetworkModification(studyUuid, nodeUuid, modificationUuid, beforeUuid, userId); + rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, + () -> handleMoveNetworkModification(studyUuid, nodeUuid, modificationUuid, beforeUuid, userId)); return ResponseEntity.ok().build(); } private void handleMoveNetworkModification(UUID studyUuid, UUID nodeUuid, UUID modificationUuid, UUID beforeUuid, String userId) { studyService.assertNoBlockedNodeInStudy(studyUuid, nodeUuid); - Map buildStatusByRootNetworkUuid = studyService.getNodeBuildStatusByRootNetworkUuid(studyUuid, nodeUuid); studyService.invalidateNodeTreeWhenMoveModification(studyUuid, nodeUuid); try { studyService.moveNetworkModifications(studyUuid, nodeUuid, nodeUuid, List.of(modificationUuid), beforeUuid, false, userId); } finally { studyService.unblockNodeTree(studyUuid, nodeUuid); - buildStatusByRootNetworkUuid.entrySet().stream() - .filter(entry -> entry.getValue().isBuilt()) - .forEach(entry -> studyService.buildNode(studyUuid, nodeUuid, entry.getKey(), userId)); } } @@ -677,7 +682,8 @@ public ResponseEntity moveOrCopyModifications(@PathVariable("studyUuid") U if (!studyUuid.equals(originStudyUuid)) { throw new StudyException(MOVE_NETWORK_MODIFICATION_FORBIDDEN); } - handleMoveNetworkModifications(studyUuid, nodeUuid, originNodeUuid, modificationsToCopyUuidList, userId); + rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, + () -> handleMoveNetworkModifications(studyUuid, nodeUuid, originNodeUuid, modificationsToCopyUuidList, userId)); break; } return ResponseEntity.ok().build(); @@ -696,9 +702,6 @@ private void handleDuplicateOrInsertNetworkModifications(UUID targetStudyUuid, U private void handleMoveNetworkModifications(UUID studyUuid, UUID targetNodeUuid, UUID originNodeUuid, List modificationsToCopyUuidList, String userId) { studyService.assertNoBlockedNodeInStudy(studyUuid, originNodeUuid); studyService.assertNoBlockedNodeInStudy(studyUuid, targetNodeUuid); - - Map buildStatusByRootNetworkUuid = studyService.getNodeBuildStatusByRootNetworkUuid(studyUuid, originNodeUuid); - boolean isTargetInDifferentNodeTree = studyService.invalidateNodeTreeWhenMoveModifications(studyUuid, targetNodeUuid, originNodeUuid); try { studyService.moveNetworkModifications(studyUuid, targetNodeUuid, originNodeUuid, modificationsToCopyUuidList, null, isTargetInDifferentNodeTree, userId); @@ -707,9 +710,6 @@ private void handleMoveNetworkModifications(UUID studyUuid, UUID targetNodeUuid, if (isTargetInDifferentNodeTree) { studyService.unblockNodeTree(studyUuid, targetNodeUuid); } - buildStatusByRootNetworkUuid.entrySet().stream() - .filter(entry -> entry.getValue().isBuilt()) - .forEach(entry -> studyService.buildNode(studyUuid, originNodeUuid, entry.getKey(), userId)); } } @@ -1378,7 +1378,7 @@ public ResponseEntity updateNetworkModification(@Parameter(description = " @RequestHeader(HEADER_USER_ID) String userId) { studyService.assertCanUpdateModifications(studyUuid, nodeUuid); studyService.assertNoBlockedNodeInStudy(studyUuid, nodeUuid); - studyService.updateNetworkModification(studyUuid, modificationAttributes, nodeUuid, networkModificationUuid, userId); + rebuildNodeService.updateNetworkModification(studyUuid, modificationAttributes, nodeUuid, networkModificationUuid, userId); return ResponseEntity.ok().build(); } @@ -1406,9 +1406,9 @@ public ResponseEntity stashNetworkModifications(@Parameter(description = " studyService.assertCanUpdateModifications(studyUuid, nodeUuid); studyService.assertNoBlockedNodeInStudy(studyUuid, nodeUuid); if (stashed.booleanValue()) { - studyService.stashNetworkModifications(studyUuid, nodeUuid, networkModificationUuids, userId); + rebuildNodeService.stashNetworkModifications(studyUuid, nodeUuid, networkModificationUuids, userId); } else { - studyService.restoreNetworkModifications(studyUuid, nodeUuid, networkModificationUuids, userId); + rebuildNodeService.restoreNetworkModifications(studyUuid, nodeUuid, networkModificationUuids, userId); } return ResponseEntity.ok().build(); } @@ -1423,7 +1423,7 @@ public ResponseEntity updateNetworkModificationsMetadata(@Parameter(descri @RequestHeader(HEADER_USER_ID) String userId) { studyService.assertCanUpdateModifications(studyUuid, nodeUuid); studyService.assertNoBlockedNodeInStudy(studyUuid, nodeUuid); - studyService.updateNetworkModificationsMetadata(studyUuid, nodeUuid, networkModificationUuids, userId, metadata); + rebuildNodeService.updateNetworkModificationsMetadata(studyUuid, nodeUuid, networkModificationUuids, userId, metadata); return ResponseEntity.ok().build(); } @@ -1439,7 +1439,7 @@ public ResponseEntity updateNetworkModificationsActivation(@Parameter(desc studyService.assertCanUpdateModifications(studyUuid, nodeUuid); studyService.assertNoBuildNoComputationForRootNetworkNode(nodeUuid, rootNetworkUuid); studyService.assertNoBlockedNodeInTree(nodeUuid, rootNetworkUuid); - studyService.updateNetworkModificationsActivationInRootNetwork(studyUuid, nodeUuid, rootNetworkUuid, networkModificationUuids, userId, activated); + rebuildNodeService.updateNetworkModificationsActivationInRootNetwork(studyUuid, nodeUuid, rootNetworkUuid, networkModificationUuids, userId, activated); return ResponseEntity.ok().build(); } diff --git a/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java b/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java new file mode 100644 index 0000000000..eea68c5a56 --- /dev/null +++ b/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java @@ -0,0 +1,73 @@ +package org.gridsuite.study.server.handler; + +import org.gridsuite.study.server.networkmodificationtree.dto.NodeBuildStatus; +import org.gridsuite.study.server.service.NetworkModificationTreeService; +import org.gridsuite.study.server.service.StudyService; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Callable; + +@Component +public class RebuildPreviouslyBuiltNodeHandler { + private final StudyService studyService; + private final NetworkModificationTreeService networkModificationTreeService; + + public RebuildPreviouslyBuiltNodeHandler( + StudyService studyService, + NetworkModificationTreeService networkModificationTreeService + ) { + this.studyService = studyService; + this.networkModificationTreeService = networkModificationTreeService; + } + + public T execute( + UUID studyUuid, + UUID nodeUuid, + String userId, + Callable operation + ) throws Exception { + if (networkModificationTreeService.isRootOrConstructionNode(nodeUuid)) { + return operation.call(); + } + + Map previousBuildStatus = + studyService.getNodeBuildStatusByRootNetworkUuid(studyUuid, nodeUuid); + + T result = operation.call(); + + previousBuildStatus.entrySet().stream() + .filter(entry -> entry.getValue().isBuilt()) + .forEach(entry -> + studyService.buildNode( + studyUuid, + nodeUuid, + entry.getKey(), + userId + )); + + return result; + } + + public void execute( + UUID studyUuid, + UUID nodeUuid, + String userId, + Runnable operation + ) { + try { + execute( + studyUuid, + nodeUuid, + userId, + () -> { + operation.run(); + return null; + } + ); + } catch (Exception e) { + throw new RuntimeException(e); //TODO: better exception + } + } +} diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index 94b862a307..f2978c0fcf 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -16,10 +16,6 @@ import org.gridsuite.filter.globalfilter.GlobalFilter; import org.gridsuite.filter.utils.EquipmentType; import org.gridsuite.study.server.StudyConstants; -import org.gridsuite.study.server.annotation.RebuildNodeIfPreviouslyBuilt; -import org.gridsuite.study.server.annotation.RebuildNodeUuid; -import org.gridsuite.study.server.annotation.RebuildStudyUuid; -import org.gridsuite.study.server.annotation.RebuildUserId; import org.gridsuite.study.server.dto.modification.*; import org.gridsuite.study.server.dto.voltageinit.ContextInfos; import org.gridsuite.study.server.error.StudyException; @@ -1839,9 +1835,8 @@ public void createNetworkModification(UUID studyUuid, UUID nodeUuid, String crea notificationService.emitElementUpdated(studyUuid, userId); } - @RebuildNodeIfPreviouslyBuilt @Transactional - public void updateNetworkModification(@RebuildStudyUuid UUID studyUuid, String updateModificationAttributes, @RebuildNodeUuid UUID nodeUuid, UUID modificationUuid, @RebuildUserId String userId) { + public void updateNetworkModification(UUID studyUuid, String updateModificationAttributes, UUID nodeUuid, UUID modificationUuid, String userId) { List childrenUuids = networkModificationTreeService.getChildrenUuids(nodeUuid); notificationService.emitStartModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids, NotificationService.MODIFICATIONS_UPDATING_IN_PROGRESS); try { @@ -2227,8 +2222,7 @@ public void deleteNetworkModifications(UUID studyUuid, UUID nodeUuid, List } @Transactional - @RebuildNodeIfPreviouslyBuilt - public void stashNetworkModifications(@RebuildStudyUuid UUID studyUuid, @RebuildNodeUuid UUID nodeUuid, List modificationsUuids, @RebuildUserId String userId) { + public void stashNetworkModifications(UUID studyUuid, UUID nodeUuid, List modificationsUuids, String userId) { List childrenUuids = networkModificationTreeService.getChildrenUuids(nodeUuid); notificationService.emitStartModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids, NotificationService.MODIFICATIONS_STASHING_IN_PROGRESS); try { @@ -2245,12 +2239,7 @@ public void stashNetworkModifications(@RebuildStudyUuid UUID studyUuid, @Rebuild } @Transactional - @RebuildNodeIfPreviouslyBuilt - public void updateNetworkModificationsMetadata(@RebuildStudyUuid UUID studyUuid, - @RebuildNodeUuid UUID nodeUuid, - List modificationsUuids, - @RebuildUserId String userId, - NetworkModificationMetadata metadata) { + public void updateNetworkModificationsMetadata(UUID studyUuid, UUID nodeUuid, List modificationsUuids, String userId, NetworkModificationMetadata metadata) { List childrenUuids = networkModificationTreeService.getChildrenUuids(nodeUuid); notificationService.emitStartModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids, NotificationService.MODIFICATIONS_UPDATING_IN_PROGRESS); try { @@ -2269,14 +2258,7 @@ public void updateNetworkModificationsMetadata(@RebuildStudyUuid UUID studyUuid, } @Transactional - @RebuildNodeIfPreviouslyBuilt - public void updateNetworkModificationsActivationInRootNetwork( - @RebuildStudyUuid UUID studyUuid, - @RebuildNodeUuid UUID nodeUuid, - UUID rootNetworkUuid, - Set modificationsUuids, - @RebuildUserId String userId, - boolean activated) { + public void updateNetworkModificationsActivationInRootNetwork(UUID studyUuid, UUID nodeUuid, UUID rootNetworkUuid, Set modificationsUuids, String userId, boolean activated) { List childrenUuids = networkModificationTreeService.getChildrenUuids(nodeUuid); networkModificationService.verifyModifications(networkModificationTreeService.getModificationGroupUuid(nodeUuid), modificationsUuids); notificationService.emitStartModificationEquipmentNotification(studyUuid, nodeUuid, Optional.of(rootNetworkUuid), childrenUuids, NotificationService.MODIFICATIONS_UPDATING_IN_PROGRESS); @@ -2293,8 +2275,7 @@ public void updateNetworkModificationsActivationInRootNetwork( } @Transactional - @RebuildNodeIfPreviouslyBuilt - public void restoreNetworkModifications(@RebuildStudyUuid UUID studyUuid, @RebuildNodeUuid UUID nodeUuid, List modificationsUuids, @RebuildUserId String userId) { + public void restoreNetworkModifications(UUID studyUuid, UUID nodeUuid, List modificationsUuids, String userId) { List childrenUuids = networkModificationTreeService.getChildrenUuids(nodeUuid); notificationService.emitStartModificationEquipmentNotification(studyUuid, nodeUuid, childrenUuids, NotificationService.MODIFICATIONS_RESTORING_IN_PROGRESS); try { From eb3db27d1ea18282e95df64bb77f784e98a40fae Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Fri, 2 Jan 2026 09:03:40 +0100 Subject: [PATCH 06/26] fix: create modification on node with loadflow Signed-off-by: LE SAULNIER Kevin --- .../server/controller/StudyController.java | 3 +- .../RebuildPreviouslyBuiltNodeHandler.java | 44 ++++++++++----- .../study/server/service/ConsumerService.java | 19 +++++-- .../study/server/service/StudyService.java | 56 +++++++++---------- 4 files changed, 74 insertions(+), 48 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/controller/StudyController.java b/src/main/java/org/gridsuite/study/server/controller/StudyController.java index c6b3280544..a16934bc2d 100644 --- a/src/main/java/org/gridsuite/study/server/controller/StudyController.java +++ b/src/main/java/org/gridsuite/study/server/controller/StudyController.java @@ -1355,7 +1355,8 @@ public ResponseEntity createNetworkModification(@Parameter(description = " @RequestHeader(HEADER_USER_ID) String userId) { studyService.assertCanUpdateModifications(studyUuid, nodeUuid); studyService.assertNoBlockedNodeInStudy(studyUuid, nodeUuid); - handleCreateNetworkModification(studyUuid, nodeUuid, modificationAttributes, userId); + rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, + () -> handleCreateNetworkModification(studyUuid, nodeUuid, modificationAttributes, userId)); return ResponseEntity.ok().build(); } diff --git a/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java b/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java index eea68c5a56..72aca7c982 100644 --- a/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java +++ b/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java @@ -1,23 +1,26 @@ package org.gridsuite.study.server.handler; -import org.gridsuite.study.server.networkmodificationtree.dto.NodeBuildStatus; import org.gridsuite.study.server.service.NetworkModificationTreeService; import org.gridsuite.study.server.service.StudyService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; +import java.util.stream.Collectors; @Component public class RebuildPreviouslyBuiltNodeHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(RebuildPreviouslyBuiltNodeHandler.class); private final StudyService studyService; private final NetworkModificationTreeService networkModificationTreeService; public RebuildPreviouslyBuiltNodeHandler( StudyService studyService, - NetworkModificationTreeService networkModificationTreeService - ) { + NetworkModificationTreeService networkModificationTreeService) { this.studyService = studyService; this.networkModificationTreeService = networkModificationTreeService; } @@ -32,20 +35,33 @@ public T execute( return operation.call(); } - Map previousBuildStatus = - studyService.getNodeBuildStatusByRootNetworkUuid(studyUuid, nodeUuid); + Set rootNetworkUuidsWithBuiltNodeBefore = + studyService.getNodeBuildStatusByRootNetworkUuid(studyUuid, nodeUuid).entrySet().stream() + .filter(entry -> entry.getValue().isBuilt()) + .map(Map.Entry::getKey).collect(Collectors.toSet()); T result = operation.call(); - previousBuildStatus.entrySet().stream() - .filter(entry -> entry.getValue().isBuilt()) - .forEach(entry -> - studyService.buildNode( - studyUuid, - nodeUuid, - entry.getKey(), - userId - )); + Set rootNetworkUuidsWithBuiltNodeAfter = + studyService.getNodeBuildStatusByRootNetworkUuid(studyUuid, nodeUuid).entrySet().stream() + .filter(entry -> entry.getValue().isBuilt()) + .map(Map.Entry::getKey).collect(Collectors.toSet()); + + try { + rootNetworkUuidsWithBuiltNodeBefore.stream() + .filter(uuid -> !rootNetworkUuidsWithBuiltNodeAfter.contains(uuid)) + .forEach(rootNetworkUuid -> + studyService.buildNode( + studyUuid, + nodeUuid, + rootNetworkUuid, + userId + ) + ); + } catch (Exception e) { + // if rebuild fails, we don't want to rollback main operation transaction + LOGGER.warn(e.getMessage()); + } return result; } diff --git a/src/main/java/org/gridsuite/study/server/service/ConsumerService.java b/src/main/java/org/gridsuite/study/server/service/ConsumerService.java index 6829556627..e149df04d4 100644 --- a/src/main/java/org/gridsuite/study/server/service/ConsumerService.java +++ b/src/main/java/org/gridsuite/study/server/service/ConsumerService.java @@ -640,17 +640,28 @@ public void consumeCalculationResult(Message msg, ComputationType comput // unblock node handleUnblockNode(receiverObj, computationType); - // send notifications + // build 1st level children if loadflow results UUID studyUuid = networkModificationTreeService.getStudyUuidForNodeId(receiverObj.getNodeUuid()); - if (computationType == ComputationType.LOAD_FLOW && networkModificationTreeService.isSecurityNode(receiverObj.getNodeUuid())) { - String userId = (String) msg.getHeaders().get(HEADER_USER_ID); - studyService.buildSubtree(studyUuid, receiverObj.getNodeUuid(), receiverObj.getRootNetworkUuid(), userId); + String userId = (String) msg.getHeaders().get(HEADER_USER_ID); + if (computationType == LOAD_FLOW) { + handleLoadFlowSuccess(studyUuid, receiverObj.getNodeUuid(), receiverObj.getRootNetworkUuid(), resultUuid, userId); } + + // send notifications notificationService.emitStudyChanged(studyUuid, receiverObj.getNodeUuid(), receiverObj.getRootNetworkUuid(), computationType.getUpdateStatusType()); notificationService.emitStudyChanged(studyUuid, receiverObj.getNodeUuid(), receiverObj.getRootNetworkUuid(), computationType.getUpdateResultType()); })); } + private void handleLoadFlowSuccess(UUID studyUuid, UUID nodeUuid, UUID rootNetworkUuid, UUID resultUuid, String userId) { + if (userId != null && networkModificationTreeService.isSecurityNode(nodeUuid)) { + LoadFlowStatus loadFlowStatus = loadFlowService.getLoadFlowStatus(resultUuid); + if (loadFlowStatus == LoadFlowStatus.CONVERGED) { + studyService.buildFirstLevelChildren(studyUuid, nodeUuid, rootNetworkUuid, userId); + } + } + } + Optional getNodeReceiver(Message msg) { String receiver = msg.getHeaders().get(HEADER_RECEIVER, String.class); if (Strings.isBlank(receiver)) { diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index f2978c0fcf..e006165cb9 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -63,6 +63,7 @@ import org.springframework.data.util.Pair; import org.springframework.lang.Nullable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.util.UriUtils; @@ -1885,6 +1886,11 @@ public String getVoltageLevelTopologyInfos(UUID nodeUuid, UUID rootNetworkUuid, return getVoltageLevelTopologyInfos(nodeUuidToSearchIn, rootNetworkUuid, voltageLevelId, path); } + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void buildNodeInNewTransaction(@NonNull UUID studyUuid, @NonNull UUID nodeUuid, @NonNull UUID rootNetworkUuid, @NonNull String userId) { + buildNode(studyUuid, nodeUuid, rootNetworkUuid, userId, null); + } + @Transactional public void buildNode(@NonNull UUID studyUuid, @NonNull UUID nodeUuid, @NonNull UUID rootNetworkUuid, @NonNull String userId) { buildNode(studyUuid, nodeUuid, rootNetworkUuid, userId, null); @@ -1910,43 +1916,23 @@ private void buildNode(@NonNull UUID studyUuid, @NonNull UUID nodeUuid, @NonNull } @Transactional - public void buildSubtree(@NonNull UUID studyUuid, @NonNull UUID parentNodeUuid, @NonNull UUID rootNetworkUuid, @NonNull String userId) { + public void buildFirstLevelChildren(@NonNull UUID studyUuid, @NonNull UUID parentNodeUuid, @NonNull UUID rootNetworkUuid, @NonNull String userId) { AbstractNode studySubTree = networkModificationTreeService.getStudySubtree(studyUuid, parentNodeUuid, rootNetworkUuid); - studySubTree.getChildren().forEach(child -> - buildStudySubTree( - child, - studyUuid, - rootNetworkUuid, - userId - ) - ); - } + long builtNodesUpToQuota = getBuiltNodesUpToQuota(studyUuid, rootNetworkUuid, userId); + for (AbstractNode child : studySubTree.getChildren()) { + if (builtNodesUpToQuota <= 0) { + return; + } - private void buildStudySubTree( - @NonNull AbstractNode nodeToBuild, - @NonNull UUID studyUuid, - @NonNull UUID rootNetworkUuid, - @NonNull String userId - ) { - if (!NodeType.ROOT.equals(nodeToBuild.getType())) { buildNode( studyUuid, - nodeToBuild.getId(), + child.getId(), rootNetworkUuid, userId, null ); + builtNodesUpToQuota--; } - - nodeToBuild.getChildren() - .forEach(child -> - buildStudySubTree( - child, - studyUuid, - rootNetworkUuid, - userId - ) - ); } public void handleBuildSuccess(UUID studyUuid, UUID nodeUuid, UUID rootNetworkUuid, NetworkModificationResult networkModificationResult) { @@ -1958,6 +1944,13 @@ public void handleBuildSuccess(UUID studyUuid, UUID nodeUuid, UUID rootNetworkUu notificationService.emitStudyChanged(studyUuid, nodeUuid, rootNetworkUuid, NotificationService.UPDATE_TYPE_BUILD_COMPLETED, networkModificationResult.getImpactedSubstationsIds()); } + private long getBuiltNodesUpToQuota(@NonNull UUID studyUuid, @NonNull UUID rootNetworkUuid, @NonNull String userId) { + return userAdminService.getUserMaxAllowedBuilds(userId).map(maxBuilds -> { + long nbBuiltNodes = networkModificationTreeService.countBuiltNodes(studyUuid, rootNetworkUuid); + return maxBuilds - nbBuiltNodes; + }).orElse(Long.MAX_VALUE); + } + public void assertCanBuildNode(@NonNull UUID studyUuid, @NonNull UUID rootNetworkUuid, @NonNull String userId) { // check restrictions on node builds number userAdminService.getUserMaxAllowedBuilds(userId).ifPresent(maxBuilds -> { @@ -3545,7 +3538,12 @@ public NetworkModificationNode createNode(UUID studyUuid, UUID nodeId, NetworkMo networkModificationTreeService.assertCreateNode(nodeId, nodeInfo.getNodeType(), insertMode); NetworkModificationNode newNode = networkModificationTreeService.createNode(study, nodeId, nodeInfo, insertMode, userId); - createNodePostAction(studyUuid, nodeId, newNode, userId); + try { + createNodePostAction(studyUuid, nodeId, newNode, userId); + } catch (Exception e) { + // if post action fails, don't interrupt / rollback current transaction + LOGGER.warn(e.getMessage()); + } UUID parentUuid = networkModificationTreeService.getParentNodeUuid(newNode.getId()).orElse(null); notificationService.emitNodeInserted(study.getId(), parentUuid, newNode.getId(), insertMode, nodeId); From 890bbe4a365fa42c4042232545416c673618ee3e Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Fri, 2 Jan 2026 10:17:00 +0100 Subject: [PATCH 07/26] fix: handle modification moves Signed-off-by: LE SAULNIER Kevin --- .../server/controller/StudyController.java | 2 +- .../RebuildPreviouslyBuiltNodeHandler.java | 81 ++++++++++++++----- .../NetworkModificationTreeService.java | 28 +++++++ 3 files changed, 90 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/controller/StudyController.java b/src/main/java/org/gridsuite/study/server/controller/StudyController.java index a16934bc2d..591a009a56 100644 --- a/src/main/java/org/gridsuite/study/server/controller/StudyController.java +++ b/src/main/java/org/gridsuite/study/server/controller/StudyController.java @@ -682,7 +682,7 @@ public ResponseEntity moveOrCopyModifications(@PathVariable("studyUuid") U if (!studyUuid.equals(originStudyUuid)) { throw new StudyException(MOVE_NETWORK_MODIFICATION_FORBIDDEN); } - rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, + rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, originNodeUuid, userId, () -> handleMoveNetworkModifications(studyUuid, nodeUuid, originNodeUuid, modificationsToCopyUuidList, userId)); break; } diff --git a/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java b/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java index 72aca7c982..2a12170fec 100644 --- a/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java +++ b/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java @@ -6,10 +6,12 @@ import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; +import java.util.function.Predicate; import java.util.stream.Collectors; @Component @@ -27,37 +29,46 @@ public RebuildPreviouslyBuiltNodeHandler( public T execute( UUID studyUuid, - UUID nodeUuid, + UUID node1Uuid, + UUID node2Uuid, String userId, Callable operation ) throws Exception { - if (networkModificationTreeService.isRootOrConstructionNode(nodeUuid)) { + // if node 1 and 2 are in the same "subtree", rebuild only the highest one - otherwise, rebuild both + List nodesToReBuild = networkModificationTreeService.getHighestNodeUuids(node1Uuid, node2Uuid).stream() + .filter(Predicate.not(networkModificationTreeService::isRootOrConstructionNode)).toList(); + + if (nodesToReBuild.isEmpty()) { return operation.call(); } - Set rootNetworkUuidsWithBuiltNodeBefore = - studyService.getNodeBuildStatusByRootNetworkUuid(studyUuid, nodeUuid).entrySet().stream() - .filter(entry -> entry.getValue().isBuilt()) - .map(Map.Entry::getKey).collect(Collectors.toSet()); + Map> rootNetworkUuidsWithBuiltNodeBeforeMap = nodesToReBuild.stream().collect(Collectors.toMap( + nodeUuid -> nodeUuid, + nodeUuid -> getRootNetworkWhereNotHasToBeRebuilt(studyUuid, nodeUuid) + )); T result = operation.call(); - Set rootNetworkUuidsWithBuiltNodeAfter = - studyService.getNodeBuildStatusByRootNetworkUuid(studyUuid, nodeUuid).entrySet().stream() - .filter(entry -> entry.getValue().isBuilt()) - .map(Map.Entry::getKey).collect(Collectors.toSet()); + Map> rootNetworkUuidsWithBuiltNodeAfterMap = nodesToReBuild.stream().collect(Collectors.toMap( + nodeUuid -> nodeUuid, + nodeUuid -> getRootNetworkWhereNotHasToBeRebuilt(studyUuid, nodeUuid) + )); try { - rootNetworkUuidsWithBuiltNodeBefore.stream() - .filter(uuid -> !rootNetworkUuidsWithBuiltNodeAfter.contains(uuid)) - .forEach(rootNetworkUuid -> - studyService.buildNode( - studyUuid, - nodeUuid, - rootNetworkUuid, - userId - ) - ); + rootNetworkUuidsWithBuiltNodeAfterMap.forEach((nodeUuid, rootNetworkUuidsWithBuiltNodeAfter) -> { + Set rootNetworkUuidsWithBuiltNodeBefore = rootNetworkUuidsWithBuiltNodeBeforeMap.get(nodeUuid); + + rootNetworkUuidsWithBuiltNodeBefore.stream() + .filter(uuid -> !rootNetworkUuidsWithBuiltNodeAfter.contains(uuid)) + .forEach(rootNetworkUuid -> + studyService.buildNode( + studyUuid, + nodeUuid, + rootNetworkUuid, + userId + ) + ); + }); } catch (Exception e) { // if rebuild fails, we don't want to rollback main operation transaction LOGGER.warn(e.getMessage()); @@ -76,6 +87,7 @@ public void execute( execute( studyUuid, nodeUuid, + nodeUuid, userId, () -> { operation.run(); @@ -86,4 +98,33 @@ public void execute( throw new RuntimeException(e); //TODO: better exception } } + + public void execute( + UUID studyUuid, + UUID node1Uuid, + UUID node2Uuid, + String userId, + Runnable operation + ) { + try { + execute( + studyUuid, + node1Uuid, + node2Uuid, + userId, + () -> { + operation.run(); + return null; + } + ); + } catch (Exception e) { + throw new RuntimeException(e); //TODO: better exception + } + } + + private Set getRootNetworkWhereNotHasToBeRebuilt(UUID studyUuid, UUID nodeUuid) { + return studyService.getNodeBuildStatusByRootNetworkUuid(studyUuid, nodeUuid).entrySet().stream() + .filter(entry -> entry.getValue().isBuilt()) + .map(Map.Entry::getKey).collect(Collectors.toSet()); + } } diff --git a/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java b/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java index 9084aa45f7..5c99804f8a 100644 --- a/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java +++ b/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java @@ -1266,4 +1266,32 @@ public NetworkModificationNode createNodeTree(@NonNull StudyEntity study, @NonNu return nodeInfo; } + + @Transactional + public List getHighestNodeUuids(UUID node1Uuid, UUID node2Uuid) { + if (node1Uuid.equals(node2Uuid)) { + return List.of(node1Uuid); + } + + Set ancestorsOfA = new HashSet<>(); + + NodeEntity currentNode = getNodeEntity(node1Uuid).getParentNode(); + while (currentNode != null) { + ancestorsOfA.add(currentNode.getIdNode()); + currentNode = currentNode.getParentNode(); + } + + currentNode = getNodeEntity(node2Uuid).getParentNode(); + while (currentNode != null) { + if (currentNode.getIdNode().equals(node1Uuid)) { + return List.of(node1Uuid); + } + if (ancestorsOfA.contains(currentNode.getIdNode())) { + return List.of(node2Uuid); + } + currentNode = currentNode.getParentNode(); + } + + return List.of(node1Uuid, node2Uuid); + } } From 5b2bb9479c21f37cf47d2b231d73c96ecd3acff5 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Mon, 5 Jan 2026 10:27:14 +0100 Subject: [PATCH 08/26] fix: some tests Signed-off-by: LE SAULNIER Kevin --- .../study/server/RebuildNodeService.java | 6 --- .../RebuildPreviouslyBuiltNodeHandler.java | 10 +++-- .../study/server/service/StudyService.java | 5 --- .../study/server/NetworkModificationTest.java | 26 +++++++++-- .../server/NetworkModificationUnitTest.java | 15 +++++++ .../study/server/NodeSequenceTest.java | 43 ++++++++++++++++--- .../server/studycontroller/StudyTest.java | 15 +++++++ 7 files changed, 96 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/RebuildNodeService.java b/src/main/java/org/gridsuite/study/server/RebuildNodeService.java index 6d1d016cf0..ad6226f74e 100644 --- a/src/main/java/org/gridsuite/study/server/RebuildNodeService.java +++ b/src/main/java/org/gridsuite/study/server/RebuildNodeService.java @@ -4,7 +4,6 @@ import org.gridsuite.study.server.handler.RebuildPreviouslyBuiltNodeHandler; import org.gridsuite.study.server.service.StudyService; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Set; @@ -20,31 +19,26 @@ public RebuildNodeService(RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBui this.studyService = studyService; } - @Transactional public void updateNetworkModification(UUID studyUuid, String updateModificationAttributes, UUID nodeUuid, UUID modificationUuid, String userId) { rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, () -> studyService.updateNetworkModification(studyUuid, updateModificationAttributes, nodeUuid, modificationUuid, userId)); } - @Transactional public void stashNetworkModifications(UUID studyUuid, UUID nodeUuid, List modificationsUuids, String userId) { rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, () -> studyService.stashNetworkModifications(studyUuid, nodeUuid, modificationsUuids, userId)); } - @Transactional public void updateNetworkModificationsMetadata(UUID studyUuid, UUID nodeUuid, List modificationsUuids, String userId, NetworkModificationMetadata metadata) { rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, () -> studyService.updateNetworkModificationsMetadata(studyUuid, nodeUuid, modificationsUuids, userId, metadata)); } - @Transactional public void updateNetworkModificationsActivationInRootNetwork(UUID studyUuid, UUID nodeUuid, UUID rootNetworkUuid, Set modificationsUuids, String userId, boolean activated) { rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, () -> studyService.updateNetworkModificationsActivationInRootNetwork(studyUuid, nodeUuid, rootNetworkUuid, modificationsUuids, userId, activated)); } - @Transactional public void restoreNetworkModifications(UUID studyUuid, UUID nodeUuid, List modificationsUuids, String userId) { rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, () -> studyService.restoreNetworkModifications(studyUuid, nodeUuid, modificationsUuids, userId)); diff --git a/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java b/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java index 2a12170fec..9c49d1c014 100644 --- a/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java +++ b/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java @@ -27,7 +27,7 @@ public RebuildPreviouslyBuiltNodeHandler( this.networkModificationTreeService = networkModificationTreeService; } - public T execute( + private T execute( UUID studyUuid, UUID node1Uuid, UUID node2Uuid, @@ -94,8 +94,10 @@ public void execute( return null; } ); + } catch (RuntimeException e) { + throw e; } catch (Exception e) { - throw new RuntimeException(e); //TODO: better exception + throw new RuntimeException(e); //TODO improve exception handling } } @@ -117,8 +119,10 @@ public void execute( return null; } ); + } catch (RuntimeException e) { + throw e; } catch (Exception e) { - throw new RuntimeException(e); //TODO: better exception + throw new RuntimeException(e); //TODO improve exception handling } } diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index e9bb263778..7e49a9434d 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -1886,11 +1886,6 @@ public String getVoltageLevelTopologyInfos(UUID nodeUuid, UUID rootNetworkUuid, return getVoltageLevelTopologyInfos(nodeUuidToSearchIn, rootNetworkUuid, voltageLevelId, path); } - @Transactional(propagation = Propagation.REQUIRES_NEW) - public void buildNodeInNewTransaction(@NonNull UUID studyUuid, @NonNull UUID nodeUuid, @NonNull UUID rootNetworkUuid, @NonNull String userId) { - buildNode(studyUuid, nodeUuid, rootNetworkUuid, userId, null); - } - @Transactional public void buildNode(@NonNull UUID studyUuid, @NonNull UUID nodeUuid, @NonNull UUID rootNetworkUuid, @NonNull String userId) { buildNode(studyUuid, nodeUuid, rootNetworkUuid, userId, null); diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java index 44df470fbf..fc8f10ec37 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java @@ -36,6 +36,7 @@ import org.gridsuite.study.server.dto.impacts.SimpleElementImpact.SimpleImpactType; import org.gridsuite.study.server.dto.modification.*; import org.gridsuite.study.server.error.StudyException; +import org.gridsuite.study.server.handler.RebuildPreviouslyBuiltNodeHandler; import org.gridsuite.study.server.networkmodificationtree.dto.*; import org.gridsuite.study.server.networkmodificationtree.entities.NetworkModificationNodeType; import org.gridsuite.study.server.networkmodificationtree.entities.NodeBuildStatusEmbeddable; @@ -79,6 +80,7 @@ import org.springframework.test.web.servlet.MvcResult; import java.util.*; +import java.util.concurrent.Callable; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.gridsuite.study.server.error.StudyBusinessErrorCode.*; @@ -261,14 +263,29 @@ class NetworkModificationTest { @Autowired private PccMinService pccMinService; + @MockitoBean + RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler; + @BeforeEach - void setup(final MockWebServer server) { + void setup(final MockWebServer server) throws Exception { ReadOnlyDataSource dataSource = new ResourceDataSource("testCase", new ResourceSet("", TEST_FILE)); Network network = new XMLImporter().importData(dataSource, new NetworkFactoryImpl(), null); network.getVariantManager().cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, VARIANT_ID); network.getVariantManager().setWorkingVariant(VariantManagerConstants.INITIAL_VARIANT_ID); when(networkStoreService.getNetwork(NETWORK_UUID)).thenReturn(network); + doAnswer(inv -> { + inv.getArgument(inv.getArguments().length - 1, Runnable.class).run(); + return null; + }).when(rebuildPreviouslyBuiltNodeHandler) + .execute(any(), any(), any(), anyString(), any(Runnable.class)); + + doAnswer(inv -> { + inv.getArgument(inv.getArguments().length - 1, Runnable.class).run(); + return null; + }).when(rebuildPreviouslyBuiltNodeHandler) + .execute(any(), any(), anyString(), any(Runnable.class)); + synchronizeStudyServerExecutionService(studyServerExecutionService); @@ -2504,7 +2521,8 @@ void testRemoveLoadFlowComputationReport(final MockWebServer server) throws Exce UUID studyNameUserIdUuid = studyEntity.getId(); UUID firstRootNetworkUuid = studyTestUtils.getOneRootNetworkUuid(studyNameUserIdUuid); UUID rootNodeUuid = getRootNode(studyNameUserIdUuid).getId(); - NetworkModificationNode modificationNode1 = createNetworkModificationNode(studyNameUserIdUuid, rootNodeUuid, UUID.randomUUID(), VARIANT_ID, "node 1", NetworkModificationNodeType.SECURITY, BuildStatus.BUILT, userId); + NetworkModificationNode modificationNode1 = createNetworkModificationNode(studyNameUserIdUuid, rootNodeUuid, UUID.randomUUID(), VARIANT_ID, "node 1", NetworkModificationNodeType.CONSTRUCTION + , BuildStatus.BUILT, userId); UUID modificationNode1Uuid = modificationNode1.getId(); // In this node, let's say we have all computations results RootNetworkNodeInfoEntity rootNetworkNodeInfoEntity = rootNetworkNodeInfoRepository.findByNodeInfoIdAndRootNetworkId(modificationNode1Uuid, studyTestUtils.getOneRootNetworkUuid(studyNameUserIdUuid)).orElseThrow(() -> new StudyException(NOT_FOUND, "Root network not found")); @@ -2931,13 +2949,13 @@ private RootNode getRootNode(UUID study) throws Exception { } private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UUID parentNodeUuid, String variantId, String nodeName, String userId) throws Exception { - return createNetworkModificationNode(studyUuid, parentNodeUuid, UUID.randomUUID(), variantId, nodeName, NetworkModificationNodeType.SECURITY, userId); + return createNetworkModificationNode(studyUuid, parentNodeUuid, UUID.randomUUID(), variantId, nodeName, NetworkModificationNodeType.CONSTRUCTION, userId); } private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UUID parentNodeUuid, UUID modificationGroupUuid, String variantId, String nodeName, String userId) throws Exception { return createNetworkModificationNode(studyUuid, parentNodeUuid, - modificationGroupUuid, variantId, nodeName, NetworkModificationNodeType.SECURITY, BuildStatus.NOT_BUILT, userId); + modificationGroupUuid, variantId, nodeName, NetworkModificationNodeType.CONSTRUCTION, BuildStatus.NOT_BUILT, userId); } private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UUID parentNodeUuid, diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationUnitTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationUnitTest.java index 17af542bce..099c18cae2 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationUnitTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationUnitTest.java @@ -13,6 +13,7 @@ import org.gridsuite.study.server.dto.modification.NetworkModificationMetadata; import org.gridsuite.study.server.dto.workflow.RerunLoadFlowInfos; import org.gridsuite.study.server.error.StudyException; +import org.gridsuite.study.server.handler.RebuildPreviouslyBuiltNodeHandler; import org.gridsuite.study.server.networkmodificationtree.dto.BuildStatus; import org.gridsuite.study.server.networkmodificationtree.dto.NodeBuildStatus; import org.gridsuite.study.server.networkmodificationtree.entities.*; @@ -129,6 +130,8 @@ class NetworkModificationUnitTest { @MockitoSpyBean private NetworkModificationTreeService networkModificationTreeService; + @MockitoBean + private RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler; @BeforeEach void setup() { @@ -137,6 +140,18 @@ void setup() { nodeRepository.deleteAll(); rootNetworkRepository.deleteAll(); studyRepository.deleteAll(); + + doAnswer(inv -> { + inv.getArgument(inv.getArguments().length - 1, Runnable.class).run(); + return null; + }).when(rebuildPreviouslyBuiltNodeHandler) + .execute(any(), any(), any(), anyString(), any(Runnable.class)); + + doAnswer(inv -> { + inv.getArgument(inv.getArguments().length - 1, Runnable.class).run(); + return null; + }).when(rebuildPreviouslyBuiltNodeHandler) + .execute(any(), any(), anyString(), any(Runnable.class)); } @Test diff --git a/src/test/java/org/gridsuite/study/server/NodeSequenceTest.java b/src/test/java/org/gridsuite/study/server/NodeSequenceTest.java index 64543689e2..e166c91dfe 100644 --- a/src/test/java/org/gridsuite/study/server/NodeSequenceTest.java +++ b/src/test/java/org/gridsuite/study/server/NodeSequenceTest.java @@ -21,8 +21,10 @@ import org.gridsuite.study.server.repository.networkmodificationtree.NetworkModificationNodeInfoRepository; import org.gridsuite.study.server.repository.networkmodificationtree.NodeRepository; import org.gridsuite.study.server.repository.networkmodificationtree.RootNodeInfoRepository; +import org.gridsuite.study.server.service.NetworkModificationService; import org.gridsuite.study.server.service.NetworkModificationTreeService; import org.gridsuite.study.server.service.StudyService; +import org.gridsuite.study.server.service.UserAdminService; import org.gridsuite.study.server.utils.TestUtils; import org.gridsuite.study.server.utils.elasticsearch.DisableElasticsearch; import org.junit.jupiter.api.AfterEach; @@ -32,12 +34,12 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.bean.override.mockito.MockitoBean; +import java.util.Optional; import java.util.UUID; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.*; /** * @author Kevin Le Saulnier ) invocation -> Long.parseLong("32")); @@ -388,6 +391,18 @@ private void initMockBeans(Network network) { doNothing().when(networkStoreService).deleteNetwork(NETWORK_UUID); + doAnswer(inv -> { + inv.getArgument(inv.getArguments().length - 1, Runnable.class).run(); + return null; + }).when(rebuildPreviouslyBuiltNodeHandler) + .execute(any(), any(), any(), anyString(), any(Runnable.class)); + + doAnswer(inv -> { + inv.getArgument(inv.getArguments().length - 1, Runnable.class).run(); + return null; + }).when(rebuildPreviouslyBuiltNodeHandler) + .execute(any(), any(), anyString(), any(Runnable.class)); + // Synchronize for tests synchronizeStudyServerExecutionService(studyServerExecutionService); } From a0660117e1569a32421ddca5776e07db2533a561 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Tue, 6 Jan 2026 10:37:42 +0100 Subject: [PATCH 09/26] fix: all tests Signed-off-by: LE SAULNIER Kevin --- .../gridsuite/study/server/LoadFlowTest.java | 10 +++++-- .../study/server/NetworkModificationTest.java | 11 +++++--- .../server/SupervisionControllerTest.java | 1 + .../study/server/VoltageInitTest.java | 27 +++++++++++++++++-- .../server/studycontroller/StudyTest.java | 4 +-- 5 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/test/java/org/gridsuite/study/server/LoadFlowTest.java b/src/test/java/org/gridsuite/study/server/LoadFlowTest.java index 5f12d496ea..1131fa9dde 100644 --- a/src/test/java/org/gridsuite/study/server/LoadFlowTest.java +++ b/src/test/java/org/gridsuite/study/server/LoadFlowTest.java @@ -75,7 +75,7 @@ import static org.gridsuite.study.server.utils.TestUtils.USER_DEFAULT_PROFILE_JSON; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -163,7 +163,7 @@ class LoadFlowTest { private LoadFlowService loadFlowService; @Autowired private StudyRepository studyRepository; - @Autowired + @MockitoSpyBean private UserAdminService userAdminService; @Autowired private ReportService reportService; @@ -1042,6 +1042,12 @@ private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UU jsonObject.put("modificationGroupUuid", modificationGroupUuid); mnBodyJson = jsonObject.toString(); + if (nodeType == NetworkModificationNodeType.SECURITY) { + // with new development, when node is of security type, we build it after creation + // to prevent existing tests to fail, we set it to 0 to keep previous behaviour -> we don't build security node after creation + doReturn(Optional.of(0)).when(userAdminService).getUserMaxAllowedBuilds("userId"); + } + mockMvc.perform(post("/v1/studies/{studyUuid}/tree/nodes/{id}", studyUuid, parentNodeUuid).content(mnBodyJson).contentType(MediaType.APPLICATION_JSON).header("userId", "userId")) .andExpect(status().isOk()); var mess = output.receive(TIMEOUT, STUDY_UPDATE_DESTINATION); diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java index fc8f10ec37..d3d8aa4957 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java @@ -215,7 +215,7 @@ class NetworkModificationTest { @Autowired private StateEstimationService stateEstimationService; - @Autowired + @MockitoSpyBean private UserAdminService userAdminService; @MockitoBean @@ -2521,8 +2521,7 @@ void testRemoveLoadFlowComputationReport(final MockWebServer server) throws Exce UUID studyNameUserIdUuid = studyEntity.getId(); UUID firstRootNetworkUuid = studyTestUtils.getOneRootNetworkUuid(studyNameUserIdUuid); UUID rootNodeUuid = getRootNode(studyNameUserIdUuid).getId(); - NetworkModificationNode modificationNode1 = createNetworkModificationNode(studyNameUserIdUuid, rootNodeUuid, UUID.randomUUID(), VARIANT_ID, "node 1", NetworkModificationNodeType.CONSTRUCTION - , BuildStatus.BUILT, userId); + NetworkModificationNode modificationNode1 = createNetworkModificationNode(studyNameUserIdUuid, rootNodeUuid, UUID.randomUUID(), VARIANT_ID, "node 1", NetworkModificationNodeType.SECURITY, BuildStatus.BUILT, userId); UUID modificationNode1Uuid = modificationNode1.getId(); // In this node, let's say we have all computations results RootNetworkNodeInfoEntity rootNetworkNodeInfoEntity = rootNetworkNodeInfoRepository.findByNodeInfoIdAndRootNetworkId(modificationNode1Uuid, studyTestUtils.getOneRootNetworkUuid(studyNameUserIdUuid)).orElseThrow(() -> new StudyException(NOT_FOUND, "Root network not found")); @@ -2979,6 +2978,12 @@ private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UU jsonObject.put("modificationGroupUuid", modificationGroupUuid); mnBodyJson = jsonObject.toString(); + if (nodeType == NetworkModificationNodeType.SECURITY) { + // with new development, when node is of security type, we build it after creation + // to prevent existing tests to fail, we set it to 0 to keep previous behaviour -> we don't build security node after creation + doReturn(Optional.of(0)).when(userAdminService).getUserMaxAllowedBuilds("userId"); + } + mockMvc.perform(post("/v1/studies/{studyUuid}/tree/nodes/{id}", studyUuid, parentNodeUuid).header(USER_ID_HEADER, userId).content(mnBodyJson).contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()); checkElementUpdatedMessageSent(studyUuid, userId); diff --git a/src/test/java/org/gridsuite/study/server/SupervisionControllerTest.java b/src/test/java/org/gridsuite/study/server/SupervisionControllerTest.java index 911eda8cd9..70c885e9aa 100644 --- a/src/test/java/org/gridsuite/study/server/SupervisionControllerTest.java +++ b/src/test/java/org/gridsuite/study/server/SupervisionControllerTest.java @@ -131,6 +131,7 @@ void setup() { network.getIdentifiables().forEach(idable -> equipmentInfosService.addEquipmentInfos(toEquipmentInfos(idable))); when(networkConversionService.checkStudyIndexationStatus(NETWORK_UUID)).thenReturn(true); + studyRepository.deleteAll(); } @AfterEach diff --git a/src/test/java/org/gridsuite/study/server/VoltageInitTest.java b/src/test/java/org/gridsuite/study/server/VoltageInitTest.java index 0cbbc52144..9724376c68 100644 --- a/src/test/java/org/gridsuite/study/server/VoltageInitTest.java +++ b/src/test/java/org/gridsuite/study/server/VoltageInitTest.java @@ -33,6 +33,7 @@ import org.gridsuite.study.server.dto.modification.NetworkModificationResult; import org.gridsuite.study.server.dto.modification.NetworkModificationsResult; import org.gridsuite.study.server.dto.voltageinit.parameters.*; +import org.gridsuite.study.server.handler.RebuildPreviouslyBuiltNodeHandler; import org.gridsuite.study.server.networkmodificationtree.dto.*; import org.gridsuite.study.server.networkmodificationtree.entities.*; import org.gridsuite.study.server.notification.NotificationService; @@ -240,7 +241,7 @@ class VoltageInitTest { @Autowired private VoltageInitService voltageInitService; - @Autowired + @MockitoSpyBean private UserAdminService userAdminService; @MockitoBean @@ -273,6 +274,9 @@ class VoltageInitTest { @Autowired private VoltageInitResultConsumer voltageInitResultConsumer; + @MockitoBean + private RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler; + //output destinations private final String studyUpdateDestination = "study.update"; private final String voltageInitDebugDestination = "voltageinit.debug"; @@ -414,6 +418,19 @@ public MockResponse dispatch(RecordedRequest request) { private void initMockBeans(Network network) { when(networkStoreService.getNetwork(NETWORK_UUID)).thenReturn(network); when(networkStoreService.getNetwork(SECOND_NETWORK_UUID)).thenReturn(network); + + doAnswer(inv -> { + inv.getArgument(inv.getArguments().length - 1, Runnable.class).run(); + return null; + }).when(rebuildPreviouslyBuiltNodeHandler) + .execute(any(), any(), any(), anyString(), any(Runnable.class)); + + doAnswer(inv -> { + inv.getArgument(inv.getArguments().length - 1, Runnable.class).run(); + return null; + }).when(rebuildPreviouslyBuiltNodeHandler) + .execute(any(), any(), anyString(), any(Runnable.class)); + } private void createOrUpdateParametersAndDoChecks(UUID studyNameUserIdUuid, StudyVoltageInitParameters parameters, String userId, HttpStatusCode status) throws Exception { @@ -1156,7 +1173,7 @@ private RootNode getRootNode(UUID study) throws Exception { private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UUID parentNodeUuid, UUID modificationGroupUuid, String variantId, String nodeName) throws Exception { return createNetworkModificationNode(studyUuid, parentNodeUuid, - modificationGroupUuid, variantId, nodeName, NetworkModificationNodeType.SECURITY, BuildStatus.NOT_BUILT); + modificationGroupUuid, variantId, nodeName, NetworkModificationNodeType.CONSTRUCTION, BuildStatus.NOT_BUILT); } private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UUID parentNodeUuid, @@ -1174,6 +1191,12 @@ private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UU jsonObject.put("modificationGroupUuid", modificationGroupUuid); mnBodyJson = jsonObject.toString(); + if (nodeType == NetworkModificationNodeType.SECURITY) { + // with new development, when node is of security type, we build it after creation + // to prevent existing tests to fail, we set it to 0 to keep previous behaviour -> we don't build security node after creation + doReturn(Optional.of(0)).when(userAdminService).getUserMaxAllowedBuilds("userId"); + } + mockMvc.perform(post("/v1/studies/{studyUuid}/tree/nodes/{id}", studyUuid, parentNodeUuid).content(mnBodyJson).contentType(MediaType.APPLICATION_JSON).header("userId", "userId")) .andExpect(status().isOk()); var mess = output.receive(TIMEOUT, studyUpdateDestination); diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java b/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java index 021300e51c..84111285fc 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java @@ -1190,12 +1190,12 @@ private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UU private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UUID parentNodeUuid, UUID modificationGroupUuid, String variantId, String nodeName, String userId) throws Exception { return createNetworkModificationNode(studyUuid, parentNodeUuid, - modificationGroupUuid, variantId, nodeName, NetworkModificationNodeType.SECURITY, BuildStatus.NOT_BUILT, userId); + modificationGroupUuid, variantId, nodeName, NetworkModificationNodeType.CONSTRUCTION, BuildStatus.NOT_BUILT, userId); } private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UUID parentNodeUuid, UUID modificationGroupUuid, String variantId, String nodeName, BuildStatus buildStatus, String userId) throws Exception { - return createNetworkModificationNode(studyUuid, parentNodeUuid, modificationGroupUuid, variantId, nodeName, NetworkModificationNodeType.SECURITY, buildStatus, userId); + return createNetworkModificationNode(studyUuid, parentNodeUuid, modificationGroupUuid, variantId, nodeName, NetworkModificationNodeType.CONSTRUCTION, buildStatus, userId); } private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UUID parentNodeUuid, From 5f1b820b096abb8bb15055502e1a8265d9d4eb43 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Tue, 6 Jan 2026 10:37:55 +0100 Subject: [PATCH 10/26] test: cover controller Signed-off-by: LE SAULNIER Kevin --- .../StudyControllerRebuildNodeTest.java | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeTest.java diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeTest.java b/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeTest.java new file mode 100644 index 0000000000..d93316a714 --- /dev/null +++ b/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeTest.java @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2025, 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.studycontroller; + + +import mockwebserver3.junit5.internal.MockWebServerExtension; +import org.gridsuite.study.server.ContextConfigurationWithTestChannel; +import org.gridsuite.study.server.RebuildNodeService; +import org.gridsuite.study.server.StudyConstants; +import org.gridsuite.study.server.controller.StudyController; +import org.gridsuite.study.server.dto.modification.NetworkModificationMetadata; +import org.gridsuite.study.server.handler.RebuildPreviouslyBuiltNodeHandler; +import org.gridsuite.study.server.service.StudyService; +import org.gridsuite.study.server.utils.elasticsearch.DisableElasticsearch; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +/** + * @author Kevin Le Saulnier + */ +@ExtendWith(MockWebServerExtension.class) +@SpringBootTest +@DisableElasticsearch +class StudyControllerRebuildNodeHandlerTest { + + @MockitoBean + private RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler; + @MockitoBean + private StudyService studyService; + + @Autowired + private StudyController studyController; + + private UUID nodeUuid = UUID.randomUUID(); + private UUID studyUuid = UUID.randomUUID(); + private String userId = "userId"; + + @Test + void testCreateNetworkModification() { + studyController.createNetworkModification(studyUuid, nodeUuid, "modificationBody", userId); + + verify(rebuildPreviouslyBuiltNodeHandler, times(1)).execute(eq(studyUuid), eq(nodeUuid), eq(userId), any(Runnable.class)); + } + + @Test + void testMoveNetworkModification() { + UUID modificationUuid = UUID.randomUUID(); + studyController.moveModification(studyUuid, nodeUuid, modificationUuid, null, userId); + + verify(rebuildPreviouslyBuiltNodeHandler, times(1)).execute(eq(studyUuid), eq(nodeUuid), eq(userId), any(Runnable.class)); + } + + @Test + void testMoveNetworkModifications() { + List modificationUuids = List.of(UUID.randomUUID()); + UUID originNodeUuid = UUID.randomUUID(); + studyController.moveOrCopyModifications(studyUuid, nodeUuid, StudyConstants.ModificationsActionType.MOVE, studyUuid, originNodeUuid, modificationUuids, userId); + + verify(rebuildPreviouslyBuiltNodeHandler, times(1)).execute(eq(studyUuid), eq(nodeUuid), eq(originNodeUuid), eq(userId), any(Runnable.class)); + } + + @Test + void updateNetworkModification() { + UUID modificationUuid = UUID.randomUUID(); + studyController.updateNetworkModification(studyUuid, nodeUuid, modificationUuid, "modificationAttributes", userId); + + verify(rebuildPreviouslyBuiltNodeHandler, times(1)).execute(eq(studyUuid), eq(nodeUuid), eq(userId), any(Runnable.class)); + } + + @Test + void stashNetworkModification() { + List modificationUuids = List.of(UUID.randomUUID()); + studyController.stashNetworkModifications(studyUuid, nodeUuid, modificationUuids, true, userId); + + verify(rebuildPreviouslyBuiltNodeHandler, times(1)).execute(eq(studyUuid), eq(nodeUuid), eq(userId), any(Runnable.class)); + } + + @Test + void restoreNetworkModification() { + List modificationUuids = List.of(UUID.randomUUID()); + studyController.stashNetworkModifications(studyUuid, nodeUuid, modificationUuids, false, userId); + + verify(rebuildPreviouslyBuiltNodeHandler, times(1)).execute(eq(studyUuid), eq(nodeUuid), eq(userId), any(Runnable.class)); + } + + // when a modification is enabled/disabled, this method is called + @Test + void updateNetworkModificationMetadata() { + List modificationUuids = List.of(UUID.randomUUID()); + studyController.updateNetworkModificationsMetadata(studyUuid, nodeUuid, modificationUuids, new NetworkModificationMetadata(true, "description", "type"), userId); + + verify(rebuildPreviouslyBuiltNodeHandler, times(1)).execute(eq(studyUuid), eq(nodeUuid), eq(userId), any(Runnable.class)); + } + + @Test + void testUpdateNetworkModificationActivationByRootNetwork() { + Set modificationUuids = Set.of(UUID.randomUUID()); + UUID rootNetworkUuid = UUID.randomUUID(); + studyController.updateNetworkModificationsActivation(studyUuid, rootNetworkUuid, nodeUuid, modificationUuids, true, userId); + + verify(rebuildPreviouslyBuiltNodeHandler, times(1)).execute(eq(studyUuid), eq(nodeUuid), eq(userId), any(Runnable.class)); + } +} From ed453671ad04f5fc4c9c825278dc3b5a4790c8bc Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Tue, 6 Jan 2026 11:22:46 +0100 Subject: [PATCH 11/26] test: cover and checkstyle Signed-off-by: LE SAULNIER Kevin --- .../RebuildPreviouslyBuiltNodeHandler.java | 2 +- .../study/server/service/StudyService.java | 1 - .../study/server/NetworkModificationTest.java | 2 - ...RebuildPreviouslyBuiltNodeHandlerTest.java | 159 ++++++++++++++++++ ...tudyControllerRebuildNodeHandlerTest.java} | 16 +- 5 files changed, 165 insertions(+), 15 deletions(-) create mode 100644 src/test/java/org/gridsuite/study/server/RebuildPreviouslyBuiltNodeHandlerTest.java rename src/test/java/org/gridsuite/study/server/studycontroller/{StudyControllerRebuildNodeTest.java => StudyControllerRebuildNodeHandlerTest.java} (89%) diff --git a/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java b/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java index 9c49d1c014..ac393d2910 100644 --- a/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java +++ b/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java @@ -56,7 +56,7 @@ private T execute( try { rootNetworkUuidsWithBuiltNodeAfterMap.forEach((nodeUuid, rootNetworkUuidsWithBuiltNodeAfter) -> { - Set rootNetworkUuidsWithBuiltNodeBefore = rootNetworkUuidsWithBuiltNodeBeforeMap.get(nodeUuid); + Set rootNetworkUuidsWithBuiltNodeBefore = rootNetworkUuidsWithBuiltNodeBeforeMap.getOrDefault(nodeUuid, Set.of()); rootNetworkUuidsWithBuiltNodeBefore.stream() .filter(uuid -> !rootNetworkUuidsWithBuiltNodeAfter.contains(uuid)) diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index 7e49a9434d..2691020278 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -63,7 +63,6 @@ import org.springframework.data.util.Pair; import org.springframework.lang.Nullable; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.util.UriUtils; diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java index d3d8aa4957..f93b3b9fa2 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java @@ -80,7 +80,6 @@ import org.springframework.test.web.servlet.MvcResult; import java.util.*; -import java.util.concurrent.Callable; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.gridsuite.study.server.error.StudyBusinessErrorCode.*; @@ -286,7 +285,6 @@ void setup(final MockWebServer server) throws Exception { }).when(rebuildPreviouslyBuiltNodeHandler) .execute(any(), any(), anyString(), any(Runnable.class)); - synchronizeStudyServerExecutionService(studyServerExecutionService); wireMockServer = new WireMockServer(wireMockConfig().dynamicPort().extensions(new SendInput(input))); diff --git a/src/test/java/org/gridsuite/study/server/RebuildPreviouslyBuiltNodeHandlerTest.java b/src/test/java/org/gridsuite/study/server/RebuildPreviouslyBuiltNodeHandlerTest.java new file mode 100644 index 0000000000..e04c38dc76 --- /dev/null +++ b/src/test/java/org/gridsuite/study/server/RebuildPreviouslyBuiltNodeHandlerTest.java @@ -0,0 +1,159 @@ +package org.gridsuite.study.server; + +import org.gridsuite.study.server.handler.RebuildPreviouslyBuiltNodeHandler; +import org.gridsuite.study.server.networkmodificationtree.dto.BuildStatus; +import org.gridsuite.study.server.networkmodificationtree.dto.NodeBuildStatus; +import org.gridsuite.study.server.service.NetworkModificationTreeService; +import org.gridsuite.study.server.service.StudyService; +import org.gridsuite.study.server.utils.elasticsearch.DisableElasticsearch; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.*; + +@SpringBootTest +@DisableElasticsearch +class RebuildPreviouslyBuiltNodeHandlerTest { + @Autowired + private RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler; + + @MockitoBean + private NetworkModificationTreeService networkModificationTreeService; + @MockitoBean + private StudyService studyService; + + UUID studyUuid = UUID.randomUUID(); + UUID node1Uuid = UUID.randomUUID(); + UUID node2Uuid = UUID.randomUUID(); + String userId = "userId"; + + UUID rootNetworkUuid = UUID.randomUUID(); + UUID rootNetwork2Uuid = UUID.randomUUID(); + + @BeforeEach + void setUp() { + doReturn(List.of(node1Uuid, node2Uuid)).when(networkModificationTreeService).getHighestNodeUuids(node1Uuid, node2Uuid); + doReturn(List.of(node1Uuid)).when(networkModificationTreeService).getHighestNodeUuids(node1Uuid, node1Uuid); + } + + @Test + void testRebuildSingleNode() { + Runnable runnable = Mockito.spy(Runnable.class); + doReturn( + Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT)), + Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT)) + ).when(studyService).getNodeBuildStatusByRootNetworkUuid(studyUuid, node1Uuid); + + rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, node1Uuid, userId, runnable); + + InOrder inOrder = Mockito.inOrder(runnable, studyService); + inOrder.verify(runnable, times(1)).run(); + inOrder.verify(studyService, times(1)).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); + } + + @Test + void testRebuildMultipleNodes() { + Runnable runnable = Mockito.spy(Runnable.class); + doReturn( + Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT)), + Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT)) + ).when(studyService).getNodeBuildStatusByRootNetworkUuid(studyUuid, node1Uuid); + doReturn( + Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT)), + Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT)) + ).when(studyService).getNodeBuildStatusByRootNetworkUuid(studyUuid, node2Uuid); + + rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, node1Uuid, node2Uuid, userId, runnable); + + InOrder inOrder = Mockito.inOrder(runnable, studyService); + inOrder.verify(runnable, times(1)).run(); + inOrder.verify(studyService, times(1)).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); + inOrder.verify(studyService, times(1)).buildNode(studyUuid, node2Uuid, rootNetworkUuid, userId); + } + + @Test + void testRebuildMultipleRootNetworks() { + Runnable runnable = Mockito.spy(Runnable.class); + doReturn( + Map.of( + rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT), + rootNetwork2Uuid, NodeBuildStatus.from(BuildStatus.BUILT) + ), + Map.of( + rootNetworkUuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT), + rootNetwork2Uuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT) + ) + ).when(studyService).getNodeBuildStatusByRootNetworkUuid(studyUuid, node1Uuid); + + rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, node1Uuid, userId, runnable); + + InOrder inOrder = Mockito.inOrder(runnable, studyService); + inOrder.verify(runnable, times(1)).run(); + inOrder.verify(studyService, times(1)).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); + // this does not need to be checked in order, what matters is that "runnable" is called BEFORE nodes are rebuilt + Mockito.verify(studyService, times(1)).buildNode(studyUuid, node1Uuid, rootNetwork2Uuid, userId); + } + + @Test + void testRebuildMultipleRootNetworksAndNodes() { + Runnable runnable = Mockito.spy(Runnable.class); + doReturn( + Map.of( + rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT), + rootNetwork2Uuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT) + ), + Map.of( + rootNetworkUuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT), + rootNetwork2Uuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT) + ) + ).when(studyService).getNodeBuildStatusByRootNetworkUuid(studyUuid, node1Uuid); + + doReturn( + Map.of( + rootNetworkUuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT), + rootNetwork2Uuid, NodeBuildStatus.from(BuildStatus.BUILT) + ), + Map.of( + rootNetworkUuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT), + rootNetwork2Uuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT) + ) + ).when(studyService).getNodeBuildStatusByRootNetworkUuid(studyUuid, node2Uuid); + + rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, node1Uuid, node2Uuid, userId, runnable); + + InOrder inOrder = Mockito.inOrder(runnable, studyService); + inOrder.verify(runnable, times(1)).run(); + inOrder.verify(studyService, times(1)).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); + // this does not need to be checked in order, what matters is that "runnable" is called BEFORE nodes are rebuilt + Mockito.verify(studyService, times(1)).buildNode(studyUuid, node2Uuid, rootNetwork2Uuid, userId); + } + + @Test + void testRebuildNodeException() { + // operation should not be canceled and no exception should be thrown if node rebuild fails + Runnable runnable = Mockito.spy(Runnable.class); + doReturn( + Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT)), + Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT)) + ).when(studyService).getNodeBuildStatusByRootNetworkUuid(studyUuid, node1Uuid); + + doThrow(new RuntimeException("Something wrong happened during node building")) + .when(studyService).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); + + assertDoesNotThrow(() -> rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, node1Uuid, userId, runnable)); + + InOrder inOrder = Mockito.inOrder(runnable, studyService); + inOrder.verify(runnable, times(1)).run(); + inOrder.verify(studyService, times(1)).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); + } +} diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeTest.java b/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeHandlerTest.java similarity index 89% rename from src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeTest.java rename to src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeHandlerTest.java index d93316a714..60bf404449 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeTest.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeHandlerTest.java @@ -6,10 +6,7 @@ */ package org.gridsuite.study.server.studycontroller; - import mockwebserver3.junit5.internal.MockWebServerExtension; -import org.gridsuite.study.server.ContextConfigurationWithTestChannel; -import org.gridsuite.study.server.RebuildNodeService; import org.gridsuite.study.server.StudyConstants; import org.gridsuite.study.server.controller.StudyController; import org.gridsuite.study.server.dto.modification.NetworkModificationMetadata; @@ -19,10 +16,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.bean.override.mockito.MockitoBean; -import org.springframework.test.web.servlet.MockMvc; import java.util.List; import java.util.Set; @@ -32,9 +27,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - /** * @author Kevin Le Saulnier @@ -46,15 +38,17 @@ class StudyControllerRebuildNodeHandlerTest { @MockitoBean private RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler; + // this test is only making sure all those endpoint are actually calling rebuildPreviouslyBuiltNodeHandler + // we mock studyService since we don't cover all the assertions @MockitoBean private StudyService studyService; @Autowired private StudyController studyController; - private UUID nodeUuid = UUID.randomUUID(); - private UUID studyUuid = UUID.randomUUID(); - private String userId = "userId"; + private final UUID nodeUuid = UUID.randomUUID(); + private final UUID studyUuid = UUID.randomUUID(); + private final String userId = "userId"; @Test void testCreateNetworkModification() { From 5f33fb1b77117930efc9e5e303a3e8d6117cd290 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Tue, 6 Jan 2026 15:09:24 +0100 Subject: [PATCH 12/26] test: improve coverage Signed-off-by: LE SAULNIER Kevin --- .../NetworkModificationTreeService.java | 33 ++-- .../study/server/service/StudyService.java | 6 +- .../gridsuite/study/server/LoadFlowTest.java | 24 ++- ...etworkModificationTreeServiceUnitTest.java | 82 ++++++++++ ...RebuildPreviouslyBuiltNodeHandlerTest.java | 20 ++- .../study/server/StudyServiceTest.java | 154 +++++++++++++++++- 6 files changed, 291 insertions(+), 28 deletions(-) create mode 100644 src/test/java/org/gridsuite/study/server/NetworkModificationTreeServiceUnitTest.java diff --git a/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java b/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java index 5c99804f8a..394fcc5aa8 100644 --- a/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java +++ b/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java @@ -1273,23 +1273,32 @@ public List getHighestNodeUuids(UUID node1Uuid, UUID node2Uuid) { return List.of(node1Uuid); } - Set ancestorsOfA = new HashSet<>(); + // 1️⃣ Ancêtres de node1 + Set ancestorsOfNode1 = new HashSet<>(); + NodeEntity current = getNodeEntity(node1Uuid).getParentNode(); - NodeEntity currentNode = getNodeEntity(node1Uuid).getParentNode(); - while (currentNode != null) { - ancestorsOfA.add(currentNode.getIdNode()); - currentNode = currentNode.getParentNode(); + while (current != null) { + ancestorsOfNode1.add(current.getIdNode()); + current = current.getParentNode(); } - currentNode = getNodeEntity(node2Uuid).getParentNode(); - while (currentNode != null) { - if (currentNode.getIdNode().equals(node1Uuid)) { + // 2️⃣ Remontée depuis node2 + current = getNodeEntity(node2Uuid).getParentNode(); + while (current != null) { + UUID currentId = current.getIdNode(); + + // node1 est ancêtre de node2 + if (currentId.equals(node1Uuid)) { return List.of(node1Uuid); } - if (ancestorsOfA.contains(currentNode.getIdNode())) { - return List.of(node2Uuid); - } - currentNode = currentNode.getParentNode(); + + // ancêtre commun ≠ relation hiérarchique directe → on ignore + current = current.getParentNode(); + } + + // 3️⃣ Vérifier l’inverse SANS rebalayer tout + if (ancestorsOfNode1.contains(node2Uuid)) { + return List.of(node2Uuid); } return List.of(node1Uuid, node2Uuid); diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index 2691020278..e7cc29b2bb 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -1911,16 +1911,16 @@ private void buildNode(@NonNull UUID studyUuid, @NonNull UUID nodeUuid, @NonNull @Transactional public void buildFirstLevelChildren(@NonNull UUID studyUuid, @NonNull UUID parentNodeUuid, @NonNull UUID rootNetworkUuid, @NonNull String userId) { - AbstractNode studySubTree = networkModificationTreeService.getStudySubtree(studyUuid, parentNodeUuid, rootNetworkUuid); + List firstLevelChildren = networkModificationTreeService.getChildren(parentNodeUuid); long builtNodesUpToQuota = getBuiltNodesUpToQuota(studyUuid, rootNetworkUuid, userId); - for (AbstractNode child : studySubTree.getChildren()) { + for (NodeEntity child : firstLevelChildren) { if (builtNodesUpToQuota <= 0) { return; } buildNode( studyUuid, - child.getId(), + child.getIdNode(), rootNetworkUuid, userId, null diff --git a/src/test/java/org/gridsuite/study/server/LoadFlowTest.java b/src/test/java/org/gridsuite/study/server/LoadFlowTest.java index 1131fa9dde..294da78f56 100644 --- a/src/test/java/org/gridsuite/study/server/LoadFlowTest.java +++ b/src/test/java/org/gridsuite/study/server/LoadFlowTest.java @@ -352,19 +352,31 @@ private void assertNodeBlocked(UUID nodeUuid, UUID rootNetworkUuid, boolean isNo assertEquals(isNodeBlocked, networkNodeInfoEntity.get().getBlockedNode()); } - private void consumeLoadFlowResult(UUID studyUuid, UUID rootNetworkUuid, NetworkModificationNode modificationNode) throws JsonProcessingException { + private void consumeLoadFlowResult(MockWebServer server, UUID studyUuid, UUID rootNetworkUuid, NetworkModificationNode modificationNode) throws JsonProcessingException { UUID nodeUuid = modificationNode.getId(); assertNodeBlocked(nodeUuid, rootNetworkUuid, true); + doNothing().when(studyService).buildFirstLevelChildren(studyUuid, nodeUuid, rootNetworkUuid, "userId"); + // consume loadflow result String resultUuidJson = objectMapper.writeValueAsString(new NodeReceiver(nodeUuid, rootNetworkUuid)); - MessageHeaders messageHeaders = new MessageHeaders(Map.of("resultUuid", LOADFLOW_RESULT_UUID, "withRatioTapChangers", false, HEADER_RECEIVER, resultUuidJson)); + MessageHeaders messageHeaders = new MessageHeaders( + Map.of( + "resultUuid", LOADFLOW_RESULT_UUID, + "withRatioTapChangers", false, + HEADER_RECEIVER, resultUuidJson, + USER_ID_HEADER, "userId")); consumerService.consumeLoadFlowResult().accept(MessageBuilder.createMessage("", messageHeaders)); checkUpdateModelStatusMessagesReceived(studyUuid, NotificationService.UPDATE_TYPE_LOADFLOW_STATUS); checkUpdateModelStatusMessagesReceived(studyUuid, NotificationService.UPDATE_TYPE_LOADFLOW_RESULT); assertNodeBlocked(nodeUuid, rootNetworkUuid, false); + if (modificationNode.isSecurityNode()) { + // if running successful loadflow on security node -> first children are built + assertTrue(TestUtils.getRequestsDone(1, server).stream().anyMatch(r -> r.matches("/v1/results/" + LOADFLOW_RESULT_UUID + "/status"))); + verify(studyService, times(1)).buildFirstLevelChildren(studyUuid, nodeUuid, rootNetworkUuid, "userId"); + } } @Test @@ -425,7 +437,7 @@ private void testLoadFlow(final MockWebServer server, UUID studyNameUserIdUuid, } assertRequestsDone(server, expectedRequestsPatterns); - consumeLoadFlowResult(studyNameUserIdUuid, firstRootNetworkUuid, modificationNode); + consumeLoadFlowResult(server, studyNameUserIdUuid, firstRootNetworkUuid, modificationNode); // get loadflow result MvcResult mvcResult = mockMvc.perform(get("/v1/studies/{studyUuid}/root-networks/{rootNetworkUuid}/nodes/{nodeUuid}/loadflow/result", studyNameUserIdUuid, firstRootNetworkUuid, modificationNodeUuid)).andExpectAll( @@ -496,7 +508,7 @@ void testGetLimitViolations(final MockWebServer server) throws Exception { checkUpdateModelStatusMessagesReceived(studyNameUserIdUuid, NotificationService.UPDATE_TYPE_LOADFLOW_STATUS); assertRequestsDone(server, List.of("/v1/networks/" + NETWORK_UUID_STRING + "/run-and-save\\?withRatioTapChangers=.*&receiver=.*&reportUuid=.*&reporterId=.*&variantId=" + VARIANT_ID_2)); - consumeLoadFlowResult(studyNameUserIdUuid, firstRootNetworkUuid, modificationNode1); + consumeLoadFlowResult(server, studyNameUserIdUuid, firstRootNetworkUuid, modificationNode1); // get computing status mockMvc.perform(get("/v1/studies/{studyUuid}/root-networks/{rootNetworkUuid}/nodes/{nodeUuid}/computation/result/enum-values?computingType={computingType}&enumName={enumName}", @@ -552,7 +564,7 @@ void testInvalidateStatus(final MockWebServer server) throws Exception { checkUpdateModelStatusMessagesReceived(studyNameUserIdUuid, NotificationService.UPDATE_TYPE_LOADFLOW_STATUS); assertRequestsDone(server, List.of("/v1/networks/" + NETWORK_UUID_STRING + "/run-and-save\\?withRatioTapChangers=.*&receiver=.*&reportUuid=.*&reporterId=.*&variantId=" + VARIANT_ID_2)); - consumeLoadFlowResult(studyNameUserIdUuid, firstRootNetworkUuid, modificationNode1); + consumeLoadFlowResult(server, studyNameUserIdUuid, firstRootNetworkUuid, modificationNode1); } @Test @@ -589,7 +601,7 @@ void testDeleteLoadFlowResults(final MockWebServer server) throws Exception { checkUpdateModelStatusMessagesReceived(studyNameUserIdUuid, NotificationService.UPDATE_TYPE_LOADFLOW_STATUS); assertRequestsDone(server, List.of("/v1/networks/" + NETWORK_UUID_STRING + "/run-and-save\\?withRatioTapChangers=.*&receiver=.*&reportUuid=.*&reporterId=.*&variantId=" + VARIANT_ID_2)); - consumeLoadFlowResult(studyNameUserIdUuid, firstRootNetworkUuid, modificationNode3); + consumeLoadFlowResult(server, studyNameUserIdUuid, firstRootNetworkUuid, modificationNode3); //Test result count testResultCount(server); diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTreeServiceUnitTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTreeServiceUnitTest.java new file mode 100644 index 0000000000..ac2b89656c --- /dev/null +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTreeServiceUnitTest.java @@ -0,0 +1,82 @@ +package org.gridsuite.study.server; + +import org.gridsuite.study.server.networkmodificationtree.entities.NodeEntity; +import org.gridsuite.study.server.networkmodificationtree.entities.NodeType; +import org.gridsuite.study.server.repository.StudyEntity; +import org.gridsuite.study.server.repository.networkmodificationtree.NodeRepository; +import org.gridsuite.study.server.service.NetworkModificationTreeService; +import org.gridsuite.study.server.utils.elasticsearch.DisableElasticsearch; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@DisableElasticsearch +class NetworkModificationTreeServiceUnitTest { + + @Autowired + private NodeRepository nodeRepository; + @Autowired + private NetworkModificationTreeService networkModificationTreeService; + + NodeEntity rootNode; + NodeEntity node1; + NodeEntity node2; + NodeEntity node3; + NodeEntity node4; + + @Test + void testGetHighestNodeUuidOfSameSubtree() { + createNodeTree(); + assertThat(networkModificationTreeService.getHighestNodeUuids(node1.getIdNode(), node2.getIdNode())) + .usingRecursiveComparison() + .isEqualTo(List.of(node1.getIdNode())); + + assertThat(networkModificationTreeService.getHighestNodeUuids(node1.getIdNode(), node4.getIdNode())) + .usingRecursiveComparison() + .isEqualTo(List.of(node1.getIdNode())); + } + + @Test + void testGetHighestNodeUuidOfDifferentSubtree() { + createNodeTree(); + assertThat(networkModificationTreeService.getHighestNodeUuids(node2.getIdNode(), node3.getIdNode())) + .usingRecursiveComparison() + .isEqualTo(List.of(node2.getIdNode(), node3.getIdNode())); + + assertThat(networkModificationTreeService.getHighestNodeUuids(node2.getIdNode(), node4.getIdNode())) + .usingRecursiveComparison() + .isEqualTo(List.of(node2.getIdNode(), node4.getIdNode())); + } + + @Test + void testGetHighestNodeUuidOfSameUuids() { + UUID randomUuid = UUID.randomUUID(); + assertThat(networkModificationTreeService.getHighestNodeUuids(randomUuid, randomUuid)) + .usingRecursiveComparison() + .isEqualTo(List.of(randomUuid)); + } + + private void createNodeTree() { + /* + root + | + N1 + ------ + | | + N2 N3 + | + N4 + */ + rootNode = nodeRepository.save(new NodeEntity(null, null, NodeType.ROOT, null, false, null)); + node1 = nodeRepository.save(new NodeEntity(null, rootNode, NodeType.NETWORK_MODIFICATION, null, false, null)); + node2 = nodeRepository.save(new NodeEntity(null, node1, NodeType.NETWORK_MODIFICATION, null, false, null)); + node3 = nodeRepository.save(new NodeEntity(null, node1, NodeType.NETWORK_MODIFICATION, null, false, null)); + node4 = nodeRepository.save(new NodeEntity(null, node3, NodeType.NETWORK_MODIFICATION, null, false, null)); + } +} diff --git a/src/test/java/org/gridsuite/study/server/RebuildPreviouslyBuiltNodeHandlerTest.java b/src/test/java/org/gridsuite/study/server/RebuildPreviouslyBuiltNodeHandlerTest.java index e04c38dc76..960358e0b8 100644 --- a/src/test/java/org/gridsuite/study/server/RebuildPreviouslyBuiltNodeHandlerTest.java +++ b/src/test/java/org/gridsuite/study/server/RebuildPreviouslyBuiltNodeHandlerTest.java @@ -44,6 +44,7 @@ class RebuildPreviouslyBuiltNodeHandlerTest { void setUp() { doReturn(List.of(node1Uuid, node2Uuid)).when(networkModificationTreeService).getHighestNodeUuids(node1Uuid, node2Uuid); doReturn(List.of(node1Uuid)).when(networkModificationTreeService).getHighestNodeUuids(node1Uuid, node1Uuid); + doReturn(false).when(networkModificationTreeService).isRootOrConstructionNode(any()); } @Test @@ -78,7 +79,8 @@ void testRebuildMultipleNodes() { InOrder inOrder = Mockito.inOrder(runnable, studyService); inOrder.verify(runnable, times(1)).run(); inOrder.verify(studyService, times(1)).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); - inOrder.verify(studyService, times(1)).buildNode(studyUuid, node2Uuid, rootNetworkUuid, userId); + // this does not need to be checked in order, what matters is that "runnable" is called BEFORE nodes are rebuilt + Mockito.verify(studyService, times(1)).buildNode(studyUuid, node2Uuid, rootNetworkUuid, userId); } @Test @@ -156,4 +158,20 @@ void testRebuildNodeException() { inOrder.verify(runnable, times(1)).run(); inOrder.verify(studyService, times(1)).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); } + + @Test + void testRebuildConstructionNode() { + Runnable runnable = Mockito.spy(Runnable.class); + doReturn( + Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT)), + Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT)) + ).when(studyService).getNodeBuildStatusByRootNetworkUuid(studyUuid, node1Uuid); + + doReturn(true).when(networkModificationTreeService).isRootOrConstructionNode(any()); + + rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, node1Uuid, userId, runnable); + + Mockito.verify(runnable, times(1)).run(); + Mockito.verify(studyService, times(0)).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); + } } diff --git a/src/test/java/org/gridsuite/study/server/StudyServiceTest.java b/src/test/java/org/gridsuite/study/server/StudyServiceTest.java index 0ef9618671..92dcdd92c4 100644 --- a/src/test/java/org/gridsuite/study/server/StudyServiceTest.java +++ b/src/test/java/org/gridsuite/study/server/StudyServiceTest.java @@ -13,8 +13,14 @@ import com.powsybl.commons.exceptions.UncheckedInterruptedException; import com.powsybl.network.store.client.NetworkStoreService; import org.gridsuite.study.server.dto.BasicStudyInfos; +import org.gridsuite.study.server.dto.BuildInfos; +import org.gridsuite.study.server.networkmodificationtree.dto.BuildStatus; +import org.gridsuite.study.server.networkmodificationtree.dto.NodeBuildStatus; +import org.gridsuite.study.server.networkmodificationtree.entities.NodeEntity; +import org.gridsuite.study.server.networkmodificationtree.entities.NodeType; import org.gridsuite.study.server.notification.NotificationService; import org.gridsuite.study.server.repository.StudyRepository; +import org.gridsuite.study.server.repository.networkmodificationtree.NodeRepository; import org.gridsuite.study.server.service.*; import org.gridsuite.study.server.service.shortcircuit.ShortCircuitService; import org.gridsuite.study.server.utils.SendInput; @@ -36,22 +42,20 @@ import org.springframework.messaging.MessageHeaders; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; import java.util.concurrent.CountDownLatch; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.gridsuite.study.server.StudyConstants.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -104,7 +108,7 @@ class StudyServiceTest { @Autowired private StudyRepository studyRepository; - @Autowired + @MockitoSpyBean private NetworkModificationTreeService networkModificationTreeService; @Autowired @@ -121,8 +125,19 @@ class StudyServiceTest { @MockitoBean private StudyConfigService studyConfigService; + + @MockitoBean + private UserAdminService userAdminService; + + @MockitoBean + private NetworkModificationService networkModificationService; + @Autowired private SingleLineDiagramService singleLineDiagramService; + @Autowired + private NodeRepository nodeRepository; + @Autowired + private StudyService studyService; @BeforeEach void setup() { @@ -294,6 +309,133 @@ void testImportCsv() throws Exception { wireMockStubs.verifyStubCreatePositionsFromCsv(positionsFromCsvUuid); } + @Test + void testBuildFirstLevelChildren() { + UUID studyUuid = UUID.randomUUID(); + UUID rootNetworkUuid = UUID.randomUUID(); + String userId = "userId"; + + NodeEntity rootNode = nodeRepository.save(new NodeEntity(null, null, NodeType.ROOT, null, false, null)); + NodeEntity node1 = nodeRepository.save(new NodeEntity(null, rootNode, NodeType.NETWORK_MODIFICATION, null, false, null)); + NodeEntity node2 = nodeRepository.save(new NodeEntity(null, node1, NodeType.NETWORK_MODIFICATION, null, false, null)); + NodeEntity node3 = nodeRepository.save(new NodeEntity(null, node1, NodeType.NETWORK_MODIFICATION, null, false, null)); + NodeEntity node4 = nodeRepository.save(new NodeEntity(null, node3, NodeType.NETWORK_MODIFICATION, null, false, null)); + /* + root + | + N1 + ------ + | | + N2 N3 + | + N4 + */ + + + // quota not reached, all first level children of N1 will be built + doReturn(Optional.of(10)).when(userAdminService).getUserMaxAllowedBuilds(userId); + doReturn(0L).when(networkModificationTreeService).countBuiltNodes(studyUuid, rootNetworkUuid); + mockNodeBuild(node2.getIdNode(), rootNetworkUuid); + mockNodeBuild(node3.getIdNode(), rootNetworkUuid); + + studyService.buildFirstLevelChildren(studyUuid, node1.getIdNode(), rootNetworkUuid, userId); + + verifyNodeBuild(node2.getIdNode(), rootNetworkUuid); + verifyNodeBuild(node3.getIdNode(), rootNetworkUuid); + // check n4 has actually not been built + verify(networkModificationService, times(0)).buildNode(eq(node4.getIdNode()), eq(rootNetworkUuid), any(), eq(null)); + + // 1 to check how many children will be built, then 1 for each built children + verify(userAdminService, times(3)).getUserMaxAllowedBuilds(userId); + verify(networkModificationTreeService, times(3)).countBuiltNodes(studyUuid, rootNetworkUuid); + } + + @Test + void testBuildFirstLevelChildrenWithQuotaAlreadyReached() { + UUID studyUuid = UUID.randomUUID(); + UUID rootNetworkUuid = UUID.randomUUID(); + String userId = "userId"; + + NodeEntity rootNode = nodeRepository.save(new NodeEntity(null, null, NodeType.ROOT, null, false, null)); + NodeEntity node1 = nodeRepository.save(new NodeEntity(null, rootNode, NodeType.NETWORK_MODIFICATION, null, false, null)); + NodeEntity node2 = nodeRepository.save(new NodeEntity(null, node1, NodeType.NETWORK_MODIFICATION, null, false, null)); + NodeEntity node3 = nodeRepository.save(new NodeEntity(null, node1, NodeType.NETWORK_MODIFICATION, null, false, null)); + NodeEntity node4 = nodeRepository.save(new NodeEntity(null, node3, NodeType.NETWORK_MODIFICATION, null, false, null)); + /* + root + | + N1 + ------ + | | + N2 N3 + | + N4 + */ + + // quota already reached, nothing will be built + doReturn(Optional.of(10)).when(userAdminService).getUserMaxAllowedBuilds(userId); + doReturn(10L).when(networkModificationTreeService).countBuiltNodes(studyUuid, rootNetworkUuid); + + studyService.buildFirstLevelChildren(studyUuid, node1.getIdNode(), rootNetworkUuid, userId); + + verify(networkModificationService, times(0)).buildNode(eq(node2.getIdNode()), eq(rootNetworkUuid), any(), eq(null)); + verify(networkModificationService, times(0)).buildNode(eq(node3.getIdNode()), eq(rootNetworkUuid), any(), eq(null)); + verify(networkModificationService, times(0)).buildNode(eq(node4.getIdNode()), eq(rootNetworkUuid), any(), eq(null)); + + verify(userAdminService, times(1)).getUserMaxAllowedBuilds(userId); + verify(networkModificationTreeService, times(1)).countBuiltNodes(studyUuid, rootNetworkUuid); + } + + @Test + void testBuildFirstLevelChildrenWithQuotaReached() { + UUID studyUuid = UUID.randomUUID(); + UUID rootNetworkUuid = UUID.randomUUID(); + String userId = "userId"; + + NodeEntity rootNode = nodeRepository.save(new NodeEntity(null, null, NodeType.ROOT, null, false, null)); + NodeEntity node1 = nodeRepository.save(new NodeEntity(null, rootNode, NodeType.NETWORK_MODIFICATION, null, false, null)); + NodeEntity node2 = nodeRepository.save(new NodeEntity(null, node1, NodeType.NETWORK_MODIFICATION, null, false, null)); + NodeEntity node3 = nodeRepository.save(new NodeEntity(null, node1, NodeType.NETWORK_MODIFICATION, null, false, null)); + NodeEntity node4 = nodeRepository.save(new NodeEntity(null, node3, NodeType.NETWORK_MODIFICATION, null, false, null)); + /* + root + | + N1 + ------ + | | + N2 N3 + | + N4 + */ + + // quota will be reached, only one child will be built + doReturn(Optional.of(10)).when(userAdminService).getUserMaxAllowedBuilds(userId); + doReturn(9L).when(networkModificationTreeService).countBuiltNodes(studyUuid, rootNetworkUuid); + + mockNodeBuild(node2.getIdNode(), rootNetworkUuid); + + studyService.buildFirstLevelChildren(studyUuid, node1.getIdNode(), rootNetworkUuid, userId); + + verifyNodeBuild(node2.getIdNode(), rootNetworkUuid); + verify(networkModificationService, times(0)).buildNode(eq(node3.getIdNode()), eq(rootNetworkUuid), any(), eq(null)); + verify(networkModificationService, times(0)).buildNode(eq(node4.getIdNode()), eq(rootNetworkUuid), any(), eq(null)); + + // 1 to check how many children will be built, then 1 for each built children + verify(userAdminService, times(2)).getUserMaxAllowedBuilds(userId); + verify(networkModificationTreeService, times(2)).countBuiltNodes(studyUuid, rootNetworkUuid); + } + + private void mockNodeBuild(UUID nodeUuid, UUID rootNetworkUuid) { + doReturn(new BuildInfos()).when(networkModificationTreeService).getBuildInfos(nodeUuid, rootNetworkUuid); + doNothing().when(networkModificationTreeService).setModificationReports(eq(nodeUuid), eq(rootNetworkUuid), any()); + doNothing().when(networkModificationTreeService).updateNodeBuildStatus(nodeUuid, rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILDING)); + } + + private void verifyNodeBuild(UUID nodeUuid, UUID rootNetworkUuid) { + verify(networkModificationTreeService, times(1)).updateNodeBuildStatus(nodeUuid, rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILDING)); + verify(networkModificationService, times(1)).buildNode(eq(nodeUuid), eq(rootNetworkUuid), any(), eq(null)); + } + private UUID createStudy(String userId, UUID caseUuid, Map importParameters) throws Exception { // mock API calls UUID caseExistsStubId = wireMockStubs.caseServer.stubCaseExists(caseUuid.toString(), true); From 4b1acae8717af8b1ebe3b1cff8442156df4d4106 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Tue, 6 Jan 2026 15:10:16 +0100 Subject: [PATCH 13/26] fix: checkstyle Signed-off-by: LE SAULNIER Kevin --- .../study/server/NetworkModificationTreeServiceUnitTest.java | 1 - src/test/java/org/gridsuite/study/server/StudyServiceTest.java | 1 - 2 files changed, 2 deletions(-) diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTreeServiceUnitTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTreeServiceUnitTest.java index ac2b89656c..c1cdd4c519 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationTreeServiceUnitTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTreeServiceUnitTest.java @@ -2,7 +2,6 @@ import org.gridsuite.study.server.networkmodificationtree.entities.NodeEntity; import org.gridsuite.study.server.networkmodificationtree.entities.NodeType; -import org.gridsuite.study.server.repository.StudyEntity; import org.gridsuite.study.server.repository.networkmodificationtree.NodeRepository; import org.gridsuite.study.server.service.NetworkModificationTreeService; import org.gridsuite.study.server.utils.elasticsearch.DisableElasticsearch; diff --git a/src/test/java/org/gridsuite/study/server/StudyServiceTest.java b/src/test/java/org/gridsuite/study/server/StudyServiceTest.java index 92dcdd92c4..9f7bbc8511 100644 --- a/src/test/java/org/gridsuite/study/server/StudyServiceTest.java +++ b/src/test/java/org/gridsuite/study/server/StudyServiceTest.java @@ -331,7 +331,6 @@ void testBuildFirstLevelChildren() { N4 */ - // quota not reached, all first level children of N1 will be built doReturn(Optional.of(10)).when(userAdminService).getUserMaxAllowedBuilds(userId); doReturn(0L).when(networkModificationTreeService).countBuiltNodes(studyUuid, rootNetworkUuid); From b46fcf6a9013c9d99705e7e1835cfd8ef15df2f9 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Tue, 6 Jan 2026 15:52:13 +0100 Subject: [PATCH 14/26] fix: sonar issues Signed-off-by: LE SAULNIER Kevin --- .../RebuildPreviouslyBuiltNodeHandler.java | 62 ++++++++----------- .../study/server/NetworkModificationTest.java | 2 +- .../study/server/NodeSequenceTest.java | 5 -- 3 files changed, 26 insertions(+), 43 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java b/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java index ac393d2910..4779f46f12 100644 --- a/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java +++ b/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java @@ -10,8 +10,8 @@ import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.concurrent.Callable; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.stream.Collectors; @Component @@ -32,14 +32,14 @@ private T execute( UUID node1Uuid, UUID node2Uuid, String userId, - Callable operation - ) throws Exception { + Supplier operation + ) { // if node 1 and 2 are in the same "subtree", rebuild only the highest one - otherwise, rebuild both List nodesToReBuild = networkModificationTreeService.getHighestNodeUuids(node1Uuid, node2Uuid).stream() .filter(Predicate.not(networkModificationTreeService::isRootOrConstructionNode)).toList(); if (nodesToReBuild.isEmpty()) { - return operation.call(); + return operation.get(); } Map> rootNetworkUuidsWithBuiltNodeBeforeMap = nodesToReBuild.stream().collect(Collectors.toMap( @@ -47,7 +47,7 @@ private T execute( nodeUuid -> getRootNetworkWhereNotHasToBeRebuilt(studyUuid, nodeUuid) )); - T result = operation.call(); + T result = operation.get(); Map> rootNetworkUuidsWithBuiltNodeAfterMap = nodesToReBuild.stream().collect(Collectors.toMap( nodeUuid -> nodeUuid, @@ -83,22 +83,16 @@ public void execute( String userId, Runnable operation ) { - try { - execute( - studyUuid, - nodeUuid, - nodeUuid, - userId, - () -> { - operation.run(); - return null; - } - ); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new RuntimeException(e); //TODO improve exception handling - } + execute( + studyUuid, + nodeUuid, + nodeUuid, + userId, + () -> { + operation.run(); + return null; + } + ); } public void execute( @@ -108,22 +102,16 @@ public void execute( String userId, Runnable operation ) { - try { - execute( - studyUuid, - node1Uuid, - node2Uuid, - userId, - () -> { - operation.run(); - return null; - } - ); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new RuntimeException(e); //TODO improve exception handling - } + execute( + studyUuid, + node1Uuid, + node2Uuid, + userId, + () -> { + operation.run(); + return null; + } + ); } private Set getRootNetworkWhereNotHasToBeRebuilt(UUID studyUuid, UUID nodeUuid) { diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java index f93b3b9fa2..131d033381 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java @@ -266,7 +266,7 @@ class NetworkModificationTest { RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler; @BeforeEach - void setup(final MockWebServer server) throws Exception { + void setup(final MockWebServer server) { ReadOnlyDataSource dataSource = new ResourceDataSource("testCase", new ResourceSet("", TEST_FILE)); Network network = new XMLImporter().importData(dataSource, new NetworkFactoryImpl(), null); network.getVariantManager().cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, VARIANT_ID); diff --git a/src/test/java/org/gridsuite/study/server/NodeSequenceTest.java b/src/test/java/org/gridsuite/study/server/NodeSequenceTest.java index e166c91dfe..f41ee1e2c7 100644 --- a/src/test/java/org/gridsuite/study/server/NodeSequenceTest.java +++ b/src/test/java/org/gridsuite/study/server/NodeSequenceTest.java @@ -164,11 +164,6 @@ NetworkModificationNode createNode(UUID parentNodeUuid, String name, NetworkModi return networkModificationTreeService.createNode(studyEntity, parentNodeUuid, NetworkModificationNode.builder().name(name).nodeType(networkModificationNodeType).build(), InsertMode.CHILD, userId); } - private void assertNodeHasBeenBuilt(UUID nodeUuid, String userId) { - verify(userAdminService, times(1)).getUserMaxAllowedBuilds(userId); - verify(networkModificationService, times(1)).buildNode(eq(nodeUuid), any(), any(), eq(null)); - } - void checkSecuritySequence(AbstractNode nNode, String nameSuffix) { NetworkModificationNodeInfoEntity nNodeEntity = networkModificationTreeService.getNetworkModificationNodeInfoEntity(nNode.getId()); assertEquals(SecuritySequence.N_NODE_NAME + nameSuffix, nNodeEntity.getName()); From 49427caf60968f3843ffb5f85c553625386ece5f Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Tue, 6 Jan 2026 16:04:06 +0100 Subject: [PATCH 15/26] fix: small fixes Signed-off-by: LE SAULNIER Kevin --- .../study/server/service/ConsumerService.java | 2 +- .../NetworkModificationTreeService.java | 35 +++++++------------ 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/service/ConsumerService.java b/src/main/java/org/gridsuite/study/server/service/ConsumerService.java index e149df04d4..11466a4614 100644 --- a/src/main/java/org/gridsuite/study/server/service/ConsumerService.java +++ b/src/main/java/org/gridsuite/study/server/service/ConsumerService.java @@ -640,7 +640,7 @@ public void consumeCalculationResult(Message msg, ComputationType comput // unblock node handleUnblockNode(receiverObj, computationType); - // build 1st level children if loadflow results + // build 1st level children if loadflow is converged, and node if of security type UUID studyUuid = networkModificationTreeService.getStudyUuidForNodeId(receiverObj.getNodeUuid()); String userId = (String) msg.getHeaders().get(HEADER_USER_ID); if (computationType == LOAD_FLOW) { diff --git a/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java b/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java index 394fcc5aa8..a565d1e8ca 100644 --- a/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java +++ b/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java @@ -1273,34 +1273,25 @@ public List getHighestNodeUuids(UUID node1Uuid, UUID node2Uuid) { return List.of(node1Uuid); } - // 1️⃣ Ancêtres de node1 - Set ancestorsOfNode1 = new HashSet<>(); - NodeEntity current = getNodeEntity(node1Uuid).getParentNode(); + if (isAncestor(node1Uuid, node2Uuid)) { + return List.of(node1Uuid); + } - while (current != null) { - ancestorsOfNode1.add(current.getIdNode()); - current = current.getParentNode(); + if (isAncestor(node2Uuid, node1Uuid)) { + return List.of(node2Uuid); } - // 2️⃣ Remontée depuis node2 - current = getNodeEntity(node2Uuid).getParentNode(); - while (current != null) { - UUID currentId = current.getIdNode(); + return List.of(node1Uuid, node2Uuid); + } - // node1 est ancêtre de node2 - if (currentId.equals(node1Uuid)) { - return List.of(node1Uuid); + private boolean isAncestor(UUID ancestorUuid, UUID nodeUuid) { + NodeEntity current = getNodeEntity(nodeUuid).getParentNode(); + while (current != null) { + if (current.getIdNode().equals(ancestorUuid)) { + return true; } - - // ancêtre commun ≠ relation hiérarchique directe → on ignore current = current.getParentNode(); } - - // 3️⃣ Vérifier l’inverse SANS rebalayer tout - if (ancestorsOfNode1.contains(node2Uuid)) { - return List.of(node2Uuid); - } - - return List.of(node1Uuid, node2Uuid); + return false; } } From 7e116ebb73313473ac9d7373be41e1defd7d1f72 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Mon, 12 Jan 2026 09:41:32 +0100 Subject: [PATCH 16/26] fix: PR remarks Signed-off-by: LE SAULNIER Kevin --- .../service/NetworkModificationTreeService.java | 2 +- .../study/server/service/StudyService.java | 16 +++++----------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java b/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java index a565d1e8ca..da63c13fe0 100644 --- a/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java +++ b/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java @@ -604,7 +604,7 @@ public void assertIsRootOrConstructionNode(UUID nodeUuid) { } public boolean isRootOrConstructionNode(UUID nodeUuid) { - return self.getNode(nodeUuid, null).getType().equals(NodeType.ROOT) || isConstructionNode(nodeUuid); + return getNodeEntity(nodeUuid).getType() == NodeType.ROOT || isConstructionNode(nodeUuid); } private void assertInsertNode( diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index e7cc29b2bb..7c42d71d13 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -1912,9 +1912,9 @@ private void buildNode(@NonNull UUID studyUuid, @NonNull UUID nodeUuid, @NonNull @Transactional public void buildFirstLevelChildren(@NonNull UUID studyUuid, @NonNull UUID parentNodeUuid, @NonNull UUID rootNetworkUuid, @NonNull String userId) { List firstLevelChildren = networkModificationTreeService.getChildren(parentNodeUuid); - long builtNodesUpToQuota = getBuiltNodesUpToQuota(studyUuid, rootNetworkUuid, userId); + long allowedBuildNodesUpToQuota = getAllowedBuildNodesUpToQuota(studyUuid, rootNetworkUuid, userId); for (NodeEntity child : firstLevelChildren) { - if (builtNodesUpToQuota <= 0) { + if (allowedBuildNodesUpToQuota <= 0) { return; } @@ -1925,7 +1925,7 @@ public void buildFirstLevelChildren(@NonNull UUID studyUuid, @NonNull UUID paren userId, null ); - builtNodesUpToQuota--; + allowedBuildNodesUpToQuota--; } } @@ -1938,7 +1938,7 @@ public void handleBuildSuccess(UUID studyUuid, UUID nodeUuid, UUID rootNetworkUu notificationService.emitStudyChanged(studyUuid, nodeUuid, rootNetworkUuid, NotificationService.UPDATE_TYPE_BUILD_COMPLETED, networkModificationResult.getImpactedSubstationsIds()); } - private long getBuiltNodesUpToQuota(@NonNull UUID studyUuid, @NonNull UUID rootNetworkUuid, @NonNull String userId) { + private long getAllowedBuildNodesUpToQuota(@NonNull UUID studyUuid, @NonNull UUID rootNetworkUuid, @NonNull String userId) { return userAdminService.getUserMaxAllowedBuilds(userId).map(maxBuilds -> { long nbBuiltNodes = networkModificationTreeService.countBuiltNodes(studyUuid, rootNetworkUuid); return maxBuilds - nbBuiltNodes; @@ -2448,13 +2448,7 @@ public RootNetworkIndexationStatus getRootNetworkIndexationStatus(UUID studyUuid } @Transactional - public void moveNetworkModifications(@NonNull UUID studyUuid, - UUID targetNodeUuid, - @NonNull UUID originNodeUuid, - List modificationUuidList, - UUID beforeUuid, - boolean isTargetInDifferentNodeTree, - String userId) { + public void moveNetworkModifications(@NonNull UUID studyUuid, UUID targetNodeUuid, @NonNull UUID originNodeUuid, List modificationUuidList, UUID beforeUuid, boolean isTargetInDifferentNodeTree, String userId) { boolean isTargetDifferentNode = !targetNodeUuid.equals(originNodeUuid); List childrenUuids = networkModificationTreeService.getChildrenUuids(targetNodeUuid); From 4b6c5c28f7203fae6f629a7a252325d880303a0f Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Mon, 12 Jan 2026 10:55:26 +0100 Subject: [PATCH 17/26] fix: checkstyle Signed-off-by: LE SAULNIER Kevin --- .../org/gridsuite/study/server/StudyServiceTest.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/test/java/org/gridsuite/study/server/StudyServiceTest.java b/src/test/java/org/gridsuite/study/server/StudyServiceTest.java index d4c73ac833..e2b75e5a5a 100644 --- a/src/test/java/org/gridsuite/study/server/StudyServiceTest.java +++ b/src/test/java/org/gridsuite/study/server/StudyServiceTest.java @@ -8,15 +8,11 @@ import com.github.tomakehurst.wiremock.WireMockServer; import com.powsybl.commons.exceptions.UncheckedInterruptedException; -import com.powsybl.network.store.client.NetworkStoreService; -import org.gridsuite.study.server.dto.BasicStudyInfos; import org.gridsuite.study.server.dto.BuildInfos; import org.gridsuite.study.server.networkmodificationtree.dto.BuildStatus; import org.gridsuite.study.server.networkmodificationtree.dto.NodeBuildStatus; import org.gridsuite.study.server.networkmodificationtree.entities.NodeEntity; import org.gridsuite.study.server.networkmodificationtree.entities.NodeType; -import org.gridsuite.study.server.notification.NotificationService; -import org.gridsuite.study.server.repository.StudyRepository; import org.gridsuite.study.server.repository.networkmodificationtree.NodeRepository; import org.gridsuite.study.server.service.*; import org.gridsuite.study.server.utils.TestUtils; @@ -39,14 +35,9 @@ import java.nio.charset.StandardCharsets; import java.util.*; -import java.util.concurrent.CountDownLatch; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static org.gridsuite.study.server.StudyConstants.*; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @AutoConfigureMockMvc From 58030332045b9e02ff9262bf2fd0e06c25bb2595 Mon Sep 17 00:00:00 2001 From: Slimane AMAR Date: Thu, 15 Jan 2026 10:18:19 +0100 Subject: [PATCH 18/26] Review --- .../study/server/RebuildNodeService.java | 113 ++++++++++- .../server/controller/StudyController.java | 58 ++---- .../RebuildPreviouslyBuiltNodeHandler.java | 122 ------------ .../study/server/service/StudyService.java | 5 +- .../study/server/NetworkModificationTest.java | 27 +-- .../server/NetworkModificationUnitTest.java | 15 +- .../study/server/RebuildNodeServiceTest.java | 125 +++++++++++++ ...RebuildPreviouslyBuiltNodeHandlerTest.java | 177 ------------------ .../study/server/VoltageInitTest.java | 19 +- ...StudyControllerRebuildNodeHandlerTest.java | 118 ------------ .../StudyControllerRebuildNodeTest.java | 140 ++++++++++++++ .../server/studycontroller/StudyTest.java | 17 +- 12 files changed, 397 insertions(+), 539 deletions(-) delete mode 100644 src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java create mode 100644 src/test/java/org/gridsuite/study/server/RebuildNodeServiceTest.java delete mode 100644 src/test/java/org/gridsuite/study/server/RebuildPreviouslyBuiltNodeHandlerTest.java delete mode 100644 src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeHandlerTest.java create mode 100644 src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeTest.java diff --git a/src/main/java/org/gridsuite/study/server/RebuildNodeService.java b/src/main/java/org/gridsuite/study/server/RebuildNodeService.java index ad6226f74e..1b0071c19f 100644 --- a/src/main/java/org/gridsuite/study/server/RebuildNodeService.java +++ b/src/main/java/org/gridsuite/study/server/RebuildNodeService.java @@ -1,46 +1,139 @@ +/** + * 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; import org.gridsuite.study.server.dto.modification.NetworkModificationMetadata; -import org.gridsuite.study.server.handler.RebuildPreviouslyBuiltNodeHandler; +import org.gridsuite.study.server.service.NetworkModificationTreeService; import org.gridsuite.study.server.service.StudyService; import org.springframework.stereotype.Service; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.function.Predicate; +import java.util.stream.Collectors; @Service public class RebuildNodeService { - private final RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler; private final StudyService studyService; + private final NetworkModificationTreeService networkModificationTreeService; - public RebuildNodeService(RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler, StudyService studyService) { - this.rebuildPreviouslyBuiltNodeHandler = rebuildPreviouslyBuiltNodeHandler; + public RebuildNodeService(StudyService studyService, NetworkModificationTreeService networkModificationTreeService) { this.studyService = studyService; + this.networkModificationTreeService = networkModificationTreeService; + } + + public void createNetworkModification(UUID studyUuid, UUID nodeUuid, String modificationAttributes, String userId) { + handleRebuildNode(studyUuid, nodeUuid, userId, + () -> handleCreateNetworkModification(studyUuid, nodeUuid, modificationAttributes, userId)); + } + + private void handleCreateNetworkModification(UUID studyUuid, UUID nodeUuid, String modificationAttributes, String userId) { + studyService.invalidateNodeTreeWithLF(studyUuid, nodeUuid); + try { + studyService.createNetworkModification(studyUuid, nodeUuid, modificationAttributes, userId); + } finally { + studyService.unblockNodeTree(studyUuid, nodeUuid); + } } public void updateNetworkModification(UUID studyUuid, String updateModificationAttributes, UUID nodeUuid, UUID modificationUuid, String userId) { - rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, + handleRebuildNode(studyUuid, nodeUuid, userId, () -> studyService.updateNetworkModification(studyUuid, updateModificationAttributes, nodeUuid, modificationUuid, userId)); } public void stashNetworkModifications(UUID studyUuid, UUID nodeUuid, List modificationsUuids, String userId) { - rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, + handleRebuildNode(studyUuid, nodeUuid, userId, () -> studyService.stashNetworkModifications(studyUuid, nodeUuid, modificationsUuids, userId)); } public void updateNetworkModificationsMetadata(UUID studyUuid, UUID nodeUuid, List modificationsUuids, String userId, NetworkModificationMetadata metadata) { - rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, + handleRebuildNode(studyUuid, nodeUuid, userId, () -> studyService.updateNetworkModificationsMetadata(studyUuid, nodeUuid, modificationsUuids, userId, metadata)); } - public void updateNetworkModificationsActivationInRootNetwork(UUID studyUuid, UUID nodeUuid, UUID rootNetworkUuid, Set modificationsUuids, String userId, boolean activated) { - rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, + public void updateNetworkModificationsActivation(UUID studyUuid, UUID nodeUuid, UUID rootNetworkUuid, Set modificationsUuids, String userId, boolean activated) { + handleRebuildNode(studyUuid, nodeUuid, userId, () -> studyService.updateNetworkModificationsActivationInRootNetwork(studyUuid, nodeUuid, rootNetworkUuid, modificationsUuids, userId, activated)); } public void restoreNetworkModifications(UUID studyUuid, UUID nodeUuid, List modificationsUuids, String userId) { - rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, + handleRebuildNode(studyUuid, nodeUuid, userId, () -> studyService.restoreNetworkModifications(studyUuid, nodeUuid, modificationsUuids, userId)); } + + public void moveNetworkModification(UUID studyUuid, UUID nodeUuid, UUID modificationUuid, UUID beforeUuid, String userId) { + handleRebuildNode(studyUuid, nodeUuid, userId, + () -> handleMoveNetworkModification(studyUuid, nodeUuid, modificationUuid, beforeUuid, userId)); + } + + private void handleMoveNetworkModification(UUID studyUuid, UUID nodeUuid, UUID modificationUuid, UUID beforeUuid, String userId) { + studyService.invalidateNodeTreeWhenMoveModification(studyUuid, nodeUuid); + try { + studyService.moveNetworkModifications(studyUuid, nodeUuid, nodeUuid, List.of(modificationUuid), beforeUuid, false, userId); + } finally { + studyService.unblockNodeTree(studyUuid, nodeUuid); + } + } + + public void moveNetworkModifications(UUID studyUuid, UUID targetNodeUuid, UUID originNodeUuid, List modificationsToCopyUuidList, String userId) { + handleRebuildNode(studyUuid, targetNodeUuid, originNodeUuid, userId, + () -> handleMoveNetworkModifications(studyUuid, targetNodeUuid, originNodeUuid, modificationsToCopyUuidList, userId)); + } + + private void handleMoveNetworkModifications(UUID studyUuid, UUID targetNodeUuid, UUID originNodeUuid, List modificationsToCopyUuidList, String userId) { + boolean isTargetInDifferentNodeTree = studyService.invalidateNodeTreeWhenMoveModifications(studyUuid, targetNodeUuid, originNodeUuid); + try { + studyService.moveNetworkModifications(studyUuid, targetNodeUuid, originNodeUuid, modificationsToCopyUuidList, null, isTargetInDifferentNodeTree, userId); + } finally { + studyService.unblockNodeTree(studyUuid, originNodeUuid); + if (isTargetInDifferentNodeTree) { + studyService.unblockNodeTree(studyUuid, targetNodeUuid); + } + } + } + + private void handleRebuildNode(UUID studyUuid, UUID nodeUuid, String userId, Runnable action) { + handleRebuildNode(studyUuid, nodeUuid, nodeUuid, userId, action); + } + + private void handleRebuildNode(UUID studyUuid, UUID node1Uuid, UUID node2Uuid, String userId, Runnable action) { + // if node 1 and 2 are in the same "subtree", rebuild only the highest one - otherwise, rebuild both + List nodesToReBuild = networkModificationTreeService.getHighestNodeUuids(node1Uuid, node2Uuid).stream() + .filter(Predicate.not(networkModificationTreeService::isRootOrConstructionNode)).toList(); + + if (nodesToReBuild.isEmpty()) { + action.run(); + return; + } + + Map> rootNetworkUuidsByNodeBuilt = nodesToReBuild.stream().collect(Collectors.toMap( + nodeUuid -> nodeUuid, + nodeUuid -> getRootNetworkWhereNodeIsBuilt(studyUuid, nodeUuid) + )); + + action.run(); + + rootNetworkUuidsByNodeBuilt.forEach((nodeUuid, rootNetworkUuids) -> + rootNetworkUuids.stream().forEach(rootNetworkUuid -> + studyService.buildNode( + studyUuid, + nodeUuid, + rootNetworkUuid, + userId + ) + ) + ); + } + + private Set getRootNetworkWhereNodeIsBuilt(UUID studyUuid, UUID nodeUuid) { + return studyService.getNodeBuildStatusByRootNetwork(studyUuid, nodeUuid).entrySet().stream() + .filter(entry -> entry.getValue().isBuilt()) + .map(Map.Entry::getKey).collect(Collectors.toSet()); + } } diff --git a/src/main/java/org/gridsuite/study/server/controller/StudyController.java b/src/main/java/org/gridsuite/study/server/controller/StudyController.java index 0d400e832b..30af668ed4 100644 --- a/src/main/java/org/gridsuite/study/server/controller/StudyController.java +++ b/src/main/java/org/gridsuite/study/server/controller/StudyController.java @@ -20,9 +20,7 @@ import org.gridsuite.filter.utils.EquipmentType; import org.gridsuite.study.server.RebuildNodeService; import org.gridsuite.study.server.StudyApi; -import org.gridsuite.study.server.StudyConstants.ModificationsActionType; -import org.gridsuite.study.server.dto.modification.NetworkModificationMetadata; -import org.gridsuite.study.server.error.StudyException; +import org.gridsuite.study.server.StudyConstants.*; import org.gridsuite.study.server.dto.*; import org.gridsuite.study.server.dto.computation.LoadFlowComputationInfos; import org.gridsuite.study.server.dto.diagramgridlayout.DiagramGridLayout; @@ -36,6 +34,7 @@ import org.gridsuite.study.server.dto.elasticsearch.EquipmentInfos; import org.gridsuite.study.server.dto.modification.ModificationType; import org.gridsuite.study.server.dto.modification.ModificationsSearchResultByNode; +import org.gridsuite.study.server.dto.modification.NetworkModificationMetadata; import org.gridsuite.study.server.dto.networkexport.ExportNetworkStatus; import org.gridsuite.study.server.dto.sensianalysis.SensitivityAnalysisCsvFileInfos; import org.gridsuite.study.server.dto.sequence.NodeSequenceType; @@ -43,8 +42,8 @@ import org.gridsuite.study.server.dto.timeseries.TimelineEventInfos; import org.gridsuite.study.server.dto.voltageinit.parameters.StudyVoltageInitParameters; import org.gridsuite.study.server.elasticsearch.EquipmentInfosService; +import org.gridsuite.study.server.error.StudyException; import org.gridsuite.study.server.exception.PartialResultException; -import org.gridsuite.study.server.handler.RebuildPreviouslyBuiltNodeHandler; import org.gridsuite.study.server.networkmodificationtree.dto.*; import org.gridsuite.study.server.service.*; import org.gridsuite.study.server.service.securityanalysis.SecurityAnalysisResultType; @@ -90,7 +89,6 @@ public class StudyController { private final RootNetworkService rootNetworkService; private final RootNetworkNodeInfoService rootNetworkNodeInfoService; private final SensitivityAnalysisService sensitivityAnalysisService; - private final RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler; private final RebuildNodeService rebuildNodeService; public StudyController(StudyService studyService, @@ -103,7 +101,7 @@ public StudyController(StudyService studyService, RootNetworkService rootNetworkService, RootNetworkNodeInfoService rootNetworkNodeInfoService, SensitivityAnalysisService sensitivityAnalysisService, - RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler, RebuildNodeService rebuildNodeService) { + RebuildNodeService rebuildNodeService) { this.studyService = studyService; this.networkModificationTreeService = networkModificationTreeService; this.networkStoreService = networkStoreService; @@ -114,7 +112,6 @@ public StudyController(StudyService studyService, this.rootNetworkService = rootNetworkService; this.rootNetworkNodeInfoService = rootNetworkNodeInfoService; this.sensitivityAnalysisService = sensitivityAnalysisService; - this.rebuildPreviouslyBuiltNodeHandler = rebuildPreviouslyBuiltNodeHandler; this.rebuildNodeService = rebuildNodeService; } @@ -647,19 +644,9 @@ public ResponseEntity moveModification(@PathVariable("studyUuid") UUID stu @Nullable @Parameter(description = "move before, if no value move to end") @RequestParam(value = "beforeUuid") UUID beforeUuid, @RequestHeader(HEADER_USER_ID) String userId) { studyService.assertCanUpdateModifications(studyUuid, nodeUuid); - rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, - () -> handleMoveNetworkModification(studyUuid, nodeUuid, modificationUuid, beforeUuid, userId)); - return ResponseEntity.ok().build(); - } - - private void handleMoveNetworkModification(UUID studyUuid, UUID nodeUuid, UUID modificationUuid, UUID beforeUuid, String userId) { studyService.assertNoBlockedNodeInStudy(studyUuid, nodeUuid); - studyService.invalidateNodeTreeWhenMoveModification(studyUuid, nodeUuid); - try { - studyService.moveNetworkModifications(studyUuid, nodeUuid, nodeUuid, List.of(modificationUuid), beforeUuid, false, userId); - } finally { - studyService.unblockNodeTree(studyUuid, nodeUuid); - } + rebuildNodeService.moveNetworkModification(studyUuid, nodeUuid, modificationUuid, beforeUuid, userId); + return ResponseEntity.ok().build(); } @PutMapping(value = "/studies/{studyUuid}/nodes/{nodeUuid}", produces = MediaType.APPLICATION_JSON_VALUE) @@ -684,8 +671,9 @@ public ResponseEntity moveOrCopyModifications(@PathVariable("studyUuid") U if (!studyUuid.equals(originStudyUuid)) { throw new StudyException(MOVE_NETWORK_MODIFICATION_FORBIDDEN); } - rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, originNodeUuid, userId, - () -> handleMoveNetworkModifications(studyUuid, nodeUuid, originNodeUuid, modificationsToCopyUuidList, userId)); + studyService.assertNoBlockedNodeInStudy(studyUuid, originNodeUuid); + studyService.assertNoBlockedNodeInStudy(studyUuid, nodeUuid); + rebuildNodeService.moveNetworkModifications(studyUuid, nodeUuid, originNodeUuid, modificationsToCopyUuidList, userId); break; } return ResponseEntity.ok().build(); @@ -701,20 +689,6 @@ private void handleDuplicateOrInsertNetworkModifications(UUID targetStudyUuid, U } } - private void handleMoveNetworkModifications(UUID studyUuid, UUID targetNodeUuid, UUID originNodeUuid, List modificationsToCopyUuidList, String userId) { - studyService.assertNoBlockedNodeInStudy(studyUuid, originNodeUuid); - studyService.assertNoBlockedNodeInStudy(studyUuid, targetNodeUuid); - boolean isTargetInDifferentNodeTree = studyService.invalidateNodeTreeWhenMoveModifications(studyUuid, targetNodeUuid, originNodeUuid); - try { - studyService.moveNetworkModifications(studyUuid, targetNodeUuid, originNodeUuid, modificationsToCopyUuidList, null, isTargetInDifferentNodeTree, userId); - } finally { - studyService.unblockNodeTree(studyUuid, originNodeUuid); - if (isTargetInDifferentNodeTree) { - studyService.unblockNodeTree(studyUuid, targetNodeUuid); - } - } - } - @PutMapping(value = "/studies/{studyUuid}/root-networks/{rootNetworkUuid}/nodes/{nodeUuid}/loadflow/run") @Operation(summary = "run loadflow on study") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The loadflow has started")}) @@ -1373,20 +1347,10 @@ public ResponseEntity createNetworkModification(@Parameter(description = " @RequestHeader(HEADER_USER_ID) String userId) { studyService.assertCanUpdateModifications(studyUuid, nodeUuid); studyService.assertNoBlockedNodeInStudy(studyUuid, nodeUuid); - rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, nodeUuid, userId, - () -> handleCreateNetworkModification(studyUuid, nodeUuid, modificationAttributes, userId)); + rebuildNodeService.createNetworkModification(studyUuid, nodeUuid, modificationAttributes, userId); return ResponseEntity.ok().build(); } - private void handleCreateNetworkModification(UUID studyUuid, UUID nodeUuid, String modificationAttributes, String userId) { - studyService.invalidateNodeTreeWithLF(studyUuid, nodeUuid); - try { - studyService.createNetworkModification(studyUuid, nodeUuid, modificationAttributes, userId); - } finally { - studyService.unblockNodeTree(studyUuid, nodeUuid); - } - } - @PutMapping(value = "/studies/{studyUuid}/nodes/{nodeUuid}/network-modifications/{uuid}") @Operation(summary = "Update a modification in the study network") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The network modification was updated"), @ApiResponse(responseCode = "404", description = "The study/node is not found")}) @@ -1458,7 +1422,7 @@ public ResponseEntity updateNetworkModificationsActivation(@Parameter(desc studyService.assertCanUpdateModifications(studyUuid, nodeUuid); studyService.assertNoBuildNoComputationForRootNetworkNode(nodeUuid, rootNetworkUuid); studyService.assertNoBlockedNodeInTree(nodeUuid, rootNetworkUuid); - rebuildNodeService.updateNetworkModificationsActivationInRootNetwork(studyUuid, nodeUuid, rootNetworkUuid, networkModificationUuids, userId, activated); + rebuildNodeService.updateNetworkModificationsActivation(studyUuid, nodeUuid, rootNetworkUuid, networkModificationUuids, userId, activated); return ResponseEntity.ok().build(); } diff --git a/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java b/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java deleted file mode 100644 index 4779f46f12..0000000000 --- a/src/main/java/org/gridsuite/study/server/handler/RebuildPreviouslyBuiltNodeHandler.java +++ /dev/null @@ -1,122 +0,0 @@ -package org.gridsuite.study.server.handler; - -import org.gridsuite.study.server.service.NetworkModificationTreeService; -import org.gridsuite.study.server.service.StudyService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; - -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.function.Predicate; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -@Component -public class RebuildPreviouslyBuiltNodeHandler { - private static final Logger LOGGER = LoggerFactory.getLogger(RebuildPreviouslyBuiltNodeHandler.class); - private final StudyService studyService; - private final NetworkModificationTreeService networkModificationTreeService; - - public RebuildPreviouslyBuiltNodeHandler( - StudyService studyService, - NetworkModificationTreeService networkModificationTreeService) { - this.studyService = studyService; - this.networkModificationTreeService = networkModificationTreeService; - } - - private T execute( - UUID studyUuid, - UUID node1Uuid, - UUID node2Uuid, - String userId, - Supplier operation - ) { - // if node 1 and 2 are in the same "subtree", rebuild only the highest one - otherwise, rebuild both - List nodesToReBuild = networkModificationTreeService.getHighestNodeUuids(node1Uuid, node2Uuid).stream() - .filter(Predicate.not(networkModificationTreeService::isRootOrConstructionNode)).toList(); - - if (nodesToReBuild.isEmpty()) { - return operation.get(); - } - - Map> rootNetworkUuidsWithBuiltNodeBeforeMap = nodesToReBuild.stream().collect(Collectors.toMap( - nodeUuid -> nodeUuid, - nodeUuid -> getRootNetworkWhereNotHasToBeRebuilt(studyUuid, nodeUuid) - )); - - T result = operation.get(); - - Map> rootNetworkUuidsWithBuiltNodeAfterMap = nodesToReBuild.stream().collect(Collectors.toMap( - nodeUuid -> nodeUuid, - nodeUuid -> getRootNetworkWhereNotHasToBeRebuilt(studyUuid, nodeUuid) - )); - - try { - rootNetworkUuidsWithBuiltNodeAfterMap.forEach((nodeUuid, rootNetworkUuidsWithBuiltNodeAfter) -> { - Set rootNetworkUuidsWithBuiltNodeBefore = rootNetworkUuidsWithBuiltNodeBeforeMap.getOrDefault(nodeUuid, Set.of()); - - rootNetworkUuidsWithBuiltNodeBefore.stream() - .filter(uuid -> !rootNetworkUuidsWithBuiltNodeAfter.contains(uuid)) - .forEach(rootNetworkUuid -> - studyService.buildNode( - studyUuid, - nodeUuid, - rootNetworkUuid, - userId - ) - ); - }); - } catch (Exception e) { - // if rebuild fails, we don't want to rollback main operation transaction - LOGGER.warn(e.getMessage()); - } - - return result; - } - - public void execute( - UUID studyUuid, - UUID nodeUuid, - String userId, - Runnable operation - ) { - execute( - studyUuid, - nodeUuid, - nodeUuid, - userId, - () -> { - operation.run(); - return null; - } - ); - } - - public void execute( - UUID studyUuid, - UUID node1Uuid, - UUID node2Uuid, - String userId, - Runnable operation - ) { - execute( - studyUuid, - node1Uuid, - node2Uuid, - userId, - () -> { - operation.run(); - return null; - } - ); - } - - private Set getRootNetworkWhereNotHasToBeRebuilt(UUID studyUuid, UUID nodeUuid) { - return studyService.getNodeBuildStatusByRootNetworkUuid(studyUuid, nodeUuid).entrySet().stream() - .filter(entry -> entry.getValue().isBuilt()) - .map(Map.Entry::getKey).collect(Collectors.toSet()); - } -} diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index 8414d09b8c..10ea41fc0a 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -1897,6 +1897,9 @@ private void buildNode(@NonNull UUID studyUuid, @NonNull UUID nodeUuid, @NonNull } private void buildNode(@NonNull UUID studyUuid, @NonNull UUID nodeUuid, @NonNull UUID rootNetworkUuid, @NonNull String userId, AbstractWorkflowInfos workflowInfos) { + if (networkModificationTreeService.getNodeBuildStatus(nodeUuid, rootNetworkUuid).isBuilt()) { + return; + } assertCanBuildNode(studyUuid, rootNetworkUuid, userId); BuildInfos buildInfos = networkModificationTreeService.getBuildInfos(nodeUuid, rootNetworkUuid); @@ -2429,7 +2432,7 @@ private StudyEntity getStudy(UUID studyUuid) { } @Transactional - public Map getNodeBuildStatusByRootNetworkUuid(UUID studyUuid, UUID nodeUuid) { + public Map getNodeBuildStatusByRootNetwork(UUID studyUuid, UUID nodeUuid) { return getStudyRootNetworks(studyUuid).stream().collect(Collectors.toMap( RootNetworkEntity::getId, rn -> rootNetworkNodeInfoService.getRootNetworkNodeInfo(nodeUuid, rn.getId()).map(rni -> rni.getNodeBuildStatus().toDto()).orElseThrow(() -> new StudyException(NOT_FOUND, "Root network not found")) diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java index 131d033381..c5687ef2f0 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java @@ -36,7 +36,6 @@ import org.gridsuite.study.server.dto.impacts.SimpleElementImpact.SimpleImpactType; import org.gridsuite.study.server.dto.modification.*; import org.gridsuite.study.server.error.StudyException; -import org.gridsuite.study.server.handler.RebuildPreviouslyBuiltNodeHandler; import org.gridsuite.study.server.networkmodificationtree.dto.*; import org.gridsuite.study.server.networkmodificationtree.entities.NetworkModificationNodeType; import org.gridsuite.study.server.networkmodificationtree.entities.NodeBuildStatusEmbeddable; @@ -50,7 +49,10 @@ import org.gridsuite.study.server.service.client.dynamicsecurityanalysis.DynamicSecurityAnalysisClient; import org.gridsuite.study.server.service.client.dynamicsimulation.DynamicSimulationClient; import org.gridsuite.study.server.service.shortcircuit.ShortCircuitService; -import org.gridsuite.study.server.utils.*; +import org.gridsuite.study.server.utils.MatcherJson; +import org.gridsuite.study.server.utils.RequestWithBody; +import org.gridsuite.study.server.utils.SendInput; +import org.gridsuite.study.server.utils.TestUtils; import org.gridsuite.study.server.utils.elasticsearch.DisableElasticsearch; import org.gridsuite.study.server.utils.wiremock.WireMockStubs; import org.gridsuite.study.server.utils.wiremock.WireMockUtils; @@ -82,9 +84,10 @@ import java.util.*; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static org.gridsuite.study.server.error.StudyBusinessErrorCode.*; import static org.gridsuite.study.server.StudyConstants.HEADER_ERROR_MESSAGE; import static org.gridsuite.study.server.StudyConstants.QUERY_PARAM_RECEIVER; +import static org.gridsuite.study.server.error.StudyBusinessErrorCode.MAX_NODE_BUILDS_EXCEEDED; +import static org.gridsuite.study.server.error.StudyBusinessErrorCode.NOT_FOUND; import static org.gridsuite.study.server.utils.ImpactUtils.createModificationResultWithElementImpact; import static org.gridsuite.study.server.utils.JsonUtils.getModificationContextJsonString; import static org.gridsuite.study.server.utils.MatcherCreatedStudyBasicInfos.createMatcherCreatedStudyBasicInfos; @@ -187,7 +190,7 @@ class NetworkModificationTest { @Autowired private ObjectMapper mapper; - @Autowired + @MockitoSpyBean private NetworkModificationTreeService networkModificationTreeService; @Autowired @@ -262,9 +265,6 @@ class NetworkModificationTest { @Autowired private PccMinService pccMinService; - @MockitoBean - RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler; - @BeforeEach void setup(final MockWebServer server) { ReadOnlyDataSource dataSource = new ResourceDataSource("testCase", new ResourceSet("", TEST_FILE)); @@ -273,17 +273,8 @@ void setup(final MockWebServer server) { network.getVariantManager().setWorkingVariant(VariantManagerConstants.INITIAL_VARIANT_ID); when(networkStoreService.getNetwork(NETWORK_UUID)).thenReturn(network); - doAnswer(inv -> { - inv.getArgument(inv.getArguments().length - 1, Runnable.class).run(); - return null; - }).when(rebuildPreviouslyBuiltNodeHandler) - .execute(any(), any(), any(), anyString(), any(Runnable.class)); - - doAnswer(inv -> { - inv.getArgument(inv.getArguments().length - 1, Runnable.class).run(); - return null; - }).when(rebuildPreviouslyBuiltNodeHandler) - .execute(any(), any(), anyString(), any(Runnable.class)); + + doAnswer(invocation -> List.of()).when(networkModificationTreeService).getHighestNodeUuids(any(), any()); synchronizeStudyServerExecutionService(studyServerExecutionService); diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationUnitTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationUnitTest.java index e87b456560..0149a79280 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationUnitTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationUnitTest.java @@ -13,7 +13,6 @@ import org.gridsuite.study.server.dto.modification.NetworkModificationMetadata; import org.gridsuite.study.server.dto.workflow.RerunLoadFlowInfos; import org.gridsuite.study.server.error.StudyException; -import org.gridsuite.study.server.handler.RebuildPreviouslyBuiltNodeHandler; import org.gridsuite.study.server.networkmodificationtree.dto.BuildStatus; import org.gridsuite.study.server.networkmodificationtree.dto.NodeBuildStatus; import org.gridsuite.study.server.networkmodificationtree.entities.*; @@ -127,8 +126,6 @@ class NetworkModificationUnitTest { @MockitoSpyBean private NetworkModificationTreeService networkModificationTreeService; - @MockitoBean - private RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler; @BeforeEach void setup() { @@ -138,17 +135,7 @@ void setup() { rootNetworkRepository.deleteAll(); studyRepository.deleteAll(); - doAnswer(inv -> { - inv.getArgument(inv.getArguments().length - 1, Runnable.class).run(); - return null; - }).when(rebuildPreviouslyBuiltNodeHandler) - .execute(any(), any(), any(), anyString(), any(Runnable.class)); - - doAnswer(inv -> { - inv.getArgument(inv.getArguments().length - 1, Runnable.class).run(); - return null; - }).when(rebuildPreviouslyBuiltNodeHandler) - .execute(any(), any(), anyString(), any(Runnable.class)); + doAnswer(invocation -> List.of()).when(networkModificationTreeService).getHighestNodeUuids(any(), any()); } @Test diff --git a/src/test/java/org/gridsuite/study/server/RebuildNodeServiceTest.java b/src/test/java/org/gridsuite/study/server/RebuildNodeServiceTest.java new file mode 100644 index 0000000000..5d871a7708 --- /dev/null +++ b/src/test/java/org/gridsuite/study/server/RebuildNodeServiceTest.java @@ -0,0 +1,125 @@ +package org.gridsuite.study.server; + +import org.gridsuite.study.server.networkmodificationtree.dto.BuildStatus; +import org.gridsuite.study.server.networkmodificationtree.dto.NodeBuildStatus; +import org.gridsuite.study.server.service.NetworkModificationTreeService; +import org.gridsuite.study.server.service.StudyService; +import org.gridsuite.study.server.utils.elasticsearch.DisableElasticsearch; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static org.mockito.Mockito.*; + +@SpringBootTest +@DisableElasticsearch +class RebuildNodeServiceTest { + @MockitoSpyBean + private RebuildNodeService rebuildNodeService; + + @MockitoBean + private NetworkModificationTreeService networkModificationTreeService; + + @MockitoBean + private StudyService studyService; + + UUID studyUuid = UUID.randomUUID(); + UUID node1Uuid = UUID.randomUUID(); + UUID node2Uuid = UUID.randomUUID(); + String userId = "userId"; + + UUID rootNetworkUuid = UUID.randomUUID(); + UUID rootNetwork2Uuid = UUID.randomUUID(); + + @BeforeEach + void setUp() { + doReturn(List.of(node1Uuid, node2Uuid)).when(networkModificationTreeService).getHighestNodeUuids(node1Uuid, node2Uuid); + doReturn(List.of(node1Uuid)).when(networkModificationTreeService).getHighestNodeUuids(node1Uuid, node1Uuid); + doReturn(false).when(networkModificationTreeService).isRootOrConstructionNode(any()); + } + + @Test + void testRebuildSingleNode() { + doReturn( + Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT)) + ).when(studyService).getNodeBuildStatusByRootNetwork(studyUuid, node1Uuid); + + rebuildNodeService.moveNetworkModification(studyUuid, node1Uuid, UUID.randomUUID(), UUID.randomUUID(), userId); + + verify(studyService, times(1)).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); + } + + @Test + void testRebuildMultipleNodes() { + doReturn( + Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT)) + ).when(studyService).getNodeBuildStatusByRootNetwork(studyUuid, node1Uuid); + doReturn( + Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT)) + ).when(studyService).getNodeBuildStatusByRootNetwork(studyUuid, node2Uuid); + + rebuildNodeService.moveNetworkModifications(studyUuid, node1Uuid, node2Uuid, List.of(), userId); + + verify(studyService, times(1)).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); + verify(studyService, times(1)).buildNode(studyUuid, node2Uuid, rootNetworkUuid, userId); + } + + @Test + void testRebuildMultipleRootNetworks() { + doReturn( + Map.of( + rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT), + rootNetwork2Uuid, NodeBuildStatus.from(BuildStatus.BUILT) + ) + ).when(studyService).getNodeBuildStatusByRootNetwork(studyUuid, node1Uuid); + + rebuildNodeService.moveNetworkModifications(studyUuid, node1Uuid, node2Uuid, List.of(), userId); + + verify(studyService, times(1)).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); + verify(studyService, times(1)).buildNode(studyUuid, node1Uuid, rootNetwork2Uuid, userId); + } + + @Test + void testRebuildMultipleRootNetworksAndNodes() { + Runnable runnable = Mockito.spy(Runnable.class); + doReturn( + Map.of( + rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT), + rootNetwork2Uuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT) + ) + ).when(studyService).getNodeBuildStatusByRootNetwork(studyUuid, node1Uuid); + + doReturn( + Map.of( + rootNetworkUuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT), + rootNetwork2Uuid, NodeBuildStatus.from(BuildStatus.BUILT) + ) + ).when(studyService).getNodeBuildStatusByRootNetwork(studyUuid, node2Uuid); + + rebuildNodeService.moveNetworkModifications(studyUuid, node1Uuid, node2Uuid, List.of(), userId); + + verify(studyService, times(1)).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); + verify(studyService, times(1)).buildNode(studyUuid, node2Uuid, rootNetwork2Uuid, userId); + } + + @Test + void testRebuildConstructionNode() { + doReturn( + Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT)), + Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT)) + ).when(studyService).getNodeBuildStatusByRootNetwork(studyUuid, node1Uuid); + + doReturn(true).when(networkModificationTreeService).isRootOrConstructionNode(any()); + + rebuildNodeService.moveNetworkModification(studyUuid, node1Uuid, UUID.randomUUID(), UUID.randomUUID(), userId); + + verify(studyService, times(0)).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); + } +} diff --git a/src/test/java/org/gridsuite/study/server/RebuildPreviouslyBuiltNodeHandlerTest.java b/src/test/java/org/gridsuite/study/server/RebuildPreviouslyBuiltNodeHandlerTest.java deleted file mode 100644 index 960358e0b8..0000000000 --- a/src/test/java/org/gridsuite/study/server/RebuildPreviouslyBuiltNodeHandlerTest.java +++ /dev/null @@ -1,177 +0,0 @@ -package org.gridsuite.study.server; - -import org.gridsuite.study.server.handler.RebuildPreviouslyBuiltNodeHandler; -import org.gridsuite.study.server.networkmodificationtree.dto.BuildStatus; -import org.gridsuite.study.server.networkmodificationtree.dto.NodeBuildStatus; -import org.gridsuite.study.server.service.NetworkModificationTreeService; -import org.gridsuite.study.server.service.StudyService; -import org.gridsuite.study.server.utils.elasticsearch.DisableElasticsearch; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.InOrder; -import org.mockito.Mockito; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.bean.override.mockito.MockitoBean; - -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.mockito.Mockito.*; - -@SpringBootTest -@DisableElasticsearch -class RebuildPreviouslyBuiltNodeHandlerTest { - @Autowired - private RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler; - - @MockitoBean - private NetworkModificationTreeService networkModificationTreeService; - @MockitoBean - private StudyService studyService; - - UUID studyUuid = UUID.randomUUID(); - UUID node1Uuid = UUID.randomUUID(); - UUID node2Uuid = UUID.randomUUID(); - String userId = "userId"; - - UUID rootNetworkUuid = UUID.randomUUID(); - UUID rootNetwork2Uuid = UUID.randomUUID(); - - @BeforeEach - void setUp() { - doReturn(List.of(node1Uuid, node2Uuid)).when(networkModificationTreeService).getHighestNodeUuids(node1Uuid, node2Uuid); - doReturn(List.of(node1Uuid)).when(networkModificationTreeService).getHighestNodeUuids(node1Uuid, node1Uuid); - doReturn(false).when(networkModificationTreeService).isRootOrConstructionNode(any()); - } - - @Test - void testRebuildSingleNode() { - Runnable runnable = Mockito.spy(Runnable.class); - doReturn( - Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT)), - Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT)) - ).when(studyService).getNodeBuildStatusByRootNetworkUuid(studyUuid, node1Uuid); - - rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, node1Uuid, userId, runnable); - - InOrder inOrder = Mockito.inOrder(runnable, studyService); - inOrder.verify(runnable, times(1)).run(); - inOrder.verify(studyService, times(1)).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); - } - - @Test - void testRebuildMultipleNodes() { - Runnable runnable = Mockito.spy(Runnable.class); - doReturn( - Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT)), - Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT)) - ).when(studyService).getNodeBuildStatusByRootNetworkUuid(studyUuid, node1Uuid); - doReturn( - Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT)), - Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT)) - ).when(studyService).getNodeBuildStatusByRootNetworkUuid(studyUuid, node2Uuid); - - rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, node1Uuid, node2Uuid, userId, runnable); - - InOrder inOrder = Mockito.inOrder(runnable, studyService); - inOrder.verify(runnable, times(1)).run(); - inOrder.verify(studyService, times(1)).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); - // this does not need to be checked in order, what matters is that "runnable" is called BEFORE nodes are rebuilt - Mockito.verify(studyService, times(1)).buildNode(studyUuid, node2Uuid, rootNetworkUuid, userId); - } - - @Test - void testRebuildMultipleRootNetworks() { - Runnable runnable = Mockito.spy(Runnable.class); - doReturn( - Map.of( - rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT), - rootNetwork2Uuid, NodeBuildStatus.from(BuildStatus.BUILT) - ), - Map.of( - rootNetworkUuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT), - rootNetwork2Uuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT) - ) - ).when(studyService).getNodeBuildStatusByRootNetworkUuid(studyUuid, node1Uuid); - - rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, node1Uuid, userId, runnable); - - InOrder inOrder = Mockito.inOrder(runnable, studyService); - inOrder.verify(runnable, times(1)).run(); - inOrder.verify(studyService, times(1)).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); - // this does not need to be checked in order, what matters is that "runnable" is called BEFORE nodes are rebuilt - Mockito.verify(studyService, times(1)).buildNode(studyUuid, node1Uuid, rootNetwork2Uuid, userId); - } - - @Test - void testRebuildMultipleRootNetworksAndNodes() { - Runnable runnable = Mockito.spy(Runnable.class); - doReturn( - Map.of( - rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT), - rootNetwork2Uuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT) - ), - Map.of( - rootNetworkUuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT), - rootNetwork2Uuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT) - ) - ).when(studyService).getNodeBuildStatusByRootNetworkUuid(studyUuid, node1Uuid); - - doReturn( - Map.of( - rootNetworkUuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT), - rootNetwork2Uuid, NodeBuildStatus.from(BuildStatus.BUILT) - ), - Map.of( - rootNetworkUuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT), - rootNetwork2Uuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT) - ) - ).when(studyService).getNodeBuildStatusByRootNetworkUuid(studyUuid, node2Uuid); - - rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, node1Uuid, node2Uuid, userId, runnable); - - InOrder inOrder = Mockito.inOrder(runnable, studyService); - inOrder.verify(runnable, times(1)).run(); - inOrder.verify(studyService, times(1)).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); - // this does not need to be checked in order, what matters is that "runnable" is called BEFORE nodes are rebuilt - Mockito.verify(studyService, times(1)).buildNode(studyUuid, node2Uuid, rootNetwork2Uuid, userId); - } - - @Test - void testRebuildNodeException() { - // operation should not be canceled and no exception should be thrown if node rebuild fails - Runnable runnable = Mockito.spy(Runnable.class); - doReturn( - Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT)), - Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT)) - ).when(studyService).getNodeBuildStatusByRootNetworkUuid(studyUuid, node1Uuid); - - doThrow(new RuntimeException("Something wrong happened during node building")) - .when(studyService).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); - - assertDoesNotThrow(() -> rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, node1Uuid, userId, runnable)); - - InOrder inOrder = Mockito.inOrder(runnable, studyService); - inOrder.verify(runnable, times(1)).run(); - inOrder.verify(studyService, times(1)).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); - } - - @Test - void testRebuildConstructionNode() { - Runnable runnable = Mockito.spy(Runnable.class); - doReturn( - Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT)), - Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.NOT_BUILT)) - ).when(studyService).getNodeBuildStatusByRootNetworkUuid(studyUuid, node1Uuid); - - doReturn(true).when(networkModificationTreeService).isRootOrConstructionNode(any()); - - rebuildPreviouslyBuiltNodeHandler.execute(studyUuid, node1Uuid, userId, runnable); - - Mockito.verify(runnable, times(1)).run(); - Mockito.verify(studyService, times(0)).buildNode(studyUuid, node1Uuid, rootNetworkUuid, userId); - } -} diff --git a/src/test/java/org/gridsuite/study/server/VoltageInitTest.java b/src/test/java/org/gridsuite/study/server/VoltageInitTest.java index 467d19fa74..38ae773323 100644 --- a/src/test/java/org/gridsuite/study/server/VoltageInitTest.java +++ b/src/test/java/org/gridsuite/study/server/VoltageInitTest.java @@ -33,7 +33,6 @@ import org.gridsuite.study.server.dto.modification.NetworkModificationResult; import org.gridsuite.study.server.dto.modification.NetworkModificationsResult; import org.gridsuite.study.server.dto.voltageinit.parameters.*; -import org.gridsuite.study.server.handler.RebuildPreviouslyBuiltNodeHandler; import org.gridsuite.study.server.networkmodificationtree.dto.*; import org.gridsuite.study.server.networkmodificationtree.entities.*; import org.gridsuite.study.server.notification.NotificationService; @@ -208,7 +207,7 @@ class VoltageInitTest { private ObjectWriter objectWriter; - @Autowired + @MockitoSpyBean private NetworkModificationTreeService networkModificationTreeService; @MockitoSpyBean @@ -274,9 +273,6 @@ class VoltageInitTest { @Autowired private VoltageInitResultConsumer voltageInitResultConsumer; - @MockitoBean - private RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler; - //output destinations private final String studyUpdateDestination = "study.update"; private final String voltageInitDebugDestination = "voltageinit.debug"; @@ -419,18 +415,7 @@ private void initMockBeans(Network network) { when(networkStoreService.getNetwork(NETWORK_UUID)).thenReturn(network); when(networkStoreService.getNetwork(SECOND_NETWORK_UUID)).thenReturn(network); - doAnswer(inv -> { - inv.getArgument(inv.getArguments().length - 1, Runnable.class).run(); - return null; - }).when(rebuildPreviouslyBuiltNodeHandler) - .execute(any(), any(), any(), anyString(), any(Runnable.class)); - - doAnswer(inv -> { - inv.getArgument(inv.getArguments().length - 1, Runnable.class).run(); - return null; - }).when(rebuildPreviouslyBuiltNodeHandler) - .execute(any(), any(), anyString(), any(Runnable.class)); - + doAnswer(invocation -> List.of()).when(networkModificationTreeService).getHighestNodeUuids(any(), any()); } private void createOrUpdateParametersAndDoChecks(UUID studyNameUserIdUuid, StudyVoltageInitParameters parameters, String userId, HttpStatusCode status) throws Exception { diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeHandlerTest.java b/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeHandlerTest.java deleted file mode 100644 index 60bf404449..0000000000 --- a/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeHandlerTest.java +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Copyright (c) 2025, 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.studycontroller; - -import mockwebserver3.junit5.internal.MockWebServerExtension; -import org.gridsuite.study.server.StudyConstants; -import org.gridsuite.study.server.controller.StudyController; -import org.gridsuite.study.server.dto.modification.NetworkModificationMetadata; -import org.gridsuite.study.server.handler.RebuildPreviouslyBuiltNodeHandler; -import org.gridsuite.study.server.service.StudyService; -import org.gridsuite.study.server.utils.elasticsearch.DisableElasticsearch; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.bean.override.mockito.MockitoBean; - -import java.util.List; -import java.util.Set; -import java.util.UUID; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -/** - * @author Kevin Le Saulnier - */ -@ExtendWith(MockWebServerExtension.class) -@SpringBootTest -@DisableElasticsearch -class StudyControllerRebuildNodeHandlerTest { - - @MockitoBean - private RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler; - // this test is only making sure all those endpoint are actually calling rebuildPreviouslyBuiltNodeHandler - // we mock studyService since we don't cover all the assertions - @MockitoBean - private StudyService studyService; - - @Autowired - private StudyController studyController; - - private final UUID nodeUuid = UUID.randomUUID(); - private final UUID studyUuid = UUID.randomUUID(); - private final String userId = "userId"; - - @Test - void testCreateNetworkModification() { - studyController.createNetworkModification(studyUuid, nodeUuid, "modificationBody", userId); - - verify(rebuildPreviouslyBuiltNodeHandler, times(1)).execute(eq(studyUuid), eq(nodeUuid), eq(userId), any(Runnable.class)); - } - - @Test - void testMoveNetworkModification() { - UUID modificationUuid = UUID.randomUUID(); - studyController.moveModification(studyUuid, nodeUuid, modificationUuid, null, userId); - - verify(rebuildPreviouslyBuiltNodeHandler, times(1)).execute(eq(studyUuid), eq(nodeUuid), eq(userId), any(Runnable.class)); - } - - @Test - void testMoveNetworkModifications() { - List modificationUuids = List.of(UUID.randomUUID()); - UUID originNodeUuid = UUID.randomUUID(); - studyController.moveOrCopyModifications(studyUuid, nodeUuid, StudyConstants.ModificationsActionType.MOVE, studyUuid, originNodeUuid, modificationUuids, userId); - - verify(rebuildPreviouslyBuiltNodeHandler, times(1)).execute(eq(studyUuid), eq(nodeUuid), eq(originNodeUuid), eq(userId), any(Runnable.class)); - } - - @Test - void updateNetworkModification() { - UUID modificationUuid = UUID.randomUUID(); - studyController.updateNetworkModification(studyUuid, nodeUuid, modificationUuid, "modificationAttributes", userId); - - verify(rebuildPreviouslyBuiltNodeHandler, times(1)).execute(eq(studyUuid), eq(nodeUuid), eq(userId), any(Runnable.class)); - } - - @Test - void stashNetworkModification() { - List modificationUuids = List.of(UUID.randomUUID()); - studyController.stashNetworkModifications(studyUuid, nodeUuid, modificationUuids, true, userId); - - verify(rebuildPreviouslyBuiltNodeHandler, times(1)).execute(eq(studyUuid), eq(nodeUuid), eq(userId), any(Runnable.class)); - } - - @Test - void restoreNetworkModification() { - List modificationUuids = List.of(UUID.randomUUID()); - studyController.stashNetworkModifications(studyUuid, nodeUuid, modificationUuids, false, userId); - - verify(rebuildPreviouslyBuiltNodeHandler, times(1)).execute(eq(studyUuid), eq(nodeUuid), eq(userId), any(Runnable.class)); - } - - // when a modification is enabled/disabled, this method is called - @Test - void updateNetworkModificationMetadata() { - List modificationUuids = List.of(UUID.randomUUID()); - studyController.updateNetworkModificationsMetadata(studyUuid, nodeUuid, modificationUuids, new NetworkModificationMetadata(true, "description", "type"), userId); - - verify(rebuildPreviouslyBuiltNodeHandler, times(1)).execute(eq(studyUuid), eq(nodeUuid), eq(userId), any(Runnable.class)); - } - - @Test - void testUpdateNetworkModificationActivationByRootNetwork() { - Set modificationUuids = Set.of(UUID.randomUUID()); - UUID rootNetworkUuid = UUID.randomUUID(); - studyController.updateNetworkModificationsActivation(studyUuid, rootNetworkUuid, nodeUuid, modificationUuids, true, userId); - - verify(rebuildPreviouslyBuiltNodeHandler, times(1)).execute(eq(studyUuid), eq(nodeUuid), eq(userId), any(Runnable.class)); - } -} diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeTest.java b/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeTest.java new file mode 100644 index 0000000000..01288cb82b --- /dev/null +++ b/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeTest.java @@ -0,0 +1,140 @@ +/** + * Copyright (c) 2025, 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.studycontroller; + +import org.gridsuite.study.server.RebuildNodeService; +import org.gridsuite.study.server.StudyConstants; +import org.gridsuite.study.server.controller.StudyController; +import org.gridsuite.study.server.dto.modification.NetworkModificationMetadata; +import org.gridsuite.study.server.networkmodificationtree.dto.BuildStatus; +import org.gridsuite.study.server.networkmodificationtree.dto.NodeBuildStatus; +import org.gridsuite.study.server.service.NetworkModificationTreeService; +import org.gridsuite.study.server.service.StudyService; +import org.gridsuite.study.server.utils.elasticsearch.DisableElasticsearch; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * @author Kevin Le Saulnier + */ +@SpringBootTest +@DisableElasticsearch +class StudyControllerRebuildNodeTest { + @MockitoSpyBean + private RebuildNodeService rebuildNodeService; + + // this test is only making sure all those endpoint are actually calling rebuildPreviouslyBuiltNodeHandler + // we mock studyService since we don't cover all the assertions + @MockitoBean + private StudyService studyService; + + @MockitoBean + NetworkModificationTreeService networkModificationTreeService; + + @Autowired + private StudyController studyController; + + private final UUID studyUuid = UUID.randomUUID(); + private final UUID rootNetworkUuid = UUID.randomUUID(); + private final UUID nodeUuid = UUID.randomUUID(); + private final UUID modificationUuid = UUID.randomUUID(); + + private final String userId = "userId"; + + @BeforeEach + void setUp() { + doAnswer(invocation -> Map.of(rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT))).when(studyService).getNodeBuildStatusByRootNetwork(any(), any()); + + doAnswer(invocation -> List.of(nodeUuid)).when(networkModificationTreeService).getHighestNodeUuids(any(), any()); + doAnswer(invocation -> false).when(networkModificationTreeService).isRootOrConstructionNode(any()); + } + + @Test + void testCreateNetworkModification() { + studyController.createNetworkModification(studyUuid, nodeUuid, "modificationBody", userId); + + verify(rebuildNodeService, times(1)).createNetworkModification(eq(studyUuid), eq(nodeUuid), eq("modificationBody"), eq(userId)); + verify(studyService, times(1)).buildNode(eq(studyUuid), eq(nodeUuid), any(), eq(userId)); + } + + @Test + void testMoveNetworkModification() { + studyController.moveModification(studyUuid, nodeUuid, modificationUuid, null, userId); + + verify(rebuildNodeService, times(1)).moveNetworkModification(eq(studyUuid), eq(nodeUuid), eq(modificationUuid), isNull(), eq(userId)); + verify(studyService, times(1)).buildNode(eq(studyUuid), eq(nodeUuid), any(), eq(userId)); + } + + @Test + void testMoveNetworkModifications() { + List modificationUuids = List.of(UUID.randomUUID()); + UUID originNodeUuid = UUID.randomUUID(); + studyController.moveOrCopyModifications(studyUuid, nodeUuid, StudyConstants.ModificationsActionType.MOVE, studyUuid, originNodeUuid, modificationUuids, userId); + + verify(rebuildNodeService, times(1)).moveNetworkModifications(eq(studyUuid), eq(nodeUuid), eq(originNodeUuid), eq(modificationUuids), eq(userId)); + verify(studyService, times(1)).buildNode(eq(studyUuid), eq(nodeUuid), any(), eq(userId)); + } + + @Test + void updateNetworkModification() { + UUID modificationUuid = UUID.randomUUID(); + studyController.updateNetworkModification(studyUuid, nodeUuid, modificationUuid, "modificationAttributes", userId); + + verify(rebuildNodeService, times(1)).updateNetworkModification(eq(studyUuid), eq("modificationAttributes"), eq(nodeUuid), eq(modificationUuid), eq(userId)); + verify(studyService, times(1)).buildNode(eq(studyUuid), eq(nodeUuid), any(), eq(userId)); + } + + @Test + void stashNetworkModification() { + List modificationUuids = List.of(UUID.randomUUID()); + studyController.stashNetworkModifications(studyUuid, nodeUuid, modificationUuids, true, userId); + + verify(rebuildNodeService, times(1)).stashNetworkModifications(eq(studyUuid), eq(nodeUuid), eq(modificationUuids), eq(userId)); + verify(studyService, times(1)).buildNode(eq(studyUuid), eq(nodeUuid), any(), eq(userId)); + } + + @Test + void restoreNetworkModification() { + List modificationUuids = List.of(UUID.randomUUID()); + studyController.stashNetworkModifications(studyUuid, nodeUuid, modificationUuids, false, userId); + + verify(rebuildNodeService, times(1)).restoreNetworkModifications(eq(studyUuid), eq(nodeUuid), eq(modificationUuids), eq(userId)); + verify(studyService, times(1)).buildNode(eq(studyUuid), eq(nodeUuid), any(), eq(userId)); + } + + // when a modification is enabled/disabled, this method is called + @Test + void updateNetworkModificationMetadata() { + List modificationUuids = List.of(UUID.randomUUID()); + NetworkModificationMetadata networkModificationMetadata = new NetworkModificationMetadata(true, "description", "type"); + studyController.updateNetworkModificationsMetadata(studyUuid, nodeUuid, modificationUuids, networkModificationMetadata, userId); + + verify(rebuildNodeService, times(1)).updateNetworkModificationsMetadata(eq(studyUuid), eq(nodeUuid), eq(modificationUuids), eq(userId), eq(networkModificationMetadata)); + verify(studyService, times(1)).buildNode(eq(studyUuid), eq(nodeUuid), any(), eq(userId)); + } + + @Test + void testUpdateNetworkModificationActivationByRootNetwork() { + Set modificationUuids = Set.of(UUID.randomUUID()); + studyController.updateNetworkModificationsActivation(studyUuid, rootNetworkUuid, nodeUuid, modificationUuids, true, userId); + + verify(rebuildNodeService, times(1)).updateNetworkModificationsActivation(eq(studyUuid), eq(nodeUuid), eq(rootNetworkUuid), eq(modificationUuids), eq(userId), eq(true)); + verify(studyService, times(1)).buildNode(eq(studyUuid), eq(nodeUuid), any(), eq(userId)); + } +} diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java b/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java index 29de0d9b36..a99fa5e20d 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java @@ -43,7 +43,6 @@ import org.gridsuite.study.server.dto.networkexport.NetworkExportReceiver; import org.gridsuite.study.server.elasticsearch.EquipmentInfosService; import org.gridsuite.study.server.elasticsearch.StudyInfosService; -import org.gridsuite.study.server.handler.RebuildPreviouslyBuiltNodeHandler; import org.gridsuite.study.server.networkmodificationtree.dto.*; import org.gridsuite.study.server.networkmodificationtree.entities.NetworkModificationNodeType; import org.gridsuite.study.server.networkmodificationtree.entities.NodeEntity; @@ -341,7 +340,7 @@ class StudyTest { @Autowired private StudyRepository studyRepository; - @Autowired + @MockitoSpyBean private NetworkModificationTreeService networkModificationTreeService; @Autowired @@ -371,8 +370,6 @@ class StudyTest { @MockitoSpyBean ConsumerService consumeService; - @MockitoBean - private RebuildPreviouslyBuiltNodeHandler rebuildPreviouslyBuiltNodeHandler; private void initMockBeans(Network network) { when(equipmentInfosService.getEquipmentInfosCount()).then((Answer) invocation -> Long.parseLong("32")); @@ -391,17 +388,7 @@ private void initMockBeans(Network network) { doNothing().when(networkStoreService).deleteNetwork(NETWORK_UUID); - doAnswer(inv -> { - inv.getArgument(inv.getArguments().length - 1, Runnable.class).run(); - return null; - }).when(rebuildPreviouslyBuiltNodeHandler) - .execute(any(), any(), any(), anyString(), any(Runnable.class)); - - doAnswer(inv -> { - inv.getArgument(inv.getArguments().length - 1, Runnable.class).run(); - return null; - }).when(rebuildPreviouslyBuiltNodeHandler) - .execute(any(), any(), anyString(), any(Runnable.class)); + doAnswer(invocation -> List.of()).when(networkModificationTreeService).getHighestNodeUuids(any(), any()); // Synchronize for tests synchronizeStudyServerExecutionService(studyServerExecutionService); From 2ed9281e7d2ff28aa8c0fc566e444d8a8577d3a4 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Mon, 19 Jan 2026 10:34:25 +0100 Subject: [PATCH 19/26] fix: PR remarks Signed-off-by: LE SAULNIER Kevin --- .../server/controller/StudyController.java | 3 +-- .../NodeRepository.java | 19 +++++++++++++++++++ .../NetworkModificationTreeService.java | 15 ++------------- .../{ => service}/RebuildNodeService.java | 4 +--- ...a => NetworkModificationTreeUnitTest.java} | 2 +- .../study/server/RebuildNodeServiceTest.java | 1 + .../StudyControllerRebuildNodeTest.java | 2 +- 7 files changed, 26 insertions(+), 20 deletions(-) rename src/main/java/org/gridsuite/study/server/{ => service}/RebuildNodeService.java (97%) rename src/test/java/org/gridsuite/study/server/{NetworkModificationTreeServiceUnitTest.java => NetworkModificationTreeUnitTest.java} (98%) diff --git a/src/main/java/org/gridsuite/study/server/controller/StudyController.java b/src/main/java/org/gridsuite/study/server/controller/StudyController.java index 30af668ed4..84b9ce1af5 100644 --- a/src/main/java/org/gridsuite/study/server/controller/StudyController.java +++ b/src/main/java/org/gridsuite/study/server/controller/StudyController.java @@ -18,9 +18,8 @@ import org.apache.commons.lang3.StringUtils; import org.gridsuite.filter.globalfilter.GlobalFilter; import org.gridsuite.filter.utils.EquipmentType; -import org.gridsuite.study.server.RebuildNodeService; +import org.gridsuite.study.server.service.RebuildNodeService; import org.gridsuite.study.server.StudyApi; -import org.gridsuite.study.server.StudyConstants.*; import org.gridsuite.study.server.dto.*; import org.gridsuite.study.server.dto.computation.LoadFlowComputationInfos; import org.gridsuite.study.server.dto.diagramgridlayout.DiagramGridLayout; diff --git a/src/main/java/org/gridsuite/study/server/repository/networkmodificationtree/NodeRepository.java b/src/main/java/org/gridsuite/study/server/repository/networkmodificationtree/NodeRepository.java index 9eed7c1b8b..38c3c9ce4d 100644 --- a/src/main/java/org/gridsuite/study/server/repository/networkmodificationtree/NodeRepository.java +++ b/src/main/java/org/gridsuite/study/server/repository/networkmodificationtree/NodeRepository.java @@ -62,6 +62,25 @@ public interface NodeRepository extends JpaRepository { "WHERE n.id_node IN (SELECT nh.id_node FROM NodeHierarchy nh) AND n.id_node != :nodeUuid") List findAllChildren(UUID nodeUuid); + @NativeQuery("WITH RECURSIVE ancestors (id_node, parent_node) AS ( " + + " SELECT n.id_node, n.parent_node " + + " FROM NODE n " + + " WHERE n.id_node = :childNodeUuid " + + + " UNION ALL " + + + " SELECT p.id_node, p.parent_node " + + " FROM NODE p " + + " INNER JOIN ancestors a ON p.id_node = a.parent_node " + + ") " + + "SELECT EXISTS ( " + + " SELECT 1 " + + " FROM ancestors " + + " WHERE id_node = :ancestorNodeUuid " + + ")" + ) + boolean isAncestor(UUID ancestorNodeUuid, UUID childNodeUuid); + List findAllByStudyIdAndStashedAndParentNodeIdNodeOrderByStashDateDesc(UUID id, boolean stashed, UUID parentNode); Optional findByStudyIdAndType(UUID id, NodeType type); diff --git a/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java b/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java index ee74708625..61fa775b06 100644 --- a/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java +++ b/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java @@ -1274,28 +1274,17 @@ public List getHighestNodeUuids(UUID node1Uuid, UUID node2Uuid) { return List.of(node1Uuid); } - if (isAncestor(node1Uuid, node2Uuid)) { + if (nodesRepository.isAncestor(node1Uuid, node2Uuid)) { return List.of(node1Uuid); } - if (isAncestor(node2Uuid, node1Uuid)) { + if (nodesRepository.isAncestor(node2Uuid, node1Uuid)) { return List.of(node2Uuid); } return List.of(node1Uuid, node2Uuid); } - private boolean isAncestor(UUID ancestorUuid, UUID nodeUuid) { - NodeEntity current = getNodeEntity(nodeUuid).getParentNode(); - while (current != null) { - if (current.getIdNode().equals(ancestorUuid)) { - return true; - } - current = current.getParentNode(); - } - return false; - } - @Transactional public void updateExportNetworkStatus(UUID nodeUuid, UUID exportUuid, ExportNetworkStatus status) { nodesRepository.getReferenceById(nodeUuid).getNodeExportNetwork().add(NodeExportEmbeddable.toNodeExportEmbeddable(exportUuid, status)); diff --git a/src/main/java/org/gridsuite/study/server/RebuildNodeService.java b/src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java similarity index 97% rename from src/main/java/org/gridsuite/study/server/RebuildNodeService.java rename to src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java index 1b0071c19f..7364b4a5d8 100644 --- a/src/main/java/org/gridsuite/study/server/RebuildNodeService.java +++ b/src/main/java/org/gridsuite/study/server/service/RebuildNodeService.java @@ -4,11 +4,9 @@ * 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; +package org.gridsuite.study.server.service; import org.gridsuite.study.server.dto.modification.NetworkModificationMetadata; -import org.gridsuite.study.server.service.NetworkModificationTreeService; -import org.gridsuite.study.server.service.StudyService; import org.springframework.stereotype.Service; import java.util.List; diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTreeServiceUnitTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTreeUnitTest.java similarity index 98% rename from src/test/java/org/gridsuite/study/server/NetworkModificationTreeServiceUnitTest.java rename to src/test/java/org/gridsuite/study/server/NetworkModificationTreeUnitTest.java index a20976ac73..8db17a7cca 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationTreeServiceUnitTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTreeUnitTest.java @@ -16,7 +16,7 @@ @SpringBootTest @DisableElasticsearch -class NetworkModificationTreeServiceUnitTest { +class NetworkModificationTreeUnitTest { @Autowired private NodeRepository nodeRepository; diff --git a/src/test/java/org/gridsuite/study/server/RebuildNodeServiceTest.java b/src/test/java/org/gridsuite/study/server/RebuildNodeServiceTest.java index 5d871a7708..6b3886b7cf 100644 --- a/src/test/java/org/gridsuite/study/server/RebuildNodeServiceTest.java +++ b/src/test/java/org/gridsuite/study/server/RebuildNodeServiceTest.java @@ -3,6 +3,7 @@ import org.gridsuite.study.server.networkmodificationtree.dto.BuildStatus; import org.gridsuite.study.server.networkmodificationtree.dto.NodeBuildStatus; import org.gridsuite.study.server.service.NetworkModificationTreeService; +import org.gridsuite.study.server.service.RebuildNodeService; import org.gridsuite.study.server.service.StudyService; import org.gridsuite.study.server.utils.elasticsearch.DisableElasticsearch; import org.junit.jupiter.api.BeforeEach; diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeTest.java b/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeTest.java index 01288cb82b..0c9e04e36c 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeTest.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeTest.java @@ -6,7 +6,7 @@ */ package org.gridsuite.study.server.studycontroller; -import org.gridsuite.study.server.RebuildNodeService; +import org.gridsuite.study.server.service.RebuildNodeService; import org.gridsuite.study.server.StudyConstants; import org.gridsuite.study.server.controller.StudyController; import org.gridsuite.study.server.dto.modification.NetworkModificationMetadata; From 028a7971296df8cf8fd095c59cca188be1a0a438 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Mon, 19 Jan 2026 11:08:04 +0100 Subject: [PATCH 20/26] fix: tests Signed-off-by: LE SAULNIER Kevin --- .../java/org/gridsuite/study/server/LoadFLowUnitTest.java | 3 +++ .../org/gridsuite/study/server/NetworkModificationTest.java | 4 ++-- .../java/org/gridsuite/study/server/StudyServiceTest.java | 2 ++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/gridsuite/study/server/LoadFLowUnitTest.java b/src/test/java/org/gridsuite/study/server/LoadFLowUnitTest.java index 19e2b067e4..9c88c8baf4 100644 --- a/src/test/java/org/gridsuite/study/server/LoadFLowUnitTest.java +++ b/src/test/java/org/gridsuite/study/server/LoadFLowUnitTest.java @@ -11,6 +11,8 @@ import org.gridsuite.study.server.dto.InvalidateNodeInfos; import org.gridsuite.study.server.dto.InvalidateNodeTreeParameters; import org.gridsuite.study.server.dto.workflow.RerunLoadFlowInfos; +import org.gridsuite.study.server.networkmodificationtree.dto.BuildStatus; +import org.gridsuite.study.server.networkmodificationtree.dto.NodeBuildStatus; import org.gridsuite.study.server.notification.NotificationService; import org.gridsuite.study.server.repository.StudyEntity; import org.gridsuite.study.server.repository.StudyRepository; @@ -163,6 +165,7 @@ private void testRerunLoadFlowSecurityNode(boolean withRatioTapChangers) { when(networkModificationTreeService.invalidateNodeTree(nodeUuid, rootNetworkUuid, expectedInvalidationParameters)).thenReturn(invalidateNodeInfos); when(rootNetworkService.getNetworkUuid(rootNetworkUuid)).thenReturn(networkUuid); when(networkModificationTreeService.getBuildInfos(nodeUuid, rootNetworkUuid)).thenReturn(buildInfos); + when(networkModificationTreeService.getNodeBuildStatus(nodeUuid, rootNetworkUuid)).thenReturn(NodeBuildStatus.from(BuildStatus.NOT_BUILT)); doReturn(loadflowResultUuid).when(loadFlowService).createRunningStatus(); // execute loadflow rerun diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java index c5687ef2f0..babb00f7c3 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java @@ -586,9 +586,9 @@ void testBuild() throws Exception { assertEquals(BuildStatus.NOT_BUILT, networkModificationTreeService.getNodeBuildStatus(modificationNode4.getId(), rootNetworkUuid).getGlobalBuildStatus()); assertEquals(BuildStatus.BUILT, networkModificationTreeService.getNodeBuildStatus(modificationNode5.getId(), rootNetworkUuid).getGlobalBuildStatus()); - // Mark the node 3 status as built + // Mark the node 3 status as not built rootNetworkNodeInfo3Entity = rootNetworkNodeInfoRepository.findByNodeInfoIdAndRootNetworkId(modificationNode3.getId(), studyTestUtils.getOneRootNetworkUuid(studyNameUserIdUuid)).orElseThrow(() -> new StudyException(NOT_FOUND, "Root network not found")); - rootNetworkNodeInfo3Entity.setNodeBuildStatus(NodeBuildStatusEmbeddable.from(BuildStatus.BUILT)); + rootNetworkNodeInfo3Entity.setNodeBuildStatus(NodeBuildStatusEmbeddable.from(BuildStatus.NOT_BUILT)); rootNetworkNodeInfoRepository.save(rootNetworkNodeInfo3Entity); // build modificationNode3 and stop build diff --git a/src/test/java/org/gridsuite/study/server/StudyServiceTest.java b/src/test/java/org/gridsuite/study/server/StudyServiceTest.java index e2b75e5a5a..3e60cd78ee 100644 --- a/src/test/java/org/gridsuite/study/server/StudyServiceTest.java +++ b/src/test/java/org/gridsuite/study/server/StudyServiceTest.java @@ -122,6 +122,7 @@ void testBuildFirstLevelChildren() { // quota not reached, all first level children of N1 will be built doReturn(Optional.of(10)).when(userAdminService).getUserMaxAllowedBuilds(userId); doReturn(0L).when(networkModificationTreeService).countBuiltNodes(studyUuid, rootNetworkUuid); + mockNodeBuild(node2.getIdNode(), rootNetworkUuid); mockNodeBuild(node3.getIdNode(), rootNetworkUuid); @@ -216,6 +217,7 @@ private void mockNodeBuild(UUID nodeUuid, UUID rootNetworkUuid) { doReturn(new BuildInfos()).when(networkModificationTreeService).getBuildInfos(nodeUuid, rootNetworkUuid); doNothing().when(networkModificationTreeService).setModificationReports(eq(nodeUuid), eq(rootNetworkUuid), any()); doNothing().when(networkModificationTreeService).updateNodeBuildStatus(nodeUuid, rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILDING)); + doReturn(NodeBuildStatus.from(BuildStatus.NOT_BUILT)).when(networkModificationTreeService).getNodeBuildStatus(nodeUuid, rootNetworkUuid); } private void verifyNodeBuild(UUID nodeUuid, UUID rootNetworkUuid) { From f74f6ba156df8e3ab3a08b6274f1b3c1a1bf62c3 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Mon, 19 Jan 2026 14:28:09 +0100 Subject: [PATCH 21/26] fix: copyright Signed-off-by: LE SAULNIER Kevin --- .../study/server/NetworkModificationTreeUnitTest.java | 6 ++++++ .../org/gridsuite/study/server/RebuildNodeServiceTest.java | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTreeUnitTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTreeUnitTest.java index 8db17a7cca..71eabe25f1 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationTreeUnitTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTreeUnitTest.java @@ -1,3 +1,9 @@ +/** + * 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; import org.gridsuite.study.server.networkmodificationtree.entities.NodeEntity; diff --git a/src/test/java/org/gridsuite/study/server/RebuildNodeServiceTest.java b/src/test/java/org/gridsuite/study/server/RebuildNodeServiceTest.java index 6b3886b7cf..3c9ec8a72b 100644 --- a/src/test/java/org/gridsuite/study/server/RebuildNodeServiceTest.java +++ b/src/test/java/org/gridsuite/study/server/RebuildNodeServiceTest.java @@ -1,3 +1,9 @@ +/** + * 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; import org.gridsuite.study.server.networkmodificationtree.dto.BuildStatus; From 49865267b674456b2c619a27c92f093803c0d5da Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Mon, 19 Jan 2026 15:57:39 +0100 Subject: [PATCH 22/26] fix: move post build action in separate transactions Signed-off-by: LE SAULNIER Kevin --- .../server/controller/StudyController.java | 10 ++++++-- .../study/server/service/ConsumerService.java | 4 ++-- .../study/server/service/StudyService.java | 24 +++++++------------ src/main/resources/application-local.yml | 7 ++++++ .../gridsuite/study/server/LoadFlowTest.java | 9 ++++--- .../study/server/NetworkModificationTest.java | 11 +++++---- .../server/NetworkModificationTreeTest.java | 14 ++++++++--- .../study/server/NodeSequenceTest.java | 12 +--------- ...ControllerDynamicSecurityAnalysisTest.java | 12 ++++++++-- .../StudyControllerDynamicSimulationTest.java | 7 +++++- .../study/server/VoltageInitTest.java | 10 ++++---- 11 files changed, 69 insertions(+), 51 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/controller/StudyController.java b/src/main/java/org/gridsuite/study/server/controller/StudyController.java index 84b9ce1af5..144f5e1f28 100644 --- a/src/main/java/org/gridsuite/study/server/controller/StudyController.java +++ b/src/main/java/org/gridsuite/study/server/controller/StudyController.java @@ -1477,7 +1477,11 @@ public ResponseEntity createNode(@RequestBody NetworkMo @Parameter(description = "parent id of the node created") @PathVariable(name = "id") UUID referenceId, @Parameter(description = "node is inserted before the given node ID") @RequestParam(name = "mode", required = false, defaultValue = "CHILD") InsertMode insertMode, @RequestHeader(HEADER_USER_ID) String userId) { - return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(studyService.createNode(studyUuid, referenceId, node, insertMode, userId)); + + NetworkModificationNode newNode = studyService.createNode(studyUuid, referenceId, node, insertMode, userId); + studyService.createNodePostAction(studyUuid, referenceId, newNode, userId); + + return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(newNode); } @PostMapping(value = "/studies/{studyUuid}/tree/nodes/{id}", params = {"sequenceType"}) @@ -1490,7 +1494,9 @@ public ResponseEntity createSequence( @Parameter(description = "parent id of the node created") @PathVariable(name = "id") UUID referenceId, @Parameter(description = "sequence to create") @RequestParam("sequenceType") NodeSequenceType nodeSequenceType, @RequestHeader(HEADER_USER_ID) String userId) { - return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(studyService.createSequence(studyUuid, referenceId, nodeSequenceType, userId)); + NetworkModificationNode sequenceParentNode = studyService.createSequence(studyUuid, referenceId, nodeSequenceType, userId); + studyService.createSequencePostAction(studyUuid, referenceId, nodeSequenceType, userId); + return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(sequenceParentNode); } @DeleteMapping(value = "/studies/{studyUuid}/tree/nodes") diff --git a/src/main/java/org/gridsuite/study/server/service/ConsumerService.java b/src/main/java/org/gridsuite/study/server/service/ConsumerService.java index b0cee999cf..efa5cb3482 100644 --- a/src/main/java/org/gridsuite/study/server/service/ConsumerService.java +++ b/src/main/java/org/gridsuite/study/server/service/ConsumerService.java @@ -641,10 +641,9 @@ public void consumeCalculationResult(Message msg, ComputationType comput // unblock node handleUnblockNode(receiverObj, computationType); - // build 1st level children if loadflow is converged, and node if of security type UUID studyUuid = networkModificationTreeService.getStudyUuidForNodeId(receiverObj.getNodeUuid()); - String userId = (String) msg.getHeaders().get(HEADER_USER_ID); if (computationType == LOAD_FLOW) { + String userId = (String) msg.getHeaders().get(HEADER_USER_ID); handleLoadFlowSuccess(studyUuid, receiverObj.getNodeUuid(), receiverObj.getRootNetworkUuid(), resultUuid, userId); } @@ -655,6 +654,7 @@ public void consumeCalculationResult(Message msg, ComputationType comput } private void handleLoadFlowSuccess(UUID studyUuid, UUID nodeUuid, UUID rootNetworkUuid, UUID resultUuid, String userId) { + // Build 1st level children if loadflow is converged, and node is a security type if (userId != null && networkModificationTreeService.isSecurityNode(nodeUuid)) { LoadFlowStatus loadFlowStatus = loadFlowService.getLoadFlowStatus(resultUuid); if (loadFlowStatus == LoadFlowStatus.CONVERGED) { diff --git a/src/main/java/org/gridsuite/study/server/service/StudyService.java b/src/main/java/org/gridsuite/study/server/service/StudyService.java index 10ea41fc0a..bc6f7926f8 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -3529,13 +3529,6 @@ public NetworkModificationNode createNode(UUID studyUuid, UUID nodeId, NetworkMo networkModificationTreeService.assertCreateNode(nodeId, nodeInfo.getNodeType(), insertMode); NetworkModificationNode newNode = networkModificationTreeService.createNode(study, nodeId, nodeInfo, insertMode, userId); - try { - createNodePostAction(studyUuid, nodeId, newNode, userId); - } catch (Exception e) { - // if post action fails, don't interrupt / rollback current transaction - LOGGER.warn(e.getMessage()); - } - UUID parentUuid = networkModificationTreeService.getParentNodeUuid(newNode.getId()).orElse(null); notificationService.emitNodeInserted(study.getId(), parentUuid, newNode.getId(), insertMode, nodeId); // userId is null when creating initial nodes, we don't need to send element update notifications in this case @@ -3545,19 +3538,26 @@ public NetworkModificationNode createNode(UUID studyUuid, UUID nodeId, NetworkMo return newNode; } - private void createNodePostAction(UUID studyUuid, UUID parentNodeUuid, NetworkModificationNode newNode, String userId) { + @Transactional + public void createNodePostAction(UUID studyUuid, UUID parentNodeUuid, NetworkModificationNode newNode, String userId) { if (newNode.isSecurityNode() && networkModificationTreeService.isRootOrConstructionNode(parentNodeUuid)) { buildNode(studyUuid, newNode.getId(), userId); } } + @Transactional + public void createSequencePostAction(UUID studyUuid, UUID sequenceParentNode, NodeSequenceType nodeSequenceType, String userId) { + if (nodeSequenceType == NodeSequenceType.SECURITY_SEQUENCE) { + buildNode(studyUuid, sequenceParentNode, userId); + } + } + @Transactional public NetworkModificationNode createSequence(UUID studyUuid, UUID parentNodeUuid, NodeSequenceType nodeSequenceType, String userId) { StudyEntity study = getStudy(studyUuid); networkModificationTreeService.assertIsRootOrConstructionNode(parentNodeUuid); NetworkModificationNode newParentNode = networkModificationTreeService.createTreeNodeFromNodeSequence(study, parentNodeUuid, nodeSequenceType); - createSequencePostAction(studyUuid, newParentNode.getId(), nodeSequenceType, userId); notificationService.emitSubtreeInserted(study.getId(), newParentNode.getId(), parentNodeUuid); // userId is null when creating initial nodes, we don't need to send element update notifications in this case @@ -3567,12 +3567,6 @@ public NetworkModificationNode createSequence(UUID studyUuid, UUID parentNodeUui return newParentNode; } - private void createSequencePostAction(UUID studyUuid, UUID sequenceParentNode, NodeSequenceType nodeSequenceType, String userId) { - if (nodeSequenceType == NodeSequenceType.SECURITY_SEQUENCE) { - buildNode(studyUuid, sequenceParentNode, userId); - } - } - private List getStudyRootNetworks(UUID studyUuid) { StudyEntity studyEntity = getStudy(studyUuid); return studyEntity.getRootNetworks(); diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 237f8990cb..045f905a5b 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -1,6 +1,13 @@ server: port: 5001 +logging: + level: + org.springframework.orm.jpa: DEBUG + org.springframework.transaction: DEBUG + org.hibernate.SQL: DEBUG + org.hibernate.orm.jdbc.bind: DEBUG + spring: rabbitmq: addresses: localhost diff --git a/src/test/java/org/gridsuite/study/server/LoadFlowTest.java b/src/test/java/org/gridsuite/study/server/LoadFlowTest.java index 294da78f56..2bee5c8018 100644 --- a/src/test/java/org/gridsuite/study/server/LoadFlowTest.java +++ b/src/test/java/org/gridsuite/study/server/LoadFlowTest.java @@ -1054,11 +1054,8 @@ private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UU jsonObject.put("modificationGroupUuid", modificationGroupUuid); mnBodyJson = jsonObject.toString(); - if (nodeType == NetworkModificationNodeType.SECURITY) { - // with new development, when node is of security type, we build it after creation - // to prevent existing tests to fail, we set it to 0 to keep previous behaviour -> we don't build security node after creation - doReturn(Optional.of(0)).when(userAdminService).getUserMaxAllowedBuilds("userId"); - } + reset(studyService); + doNothing().when(studyService).createNodePostAction(eq(studyUuid), eq(parentNodeUuid), any(NetworkModificationNode.class), eq("userId")); mockMvc.perform(post("/v1/studies/{studyUuid}/tree/nodes/{id}", studyUuid, parentNodeUuid).content(mnBodyJson).contentType(MediaType.APPLICATION_JSON).header("userId", "userId")) .andExpect(status().isOk()); @@ -1070,6 +1067,8 @@ private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UU rootNetworkNodeInfoService.updateRootNetworkNode(modificationNode.getId(), studyTestUtils.getOneRootNetworkUuid(studyUuid), RootNetworkNodeInfo.builder().variantId(variantId).build()); + verify(studyService, times(1)).createNodePostAction(eq(studyUuid), eq(parentNodeUuid), any(NetworkModificationNode.class), eq("userId")); + return modificationNode; } diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java index babb00f7c3..b755279cf4 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java @@ -264,6 +264,8 @@ class NetworkModificationTest { @Autowired private PccMinService pccMinService; + @MockitoSpyBean + private StudyService studyService; @BeforeEach void setup(final MockWebServer server) { @@ -2967,11 +2969,8 @@ private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UU jsonObject.put("modificationGroupUuid", modificationGroupUuid); mnBodyJson = jsonObject.toString(); - if (nodeType == NetworkModificationNodeType.SECURITY) { - // with new development, when node is of security type, we build it after creation - // to prevent existing tests to fail, we set it to 0 to keep previous behaviour -> we don't build security node after creation - doReturn(Optional.of(0)).when(userAdminService).getUserMaxAllowedBuilds("userId"); - } + reset(studyService); + doNothing().when(studyService).createNodePostAction(eq(studyUuid), eq(parentNodeUuid), any(NetworkModificationNode.class), eq(userId)); mockMvc.perform(post("/v1/studies/{studyUuid}/tree/nodes/{id}", studyUuid, parentNodeUuid).header(USER_ID_HEADER, userId).content(mnBodyJson).contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()); @@ -2984,6 +2983,8 @@ private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UU rootNetworkNodeInfoService.updateRootNetworkNode(modificationNode.getId(), studyTestUtils.getOneRootNetworkUuid(studyUuid), RootNetworkNodeInfo.builder().variantId(variantId).nodeBuildStatus(NodeBuildStatus.from(buildStatus)).build()); + verify(studyService, times(1)).createNodePostAction(eq(studyUuid), eq(parentNodeUuid), any(NetworkModificationNode.class), eq(userId)); + return modificationNode; } diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTreeTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTreeTest.java index 1b1a57f7cb..a295d7d5da 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationTreeTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTreeTest.java @@ -82,10 +82,11 @@ import static org.gridsuite.study.server.notification.NotificationService.*; import static org.gridsuite.study.server.service.NetworkModificationTreeService.ROOT_NODE_NAME; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.times; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -208,6 +209,8 @@ class NetworkModificationTreeTest { private TestUtils studyTestUtils; @Autowired private RootNetworkRepository rootNetworkRepository; + @MockitoSpyBean + private StudyService studyService; @BeforeEach void setUp(final MockWebServer server) { @@ -1324,6 +1327,9 @@ private void createNode(UUID studyUuid, AbstractNode parentNode, NetworkModifica jsonObject.put("modificationGroupUuid", modificationGroupUuid); newNodeBodyJson = jsonObject.toString(); + reset(studyService); + doNothing().when(studyService).createNodePostAction(eq(studyUuid), eq(parentNode.getId()), any(NetworkModificationNode.class), eq("userId")); + mockMvc.perform(post("/v1/studies/{studyUuid}/tree/nodes/{id}", studyUuid, parentNode.getId()) .contentType(MediaType.APPLICATION_JSON) .content(newNodeBodyJson) @@ -1335,6 +1341,8 @@ private void createNode(UUID studyUuid, AbstractNode parentNode, NetworkModifica newNode.setId(UUID.fromString(String.valueOf(mess.getHeaders().get(NotificationService.HEADER_NEW_NODE)))); assertEquals(InsertMode.CHILD.name(), mess.getHeaders().get(NotificationService.HEADER_INSERT_MODE)); + verify(studyService, times(1)).createNodePostAction(eq(studyUuid), eq(parentNode.getId()), any(NetworkModificationNode.class), eq("userId")); + rootNetworkNodeInfoService.updateRootNetworkNode(newNode.getId(), studyTestUtils.getOneRootNetworkUuid(studyUuid), RootNetworkNodeInfo.builder() .variantId(newNode.getVariantId()) diff --git a/src/test/java/org/gridsuite/study/server/NodeSequenceTest.java b/src/test/java/org/gridsuite/study/server/NodeSequenceTest.java index f41ee1e2c7..ffe15330e8 100644 --- a/src/test/java/org/gridsuite/study/server/NodeSequenceTest.java +++ b/src/test/java/org/gridsuite/study/server/NodeSequenceTest.java @@ -97,10 +97,7 @@ void testCreateSecuritySequence() { AbstractNode nNode = parentOfSubtree.getChildren().getFirst(); checkSecuritySequence(nNode, ""); - // verify nNode has been built - verify(userAdminService, times(1)).getUserMaxAllowedBuilds(userId); - verify(networkModificationService, times(1)).buildNode(eq(nNode.getId()), any(), any(), eq(null)); - // verify notifications + // verify notifications verify(notificationService, times(1)).emitSubtreeInserted(studyUuid, nNode.getId(), parentOfSubtree.getId()); verify(notificationService, times(1)).emitElementUpdated(studyUuid, userId); @@ -116,9 +113,6 @@ void testCreateSecuritySequenceOnRootNode() { AbstractNode nNode = parentOfSubtree.getChildren().getFirst(); checkSecuritySequence(nNode, ""); - // verify nNode has been built - verify(userAdminService, times(1)).getUserMaxAllowedBuilds(userId); - verify(networkModificationService, times(1)).buildNode(eq(nNode.getId()), any(), any(), eq(null)); // verify notifications verify(notificationService, times(1)).emitSubtreeInserted(studyUuid, nNode.getId(), parentOfSubtree.getId()); verify(notificationService, times(1)).emitElementUpdated(studyUuid, userId); @@ -141,10 +135,6 @@ void testCreateTwoSecuritySequence() { AbstractNode nNode2 = parentOfSubtree2.getChildren().getFirst(); checkSecuritySequence(nNode2, " (1)"); - // verify nNode and nNode2 have been built - verify(userAdminService, times(2)).getUserMaxAllowedBuilds(userId); - verify(networkModificationService, times(1)).buildNode(eq(nNode.getId()), any(), any(), eq(null)); - verify(networkModificationService, times(1)).buildNode(eq(nNode2.getId()), any(), any(), eq(null)); //verify notifications verify(notificationService, times(1)).emitSubtreeInserted(studyUuid, nNode.getId(), constructionNode.getId()); verify(notificationService, times(1)).emitSubtreeInserted(studyUuid, nNode2.getId(), constructionNode2.getId()); diff --git a/src/test/java/org/gridsuite/study/server/StudyControllerDynamicSecurityAnalysisTest.java b/src/test/java/org/gridsuite/study/server/StudyControllerDynamicSecurityAnalysisTest.java index a4f4ac23b0..99a2ce8882 100644 --- a/src/test/java/org/gridsuite/study/server/StudyControllerDynamicSecurityAnalysisTest.java +++ b/src/test/java/org/gridsuite/study/server/StudyControllerDynamicSecurityAnalysisTest.java @@ -62,8 +62,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.BDDMockito.any; import static org.mockito.BDDMockito.eq; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.doNothing; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -139,6 +139,9 @@ class StudyControllerDynamicSecurityAnalysisTest { @Autowired private TestUtils studyTestUtils; + @MockitoSpyBean + private StudyService studyService; + @MockitoSpyBean private RootNetworkNodeInfoRepository spyRootNetworkNodeInfoRepository; @@ -199,6 +202,9 @@ private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UU .nodeBuildStatus(NodeBuildStatus.from(buildStatus)) .children(Collections.emptyList()).build(); + reset(studyService); + doNothing().when(studyService).createNodePostAction(eq(studyUuid), eq(parentNodeUuid), any(NetworkModificationNode.class), eq("userId")); + studyClient.perform(post("/v1/studies/{studyUuid}/tree/nodes/{id}", studyUuid, parentNodeUuid) .content(objectMapper.writeValueAsString(modificationNode)) .contentType(MediaType.APPLICATION_JSON) @@ -213,6 +219,8 @@ private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UU rootNetworkNodeInfoService.updateRootNetworkNode(newNodeId, studyTestUtils.getOneRootNetworkUuid(studyUuid), RootNetworkNodeInfo.builder().variantId(variantId).build()); + verify(studyService, times(1)).createNodePostAction(eq(studyUuid), eq(parentNodeUuid), any(NetworkModificationNode.class), eq("userId")); + return modificationNode; } diff --git a/src/test/java/org/gridsuite/study/server/StudyControllerDynamicSimulationTest.java b/src/test/java/org/gridsuite/study/server/StudyControllerDynamicSimulationTest.java index 2a92fad117..eeecd83253 100644 --- a/src/test/java/org/gridsuite/study/server/StudyControllerDynamicSimulationTest.java +++ b/src/test/java/org/gridsuite/study/server/StudyControllerDynamicSimulationTest.java @@ -70,7 +70,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.BDDMockito.any; import static org.mockito.BDDMockito.eq; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.times; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -254,6 +255,8 @@ private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UU .nodeBuildStatus(NodeBuildStatus.from(buildStatus)) .children(Collections.emptyList()).build(); + doNothing().when(studyService).createNodePostAction(eq(studyUuid), eq(parentNodeUuid), any(NetworkModificationNode.class), eq("userId")); + studyClient.perform(post("/v1/studies/{studyUuid}/tree/nodes/{id}", studyUuid, parentNodeUuid) .content(objectMapper.writeValueAsString(modificationNode)) .contentType(MediaType.APPLICATION_JSON) @@ -268,6 +271,8 @@ private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UU rootNetworkNodeInfoService.updateRootNetworkNode(newNodeId, studyTestUtils.getOneRootNetworkUuid(studyUuid), RootNetworkNodeInfo.builder().variantId(variantId).build()); + verify(studyService, times(1)).createNodePostAction(eq(studyUuid), eq(parentNodeUuid), any(NetworkModificationNode.class), eq("userId")); + return modificationNode; } diff --git a/src/test/java/org/gridsuite/study/server/VoltageInitTest.java b/src/test/java/org/gridsuite/study/server/VoltageInitTest.java index 38ae773323..460a8d18f8 100644 --- a/src/test/java/org/gridsuite/study/server/VoltageInitTest.java +++ b/src/test/java/org/gridsuite/study/server/VoltageInitTest.java @@ -243,6 +243,9 @@ class VoltageInitTest { @MockitoSpyBean private UserAdminService userAdminService; + @MockitoSpyBean + private StudyService studyService; + @MockitoBean private NetworkStoreService networkStoreService; @@ -1176,11 +1179,7 @@ private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UU jsonObject.put("modificationGroupUuid", modificationGroupUuid); mnBodyJson = jsonObject.toString(); - if (nodeType == NetworkModificationNodeType.SECURITY) { - // with new development, when node is of security type, we build it after creation - // to prevent existing tests to fail, we set it to 0 to keep previous behaviour -> we don't build security node after creation - doReturn(Optional.of(0)).when(userAdminService).getUserMaxAllowedBuilds("userId"); - } + doNothing().when(studyService).createNodePostAction(eq(studyUuid), eq(parentNodeUuid), any(NetworkModificationNode.class), eq("userId")); mockMvc.perform(post("/v1/studies/{studyUuid}/tree/nodes/{id}", studyUuid, parentNodeUuid).content(mnBodyJson).contentType(MediaType.APPLICATION_JSON).header("userId", "userId")) .andExpect(status().isOk()); @@ -1192,6 +1191,7 @@ private NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UU rootNetworkNodeInfoService.updateRootNetworkNode(modificationNode.getId(), studyTestUtils.getOneRootNetworkUuid(studyUuid), RootNetworkNodeInfo.builder().variantId(variantId).nodeBuildStatus(NodeBuildStatus.from(buildStatus)).build()); + verify(studyService, times(1)).createNodePostAction(eq(studyUuid), eq(parentNodeUuid), any(NetworkModificationNode.class), eq("userId")); return modificationNode; } From 305bfa530070e83c594460a30ad9fda62f453f64 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Mon, 19 Jan 2026 16:53:23 +0100 Subject: [PATCH 23/26] fix: sonar issues Signed-off-by: LE SAULNIER Kevin --- .../study/server/RebuildNodeServiceTest.java | 2 -- .../StudyControllerRebuildNodeTest.java | 15 +++++++-------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/test/java/org/gridsuite/study/server/RebuildNodeServiceTest.java b/src/test/java/org/gridsuite/study/server/RebuildNodeServiceTest.java index 3c9ec8a72b..16f020e714 100644 --- a/src/test/java/org/gridsuite/study/server/RebuildNodeServiceTest.java +++ b/src/test/java/org/gridsuite/study/server/RebuildNodeServiceTest.java @@ -14,7 +14,6 @@ import org.gridsuite.study.server.utils.elasticsearch.DisableElasticsearch; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; @@ -95,7 +94,6 @@ void testRebuildMultipleRootNetworks() { @Test void testRebuildMultipleRootNetworksAndNodes() { - Runnable runnable = Mockito.spy(Runnable.class); doReturn( Map.of( rootNetworkUuid, NodeBuildStatus.from(BuildStatus.BUILT), diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeTest.java b/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeTest.java index 0c9e04e36c..b275e1e6f8 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeTest.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/StudyControllerRebuildNodeTest.java @@ -69,7 +69,7 @@ void setUp() { void testCreateNetworkModification() { studyController.createNetworkModification(studyUuid, nodeUuid, "modificationBody", userId); - verify(rebuildNodeService, times(1)).createNetworkModification(eq(studyUuid), eq(nodeUuid), eq("modificationBody"), eq(userId)); + verify(rebuildNodeService, times(1)).createNetworkModification(studyUuid, nodeUuid, "modificationBody", userId); verify(studyService, times(1)).buildNode(eq(studyUuid), eq(nodeUuid), any(), eq(userId)); } @@ -87,16 +87,15 @@ void testMoveNetworkModifications() { UUID originNodeUuid = UUID.randomUUID(); studyController.moveOrCopyModifications(studyUuid, nodeUuid, StudyConstants.ModificationsActionType.MOVE, studyUuid, originNodeUuid, modificationUuids, userId); - verify(rebuildNodeService, times(1)).moveNetworkModifications(eq(studyUuid), eq(nodeUuid), eq(originNodeUuid), eq(modificationUuids), eq(userId)); + verify(rebuildNodeService, times(1)).moveNetworkModifications(studyUuid, nodeUuid, originNodeUuid, modificationUuids, userId); verify(studyService, times(1)).buildNode(eq(studyUuid), eq(nodeUuid), any(), eq(userId)); } @Test void updateNetworkModification() { - UUID modificationUuid = UUID.randomUUID(); studyController.updateNetworkModification(studyUuid, nodeUuid, modificationUuid, "modificationAttributes", userId); - verify(rebuildNodeService, times(1)).updateNetworkModification(eq(studyUuid), eq("modificationAttributes"), eq(nodeUuid), eq(modificationUuid), eq(userId)); + verify(rebuildNodeService, times(1)).updateNetworkModification(studyUuid, "modificationAttributes", nodeUuid, modificationUuid, userId); verify(studyService, times(1)).buildNode(eq(studyUuid), eq(nodeUuid), any(), eq(userId)); } @@ -105,7 +104,7 @@ void stashNetworkModification() { List modificationUuids = List.of(UUID.randomUUID()); studyController.stashNetworkModifications(studyUuid, nodeUuid, modificationUuids, true, userId); - verify(rebuildNodeService, times(1)).stashNetworkModifications(eq(studyUuid), eq(nodeUuid), eq(modificationUuids), eq(userId)); + verify(rebuildNodeService, times(1)).stashNetworkModifications(studyUuid, nodeUuid, modificationUuids, userId); verify(studyService, times(1)).buildNode(eq(studyUuid), eq(nodeUuid), any(), eq(userId)); } @@ -114,7 +113,7 @@ void restoreNetworkModification() { List modificationUuids = List.of(UUID.randomUUID()); studyController.stashNetworkModifications(studyUuid, nodeUuid, modificationUuids, false, userId); - verify(rebuildNodeService, times(1)).restoreNetworkModifications(eq(studyUuid), eq(nodeUuid), eq(modificationUuids), eq(userId)); + verify(rebuildNodeService, times(1)).restoreNetworkModifications(studyUuid, nodeUuid, modificationUuids, userId); verify(studyService, times(1)).buildNode(eq(studyUuid), eq(nodeUuid), any(), eq(userId)); } @@ -125,7 +124,7 @@ void updateNetworkModificationMetadata() { NetworkModificationMetadata networkModificationMetadata = new NetworkModificationMetadata(true, "description", "type"); studyController.updateNetworkModificationsMetadata(studyUuid, nodeUuid, modificationUuids, networkModificationMetadata, userId); - verify(rebuildNodeService, times(1)).updateNetworkModificationsMetadata(eq(studyUuid), eq(nodeUuid), eq(modificationUuids), eq(userId), eq(networkModificationMetadata)); + verify(rebuildNodeService, times(1)).updateNetworkModificationsMetadata(studyUuid, nodeUuid, modificationUuids, userId, networkModificationMetadata); verify(studyService, times(1)).buildNode(eq(studyUuid), eq(nodeUuid), any(), eq(userId)); } @@ -134,7 +133,7 @@ void testUpdateNetworkModificationActivationByRootNetwork() { Set modificationUuids = Set.of(UUID.randomUUID()); studyController.updateNetworkModificationsActivation(studyUuid, rootNetworkUuid, nodeUuid, modificationUuids, true, userId); - verify(rebuildNodeService, times(1)).updateNetworkModificationsActivation(eq(studyUuid), eq(nodeUuid), eq(rootNetworkUuid), eq(modificationUuids), eq(userId), eq(true)); + verify(rebuildNodeService, times(1)).updateNetworkModificationsActivation(studyUuid, nodeUuid, rootNetworkUuid, modificationUuids, userId, true); verify(studyService, times(1)).buildNode(eq(studyUuid), eq(nodeUuid), any(), eq(userId)); } } From 2bc433ea360dc1c90c114eab05ec674a5df8c4d5 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Mon, 26 Jan 2026 14:11:35 +0100 Subject: [PATCH 24/26] fix checkstyle Signed-off-by: LE SAULNIER Kevin --- .../org/gridsuite/study/server/controller/StudyController.java | 2 -- .../org/gridsuite/study/server/NetworkModificationTest.java | 1 - 2 files changed, 3 deletions(-) diff --git a/src/main/java/org/gridsuite/study/server/controller/StudyController.java b/src/main/java/org/gridsuite/study/server/controller/StudyController.java index c1dec9b4e5..6a109fff6f 100644 --- a/src/main/java/org/gridsuite/study/server/controller/StudyController.java +++ b/src/main/java/org/gridsuite/study/server/controller/StudyController.java @@ -37,7 +37,6 @@ import org.gridsuite.study.server.dto.elasticsearch.EquipmentInfos; import org.gridsuite.study.server.dto.modification.ModificationType; import org.gridsuite.study.server.dto.modification.ModificationsSearchResultByNode; -import org.gridsuite.study.server.dto.modification.NetworkModificationMetadata; import org.gridsuite.study.server.dto.networkexport.ExportNetworkStatus; import org.gridsuite.study.server.dto.sensianalysis.SensitivityAnalysisCsvFileInfos; import org.gridsuite.study.server.dto.sequence.NodeSequenceType; @@ -45,7 +44,6 @@ import org.gridsuite.study.server.dto.timeseries.TimelineEventInfos; import org.gridsuite.study.server.dto.voltageinit.parameters.StudyVoltageInitParameters; import org.gridsuite.study.server.elasticsearch.EquipmentInfosService; -import org.gridsuite.study.server.error.StudyException; import org.gridsuite.study.server.exception.PartialResultException; import org.gridsuite.study.server.networkmodificationtree.dto.*; import org.gridsuite.study.server.service.*; diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java index f55371d539..8bf3a65a1c 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java @@ -43,7 +43,6 @@ import org.gridsuite.study.server.service.client.dynamicsimulation.DynamicSimulationClient; import org.gridsuite.study.server.service.shortcircuit.ShortCircuitService; import org.gridsuite.study.server.utils.MatcherJson; -import org.gridsuite.study.server.utils.RequestWithBody; import org.gridsuite.study.server.utils.SendInput; import org.gridsuite.study.server.utils.TestUtils; import org.gridsuite.study.server.utils.elasticsearch.DisableElasticsearch; From 9150bf96d18317bc74ec46ade57af8cb179bfaef Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Mon, 26 Jan 2026 14:20:55 +0100 Subject: [PATCH 25/26] fix tests post merge Signed-off-by: LE SAULNIER Kevin --- .../gridsuite/study/server/studycontroller/StudyTestBase.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/StudyTestBase.java b/src/test/java/org/gridsuite/study/server/studycontroller/StudyTestBase.java index b40d488339..ca6f98665d 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/StudyTestBase.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/StudyTestBase.java @@ -443,12 +443,12 @@ protected NetworkModificationNode createNetworkModificationNode(UUID studyUuid, protected NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UUID parentNodeUuid, UUID modificationGroupUuid, String variantId, String nodeName, String userId) throws Exception { return createNetworkModificationNode(studyUuid, parentNodeUuid, - modificationGroupUuid, variantId, nodeName, NetworkModificationNodeType.SECURITY, BuildStatus.NOT_BUILT, userId); + modificationGroupUuid, variantId, nodeName, NetworkModificationNodeType.CONSTRUCTION, BuildStatus.NOT_BUILT, userId); } protected NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UUID parentNodeUuid, UUID modificationGroupUuid, String variantId, String nodeName, BuildStatus buildStatus, String userId) throws Exception { - return createNetworkModificationNode(studyUuid, parentNodeUuid, modificationGroupUuid, variantId, nodeName, NetworkModificationNodeType.SECURITY, buildStatus, userId); + return createNetworkModificationNode(studyUuid, parentNodeUuid, modificationGroupUuid, variantId, nodeName, NetworkModificationNodeType.CONSTRUCTION, buildStatus, userId); } protected NetworkModificationNode createNetworkModificationNode(UUID studyUuid, UUID parentNodeUuid, From cb477ad9b1bb3160b434eadfa812269ed74f9396 Mon Sep 17 00:00:00 2001 From: LE SAULNIER Kevin Date: Mon, 26 Jan 2026 16:58:59 +0100 Subject: [PATCH 26/26] pr remark Signed-off-by: LE SAULNIER Kevin --- src/main/resources/application-local.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 045f905a5b..237f8990cb 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -1,13 +1,6 @@ server: port: 5001 -logging: - level: - org.springframework.orm.jpa: DEBUG - org.springframework.transaction: DEBUG - org.hibernate.SQL: DEBUG - org.hibernate.orm.jdbc.bind: DEBUG - spring: rabbitmq: addresses: localhost