diff --git a/src/main/java/com/rte_france/antares/datamanager_back/controller/TrajectoryController.java b/src/main/java/com/rte_france/antares/datamanager_back/controller/TrajectoryController.java index cdf2bcd4..2bf9441a 100644 --- a/src/main/java/com/rte_france/antares/datamanager_back/controller/TrajectoryController.java +++ b/src/main/java/com/rte_france/antares/datamanager_back/controller/TrajectoryController.java @@ -62,7 +62,7 @@ public ResponseEntity uploadTrajectory(@RequestParam("trajectoryT String trajectoryToUse, @RequestParam("horizon") @Pattern(regexp = "^\\d{4}-\\d{4}$") @Parameter(description = "example of horizon : 2020-2021") String horizon, - @RequestParam("studyId") Integer studyId) throws IOException { + @RequestParam(value = "studyId", required = false) Integer studyId) throws IOException { return new ResponseEntity<>(toTrajectoryDTO(trajectoryService.processTrajectory(trajectoryType, trajectoryToUse, horizon, studyId)), HttpStatus.CREATED); } @@ -72,7 +72,7 @@ public ResponseEntity uploadTrajectory(@RequestParam("area") Stri @RequestParam("trajectoryToUse") String trajectoryToUse, @RequestParam("horizon") @Pattern(regexp = "^\\d{4}-\\d{4}$") @Parameter(description = "example of horizon : 2020-2021") String horizon, - @RequestParam("studyId") Integer studyId) throws IOException { + @RequestParam(value = "studyId",required = false) Integer studyId) throws IOException { return new ResponseEntity<>(toTrajectoryDTO(trajectoryService.processLoadTrajectory(area, trajectoryToUse, horizon, studyId)), HttpStatus.CREATED); } diff --git a/src/main/java/com/rte_france/antares/datamanager_back/service/impl/LinkFileProcessorServiceImpl.java b/src/main/java/com/rte_france/antares/datamanager_back/service/impl/LinkFileProcessorServiceImpl.java index 214d13c8..ff6208d6 100644 --- a/src/main/java/com/rte_france/antares/datamanager_back/service/impl/LinkFileProcessorServiceImpl.java +++ b/src/main/java/com/rte_france/antares/datamanager_back/service/impl/LinkFileProcessorServiceImpl.java @@ -31,6 +31,7 @@ import java.util.stream.Collectors; import static com.rte_france.antares.datamanager_back.util.Utils.*; +import static java.util.Objects.nonNull; /** @@ -70,9 +71,7 @@ public TrajectoryEntity processLinkFile(Path path, String horizon, Integer study ); String createdBy = userService.getCurrentUserDetails().getNni(); - List areaNames = findListArea(studyId); - - List listLink = buildLinkList(path, horizon, areaNames); + List listLink = buildLinkList(path, horizon); TrajectoryEntity trajectory; if (trajectoryEntity.isPresent() && checkTrajectoryVersion(path, trajectoryEntity.get())) { @@ -81,14 +80,17 @@ public TrajectoryEntity processLinkFile(Path path, String horizon, Integer study trajectory = buildTrajectory(path, 0, horizon, createdBy, TrajectoryType.LINK); } - validateLinksAlphabeticalOrder(path, horizon, studyId, trajectory); + if (nonNull(studyId)) { + List areaNames = findListArea(studyId); - TrajectoryEntity secondTrajectory = trajectoryRepository.findByTypeAndStudyId(TrajectoryType.AREA.name(), studyId).stream().findFirst().orElse(null); - String userNni = findUserNni(); + TrajectoryEntity secondTrajectory = trajectoryRepository.findByTypeAndStudyId(TrajectoryType.AREA.name(), studyId).stream().findFirst().orElse(null); + String userNni = findUserNni(); + validateLinksAlphabeticalOrder(path, horizon, studyId, trajectory); - checkForWarnings(path, horizon, studyId, warningMessageEntities, userNni, trajectory); - checkConsistencyTrajectoryLinkAndArea(listLink, areaNames, warningMessageEntities, studyId, trajectory.getId(), secondTrajectory, userNni); + checkForWarnings(path, horizon, studyId, warningMessageEntities, userNni, trajectory); + checkConsistencyTrajectoryLinkAndArea(listLink, areaNames, warningMessageEntities, studyId, trajectory.getId(), secondTrajectory, userNni); + } return saveTrajectory(trajectory, listLink, warningMessageEntities); } @@ -186,7 +188,7 @@ public TrajectoryEntity saveTrajectory(TrajectoryEntity trajectory, List buildLinkList(Path path, String horizon, List listArea) throws IOException { + private List buildLinkList(Path path, String horizon) throws IOException { List linkEntities = new ArrayList<>(); try (InputStream inputStream = Files.newInputStream(path); Workbook workbook = WorkbookFactory.create(inputStream)) { diff --git a/src/main/java/com/rte_france/antares/datamanager_back/service/impl/TrajectoryServiceImpl.java b/src/main/java/com/rte_france/antares/datamanager_back/service/impl/TrajectoryServiceImpl.java index 5af462d5..f4434255 100644 --- a/src/main/java/com/rte_france/antares/datamanager_back/service/impl/TrajectoryServiceImpl.java +++ b/src/main/java/com/rte_france/antares/datamanager_back/service/impl/TrajectoryServiceImpl.java @@ -27,6 +27,7 @@ import java.util.stream.Collectors; import static com.rte_france.antares.datamanager_back.util.Utils.*; +import static java.util.Objects.nonNull; @Slf4j @@ -70,9 +71,54 @@ public class TrajectoryServiceImpl implements TrajectoryService { @Transactional @Override public TrajectoryEntity processLoadTrajectory(String area, String trajectoryToUse, String horizon, Integer studyId) throws IOException { - return saveLoadTrajectoriesInDb(area, trajectoryToUse, horizon, studyId); + + String userNni = Optional.ofNullable(userService.getCurrentUserDetails()) + .map(UserInfoDto::getNni) + .orElseThrow(() -> + BusinessException.builder() + .message("User NNI could not be determined") + .httpStatus(HttpStatus.BAD_REQUEST) + .build()); + + Optional existingTrajectoryOpt = trajectoryRepository + .findFirstByFileNameAndHorizonAndLoadAreaOrderByVersionDesc(trajectoryToUse, horizon, area); + + + if(nonNull(studyId)){ + return saveLoadTrajectoriesInDb(area, trajectoryToUse, horizon, studyId, existingTrajectoryOpt, userNni); + } + else + return saveLoadTrajectoryWithoutStudyId(area,trajectoryToUse,horizon,existingTrajectoryOpt, userNni); + + } + + private TrajectoryEntity saveLoadTrajectoryWithoutStudyId(String area, String trajectoryToUse, String horizon, + Optional existingTrajectoryOpt, String userNni) throws IOException { + Path trajectoryPath = buildTrajectoryPath(trajectoryToUse); + + + if (existingTrajectoryOpt.isPresent()) { + TrajectoryEntity existingTrajectory = existingTrajectoryOpt.get(); + if (isSameLoadTrajectory(trajectoryPath, existingTrajectory) || area.equals(OTHER_AREA)) + { + throw BusinessException.builder() + .message("Trajectory already uploaded") + .httpStatus(HttpStatus.BAD_REQUEST) + .build(); + } + + // Update a version and save new trajectory + TrajectoryEntity newTrajectory = buildNewLoadTrajectory(trajectoryToUse, horizon, trajectoryPath, userNni); + newTrajectory.setVersion(existingTrajectory.getVersion() + 1); + return buildAndSaveLoadTrajectory(area, horizon, trajectoryPath, newTrajectory, null, null); + + } + TrajectoryEntity newTrajectory = buildNewLoadTrajectory(trajectoryToUse, horizon, trajectoryPath, userNni); + return buildAndSaveLoadTrajectory(area, horizon, trajectoryPath, newTrajectory, null, null); + } + /** * Processes a load trajectory file based on the given area, trajectory name, and horizon. * @@ -82,7 +128,8 @@ public TrajectoryEntity processLoadTrajectory(String area, String trajectoryToUs * @return the processed TrajectoryEntity * @throws IOException if an I/O error occurs */ - public TrajectoryEntity saveLoadTrajectoriesInDb(String area, String trajectoryToUse, String horizon, Integer studyId) throws IOException { + public TrajectoryEntity saveLoadTrajectoriesInDb(String area, String trajectoryToUse, String horizon, Integer studyId, + Optional existingTrajectoryOpt, String userNni) throws IOException { Set warningMessageEntities = new HashSet<>(); if (area == null || trajectoryToUse == null || horizon == null) { throw @@ -90,9 +137,9 @@ public TrajectoryEntity saveLoadTrajectoriesInDb(String area, String trajectoryT .message("Area, trajectory name, and horizon must not be null") .httpStatus(HttpStatus.BAD_REQUEST) .build(); - } + if (!area.equals(OTHER_AREA)) { areaRepository.findAreaByNameAndStudyId(area, studyId).orElseThrow(() -> BusinessException.builder() @@ -102,22 +149,10 @@ public TrajectoryEntity saveLoadTrajectoriesInDb(String area, String trajectoryT .build()); } - String userNni = Optional.ofNullable(userService.getCurrentUserDetails()) - .map(UserInfoDto::getNni) - .orElseThrow(() -> - BusinessException.builder() - .message("User NNI could not be determined") - .httpStatus(HttpStatus.BAD_REQUEST) - .build()); // Build and normalize the trajectory path Path trajectoryPath = buildTrajectoryPath(trajectoryToUse); - - // Try to find existing trajectory - Optional existingTrajectoryOpt = trajectoryRepository - .findFirstByFileNameAndHorizonAndLoadAreaOrderByVersionDesc(trajectoryToUse, horizon, area); - if (existingTrajectoryOpt.isPresent()) { TrajectoryEntity existingTrajectory = existingTrajectoryOpt.get(); if ((isSameLoadTrajectory(trajectoryPath, existingTrajectory) && !area.equals(OTHER_AREA)) @@ -213,7 +248,7 @@ private TrajectoryEntity buildNewLoadTrajectory(String trajectoryToUse, String h private TrajectoryEntity buildAndSaveLoadTrajectory(String area, String horizon, Path trajectoryPath, TrajectoryEntity loadTrajectory, Integer studyId, Set warningMessageEntities) throws IOException { List listCustomLoadFilesAlreadyChoosed = new ArrayList<>(); - if (area.equals(OTHER_AREA)) { + if (area.equals(OTHER_AREA) && studyId != null) { listCustomLoadFilesAlreadyChoosed = trajectoryRepository.findByTypeAndStudyId(TrajectoryType.LOAD.name(), studyId) .stream() .map(TrajectoryEntity::getLoadArea) @@ -221,7 +256,13 @@ private TrajectoryEntity buildAndSaveLoadTrajectory(String area, String horizon, .map(String::toLowerCase) .toList(); } - List areaWithStudy = areaRepository.findAllByStudyId(studyId).stream().map(areaStudy->areaStudy.getName().toLowerCase()).toList(); + + List areaWithStudy = new ArrayList<>(); + if (studyId != null) { + areaWithStudy = areaRepository.findAllByStudyId(studyId).stream() + .map(areaStudy -> areaStudy.getName().toLowerCase()) + .toList(); + } List loadsFile = getValidLoadFileNamesWithHorizon(trajectoryPath, area, horizon, listCustomLoadFilesAlreadyChoosed, areaWithStudy); if (loadsFile.isEmpty()) { diff --git a/src/main/java/com/rte_france/antares/datamanager_back/util/Utils.java b/src/main/java/com/rte_france/antares/datamanager_back/util/Utils.java index 81bcfae5..b2191bed 100644 --- a/src/main/java/com/rte_france/antares/datamanager_back/util/Utils.java +++ b/src/main/java/com/rte_france/antares/datamanager_back/util/Utils.java @@ -13,6 +13,7 @@ import lombok.experimental.UtilityClass; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.collections4.CollectionUtils; import org.apache.poi.ss.usermodel.*; import org.springframework.http.HttpStatus; @@ -36,6 +37,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import static java.util.Objects.nonNull; + /** * Utility class for file and trajectory related operations. @@ -113,9 +116,14 @@ public static List getValidLoadFileNamesWithHorizon(Path dir, String are } private static boolean isValidLoadFile(String horizon, String expectedHorizon, String areaFromFile, List areaLoadAlreadyChosen, List areaWithStudy) { + if(!CollectionUtils.isEmpty(areaWithStudy)){ boolean isAreaValid = areaWithStudy.contains(areaFromFile.toLowerCase()); boolean isAreaNotChosen = areaLoadAlreadyChosen.isEmpty() || !areaLoadAlreadyChosen.contains(areaFromFile.toLowerCase()); return horizon.equals(expectedHorizon) && isAreaValid && isAreaNotChosen; + } else { + boolean isAreaNotChosen = areaLoadAlreadyChosen.isEmpty() || !areaLoadAlreadyChosen.contains(areaFromFile.toLowerCase()); + return horizon.equals(expectedHorizon) && isAreaNotChosen; + } } /** diff --git a/src/test/java/com/rte_france/antares/datamanager_back/service/LinkFileProcessorServiceImplTest.java b/src/test/java/com/rte_france/antares/datamanager_back/service/LinkFileProcessorServiceImplTest.java index 555ba271..a4a43962 100644 --- a/src/test/java/com/rte_france/antares/datamanager_back/service/LinkFileProcessorServiceImplTest.java +++ b/src/test/java/com/rte_france/antares/datamanager_back/service/LinkFileProcessorServiceImplTest.java @@ -507,4 +507,58 @@ void testCheckConsistencyTrajectoryLinkAndArea_WithException_StopsExecution() { + @Test + void processLinkFile_whenStudyIdIsNull() throws IOException { + // Create test Excel file + tempFile = CreateExcelTestUtil.createExcelFileWithTwoSheets( + tempDir, + "TestNullStudyId.xlsx", + List.of("parameters", "2035-2036"), + List.of( + List.of("", "2035-2036"), + List.of("Name", "Winter_HP_Direct_MW", "Winter_HP_Indirect_MW", + "Winter_HC_Direct_MW", "Winter_HC_Indirect_MW", + "Summer_HP_Direct_MW", "Summer_HP_Indirect_MW", + "Summer_HC_Direct_MW", "Summer_HC_Indirect_MW", + "Flowbased_perimeter", "HVDC", "Specific_TS", "Forced_Outage_HVAC") + ), + List.of( + List.of(List.of("Hurdle Costs", 0, 5)), + List.of( + List.of("CH-FR", 10, 20, 30, 40, 50, 60, 70, 80, "TRUE", "FALSE", "TRUE", "FALSE"), + List.of("FR-IT", 15, 25, 35, 45, 55, 65, 75, 85, "TRUE", "FALSE", "TRUE", "FALSE") + ) + )); + + // Setup mocks + TrajectoryEntity trajectory = new TrajectoryEntity(); + trajectory.setFileName("TestNullStudyId.xlsx"); + trajectory.setVersion(1); + + when(userService.getCurrentUserDetails()).thenReturn(UserInfoDto.builder().nni("CF001").build()); + when(trajectoryRepository.findFirstByFileNameAndHorizonAndTypeOrderByVersionDesc("TestNullStudyId.xlsx", "2035-2036", TrajectoryType.LINK.name())) + .thenReturn(Optional.of(trajectory)); + + // Mock the save method to return the trajectory + when(trajectoryRepository.save(any(TrajectoryEntity.class))).thenReturn(trajectory); + + // Call the method with null studyId + TrajectoryEntity result = linkFileProcessorService.processLinkFile(tempFile, "2035-2036", null); + + // Verify that the trajectory was saved + verify(trajectoryRepository, times(1)).save(any(TrajectoryEntity.class)); + + // Verify that findByTypeAndStudyId was called with AREA and null + verify(trajectoryRepository, never()).findByTypeAndStudyId(TrajectoryType.AREA.name(), null); + + // Verify that studyRepository.findById was never called (since studyId is null) + verify(studyRepository, never()).findById(any()); + + // Verify that warningService.getMessage was never called (since warnings are only checked when studyId is not null) + verify(warningService, never()).getMessage(anyString(), anyString()); + + // Verify that the result is not null + assertNotNull(result); + } + } \ No newline at end of file diff --git a/src/test/java/com/rte_france/antares/datamanager_back/service/TrajectoryServiceImplAdditionalTest.java b/src/test/java/com/rte_france/antares/datamanager_back/service/TrajectoryServiceImplAdditionalTest.java index e46ea83f..0e54efcf 100644 --- a/src/test/java/com/rte_france/antares/datamanager_back/service/TrajectoryServiceImplAdditionalTest.java +++ b/src/test/java/com/rte_france/antares/datamanager_back/service/TrajectoryServiceImplAdditionalTest.java @@ -141,6 +141,14 @@ void buildAndSaveLoadTrajectory_shouldCreateListOfCustomLoadFilesWhenAreaIsOTHER String trajectoryToUse = "testTrajectory"; String horizon = "2030-2031"; Integer studyId = 1; + String userNni = "testUser"; + var existingTrajectory = TrajectoryEntity.builder() + .fileName(trajectoryToUse) + .horizon(horizon) + .loadArea(area) + .version(1) + .type(TrajectoryType.LOAD.name()) + .build(); Path trajectoryPath = tempDir.resolve(trajectoryToUse); @@ -175,15 +183,11 @@ void buildAndSaveLoadTrajectory_shouldCreateListOfCustomLoadFilesWhenAreaIsOTHER AreaEntity.builder().name("DE").build() )); - when(userService.getCurrentUserDetails()) - .thenReturn(UserInfoDto.builder().nni("testUser").build()); when(antaressDataManagerProperties.getNasDirectory()).thenReturn(tempDir.toString()); when(antaressDataManagerProperties.getTrajectoryFilePath()).thenReturn(""); when(antaressDataManagerProperties.getLoadDirectory()).thenReturn(""); - when(loadFileProcessorServiceImpl.checkForMissingLoadFiles(any(), any(), any(), any(), any())) - .thenReturn(Collections.emptySet()); when(trajectoryRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); @@ -198,7 +202,7 @@ void buildAndSaveLoadTrajectory_shouldCreateListOfCustomLoadFilesWhenAreaIsOTHER )).thenReturn(Arrays.asList("load_fr_2030-2031.txt", "load_de_2030-2031.txt")); // When - TrajectoryEntity result = trajectoryService.saveLoadTrajectoriesInDb(area, trajectoryToUse, horizon, studyId); + TrajectoryEntity result = trajectoryService.saveLoadTrajectoriesInDb(area, trajectoryToUse, horizon, studyId, Optional.ofNullable(existingTrajectory),userNni); // Then assertNotNull(result); @@ -218,18 +222,18 @@ void buildAndSaveLoadTrajectory_shouldCreateListOfCustomLoadFilesWhenAreaIsOTHER @Test void saveLoadTrajectoriesInDb_throwsBadRequestWhenParamsAreNull() { var ex = assertThrows(BusinessException.class, () -> - trajectoryService.saveLoadTrajectoriesInDb(null, "trajectory", "2023-2024", 1) + trajectoryService.saveLoadTrajectoriesInDb(null, "trajectory", "2023-2024",1 ,Optional.empty(),"nni") ); assertEquals(HttpStatus.BAD_REQUEST, ex.getHttpStatus()); assertTrue(ex.getMessage().contains("must not be null")); ex = assertThrows(BusinessException.class, () -> - trajectoryService.saveLoadTrajectoriesInDb("area", null, "2023-2024", 1) + trajectoryService.saveLoadTrajectoriesInDb("area", null, "2023-2024", 1,Optional.empty(),"nni") ); assertEquals(HttpStatus.BAD_REQUEST, ex.getHttpStatus()); ex = assertThrows(BusinessException.class, () -> - trajectoryService.saveLoadTrajectoriesInDb("area", "trajectory", null, 1) + trajectoryService.saveLoadTrajectoriesInDb("area", "trajectory", null, 1, Optional.empty(), "nni") ); assertEquals(HttpStatus.BAD_REQUEST, ex.getHttpStatus()); } @@ -241,9 +245,6 @@ void saveLoadTrajectoriesInDb_throwsBadRequestWhenTrajectoryAlreadyUploaded() { var horizon = "2023-2024"; var studyId = 1; - when(userService.getCurrentUserDetails()) - .thenReturn(UserInfoDto.builder().nni("user").build()); - when(areaRepository.findAreaByNameAndStudyId(area, studyId)) .thenReturn(Optional.of(AreaEntity.builder().name(area).build())); @@ -256,14 +257,12 @@ void saveLoadTrajectoriesInDb_throwsBadRequestWhenTrajectoryAlreadyUploaded() { .horizon(horizon) .loadArea(area) .build(); - when(trajectoryRepository.findFirstByFileNameAndHorizonAndLoadAreaOrderByVersionDesc(trajectoryToUse, horizon, area)) - .thenReturn(Optional.of(existingTrajectory)); try (var utilsMock = mockStatic(Utils.class)) { utilsMock.when(() -> Utils.isSameLoadTrajectory(any(), eq(existingTrajectory))).thenReturn(true); var ex = assertThrows(BusinessException.class, () -> - trajectoryService.saveLoadTrajectoriesInDb(area, trajectoryToUse, horizon, studyId) + trajectoryService.saveLoadTrajectoriesInDb(area, trajectoryToUse, horizon, studyId, Optional.of(existingTrajectory), "nni") ); assertEquals(HttpStatus.BAD_REQUEST, ex.getHttpStatus()); assertTrue(ex.getMessage().contains("already uploaded")); diff --git a/src/test/java/com/rte_france/antares/datamanager_back/service/TrajectoryServiceImplTest.java b/src/test/java/com/rte_france/antares/datamanager_back/service/TrajectoryServiceImplTest.java index 26890501..0adee836 100644 --- a/src/test/java/com/rte_france/antares/datamanager_back/service/TrajectoryServiceImplTest.java +++ b/src/test/java/com/rte_france/antares/datamanager_back/service/TrajectoryServiceImplTest.java @@ -10,17 +10,18 @@ import com.rte_france.antares.datamanager_back.service.impl.TrajectoryServiceImpl; import com.rte_france.antares.datamanager_back.service.impl.UserService; import com.rte_france.antares.datamanager_back.util.Utils; +import org.apache.poi.sl.draw.geom.GuideIf; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; +import org.mockito.*; import org.springframework.http.HttpStatus; import java.io.IOException; import java.io.UncheckedIOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -76,6 +77,38 @@ void setUp() { MockitoAnnotations.openMocks(this); } + /** + * Helper method to set up common AntaressDataManagerProperties mock configuration + * + * @param nasDirectory the NAS directory path + * @param trajectoryFilePath the trajectory file path + */ + private void setupAntaressProperties(String nasDirectory, String trajectoryFilePath) { + when(antaressDataManagerProperties.getNasDirectory()).thenReturn(nasDirectory); + when(antaressDataManagerProperties.getTrajectoryFilePath()).thenReturn(trajectoryFilePath); + } + + /** + * Helper method to set up common AntaressDataManagerProperties mock configuration with additional directory + * + * @param nasDirectory the NAS directory path + * @param trajectoryFilePath the trajectory file path + * @param directoryName the name of the additional directory property to mock + * @param directoryPath the path of the additional directory + */ + private void setupAntaressProperties(String nasDirectory, String trajectoryFilePath, String directoryName, String directoryPath) { + setupAntaressProperties(nasDirectory, trajectoryFilePath); + + switch (directoryName) { + case "area" -> when(antaressDataManagerProperties.getAreaDirectory()).thenReturn(directoryPath); + case "link" -> when(antaressDataManagerProperties.getLinkDirectory()).thenReturn(directoryPath); + case "thermal_capacity" -> when(antaressDataManagerProperties.getThermalCapacityDirectory()).thenReturn(directoryPath); + case "thermal_parameter" -> when(antaressDataManagerProperties.getThermalParameterDirectory()).thenReturn(directoryPath); + case "thermal_cost" -> when(antaressDataManagerProperties.getThermalCostDirectory()).thenReturn(directoryPath); + case "load" -> when(antaressDataManagerProperties.getLoadDirectory()).thenReturn(directoryPath); + } + } + @Test void processTrajectory_returnsEntityWhenTrajectoryTYpeIsAREA() throws IOException { Path path = mock(Path.class); @@ -603,6 +636,159 @@ void processLoadTrajectory_shouldThrowBusinessExceptionWhenAllLoadFilesMissing() trajectoryService.processLoadTrajectory(area, trajectoryToUse, horizon, studyId)); } + @Test + void processLoadTrajectory_createsNewTrajectoryWhenNoExisting(@TempDir Path tempDir) throws IOException { + // Given + var area = "FR"; + var trajectoryToUse = "testTrajectory"; + var horizon = "2030-2031"; + + var trajectoryDir = tempDir.resolve(trajectoryToUse); + Files.createDirectory(trajectoryDir); + var loadFile = trajectoryDir.resolve("load_fr_2030-2031.txt"); + Files.writeString(loadFile, "test content"); + + // When + when(antaressDataManagerProperties.getNasDirectory()).thenReturn(tempDir.toString()); + when(antaressDataManagerProperties.getTrajectoryFilePath()).thenReturn(""); + when(antaressDataManagerProperties.getLoadDirectory()).thenReturn(""); + when(trajectoryRepository.findFirstByFileNameAndHorizonAndLoadAreaOrderByVersionDesc(trajectoryToUse, horizon, area)) + .thenReturn(Optional.empty()); + when(userService.getCurrentUserDetails()).thenReturn(UserInfoDto.builder().nni("user").build()); + when(trajectoryRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); + when(loadRepository.findByFileNameAndTrajectoryFileName(any(), any())).thenReturn(Optional.empty()); + + try (var utils = mockStatic(Utils.class)) { + utils.when(() -> Utils.getValidLoadFileNamesWithHorizon(any(), eq(area), eq(horizon), any(), any())) + .thenReturn(List.of("load_fr_2030-2031.txt")); + + var result = trajectoryService.processLoadTrajectory(area, trajectoryToUse, horizon, null); + + // Then + assertNotNull(result); + assertEquals(trajectoryToUse, result.getFileName()); + assertEquals(1, result.getVersion()); + assertEquals(area, result.getLoadArea()); + } + } + + @Test + void processLoadTrajectory_throwsWhenExistingTrajectoryIsSameAndAreaNotOthers() { + // Given + var area = "FR"; + var trajectoryToUse = "testTrajectory"; + var horizon = "2030-2031"; + + var existing = TrajectoryEntity.builder() + .fileName(trajectoryToUse) + .version(1) + .lastModificationContentDate(LocalDateTime.now()) + .build(); + + // When + when(antaressDataManagerProperties.getNasDirectory()).thenReturn("/tmp"); + when(antaressDataManagerProperties.getTrajectoryFilePath()).thenReturn(""); + when(antaressDataManagerProperties.getLoadDirectory()).thenReturn(""); + when(trajectoryRepository.findFirstByFileNameAndHorizonAndLoadAreaOrderByVersionDesc(trajectoryToUse, horizon, area)) + .thenReturn(Optional.of(existing)); + when(userService.getCurrentUserDetails()).thenReturn(UserInfoDto.builder().nni("user").build()); + try (var utils = mockStatic(Utils.class)) { + utils.when(() -> Utils.isSameLoadTrajectory(any(), eq(existing))).thenReturn(true); + + var ex = assertThrows(BusinessException.class, () -> + trajectoryService.processLoadTrajectory(area, trajectoryToUse, horizon, null) + ); + assertTrue(ex.getMessage().contains("Trajectory already uploaded")); + } + } + + @Test + void processLoadTrajectory_createsNewVersionWhenExistingTrajectoryIsDifferent(@TempDir Path tempDir) throws IOException { + // Given + var area = "FR"; + var trajectoryToUse = "testTrajectory"; + var horizon = "2030-2031"; + + var nasDir = tempDir; + var trajectoryFilePath = nasDir.resolve("trajectories"); + Files.createDirectory(trajectoryFilePath); + var loadDir = trajectoryFilePath.resolve("load"); + Files.createDirectory(loadDir); + var trajectoryDir = loadDir.resolve(trajectoryToUse); + Files.createDirectory(trajectoryDir); + + var loadFile = trajectoryDir.resolve("load_fr_2030-2031.txt"); + Files.writeString(loadFile, "test content"); + + var existing = TrajectoryEntity.builder() + .fileName(trajectoryToUse) + .horizon(horizon) + .loadArea(area) + .lastModificationContentDate(LocalDateTime.now().minusDays(1)) + .version(1) + .build(); + + // When + when(antaressDataManagerProperties.getNasDirectory()).thenReturn(nasDir.toString()); + when(antaressDataManagerProperties.getTrajectoryFilePath()).thenReturn("trajectories"); + when(antaressDataManagerProperties.getLoadDirectory()).thenReturn("load"); + when(trajectoryRepository.findFirstByFileNameAndHorizonAndLoadAreaOrderByVersionDesc(trajectoryToUse, horizon, area)) + .thenReturn(Optional.of(existing)); + when(userService.getCurrentUserDetails()).thenReturn(UserInfoDto.builder().nni("user").build()); + when(trajectoryRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); + + try (var utils = mockStatic(Utils.class)) { + utils.when(() -> Utils.getValidLoadFileNamesWithHorizon(any(), eq(area), eq(horizon), any(), any())) + .thenReturn(List.of("load_fr_2030-2031.txt")); + utils.when(() -> Utils.isSameLoadTrajectory(any(), any())).thenReturn(false); + + var result = trajectoryService.processLoadTrajectory(area, trajectoryToUse, horizon, null); + + // Then + assertNotNull(result); + assertEquals(trajectoryToUse, result.getFileName()); + assertEquals(2, result.getVersion()); + assertEquals(area, result.getLoadArea()); + } + } + + @Test + void processLoadTrajectory_throwsWhenExistingTrajectoryIsSameAndAreaIsOthers(@TempDir Path tempDir) throws IOException { + // Given + var area = "OTHERS"; + var trajectoryToUse = "testTrajectory"; + var horizon = "2030-2031"; + + var trajectoryDir = tempDir.resolve(trajectoryToUse); + Files.createDirectory(trajectoryDir); + var loadFile = trajectoryDir.resolve("load_fr_2030-2031.txt"); + Files.writeString(loadFile, "test content"); + var existing = TrajectoryEntity.builder() + .fileName(trajectoryToUse) + .horizon(horizon) + .loadArea(area) + .lastModificationContentDate( + Files.getLastModifiedTime(trajectoryDir).toInstant() + .atZone(java.time.ZoneId.systemDefault()).toLocalDateTime() + ) + .version(1) + .build(); + + // When + when(antaressDataManagerProperties.getNasDirectory()).thenReturn(tempDir.toString()); + when(antaressDataManagerProperties.getTrajectoryFilePath()).thenReturn(""); + when(antaressDataManagerProperties.getLoadDirectory()).thenReturn(""); + when(trajectoryRepository.findFirstByFileNameAndHorizonAndLoadAreaOrderByVersionDesc(trajectoryToUse, horizon, area)) + .thenReturn(Optional.of(existing)); + when(userService.getCurrentUserDetails()).thenReturn(UserInfoDto.builder().nni("user").build()); + + // Then + var ex = assertThrows(BusinessException.class, () -> + trajectoryService.processLoadTrajectory(area, trajectoryToUse, horizon, null) + ); + assertTrue(ex.getMessage().contains("Trajectory already uploaded")); + } + @Test void shouldCallCheckForMissingLoadFilesWhenOtherArea() { String area = "OTHERS"; @@ -810,6 +996,8 @@ void saveLoadTrajectoriesInDb_shouldAddMissingAreasWhenSameLoadTrajectoryAndOthe var trajectoryToUse = "testTrajectory"; var horizon = "2030-2031"; var studyId = 1; + var userNni = "userNni"; + Path trajectoryPath = tempDir.resolve(trajectoryToUse); Files.createDirectories(trajectoryPath); @@ -853,7 +1041,7 @@ void saveLoadTrajectoriesInDb_shouldAddMissingAreasWhenSameLoadTrajectoryAndOthe when(trajectoryRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - var result = service.saveLoadTrajectoriesInDb(area, trajectoryToUse, horizon, studyId); + var result = service.saveLoadTrajectoriesInDb(area, trajectoryToUse, horizon, studyId, Optional.ofNullable(existingTrajectory),userNni); assertNotNull(result); var fileNames = result.getLoadEntities().stream().map(LoadEntity::getFileName).collect(Collectors.toSet()); @@ -900,4 +1088,102 @@ void linkTrajectoryToStudy_shouldCheckForMissingLoadFilesAndSaveWarnings_whenLoa verify(warningRepository).saveAll(warnings); verify(loadFileProcessorService).checkForMissingLoadFiles(any(), eq(horizon), eq(studyId), eq(userNni), eq(trajectory)); } + + @Test + void saveLoadTrajectoriesInDb_shouldThrowBusinessExceptionWhenParamsAreNull() { + var studyId = 1; + var area = "area"; + var trajectoryToUse = "trajectory"; + var horizon = "horizon"; + var userNni = "userNni"; + var existingTrajectory = TrajectoryEntity.builder() + .fileName(trajectoryToUse) + .horizon(horizon) + .loadArea(area) + .version(1) + .type(TrajectoryType.LOAD.name()) + .build(); + + + // area is null + var ex1 = assertThrows(BusinessException.class, () -> + trajectoryService.saveLoadTrajectoriesInDb(null, "trajectory", "horizon", studyId, Optional.ofNullable(existingTrajectory),userNni)); + assertEquals("Area, trajectory name, and horizon must not be null", ex1.getMessage()); + + // trajectoryToUse is null + var ex2 = assertThrows(BusinessException.class, () -> + trajectoryService.saveLoadTrajectoriesInDb("area", null, "horizon", studyId, Optional.ofNullable(existingTrajectory),userNni)); + assertEquals("Area, trajectory name, and horizon must not be null", ex2.getMessage()); + + // horizon is null + var ex3 = assertThrows(BusinessException.class, () -> + trajectoryService.saveLoadTrajectoriesInDb("area", "trajectory", null, studyId, Optional.ofNullable(existingTrajectory),userNni)); + assertEquals("Area, trajectory name, and horizon must not be null", ex3.getMessage()); + } + + @Test + void processLoadTrajectory_shouldThrowBusinessExceptionWhenUserNotFound() { + // Given + String area = "testArea"; + String trajectoryToUse = "testTrajectory"; + String horizon = "2023-2024"; + Integer studyId = 1; + + when(userService.getCurrentUserDetails()).thenReturn(null); + + // When & Then + BusinessException exception = assertThrows( + BusinessException.class, + () -> trajectoryService.processLoadTrajectory(area, trajectoryToUse, horizon, studyId) + ); + + assertAll( + () -> assertEquals("User NNI could not be determined", exception.getMessage()), + () -> assertEquals(HttpStatus.BAD_REQUEST, exception.getHttpStatus()) + ); + + verify(userService).getCurrentUserDetails(); + verifyNoMoreInteractions(userService); + verifyNoInteractions(trajectoryRepository); + } + + + @Test + void saveLoadTrajectoriesInDb_shouldThrowBusinessExceptionWhenSameLoadTrajectoryExists() { + // Given + var area = "area"; + var trajectoryToUse = "trajectory"; + var horizon = "horizon"; + var studyId = 1; + var userNni = "userNni"; + var existingTrajectory = TrajectoryEntity.builder() + .fileName(trajectoryToUse) + .horizon(horizon) + .loadArea(area) + .version(1) + .type(TrajectoryType.LOAD.name()) + .build(); + + Optional existingTrajectoryOpt = Optional.of(existingTrajectory); + + // When + when(areaRepository.findAreaByNameAndStudyId(area, studyId)).thenReturn(Optional.of(new AreaEntity())); + when(userService.getCurrentUserDetails()).thenReturn(UserInfoDto.builder().nni("nni").build()); + when(antaressDataManagerProperties.getNasDirectory()).thenReturn("/tmp"); + when(antaressDataManagerProperties.getTrajectoryFilePath()).thenReturn("input"); + when(antaressDataManagerProperties.getLoadDirectory()).thenReturn("load"); + //var existingTrajectory = TrajectoryEntity.builder().fileName(trajectoryToUse).horizon(horizon).loadArea(area).version(1).build(); + //when(trajectoryRepository.findFirstByFileNameAndHorizonAndLoadAreaOrderByVersionDesc(trajectoryToUse, horizon, area)) + // .thenReturn(Optional.of(existingTrajectory)); + try (var mockedStatic = org.mockito.Mockito.mockStatic( + com.rte_france.antares.datamanager_back.util.Utils.class)) { + mockedStatic.when(() -> com.rte_france.antares.datamanager_back.util.Utils.isSameLoadTrajectory(any(), any())) + .thenReturn(true); + + // Then + var ex = assertThrows(BusinessException.class, () -> + trajectoryService.saveLoadTrajectoriesInDb(area, trajectoryToUse, horizon, studyId, existingTrajectoryOpt, userNni)); + assertEquals("Trajectory already uploaded", ex.getMessage()); + } + } } \ No newline at end of file diff --git a/src/test/java/com/rte_france/antares/datamanager_back/util/DuplicationTrajectoryUtilsTest.java b/src/test/java/com/rte_france/antares/datamanager_back/util/DuplicationTrajectoryUtilsTest.java index 9bbe52dc..136db375 100644 --- a/src/test/java/com/rte_france/antares/datamanager_back/util/DuplicationTrajectoryUtilsTest.java +++ b/src/test/java/com/rte_france/antares/datamanager_back/util/DuplicationTrajectoryUtilsTest.java @@ -120,6 +120,7 @@ void trajectoryToBeAttached_LoadType_WithOthersLoadArea_HasValidArea() throws Ex List availableAreas = Arrays.asList("FR", "DE", "ES"); when(loadFileProcessorService.getAreasLoadWithoutTrajectorySelected(studyId)).thenReturn(availableAreas); + // Act invokeTrajectoryToBeAttached(trajectory, TrajectoryType.LOAD); // Then @@ -191,14 +192,27 @@ void trajectoryToBeAttached_LoadType_WithSpecificLoadArea_AreaNotAvailable() thr assertEquals(TrajectoryType.LOAD.name(), missingTrajectoryTypes.getFirst()); } + @Test + void trajectoryToBeAttached_OtherType_Success() throws Exception { + // Given + when(trajectoryService.linkTrajectoryToStudy(anyInt(), anyInt(), any(TrajectoryType.class))).thenReturn(trajectory); + + // When + invokeTrajectoryToBeAttached(trajectory, TrajectoryType.THERMAL_CAPACITY); + // Then + verify(trajectoryService).linkTrajectoryToStudy(trajectory.getId(), studyId, TrajectoryType.THERMAL_CAPACITY); + verify(trajectoryService, never()).checkLinkCoherence(anyInt(), anySet(), any(TrajectoryEntity.class), anyString()); + verify(loadFileProcessorService, never()).getAreasLoadWithoutTrajectorySelected(anyInt()); + assertTrue(missingTrajectoryTypes.isEmpty()); + } /** * Helper method to invoke the private trajectoryToBeAttached method via reflection */ private void invokeTrajectoryToBeAttached(TrajectoryEntity trajectory, TrajectoryType type) throws InvocationTargetException, IllegalAccessException { trajectoryToBeAttachedMethod.invoke( - null, + null, // static method doesn't need an instance trajectory, type, studyId, diff --git a/src/test/java/com/rte_france/antares/datamanager_back/util/UtilsTest.java b/src/test/java/com/rte_france/antares/datamanager_back/util/UtilsTest.java index bc2fe2e3..5c8e43a3 100644 --- a/src/test/java/com/rte_france/antares/datamanager_back/util/UtilsTest.java +++ b/src/test/java/com/rte_france/antares/datamanager_back/util/UtilsTest.java @@ -224,4 +224,34 @@ void checkIfHorizonExist_shouldThrowBusinessException_whenHorizonDoesNotExist() ); } + @Test + void getValidLoadFileNamesWithHorizon_shouldReturnValidFilesWhenAreaWithStudyIsNull() throws IOException { + // Given + String area = "FR"; + String expectedHorizon = "2030-2031"; + List areaLoadAlreadyChosen = List.of("de"); // Already chosen area + List areaWithStudy = null; + + // Create test files in temp directory + Path loadDir = Files.createDirectory(tempDir.resolve("load")); + + // Create valid files + Files.writeString(loadDir.resolve("load_fr_2030-2031.txt"), "test content"); + Files.writeString(loadDir.resolve("load_es_2030-2031.txt"), "test content"); + + // Create invalid files (wrong horizon or already chosen area) + Files.writeString(loadDir.resolve("load_fr_2029-2030.txt"), "wrong horizon"); + Files.writeString(loadDir.resolve("load_de_2030-2031.txt"), "already chosen area"); + + // When + List result = Utils.getValidLoadFileNamesWithHorizon( + loadDir, area, expectedHorizon, areaLoadAlreadyChosen, areaWithStudy); + + // Then + assertEquals(1, result.size()); + assertTrue(result.contains("load_fr_2030-2031.txt")); + assertFalse(result.contains("load_es_2030-2031.txt")); // Not matching area pattern + assertFalse(result.contains("load_fr_2029-2030.txt")); // Wrong horizon + assertFalse(result.contains("load_de_2030-2031.txt")); // Already chosen area + } } \ No newline at end of file