From 2d9485e07664fd69773f7375b1b9d97551a079b3 Mon Sep 17 00:00:00 2001 From: Hugo Marcellin Date: Tue, 16 Jun 2026 11:14:56 +0200 Subject: [PATCH 01/14] Clean loadflow tests instability --- .../java/org/gridsuite/study/server/loadflow/LoadFlowTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java b/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java index d6e664ce1..9fc257be9 100644 --- a/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java +++ b/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java @@ -191,6 +191,7 @@ static void initWireMock(@Autowired InputDestination input) { void setup() throws Exception { objectWriter = objectMapper.writer().withDefaultPrettyPrinter(); + wireMockServer.resetRequests(); wireMockStubs = new WireMockStubs(wireMockServer); reportService.setReportServerBaseUri(wireMockServer.baseUrl()); userAdminService.setUserAdminServerBaseUri(wireMockServer.baseUrl()); From d6bc2761babf8fa398959ab07074fd34abef3c0c Mon Sep 17 00:00:00 2001 From: Hugo Marcellin Date: Tue, 16 Jun 2026 11:35:25 +0200 Subject: [PATCH 02/14] Add verification polling for async deletion --- .../study/server/loadflow/LoadFlowTest.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java b/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java index 9fc257be9..ac48c363b 100644 --- a/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java +++ b/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java @@ -63,20 +63,27 @@ import org.springframework.test.web.servlet.MvcResult; import org.springframework.web.client.HttpClientErrorException; +import java.time.Duration; import java.util.*; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.awaitility.Awaitility.await; import static org.gridsuite.study.server.StudyConstants.HEADER_RECEIVER; import static org.gridsuite.study.server.StudyConstants.HEADER_USER_ID; import static org.gridsuite.study.server.dto.ComputationType.LOAD_FLOW; import static org.gridsuite.study.server.error.StudyBusinessErrorCode.NOT_FOUND; -import static org.gridsuite.study.server.notification.NotificationService.*; +import static org.gridsuite.study.server.notification.NotificationService.HEADER_UPDATE_TYPE; +import static org.gridsuite.study.server.notification.NotificationService.NODE_BUILD_STATUS_UPDATED; +import static org.gridsuite.study.server.notification.NotificationService.UPDATE_TYPE_COMPUTATION_PARAMETERS; import static org.gridsuite.study.server.utils.TestUtils.USER_DEFAULT_PROFILE_JSON; import static org.gridsuite.study.server.utils.wiremock.WireMockUtilsCriteria.removeRequestMatching; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +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.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -508,7 +515,7 @@ void testDeleteLoadFlowResults() throws Exception { //Test result count testResultCount(); - //Delete Voltage init results + //Delete loadflow results testDeleteResults(studyNameUserIdUuid, 1); } @@ -577,7 +584,10 @@ private void testDeleteResults(UUID studyUuid, int expectedInitialResultCount) t .queryParam("type", LOAD_FLOW.toString()) .queryParam("dryRun", "false")) .andExpect(status().isOk()); - wireMockStubs.loadflowServer.verifyDeleteLoadflowResults(); + + await().atMost(Duration.ofSeconds(2)) + .pollInterval(Duration.ofMillis(250)) + .untilAsserted(wireMockStubs.loadflowServer::verifyDeleteLoadflowResults); wireMockStubs.reportServer.verifyDeleteReport(); assertEquals(0, rootNetworkNodeInfoRepository.findAllByLoadFlowResultUuidNotNull().size()); From 9b07acb50f979f38d5b948394cd97cfb77491fd1 Mon Sep 17 00:00:00 2001 From: Hugo Marcellin Date: Tue, 16 Jun 2026 11:52:52 +0200 Subject: [PATCH 03/14] Override runAsync --- .../study/server/loadflow/LoadFlowTest.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java b/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java index ac48c363b..51026df7b 100644 --- a/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java +++ b/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java @@ -63,11 +63,10 @@ import org.springframework.test.web.servlet.MvcResult; import org.springframework.web.client.HttpClientErrorException; -import java.time.Duration; import java.util.*; +import java.util.concurrent.CompletableFuture; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static org.awaitility.Awaitility.await; import static org.gridsuite.study.server.StudyConstants.HEADER_RECEIVER; import static org.gridsuite.study.server.StudyConstants.HEADER_USER_ID; import static org.gridsuite.study.server.dto.ComputationType.LOAD_FLOW; @@ -184,6 +183,8 @@ class LoadFlowTest { private TestUtils studyTestUtils; @Autowired private ConsumerService consumerService; + @Autowired + private StudyServerExecutionService studyServerExecutionService; private static WireMockServer wireMockServer; private WireMockStubs wireMockStubs; @@ -194,6 +195,14 @@ static void initWireMock(@Autowired InputDestination input) { wireMockServer.start(); } + @BeforeEach + void runAsyncInline() { + doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return CompletableFuture.completedFuture(null); + }).when(studyServerExecutionService).runAsync(any()); + } + @BeforeEach void setup() throws Exception { objectWriter = objectMapper.writer().withDefaultPrettyPrinter(); @@ -585,9 +594,7 @@ private void testDeleteResults(UUID studyUuid, int expectedInitialResultCount) t .queryParam("dryRun", "false")) .andExpect(status().isOk()); - await().atMost(Duration.ofSeconds(2)) - .pollInterval(Duration.ofMillis(250)) - .untilAsserted(wireMockStubs.loadflowServer::verifyDeleteLoadflowResults); + wireMockStubs.loadflowServer.verifyDeleteLoadflowResults(); wireMockStubs.reportServer.verifyDeleteReport(); assertEquals(0, rootNetworkNodeInfoRepository.findAllByLoadFlowResultUuidNotNull().size()); From 772df63d083e113d9fd71332319dd6daaba58d8f Mon Sep 17 00:00:00 2001 From: Hugo Marcellin Date: Tue, 16 Jun 2026 11:59:18 +0200 Subject: [PATCH 04/14] Override runAsync bis --- .../study/server/loadflow/LoadFlowTest.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java b/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java index 51026df7b..bb607bf53 100644 --- a/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java +++ b/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java @@ -183,7 +183,7 @@ class LoadFlowTest { private TestUtils studyTestUtils; @Autowired private ConsumerService consumerService; - @Autowired + @MockitoSpyBean private StudyServerExecutionService studyServerExecutionService; private static WireMockServer wireMockServer; @@ -195,14 +195,6 @@ static void initWireMock(@Autowired InputDestination input) { wireMockServer.start(); } - @BeforeEach - void runAsyncInline() { - doAnswer(invocation -> { - ((Runnable) invocation.getArgument(0)).run(); - return CompletableFuture.completedFuture(null); - }).when(studyServerExecutionService).runAsync(any()); - } - @BeforeEach void setup() throws Exception { objectWriter = objectMapper.writer().withDefaultPrettyPrinter(); @@ -586,6 +578,11 @@ private void testDeleteResults(UUID studyUuid, int expectedInitialResultCount) t List rootNetworkNodeInfoEntities = rootNetworkNodeInfoRepository.findAllByLoadFlowResultUuidNotNull(); RootNetworkNodeInfoEntity rootNetworkNodeInfoEntity = rootNetworkNodeInfoEntities.getFirst(); + doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return CompletableFuture.completedFuture(null); + }).when(studyServerExecutionService).runAsync(any()); + assertEquals(expectedInitialResultCount, rootNetworkNodeInfoEntities.size()); wireMockStubs.loadflowServer.stubDeleteLoadflowResults(LOADFLOW_RESULT_UUID); wireMockStubs.reportServer.stubDeleteReport(); From 916acf366e4d0e97d25ea2e5f1c7a4b2c5b05a45 Mon Sep 17 00:00:00 2001 From: Hugo Marcellin Date: Tue, 16 Jun 2026 15:08:54 +0200 Subject: [PATCH 05/14] Fix import case error test --- .../org/gridsuite/study/server/service/StudyService.java | 2 +- .../gridsuite/study/server/studycontroller/StudyTest.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) 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 b5c6d1f6c..a0b990e86 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -561,7 +561,7 @@ private Optional doDeleteStudyIfNotCreationInProgress(UUID stu }); deleteStudyInfos = new DeleteStudyInfos(rootNetworkInfos, modificationGroupUuids); } else { - studyCreationRequestRepository.deleteById(studyCreationRequestEntity.get().getId()); + studyCreationRequestRepository.deleteAllByIdInBatch(List.of(studyCreationRequestEntity.get().getId())); } if (deleteStudyInfos == null) { 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 d8e80caf0..107d710e8 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java @@ -483,6 +483,11 @@ void testCreateStudyWithErrorDuringCaseImport() throws Exception { assertEquals(userId, headers.get(HEADER_USER_ID)); assertEquals(NotificationService.UPDATE_TYPE_STUDY_CREATION_STARTED, headers.get(HEADER_UPDATE_TYPE)); + message = output.receive(TIMEOUT, "study.update"); + headers = message.getHeaders(); + assertEquals(userId, headers.get(HEADER_USER_ID)); + assertEquals(NotificationService.UPDATE_TYPE_STUDY_CREATION_FINISHED, headers.get(HEADER_UPDATE_TYPE)); + MvcResult mvcResult = mockMvc.perform(get("/v1/study_creation_requests").header(USER_ID_HEADER, "userId")).andExpectAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON)) From d3f6022150d1edbc1818a2de80be003631358ff3 Mon Sep 17 00:00:00 2001 From: Hugo Marcellin Date: Tue, 16 Jun 2026 16:42:46 +0200 Subject: [PATCH 06/14] Debug messages --- .../org/gridsuite/study/server/service/ConsumerService.java | 2 ++ 1 file changed, 2 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 a256da6fb..cc0883a85 100644 --- a/src/main/java/org/gridsuite/study/server/service/ConsumerService.java +++ b/src/main/java/org/gridsuite/study/server/service/ConsumerService.java @@ -345,7 +345,9 @@ public Consumer> consumeCaseImportFailed() { String userId = receiver.getUserId(); UUID rootNetworkUuid = receiver.getRootNetworkUuid(); + System.out.println("AAAAAAAAAAAAAAAAAAAAAAA " + receiver.getCaseImportAction()); if (receiver.getCaseImportAction() == CaseImportAction.STUDY_CREATION) { + System.out.println("OOOOOOOOOOOOOOOOOOOOOOOOO"); studyService.deleteStudyIfNotCreationInProgress(studyUuid); notificationService.emitStudyCreationError(studyUuid, userId, errorMessage); } else { From 99fe7a6bfc611635c5c2a4c82cab3e65eef20bc5 Mon Sep 17 00:00:00 2001 From: Hugo Marcellin Date: Tue, 16 Jun 2026 16:58:59 +0200 Subject: [PATCH 07/14] Test --- .../java/org/gridsuite/study/server/loadflow/LoadFlowTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java b/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java index bb607bf53..f68939676 100644 --- a/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java +++ b/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java @@ -199,7 +199,6 @@ static void initWireMock(@Autowired InputDestination input) { void setup() throws Exception { objectWriter = objectMapper.writer().withDefaultPrettyPrinter(); - wireMockServer.resetRequests(); wireMockStubs = new WireMockStubs(wireMockServer); reportService.setReportServerBaseUri(wireMockServer.baseUrl()); userAdminService.setUserAdminServerBaseUri(wireMockServer.baseUrl()); From e5e010382aa19053a64f751f61676bb2c88f18cc Mon Sep 17 00:00:00 2001 From: Hugo Marcellin Date: Tue, 16 Jun 2026 17:07:43 +0200 Subject: [PATCH 08/14] Test --- .../gridsuite/study/server/studycontroller/StudyTest.java | 5 ----- 1 file changed, 5 deletions(-) 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 107d710e8..d8e80caf0 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java @@ -483,11 +483,6 @@ void testCreateStudyWithErrorDuringCaseImport() throws Exception { assertEquals(userId, headers.get(HEADER_USER_ID)); assertEquals(NotificationService.UPDATE_TYPE_STUDY_CREATION_STARTED, headers.get(HEADER_UPDATE_TYPE)); - message = output.receive(TIMEOUT, "study.update"); - headers = message.getHeaders(); - assertEquals(userId, headers.get(HEADER_USER_ID)); - assertEquals(NotificationService.UPDATE_TYPE_STUDY_CREATION_FINISHED, headers.get(HEADER_UPDATE_TYPE)); - MvcResult mvcResult = mockMvc.perform(get("/v1/study_creation_requests").header(USER_ID_HEADER, "userId")).andExpectAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON)) From 6038870365364bd10bb43067100308d5e652171a Mon Sep 17 00:00:00 2001 From: Hugo Marcellin Date: Wed, 17 Jun 2026 11:32:40 +0200 Subject: [PATCH 09/14] Make doDeleteStudyIfNotCreationInProgress idempotent --- .../study/server/service/ConsumerService.java | 2 -- .../study/server/service/StudyService.java | 24 +++++++++---------- .../server/studycontroller/StudyTest.java | 6 +++++ 3 files changed, 18 insertions(+), 14 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 cc0883a85..a256da6fb 100644 --- a/src/main/java/org/gridsuite/study/server/service/ConsumerService.java +++ b/src/main/java/org/gridsuite/study/server/service/ConsumerService.java @@ -345,9 +345,7 @@ public Consumer> consumeCaseImportFailed() { String userId = receiver.getUserId(); UUID rootNetworkUuid = receiver.getRootNetworkUuid(); - System.out.println("AAAAAAAAAAAAAAAAAAAAAAA " + receiver.getCaseImportAction()); if (receiver.getCaseImportAction() == CaseImportAction.STUDY_CREATION) { - System.out.println("OOOOOOOOOOOOOOOOOOOOOOOOO"); studyService.deleteStudyIfNotCreationInProgress(studyUuid); notificationService.emitStudyCreationError(studyUuid, userId, errorMessage); } else { 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 a0b990e86..efcc5fdd8 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -545,24 +545,24 @@ private Optional doDeleteStudyIfNotCreationInProgress(UUID stu Optional studyCreationRequestEntity = studyCreationRequestRepository.findById(studyUuid); Optional studyEntity = studyRepository.findById(studyUuid); DeleteStudyInfos deleteStudyInfos = null; - if (studyCreationRequestEntity.isEmpty()) { + if (studyCreationRequestEntity.isEmpty() && studyEntity.isPresent()) { List rootNetworkInfos = getStudyRootNetworksInfos(studyUuid); // get all modification groups related to the study List modificationGroupUuids = networkModificationTreeService.getAllStudyNetworkModificationNodeInfo(studyUuid).stream().map(NetworkModificationNodeInfoEntity::getModificationGroupUuid).toList(); - studyEntity.ifPresent(s -> { - networkModificationTreeService.doDeleteTree(studyUuid); - studyRepository.deleteById(studyUuid); - studyInfosService.deleteByUuid(studyUuid); - computationParametersService.deleteComputationsParameters(s); - removeNetworkVisualizationParameters(s.getNetworkVisualizationParametersUuid()); - removeSpreadsheetConfigCollection(s.getSpreadsheetConfigCollectionUuid()); - removeWorkspacesConfig(s.getWorkspacesConfigUuid()); - removeNadConfigs(s.getNadConfigsUuids().stream().toList()); - }); + StudyEntity s = studyEntity.get(); + networkModificationTreeService.doDeleteTree(studyUuid); + studyRepository.deleteById(studyUuid); + studyInfosService.deleteByUuid(studyUuid); + computationParametersService.deleteComputationsParameters(s); + removeNetworkVisualizationParameters(s.getNetworkVisualizationParametersUuid()); + removeSpreadsheetConfigCollection(s.getSpreadsheetConfigCollectionUuid()); + removeWorkspacesConfig(s.getWorkspacesConfigUuid()); + removeNadConfigs(s.getNadConfigsUuids().stream().toList()); deleteStudyInfos = new DeleteStudyInfos(rootNetworkInfos, modificationGroupUuids); - } else { + } else if (studyCreationRequestEntity.isPresent()) { studyCreationRequestRepository.deleteAllByIdInBatch(List.of(studyCreationRequestEntity.get().getId())); } + // else: neither creation request nor study exist -> nothing to delete (idempotent no-op) if (deleteStudyInfos == null) { return Optional.empty(); 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 d8e80caf0..1f8f75d54 100644 --- a/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java +++ b/src/test/java/org/gridsuite/study/server/studycontroller/StudyTest.java @@ -483,6 +483,12 @@ void testCreateStudyWithErrorDuringCaseImport() throws Exception { assertEquals(userId, headers.get(HEADER_USER_ID)); assertEquals(NotificationService.UPDATE_TYPE_STUDY_CREATION_STARTED, headers.get(HEADER_UPDATE_TYPE)); + message = output.receive(TIMEOUT, "study.update"); + + headers = message.getHeaders(); + assertEquals(userId, headers.get(HEADER_USER_ID)); + assertEquals(NotificationService.UPDATE_TYPE_STUDY_CREATION_FINISHED, headers.get(HEADER_UPDATE_TYPE)); + MvcResult mvcResult = mockMvc.perform(get("/v1/study_creation_requests").header(USER_ID_HEADER, "userId")).andExpectAll( status().isOk(), content().contentType(MediaType.APPLICATION_JSON)) From 027805a173923281ae41565f32ae359fbaed2cac Mon Sep 17 00:00:00 2001 From: Hugo Marcellin Date: Wed, 17 Jun 2026 15:40:19 +0200 Subject: [PATCH 10/14] add executeAsyncOnMainThread to some RootNetworkTest tests --- .../study/server/service/StudyService.java | 5 ++-- .../study/server/loadflow/LoadFlowTest.java | 9 +++---- .../server/rootnetworks/RootNetworkTest.java | 27 ++++++++++++++++--- .../study/server/utils/TestUtils.java | 13 +++++++++ 4 files changed, 42 insertions(+), 12 deletions(-) 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 efcc5fdd8..dca9d18d5 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -559,10 +559,9 @@ private Optional doDeleteStudyIfNotCreationInProgress(UUID stu removeWorkspacesConfig(s.getWorkspacesConfigUuid()); removeNadConfigs(s.getNadConfigsUuids().stream().toList()); deleteStudyInfos = new DeleteStudyInfos(rootNetworkInfos, modificationGroupUuids); - } else if (studyCreationRequestEntity.isPresent()) { - studyCreationRequestRepository.deleteAllByIdInBatch(List.of(studyCreationRequestEntity.get().getId())); + } else { + studyCreationRequestEntity.ifPresent(creationRequestEntity -> studyCreationRequestRepository.deleteAllByIdInBatch(List.of(creationRequestEntity.getId()))); } - // else: neither creation request nor study exist -> nothing to delete (idempotent no-op) if (deleteStudyInfos == null) { return Optional.empty(); diff --git a/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java b/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java index f68939676..ab0689398 100644 --- a/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java +++ b/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java @@ -64,7 +64,6 @@ import org.springframework.web.client.HttpClientErrorException; import java.util.*; -import java.util.concurrent.CompletableFuture; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.gridsuite.study.server.StudyConstants.HEADER_RECEIVER; @@ -75,6 +74,7 @@ import static org.gridsuite.study.server.notification.NotificationService.NODE_BUILD_STATUS_UPDATED; import static org.gridsuite.study.server.notification.NotificationService.UPDATE_TYPE_COMPUTATION_PARAMETERS; import static org.gridsuite.study.server.utils.TestUtils.USER_DEFAULT_PROFILE_JSON; +import static org.gridsuite.study.server.utils.TestUtils.executeAsyncOnMainThread; import static org.gridsuite.study.server.utils.wiremock.WireMockUtilsCriteria.removeRequestMatching; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; @@ -577,10 +577,9 @@ private void testDeleteResults(UUID studyUuid, int expectedInitialResultCount) t List rootNetworkNodeInfoEntities = rootNetworkNodeInfoRepository.findAllByLoadFlowResultUuidNotNull(); RootNetworkNodeInfoEntity rootNetworkNodeInfoEntity = rootNetworkNodeInfoEntities.getFirst(); - doAnswer(invocation -> { - ((Runnable) invocation.getArgument(0)).run(); - return CompletableFuture.completedFuture(null); - }).when(studyServerExecutionService).runAsync(any()); + // Run runAsync tasks inline so fire-and-forget cleanup (e.g. blocking=false remote deletions) + // completes before the test verifies it, removes the race condition. + executeAsyncOnMainThread(studyServerExecutionService); assertEquals(expectedInitialResultCount, rootNetworkNodeInfoEntities.size()); wireMockStubs.loadflowServer.stubDeleteLoadflowResults(LOADFLOW_RESULT_UUID); diff --git a/src/test/java/org/gridsuite/study/server/rootnetworks/RootNetworkTest.java b/src/test/java/org/gridsuite/study/server/rootnetworks/RootNetworkTest.java index 8af66baea..a6213b0be 100644 --- a/src/test/java/org/gridsuite/study/server/rootnetworks/RootNetworkTest.java +++ b/src/test/java/org/gridsuite/study/server/rootnetworks/RootNetworkTest.java @@ -14,11 +14,11 @@ import com.powsybl.network.store.client.NetworkStoreService; import com.powsybl.ws.commons.error.PowsyblWsProblemDetail; import org.gridsuite.study.server.ContextConfigurationWithTestChannel; -import org.gridsuite.study.server.error.StudyException; import org.gridsuite.study.server.dto.*; import org.gridsuite.study.server.dto.caseimport.CaseImportAction; import org.gridsuite.study.server.dto.caseimport.CaseImportReceiver; import org.gridsuite.study.server.elasticsearch.EquipmentInfosService; +import org.gridsuite.study.server.error.StudyException; import org.gridsuite.study.server.networkmodificationtree.dto.InsertMode; import org.gridsuite.study.server.networkmodificationtree.dto.NetworkModificationNode; import org.gridsuite.study.server.networkmodificationtree.entities.NodeEntity; @@ -57,12 +57,21 @@ import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.gridsuite.study.server.StudyConstants.HEADER_ERROR; +import static org.gridsuite.study.server.StudyConstants.HEADER_IMPORT_PARAMETERS; +import static org.gridsuite.study.server.StudyConstants.HEADER_RECEIVER; +import static org.gridsuite.study.server.StudyConstants.HEADER_USER_ID; import static org.gridsuite.study.server.error.StudyBusinessErrorCode.MAXIMUM_ROOT_NETWORK_BY_STUDY_REACHED; import static org.gridsuite.study.server.error.StudyBusinessErrorCode.NOT_FOUND; -import static org.gridsuite.study.server.StudyConstants.*; import static org.gridsuite.study.server.utils.TestUtils.createModificationNodeInfo; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; +import static org.gridsuite.study.server.utils.TestUtils.executeAsyncOnMainThread; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.springframework.http.MediaType.APPLICATION_JSON; @@ -144,6 +153,8 @@ class RootNetworkTest { @MockitoSpyBean private StudyService studyService; + @MockitoSpyBean + private StudyServerExecutionService studyServerExecutionService; @MockitoBean private ReportService reportService; @@ -460,6 +471,10 @@ void testCreateRootNetworkConsumerWithoutRequest() throws Exception { StudyEntity studyEntity = TestUtils.createDummyStudy(NETWORK_UUID, CASE_UUID, CASE_NAME, CASE_FORMAT, REPORT_UUID); studyRepository.save(studyEntity); + // Run runAsync tasks inline so fire-and-forget cleanup (e.g. blocking=false remote deletions) + // completes before the test verifies it, removes the race condition. + executeAsyncOnMainThread(studyServerExecutionService); + // DO NOT insert creation request - it means root network won't be created and remote resources will be deleted RootNetworkInfos rootNetworkInfos = RootNetworkInfos.builder().id(UUID.randomUUID()).name("newRootNetworkName").tag("newT") .caseInfos(new CaseInfos(CASE_UUID2, CASE_UUID, CASE_NAME2, CASE_FORMAT2)).networkInfos(new NetworkInfos(NETWORK_UUID2, NETWORK_ID2)) @@ -530,6 +545,10 @@ void testDeleteRootNetwork() throws Exception { // before deletion, check we have 2 root networks for study assertEquals(2, studyService.getExistingBasicRootNetworkInfos(studyEntity.getId()).size()); + // Run runAsync tasks inline so fire-and-forget cleanup (e.g. blocking=false remote deletions) + // completes before the test verifies it, removes the race condition. + executeAsyncOnMainThread(studyServerExecutionService); + mockMvc.perform(delete("/v1/studies/{studyUuid}/root-networks", studyEntity.getId()) .contentType(APPLICATION_JSON) .content(objectMapper.writeValueAsString(List.of(rootNetworkEntityToDeleteUuid))) diff --git a/src/test/java/org/gridsuite/study/server/utils/TestUtils.java b/src/test/java/org/gridsuite/study/server/utils/TestUtils.java index 98b9e7ff1..27d9567c5 100644 --- a/src/test/java/org/gridsuite/study/server/utils/TestUtils.java +++ b/src/test/java/org/gridsuite/study/server/utils/TestUtils.java @@ -307,4 +307,17 @@ public static void checkUpdateTypeMessageReceived(UUID studyUuid, UUID nodeUuid, public static void checkUpdateStatusMessagesReceived(UUID studyUuid, UUID nodeUuid, OutputDestination output) { ALL_COMPUTATION_STATUS.forEach(computationStatus -> checkUpdateTypeMessageReceived(studyUuid, nodeUuid, computationStatus, output, STUDY_UPDATE_DESTINATION)); } + + /** + * Forces {@link StudyServerExecutionService#runAsync} to execute its task inline on the calling + * thread instead of dispatching it to the executor pool. + * + */ + + public static void executeAsyncOnMainThread(StudyServerExecutionService studyServerExecutionService) { + doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return CompletableFuture.completedFuture(null); + }).when(studyServerExecutionService).runAsync(any()); + } } From 25db0db0bf6e9a87029a7bc42f2812f3fc8b127c Mon Sep 17 00:00:00 2001 From: Hugo Marcellin Date: Thu, 18 Jun 2026 14:19:18 +0200 Subject: [PATCH 11/14] Refine comment --- src/test/java/org/gridsuite/study/server/utils/TestUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/gridsuite/study/server/utils/TestUtils.java b/src/test/java/org/gridsuite/study/server/utils/TestUtils.java index 27d9567c5..0be2d3347 100644 --- a/src/test/java/org/gridsuite/study/server/utils/TestUtils.java +++ b/src/test/java/org/gridsuite/study/server/utils/TestUtils.java @@ -310,8 +310,8 @@ public static void checkUpdateStatusMessagesReceived(UUID studyUuid, UUID nodeUu /** * Forces {@link StudyServerExecutionService#runAsync} to execute its task inline on the calling - * thread instead of dispatching it to the executor pool. - * + * thread instead of dispatching it to the executor pool. Used to make testing more consistent when + * functions launch new threads in non blocking mode */ public static void executeAsyncOnMainThread(StudyServerExecutionService studyServerExecutionService) { From b3c355cf673fa184f572588fadad09a0117ab61b Mon Sep 17 00:00:00 2001 From: Hugo Marcellin Date: Mon, 22 Jun 2026 09:31:01 +0200 Subject: [PATCH 12/14] Use synchronizeStudyServerExecutionService instead --- .../study/server/loadflow/LoadFlowTest.java | 4 ++-- .../study/server/rootnetworks/RootNetworkTest.java | 6 +++--- .../org/gridsuite/study/server/utils/TestUtils.java | 13 ------------- 3 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java b/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java index ab0689398..410c19454 100644 --- a/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java +++ b/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java @@ -74,7 +74,7 @@ import static org.gridsuite.study.server.notification.NotificationService.NODE_BUILD_STATUS_UPDATED; import static org.gridsuite.study.server.notification.NotificationService.UPDATE_TYPE_COMPUTATION_PARAMETERS; import static org.gridsuite.study.server.utils.TestUtils.USER_DEFAULT_PROFILE_JSON; -import static org.gridsuite.study.server.utils.TestUtils.executeAsyncOnMainThread; +import static org.gridsuite.study.server.utils.TestUtils.synchronizeStudyServerExecutionService; import static org.gridsuite.study.server.utils.wiremock.WireMockUtilsCriteria.removeRequestMatching; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; @@ -579,7 +579,7 @@ private void testDeleteResults(UUID studyUuid, int expectedInitialResultCount) t // Run runAsync tasks inline so fire-and-forget cleanup (e.g. blocking=false remote deletions) // completes before the test verifies it, removes the race condition. - executeAsyncOnMainThread(studyServerExecutionService); + synchronizeStudyServerExecutionService(studyServerExecutionService); assertEquals(expectedInitialResultCount, rootNetworkNodeInfoEntities.size()); wireMockStubs.loadflowServer.stubDeleteLoadflowResults(LOADFLOW_RESULT_UUID); diff --git a/src/test/java/org/gridsuite/study/server/rootnetworks/RootNetworkTest.java b/src/test/java/org/gridsuite/study/server/rootnetworks/RootNetworkTest.java index a6213b0be..4a2a77bee 100644 --- a/src/test/java/org/gridsuite/study/server/rootnetworks/RootNetworkTest.java +++ b/src/test/java/org/gridsuite/study/server/rootnetworks/RootNetworkTest.java @@ -64,7 +64,7 @@ import static org.gridsuite.study.server.error.StudyBusinessErrorCode.MAXIMUM_ROOT_NETWORK_BY_STUDY_REACHED; import static org.gridsuite.study.server.error.StudyBusinessErrorCode.NOT_FOUND; import static org.gridsuite.study.server.utils.TestUtils.createModificationNodeInfo; -import static org.gridsuite.study.server.utils.TestUtils.executeAsyncOnMainThread; +import static org.gridsuite.study.server.utils.TestUtils.synchronizeStudyServerExecutionService; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -473,7 +473,7 @@ void testCreateRootNetworkConsumerWithoutRequest() throws Exception { // Run runAsync tasks inline so fire-and-forget cleanup (e.g. blocking=false remote deletions) // completes before the test verifies it, removes the race condition. - executeAsyncOnMainThread(studyServerExecutionService); + synchronizeStudyServerExecutionService(studyServerExecutionService); // DO NOT insert creation request - it means root network won't be created and remote resources will be deleted RootNetworkInfos rootNetworkInfos = RootNetworkInfos.builder().id(UUID.randomUUID()).name("newRootNetworkName").tag("newT") @@ -547,7 +547,7 @@ void testDeleteRootNetwork() throws Exception { // Run runAsync tasks inline so fire-and-forget cleanup (e.g. blocking=false remote deletions) // completes before the test verifies it, removes the race condition. - executeAsyncOnMainThread(studyServerExecutionService); + synchronizeStudyServerExecutionService(studyServerExecutionService); mockMvc.perform(delete("/v1/studies/{studyUuid}/root-networks", studyEntity.getId()) .contentType(APPLICATION_JSON) diff --git a/src/test/java/org/gridsuite/study/server/utils/TestUtils.java b/src/test/java/org/gridsuite/study/server/utils/TestUtils.java index 0be2d3347..98b9e7ff1 100644 --- a/src/test/java/org/gridsuite/study/server/utils/TestUtils.java +++ b/src/test/java/org/gridsuite/study/server/utils/TestUtils.java @@ -307,17 +307,4 @@ public static void checkUpdateTypeMessageReceived(UUID studyUuid, UUID nodeUuid, public static void checkUpdateStatusMessagesReceived(UUID studyUuid, UUID nodeUuid, OutputDestination output) { ALL_COMPUTATION_STATUS.forEach(computationStatus -> checkUpdateTypeMessageReceived(studyUuid, nodeUuid, computationStatus, output, STUDY_UPDATE_DESTINATION)); } - - /** - * Forces {@link StudyServerExecutionService#runAsync} to execute its task inline on the calling - * thread instead of dispatching it to the executor pool. Used to make testing more consistent when - * functions launch new threads in non blocking mode - */ - - public static void executeAsyncOnMainThread(StudyServerExecutionService studyServerExecutionService) { - doAnswer(invocation -> { - ((Runnable) invocation.getArgument(0)).run(); - return CompletableFuture.completedFuture(null); - }).when(studyServerExecutionService).runAsync(any()); - } } From c0820defcf7200ff944139e045657f4c5db8f9bf Mon Sep 17 00:00:00 2001 From: Hugo Marcellin Date: Mon, 22 Jun 2026 09:49:05 +0200 Subject: [PATCH 13/14] Fix import --- .../java/org/gridsuite/study/server/loadflow/LoadFlowTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java b/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java index 2f60b14ea..f1cc956e6 100644 --- a/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java +++ b/src/test/java/org/gridsuite/study/server/loadflow/LoadFlowTest.java @@ -72,6 +72,7 @@ import static org.gridsuite.study.server.error.StudyBusinessErrorCode.NOT_FOUND; import static org.gridsuite.study.server.notification.NotificationService.HEADER_UPDATE_TYPE; import static org.gridsuite.study.server.notification.NotificationService.NODE_BUILD_STATUS_UPDATED; +import static org.gridsuite.study.server.notification.NotificationService.UPDATE_TYPE_ALL_COMPUTATION_STATUS; import static org.gridsuite.study.server.notification.NotificationService.UPDATE_TYPE_COMPUTATION_PARAMETERS; import static org.gridsuite.study.server.utils.TestUtils.USER_DEFAULT_PROFILE_JSON; import static org.gridsuite.study.server.utils.TestUtils.synchronizeStudyServerExecutionService; From 63df38feac8c88367a3a6cf15a141c81d1b5cc7c Mon Sep 17 00:00:00 2001 From: Hugo Marcellin Date: Mon, 22 Jun 2026 16:14:10 +0200 Subject: [PATCH 14/14] Swap deleteByIdInBatch by deleteById --- .../java/org/gridsuite/study/server/service/StudyService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d90b7fc04..c8dba0254 100644 --- a/src/main/java/org/gridsuite/study/server/service/StudyService.java +++ b/src/main/java/org/gridsuite/study/server/service/StudyService.java @@ -561,7 +561,7 @@ private Optional doDeleteStudyIfNotCreationInProgress(UUID stu removeNadConfigs(s.getNadConfigsUuids().stream().toList()); deleteStudyInfos = new DeleteStudyInfos(rootNetworkInfos, modificationGroupUuids); } else { - studyCreationRequestEntity.ifPresent(creationRequestEntity -> studyCreationRequestRepository.deleteAllByIdInBatch(List.of(creationRequestEntity.getId()))); + studyCreationRequestEntity.ifPresent(creationRequestEntity -> studyCreationRequestRepository.deleteById(creationRequestEntity.getId())); } if (deleteStudyInfos == null) {