diff --git a/src/main/java/org/gridsuite/study/server/dto/RootNetworkTagsCheckInfos.java b/src/main/java/org/gridsuite/study/server/dto/RootNetworkTagsCheckInfos.java index 3256a94d1..19ad5536c 100644 --- a/src/main/java/org/gridsuite/study/server/dto/RootNetworkTagsCheckInfos.java +++ b/src/main/java/org/gridsuite/study/server/dto/RootNetworkTagsCheckInfos.java @@ -12,5 +12,6 @@ /** * Modifications to check against root network tags. */ -public record RootNetworkTagsCheckInfos(List containerUuids, List tags) { +public record RootNetworkTagsCheckInfos(List containerUuids, List modificationUuids, List sharedUuids, + List tags) { } 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 5b0f34e59..54a8beac4 100644 --- a/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java +++ b/src/main/java/org/gridsuite/study/server/service/NetworkModificationTreeService.java @@ -44,6 +44,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.gridsuite.study.server.error.StudyBusinessErrorCode.*; @@ -871,6 +872,14 @@ public UUID getModificationGroupUuid(UUID nodeUuid) { return getNetworkModificationNodeInfoEntity(nodeUuid).getModificationGroupUuid(); } + @Transactional(readOnly = true) + public List getSubtreeModificationGroupUuids(UUID parentNodeUuid) { + List nodeUuids = Stream.concat(Stream.of(parentNodeUuid), getAllChildrenUuids(parentNodeUuid).stream()).toList(); + return networkModificationNodeInfoRepository.findAllByIdIn(nodeUuids).stream() + .map(NetworkModificationNodeInfoEntity::getModificationGroupUuid) + .toList(); + } + @Transactional(readOnly = true) public String getNetworkModifications(@NonNull UUID nodeUuid, boolean onlyStashed, boolean onlyMetadata, String userId) { return networkModificationService.getModifications(self.getModificationGroupUuid(nodeUuid), onlyStashed, onlyMetadata, 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 4df8f30c8..0c9775229 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -415,7 +415,25 @@ private void renameRootNetworkTagInApplicabilities(UUID studyUuid, String previo * write on, which they cannot associate with a new tag, must already be. */ private void assertCanAddRootNetworkTag(UUID studyUuid, String newTag, String userId) { - networkModificationRestService.assertRootNetworkTagsAllowed(getStudyModificationGroupUuids(studyUuid), List.of(newTag), userId); + networkModificationRestService.assertRootNetworkTagsAllowed(getStudyModificationGroupUuids(studyUuid), List.of(), List.of(), List.of(newTag), userId); + } + + /** + * Modifications written into a study must be associated with all its root network tags, including the shared + * modifications they point to: the ones the user cannot write on, which they cannot associate with a new tag, must + * already be. + * + * @param containerUuids groups or composites whose content is written into the study: only the shared modifications + * their content refers to are checked, not the containers themselves + * @param modificationUuids modifications written into the study: the shared modifications they and their content + * refer to are checked + * @param sharedUuids shared modifications the study is given a reference to: they are checked themselves, along + * with the shared modifications their content refers to + */ + private void assertCanWriteModificationsInStudy(UUID studyUuid, List containerUuids, List modificationUuids, + List sharedUuids, String userId) { + networkModificationRestService.assertRootNetworkTagsAllowed(containerUuids, modificationUuids, sharedUuids, + rootNetworkService.getStudyRootNetworkTags(studyUuid), userId); } public SharedModificationsWriteAccess getSharedModificationsWriteAccess(UUID studyUuid, String userId) { @@ -1373,6 +1391,10 @@ private void assertMoveStudyNode(UUID studyUuid, UUID nodeToMoveUuid, UUID refer @Transactional public void duplicateStudyNode(UUID sourceStudyUuid, UUID targetStudyUuid, UUID nodeToCopyUuid, UUID referenceNodeUuid, InsertMode insertMode, String userId) { assertDuplicateStudyNode(sourceStudyUuid, targetStudyUuid, nodeToCopyUuid, referenceNodeUuid, insertMode); + if (!sourceStudyUuid.equals(targetStudyUuid)) { + assertCanWriteModificationsInStudy(targetStudyUuid, List.of(networkModificationTreeService.getModificationGroupUuid(nodeToCopyUuid)), + List.of(), List.of(), userId); + } UUID duplicatedNodeUuid = networkModificationTreeService.duplicateStudyNode(nodeToCopyUuid, referenceNodeUuid, insertMode, userId); boolean invalidateBuild = networkModificationTreeService.hasModifications(nodeToCopyUuid, false); @@ -1415,6 +1437,10 @@ private void assertDuplicateStudySubtree(UUID sourceStudyUuid, UUID targetStudyU @Transactional public void duplicateStudySubtree(UUID sourceStudyUuid, UUID targetStudyUuid, UUID parentNodeToCopyUuid, UUID referenceNodeUuid, String userId) { assertDuplicateStudySubtree(sourceStudyUuid, targetStudyUuid, parentNodeToCopyUuid, referenceNodeUuid); + if (!sourceStudyUuid.equals(targetStudyUuid)) { + assertCanWriteModificationsInStudy(targetStudyUuid, networkModificationTreeService.getSubtreeModificationGroupUuids(parentNodeToCopyUuid), + List.of(), List.of(), userId); + } AbstractNode studySubTree = networkModificationTreeService.getStudySubtree(sourceStudyUuid, parentNodeToCopyUuid, null); StudyEntity studyEntity = getStudy(targetStudyUuid); UUID duplicatedNodeUuid = networkModificationTreeService.cloneStudyTree(studySubTree, referenceNodeUuid, studyEntity, userId); @@ -1638,6 +1664,7 @@ public void duplicateNetworkModifications( UUID targetNodeUuid, List modificationsUuids, String userId) { + assertCanWriteModificationsInStudy(targetStudyUuid, List.of(), modificationsUuids, List.of(), userId); duplicateModificationsOrInsertComposites(targetStudyUuid, targetNodeUuid, (groupUuid, modificationApplicationContexts) -> { NetworkModificationsResult result = networkModificationRestService.duplicateModifications(groupUuid, Pair.of(modificationsUuids, modificationApplicationContexts), userId); @@ -1690,12 +1717,14 @@ public void insertCompositeNetworkModifications( List compositesInfos, String userId, StudyConstants.CompositeModificationsActionType action) { + boolean isInsert = action == StudyConstants.CompositeModificationsActionType.INSERT; + assertCanWriteCompositesInStudy(targetStudyUuid, compositesInfos, isInsert, userId); duplicateModificationsOrInsertComposites( targetStudyUuid, targetNodeUuid, (groupUuid, modificationApplicationContexts) -> { NetworkModificationsResult result = networkModificationRestService.insertCompositeModifications(groupUuid, action, Pair.of(compositesInfos, modificationApplicationContexts)); - if (action == StudyConstants.CompositeModificationsActionType.INSERT) { + if (isInsert) { createCompositesReferences(compositesInfos, result.modificationUuids(), targetStudyUuid, targetNodeUuid, userId); } return result; @@ -1704,6 +1733,19 @@ public void insertCompositeNetworkModifications( ); } + /** + * Only an INSERT of a composite marked as shared gives the study a reference to it: the shared composite is checked + * itself. Any other composite is copied: only its content is checked, the composite being passed as its container. + */ + private void assertCanWriteCompositesInStudy(UUID studyUuid, List compositesInfos, boolean isInsert, String userId) { + Map> uuidsByShared = compositesInfos.stream() + .collect(Collectors.partitioningBy(composite -> isInsert && composite.isShared(), + Collectors.mapping(CompositeInfos::id, Collectors.toList()))); + List sharedCompositeUuids = uuidsByShared.get(true); + List copiedCompositeUuids = uuidsByShared.get(false); + assertCanWriteModificationsInStudy(studyUuid, copiedCompositeUuids, List.of(), sharedCompositeUuids, userId); + } + private void createCompositesReferences(List compositesInfos, List insertedModificationUuids, UUID studyUuid, UUID nodeUuid, String userId) { for (int i = 0; i < compositesInfos.size(); i++) { diff --git a/src/main/java/org/gridsuite/study/server/service/networkmodification/NetworkModificationRestService.java b/src/main/java/org/gridsuite/study/server/service/networkmodification/NetworkModificationRestService.java index e8ae6e80a..ec5587cb4 100644 --- a/src/main/java/org/gridsuite/study/server/service/networkmodification/NetworkModificationRestService.java +++ b/src/main/java/org/gridsuite/study/server/service/networkmodification/NetworkModificationRestService.java @@ -788,12 +788,15 @@ public SharedModificationsWriteAccess getReferencedModificationsWriteAccess(List /** * Throws forbidden when the unwritable shared modifications involved miss some of the tags. */ - public void assertRootNetworkTagsAllowed(List containerUuids, List rootNetworkTags, String userId) { - if (rootNetworkTags.isEmpty() || containerUuids.isEmpty()) { + public void assertRootNetworkTagsAllowed(List containerUuids, List modificationUuids, List sharedUuids, + List rootNetworkTags, String userId) { + if (rootNetworkTags.isEmpty() || containerUuids.isEmpty() && modificationUuids.isEmpty() && sharedUuids.isEmpty()) { return; } restTemplate.exchange(getNetworkModificationServerURI(false) + CONTAINERS + DELIMITER + "root-network-tags/authorized", - HttpMethod.POST, userIdEntity(userId, new RootNetworkTagsCheckInfos(containerUuids, rootNetworkTags)), Void.class); + HttpMethod.POST, + userIdEntity(userId, new RootNetworkTagsCheckInfos(containerUuids, modificationUuids, sharedUuids, rootNetworkTags)), + Void.class); } /** diff --git a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java index 09cec6622..43ec1bb87 100644 --- a/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java +++ b/src/test/java/org/gridsuite/study/server/NetworkModificationTest.java @@ -113,6 +113,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** @@ -341,6 +342,7 @@ void setup() { networkModificationRestService.setNetworkModificationServerBaseUri(baseUrl); directoryService.setDirectoryServerServerBaseUri(baseUrl); userAdminService.setUserAdminServerBaseUri(baseUrl); + wireMockStubs.stubRootNetworkTagsAllowed(); buildOkStubId = wireMockServer.stubFor(WireMock.post(WireMock.urlPathEqualTo("/v1/networks/" + NETWORK_UUID_STRING + "/build")) .withPostServeAction(POST_ACTION_SEND_INPUT, Map.of("payload", DEFAULT_BUILD_RESULT, "destination", "build.result")) @@ -2180,6 +2182,29 @@ void testInsertComposite() throws Exception { "/v1/elements/" + sharedNetModId + "/references", Map.of(), mapper.writeValueAsString(ReferenceAttributes.createReferenceAttributes(insertedReferenceUuid, studyUuid, nodeUuid1, STUDY_NODE))); + // the shared composite is checked itself, the other one only through its content + List studyTags = List.of(studyEntity.getFirstRootNetwork().getTag()); + WireMockUtilsCriteria.verifyPostRequest(wireMockServer, WireMockStubs.ROOT_NETWORK_TAGS_AUTHORIZED_URL, false, Map.of(), + mapper.writeValueAsString(new RootNetworkTagsCheckInfos(List.of(modification1.id()), List.of(), List.of(sharedNetModId), studyTags))); + + // split composites are all checked only through their content, and the study gets no reference to them + wireMockServer.stubFor(WireMock.any(urlPathMatching("/v1/network-composite-modifications/groups/.*")) + .withQueryParam("action", equalTo("SPLIT")) + .willReturn(WireMock.ok() + .withBody(mapper.writeValueAsString(new NetworkModificationsResult(List.of(UUID.randomUUID(), UUID.randomUUID()), List.of(Optional.empty())))) + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE))); + mockMvc.perform(put("/v1/studies/{studyUuid}/nodes/{nodeUuid}/composite-modifications?action=SPLIT", + studyUuid, nodeUuid1) + .contentType(MediaType.APPLICATION_JSON) + .content(compositesData) + .header(USER_ID_HEADER, "userId")) + .andExpect(status().isOk()); + checkUpdateStatusMessagesReceived(studyUuid, nodeUuid1, output); + checkEquipmentUpdatingFinishedMessagesReceived(studyUuid, nodeUuid1); + checkElementUpdatedMessageSent(studyUuid, userId); + WireMockUtilsCriteria.verifyPutRequest(wireMockServer, url, false, Map.of("action", equalTo("SPLIT")), expectedBody); + WireMockUtilsCriteria.verifyPostRequest(wireMockServer, WireMockStubs.ROOT_NETWORK_TAGS_AUTHORIZED_URL, false, Map.of(), + mapper.writeValueAsString(new RootNetworkTagsCheckInfos(List.of(modification1.id(), sharedNetModId), List.of(), List.of(), studyTags))); } @Test @@ -2360,6 +2385,37 @@ void testAssembleModificationsIntoNewComposite() throws Exception { ); } + @Test + void testPasteForbiddenByASharedModificationNotAssociatedWithTheRootNetworkTags() throws Exception { + StudyEntity studyEntity = insertDummyStudy(UUID.fromString(NETWORK_UUID_STRING), CASE_UUID, "UCTE"); + UUID studyUuid = studyEntity.getId(); + UUID nodeUuid = createNetworkModificationNode(studyUuid, getRootNode(studyUuid).getId(), UUID.randomUUID(), VARIANT_ID, "New node 1", "userId").getId(); + UUID modificationUuid = UUID.randomUUID(); + String forbiddenBody = mapper.writeValueAsString(PowsyblWsProblemDetail.builder(HttpStatus.FORBIDDEN) + .server("network-modification-server") + .detail("forbidden") + .businessErrorCode("modification.rootNetworkTagsForbiddenByShared") + .businessErrorValues(Map.of("allowedTags", "A, B", "allowedTagsCount", 2)) + .build()); + UUID forbiddenStubId = wireMockServer.stubFor(WireMock.post(WireMock.urlPathEqualTo(WireMockStubs.ROOT_NETWORK_TAGS_AUTHORIZED_URL)) + .atPriority(1) + .willReturn(WireMock.forbidden().withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PROBLEM_JSON_VALUE).withBody(forbiddenBody))) + .getId(); + + // the error tells the user the tags they may use, and nothing is copied + mockMvc.perform(put("/v1/studies/{studyUuid}/nodes/{nodeUuid}", studyUuid, nodeUuid) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(List.of(new ModificationCopyInfos(modificationUuid, null)))) + .header(USER_ID_HEADER, "userId")) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.businessErrorCode").value("modification.rootNetworkTagsForbiddenByShared")) + .andExpect(jsonPath("$.businessErrorValues.allowedTags").value("A, B")); + + WireMockUtils.verifyPostRequest(wireMockServer, forbiddenStubId, WireMockStubs.ROOT_NETWORK_TAGS_AUTHORIZED_URL, false, Map.of(), + mapper.writeValueAsString(new RootNetworkTagsCheckInfos(List.of(), List.of(modificationUuid), List.of(), + List.of(studyEntity.getFirstRootNetwork().getTag())))); + } + @Test void testDuplicateModification() throws Exception { String userId = "userId"; diff --git a/src/test/java/org/gridsuite/study/server/rootnetworks/RootNetworkApplicabilityTest.java b/src/test/java/org/gridsuite/study/server/rootnetworks/RootNetworkApplicabilityTest.java index ef69769be..a4b43ce40 100644 --- a/src/test/java/org/gridsuite/study/server/rootnetworks/RootNetworkApplicabilityTest.java +++ b/src/test/java/org/gridsuite/study/server/rootnetworks/RootNetworkApplicabilityTest.java @@ -305,7 +305,7 @@ void testRenamingARootNetworkTagNeedsTheSharedModificationsTheUserCannotWriteOnT // the study points to a shared modification the user is not allowed to write on, and not associated with the new tag doThrow(HttpClientErrorException.create(HttpStatus.FORBIDDEN, "Forbidden", null, null, null)) - .when(networkModificationRestService).assertRootNetworkTagsAllowed(List.of(firstNode.getModificationGroupUuid()), List.of(ROOT_NETWORK_TAG_2), USER_ID); + .when(networkModificationRestService).assertRootNetworkTagsAllowed(List.of(firstNode.getModificationGroupUuid()), List.of(), List.of(), List.of(ROOT_NETWORK_TAG_2), USER_ID); UUID studyUuid = studyEntity.getId(); RootNetworkInfos renamingTagInfos = RootNetworkInfos.builder().id(rootNetworkUuid).tag(ROOT_NETWORK_TAG_2).build(); @@ -317,7 +317,7 @@ void testRenamingARootNetworkTagNeedsTheSharedModificationsTheUserCannotWriteOnT // an update keeping the tag is no rename, so it needs no permission updateRootNetwork(studyUuid, rootNetworkUuid, ROOT_NETWORK_TAG_1); - verify(networkModificationRestService, times(1)).assertRootNetworkTagsAllowed(any(), any(), any()); + verify(networkModificationRestService, times(1)).assertRootNetworkTagsAllowed(any(), any(), any(), any(), any()); } @Test diff --git a/src/test/java/org/gridsuite/study/server/service/networkmodification/NetworkModificationServiceTest.java b/src/test/java/org/gridsuite/study/server/service/networkmodification/NetworkModificationServiceTest.java index 29cf68c2d..ce1cfdcb1 100644 --- a/src/test/java/org/gridsuite/study/server/service/networkmodification/NetworkModificationServiceTest.java +++ b/src/test/java/org/gridsuite/study/server/service/networkmodification/NetworkModificationServiceTest.java @@ -265,17 +265,28 @@ void testDuplicateModificationsGroupAssociatesTheRootNetworkTags() { @Test void testAssertRootNetworkTagsAllowed() { UUID groupUuid = UUID.randomUUID(); + UUID modificationUuid = UUID.randomUUID(); + UUID sharedUuid = UUID.randomUUID(); + String url = NETWORK_MODIFICATION_SERVER_URI + "/v1/containers/root-network-tags/authorized"; HttpHeaders headers = new HttpHeaders(); headers.set(HEADER_USER_ID, USER_ID); headers.setContentType(MediaType.APPLICATION_JSON); - networkModificationService.assertRootNetworkTagsAllowed(List.of(groupUuid), List.of("PH1", "PH2"), USER_ID); - verify(restTemplate).exchange(NETWORK_MODIFICATION_SERVER_URI + "/v1/containers/root-network-tags/authorized", HttpMethod.POST, - new HttpEntity<>(new RootNetworkTagsCheckInfos(List.of(groupUuid), List.of("PH1", "PH2")), headers), Void.class); - - // there is nothing to check without a tag, or without a container to check it on - networkModificationService.assertRootNetworkTagsAllowed(List.of(groupUuid), List.of(), USER_ID); - networkModificationService.assertRootNetworkTagsAllowed(List.of(), List.of("PH1"), USER_ID); + networkModificationService.assertRootNetworkTagsAllowed(List.of(groupUuid), List.of(), List.of(), List.of("PH1", "PH2"), USER_ID); + verify(restTemplate).exchange(url, HttpMethod.POST, new HttpEntity<>( + new RootNetworkTagsCheckInfos(List.of(groupUuid), List.of(), List.of(), List.of("PH1", "PH2")), headers), Void.class); + + // the modifications and the shared modifications are checked too, even alone + networkModificationService.assertRootNetworkTagsAllowed(List.of(), List.of(modificationUuid), List.of(), List.of("PH1"), USER_ID); + verify(restTemplate).exchange(url, HttpMethod.POST, new HttpEntity<>( + new RootNetworkTagsCheckInfos(List.of(), List.of(modificationUuid), List.of(), List.of("PH1")), headers), Void.class); + networkModificationService.assertRootNetworkTagsAllowed(List.of(), List.of(), List.of(sharedUuid), List.of("PH1"), USER_ID); + verify(restTemplate).exchange(url, HttpMethod.POST, new HttpEntity<>( + new RootNetworkTagsCheckInfos(List.of(), List.of(), List.of(sharedUuid), List.of("PH1")), headers), Void.class); + + // there is nothing to check without a tag, or without anything to check it on + networkModificationService.assertRootNetworkTagsAllowed(List.of(groupUuid), List.of(), List.of(), List.of(), USER_ID); + networkModificationService.assertRootNetworkTagsAllowed(List.of(), List.of(), List.of(), List.of("PH1"), USER_ID); verifyNoMoreInteractions(restTemplate); } diff --git a/src/test/java/org/gridsuite/study/server/studycontroller/NodeControllerTest.java b/src/test/java/org/gridsuite/study/server/studycontroller/NodeControllerTest.java index 5a89f440e..2c945fe87 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/NodeControllerTest.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/NodeControllerTest.java @@ -20,6 +20,8 @@ import org.gridsuite.study.server.notification.NotificationService; import org.gridsuite.study.server.utils.MatcherReportLog; import org.gridsuite.study.server.utils.TestUtils; +import org.gridsuite.study.server.utils.wiremock.WireMockStubs; +import org.gridsuite.study.server.utils.wiremock.WireMockUtilsCriteria; import org.junit.jupiter.api.Test; import org.springframework.data.util.Pair; import org.springframework.http.HttpHeaders; @@ -652,6 +654,9 @@ void testDuplicateNode() throws Exception { assertEquals(BuildStatus.BUILT, networkModificationTreeService.getNodeBuildStatus(node3.getId(), rootNetworkUuid).getGlobalBuildStatus()); duplicateNode(study1Uuid, study1Uuid, emptyNode, node3.getId(), InsertMode.BEFORE, false, userId); assertEquals(BuildStatus.BUILT, networkModificationTreeService.getNodeBuildStatus(node3.getId(), rootNetworkUuid).getGlobalBuildStatus()); + + // copies within the study are not checked against its shared modifications + WireMockUtilsCriteria.verifyPostRequest(wireMockServer, WireMockStubs.ROOT_NETWORK_TAGS_AUTHORIZED_URL, Map.of(), 0); } @Test @@ -761,6 +766,8 @@ void testDuplicateSubtree() throws Exception { checkSubtreeCreatedMessageSent(study1Uuid, nodesAfterDuplication.get(0), node4.getId()); checkElementUpdatedMessageSent(study1Uuid, userId); wireMockStubs.verifyDuplicateModificationGroup(stubDuplicateUuid, 3); + // copies within the study are not checked against its shared modifications + WireMockUtilsCriteria.verifyPostRequest(wireMockServer, WireMockStubs.ROOT_NETWORK_TAGS_AUTHORIZED_URL, Map.of(), 0); Map allNodesInfos = networkModificationTreeService.getAllStudyNodesByUuid(study1Uuid); networkModificationTreeService.getAllChildrenUuids(node4.getId()).forEach(childUuid -> @@ -879,6 +886,8 @@ void testDuplicateNodeBetweenStudies() throws Exception { // duplicate the node1 from study 1 after node2 from study 2 UUID duplicatedNodeUuid = duplicateNode(study1Uuid, study2Uuid, node1, study2Node2.getId(), InsertMode.AFTER, true, userId); + wireMockServer.verify(1, WireMock.postRequestedFor(WireMock.urlPathEqualTo(WireMockStubs.ROOT_NETWORK_TAGS_AUTHORIZED_URL)) + .withRequestBody(WireMock.matchingJsonPath("$.containerUuids[0]", WireMock.equalTo(node1.getModificationGroupUuid().toString())))); //node2 should now have 1 child allNodes = networkModificationTreeService.getAllNodes(study2Uuid); @@ -887,6 +896,24 @@ void testDuplicateNodeBetweenStudies() throws Exception { && nodeEntity.getIdNode().equals(duplicatedNodeUuid) && nodeEntity.getParentNode().getIdNode().equals(study2Node2.getId())) .count()); + + // copy the subtree of node1 and node2 from study 1 under the first node of study 2: all its groups are checked + List study2NodesBeforeSubtreeCopy = networkModificationTreeService.getAllNodes(study2Uuid).stream().map(NodeEntity::getIdNode).toList(); + UUID stubDuplicateUuid = wireMockStubs.stubDuplicateModificationGroup(); + mockMvc.perform(post(STUDIES_URL + + "/{targetStudyUuid}/tree/subtrees?subtreeToCopyParentNodeUuid={parentNodeToCopy}&referenceNodeUuid={referenceNodeUuid}&sourceStudyUuid={sourceStudyUuid}", + study2Uuid, modificationNodeUuid, study2ModificationNodeUuid, study1Uuid) + .header(USER_ID_HEADER, userId)) + .andExpect(status().isOk()); + List copiedNodeUuids = networkModificationTreeService.getAllNodes(study2Uuid).stream().map(NodeEntity::getIdNode) + .filter(nodeUuid -> !study2NodesBeforeSubtreeCopy.contains(nodeUuid)).toList(); + assertEquals(3, copiedNodeUuids.size()); + checkSubtreeCreatedMessageSent(study2Uuid, copiedNodeUuids.getFirst(), study2ModificationNodeUuid); + checkElementUpdatedMessageSent(study2Uuid, userId); + wireMockStubs.verifyDuplicateModificationGroup(stubDuplicateUuid, 3); + wireMockServer.verify(1, WireMock.postRequestedFor(WireMock.urlPathEqualTo(WireMockStubs.ROOT_NETWORK_TAGS_AUTHORIZED_URL)) + .withRequestBody(WireMock.matchingJsonPath("$.containerUuids[?(@ == '" + node1.getModificationGroupUuid() + "')]")) + .withRequestBody(WireMock.matchingJsonPath("$.containerUuids[?(@ == '" + node2.getModificationGroupUuid() + "')]"))); } private UUID duplicateNode(UUID sourceStudyUuid, UUID targetStudyUuid, NetworkModificationNode nodeToCopy, UUID referenceNodeUuid, InsertMode insertMode, boolean checkMessagesForStatusModels,