diff --git a/src/main/java/fr/insee/genesis/domain/model/context/schedule/DeletedExpiredSchedules.java b/src/main/java/fr/insee/genesis/domain/model/context/schedule/DeletedExpiredSchedules.java new file mode 100644 index 00000000..d0f7fde9 --- /dev/null +++ b/src/main/java/fr/insee/genesis/domain/model/context/schedule/DeletedExpiredSchedules.java @@ -0,0 +1,12 @@ +package fr.insee.genesis.domain.model.context.schedule; + +import java.util.List; + +public record DeletedExpiredSchedules( + List v1Schedules, + List v2Schedules +) { + public boolean isEmpty() { + return v1Schedules.isEmpty() && v2Schedules.isEmpty(); + } +} diff --git a/src/main/java/fr/insee/genesis/domain/ports/spi/DataProcessingContextPersistancePort.java b/src/main/java/fr/insee/genesis/domain/ports/spi/DataProcessingContextPersistancePort.java index be618b23..b3d77514 100644 --- a/src/main/java/fr/insee/genesis/domain/ports/spi/DataProcessingContextPersistancePort.java +++ b/src/main/java/fr/insee/genesis/domain/ports/spi/DataProcessingContextPersistancePort.java @@ -1,7 +1,7 @@ package fr.insee.genesis.domain.ports.spi; import fr.insee.genesis.domain.model.context.DataProcessingContextModel; -import fr.insee.genesis.domain.model.context.schedule.KraftwerkExecutionSchedule; +import fr.insee.genesis.domain.model.context.schedule.DeletedExpiredSchedules; import fr.insee.genesis.infrastructure.document.context.DataProcessingContextDocument; import java.io.IOException; @@ -21,7 +21,7 @@ public interface DataProcessingContextPersistancePort { long count(); - List removeExpiredSchedules(DataProcessingContextModel dataProcessingContextModel) throws IOException; + DeletedExpiredSchedules removeExpiredSchedules(DataProcessingContextModel dataProcessingContextModel) throws IOException; List findAllByReview(boolean withReview); } diff --git a/src/main/java/fr/insee/genesis/domain/service/context/DataProcessingContextService.java b/src/main/java/fr/insee/genesis/domain/service/context/DataProcessingContextService.java index 001bbc92..d257bf97 100644 --- a/src/main/java/fr/insee/genesis/domain/service/context/DataProcessingContextService.java +++ b/src/main/java/fr/insee/genesis/domain/service/context/DataProcessingContextService.java @@ -6,7 +6,7 @@ import fr.insee.genesis.controller.dto.KraftwerkExecutionScheduleInput; import fr.insee.genesis.controller.dto.rawdata.ScheduleResponseDto; import fr.insee.genesis.domain.model.context.DataProcessingContextModel; -import fr.insee.genesis.domain.model.context.schedule.KraftwerkExecutionSchedule; +import fr.insee.genesis.domain.model.context.schedule.DeletedExpiredSchedules; import fr.insee.genesis.domain.model.context.schedule.KraftwerkExecutionScheduleV2; import fr.insee.genesis.domain.model.surveyunit.SurveyUnitModel; import fr.insee.genesis.domain.ports.api.DataProcessingContextApiPort; @@ -266,33 +266,49 @@ public List getAllSchedulesV2() { @Override public void deleteExpiredSchedules(String logFolder) throws GenesisException { List dataProcessingContextModels = - DataProcessingContextMapper.INSTANCE.listDocumentToListModel(dataProcessingContextPersistancePort.findAll()); - for(DataProcessingContextModel context : dataProcessingContextModels){ + DataProcessingContextMapper.INSTANCE.listDocumentToListModel( + dataProcessingContextPersistancePort.findAll() + ); + + for (DataProcessingContextModel context : dataProcessingContextModels) { try { - List deletedKraftwerkExecutionSchedules = dataProcessingContextPersistancePort.removeExpiredSchedules(context); - //Save in JSON log - if(!deletedKraftwerkExecutionSchedules.isEmpty()) { + DeletedExpiredSchedules deletedSchedules = + dataProcessingContextPersistancePort.removeExpiredSchedules(context); + + if (!deletedSchedules.isEmpty()) { String scheduleName = context.getCollectionInstrumentId(); - Path jsonLogPath = Path.of(logFolder, Constants.SCHEDULE_ARCHIVE_FOLDER_NAME, - scheduleName + ".json"); + Path jsonLogPath = Path.of( + logFolder, + Constants.SCHEDULE_ARCHIVE_FOLDER_NAME, + scheduleName + ".json" + ); + ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules(); objectMapper.registerModule(new JavaTimeModule()); - String jsonToWrite = objectMapper.writeValueAsString(deletedKraftwerkExecutionSchedules); - if(Files.exists(jsonLogPath)){ - //Remove last ] and append survey + + String jsonToWrite = objectMapper.writeValueAsString(deletedSchedules); + + if (Files.exists(jsonLogPath)) { StringBuilder content = new StringBuilder(Files.readString(jsonLogPath)); - content.setCharAt(content.length()-1, ','); - content.append(jsonToWrite, 1, jsonToWrite.length()-1); + content.setCharAt(content.length() - 1, ','); + content.append(jsonToWrite, 1, jsonToWrite.length() - 1); content.append(']'); - Files.write(jsonLogPath, content.toString().getBytes(), StandardOpenOption.TRUNCATE_EXISTING); - }else { + Files.write( + jsonLogPath, + content.toString().getBytes(), + StandardOpenOption.TRUNCATE_EXISTING + ); + } else { Files.createDirectories(jsonLogPath.getParent()); Files.write(jsonLogPath, jsonToWrite.getBytes()); } } } catch (IOException _) { - String name = context.getCollectionInstrumentId(); - throw new GenesisException(HttpStatus.INTERNAL_SERVER_ERROR,String.format("An error occured trying to delete expired schedules for %s",name)); + String name = context.getCollectionInstrumentId(); + throw new GenesisException( + HttpStatus.INTERNAL_SERVER_ERROR, + String.format("An error occured trying to delete expired schedules for %s", name) + ); } } } diff --git a/src/main/java/fr/insee/genesis/infrastructure/adapter/DataProcessingContextMongoAdapter.java b/src/main/java/fr/insee/genesis/infrastructure/adapter/DataProcessingContextMongoAdapter.java index bc32744a..f556b085 100644 --- a/src/main/java/fr/insee/genesis/infrastructure/adapter/DataProcessingContextMongoAdapter.java +++ b/src/main/java/fr/insee/genesis/infrastructure/adapter/DataProcessingContextMongoAdapter.java @@ -2,7 +2,9 @@ import fr.insee.genesis.Constants; import fr.insee.genesis.domain.model.context.DataProcessingContextModel; +import fr.insee.genesis.domain.model.context.schedule.DeletedExpiredSchedules; import fr.insee.genesis.domain.model.context.schedule.KraftwerkExecutionSchedule; +import fr.insee.genesis.domain.model.context.schedule.KraftwerkExecutionScheduleV2; import fr.insee.genesis.domain.ports.spi.DataProcessingContextPersistancePort; import fr.insee.genesis.infrastructure.document.context.DataProcessingContextDocument; import fr.insee.genesis.infrastructure.mappers.DataProcessingContextMapper; @@ -16,10 +18,9 @@ import org.springframework.data.mongodb.core.query.Update; import org.springframework.stereotype.Service; -import java.io.IOException; import java.time.LocalDateTime; -import java.util.ArrayList; import java.util.List; +import java.util.Optional; @Service @Qualifier("dataProcessingContextMongoAdapter") @@ -75,23 +76,62 @@ public long count() { } @Override - public List removeExpiredSchedules(DataProcessingContextModel dataProcessingContextModel) throws IOException { - //TODO move non mongo related logic to service - List deletedKraftwerkExecutionSchedules = new ArrayList<>(); - for (KraftwerkExecutionSchedule kraftwerkExecutionScheduleToRemove : - dataProcessingContextModel.getKraftwerkExecutionScheduleList().stream().filter( - kraftwerkExecutionSchedule -> kraftwerkExecutionSchedule.getScheduleEndDate().isBefore(LocalDateTime.now()) - ).toList()) { - deletedKraftwerkExecutionSchedules.add(kraftwerkExecutionScheduleToRemove); - Query query = - Query.query(Criteria.where("scheduleEndDate").is(kraftwerkExecutionScheduleToRemove.getScheduleEndDate())); - if (dataProcessingContextModel.getCollectionInstrumentId() != null){ - mongoTemplate.updateMulti(Query.query(Criteria.where("collectionInstrumentId").is(dataProcessingContextModel.getCollectionInstrumentId())), new Update().pull( - "kraftwerkExecutionScheduleList", query), - Constants.MONGODB_SCHEDULE_COLLECTION_NAME); - } + public DeletedExpiredSchedules removeExpiredSchedules(DataProcessingContextModel context) { + LocalDateTime now = LocalDateTime.now(); + + List deletedV1 = + Optional.ofNullable(context.getKraftwerkExecutionScheduleList()) + .orElse(List.of()) + .stream() + .filter(schedule -> schedule.getScheduleEndDate() != null) + .filter(schedule -> schedule.getScheduleEndDate().isBefore(now)) + .toList(); + + List deletedV2 = + Optional.ofNullable(context.getKraftwerkExecutionScheduleV2List()) + .orElse(List.of()) + .stream() + .filter(schedule -> schedule.getScheduleEndDate() != null) + .filter(schedule -> schedule.getScheduleEndDate().isBefore(now)) + .toList(); + + Query query = Query.query( + Criteria.where("collectionInstrumentId").is(context.getCollectionInstrumentId()) + ); + + for (KraftwerkExecutionSchedule scheduleToRemove : deletedV1) { + Update update = new Update().pull( + "kraftwerkExecutionScheduleList", + Query.query( + Criteria.where("scheduleEndDate") + .is(scheduleToRemove.getScheduleEndDate()) + ).getQueryObject() + ); + + mongoTemplate.updateMulti( + query, + update, + Constants.MONGODB_CONTEXT_COLLECTION_NAME + ); } - return deletedKraftwerkExecutionSchedules; + + for (KraftwerkExecutionScheduleV2 scheduleToRemove : deletedV2) { + Update update = new Update().pull( + "kraftwerkExecutionScheduleV2List", + Query.query( + Criteria.where("scheduleUuid") + .is(scheduleToRemove.getScheduleUuid()) + ).getQueryObject() + ); + + mongoTemplate.updateMulti( + query, + update, + Constants.MONGODB_CONTEXT_COLLECTION_NAME + ); + } + + return new DeletedExpiredSchedules(deletedV1, deletedV2); } @Override diff --git a/src/test/java/fr/insee/genesis/infrastructure/adapter/DataProcessingContextMongoAdapterTest.java b/src/test/java/fr/insee/genesis/infrastructure/adapter/DataProcessingContextMongoAdapterTest.java index 904a5ba1..0414e025 100644 --- a/src/test/java/fr/insee/genesis/infrastructure/adapter/DataProcessingContextMongoAdapterTest.java +++ b/src/test/java/fr/insee/genesis/infrastructure/adapter/DataProcessingContextMongoAdapterTest.java @@ -1,9 +1,13 @@ package fr.insee.genesis.infrastructure.adapter; import fr.insee.genesis.Constants; +import fr.insee.genesis.controller.utils.ExportType; import fr.insee.genesis.domain.model.context.DataProcessingContextModel; +import fr.insee.genesis.domain.model.context.schedule.DeletedExpiredSchedules; +import fr.insee.genesis.domain.model.context.schedule.DestinationType; import fr.insee.genesis.domain.model.context.schedule.KraftwerkExecutionSchedule; -import fr.insee.genesis.domain.model.context.schedule.ServiceToCall; +import fr.insee.genesis.domain.model.context.schedule.KraftwerkExecutionScheduleV2; +import fr.insee.genesis.domain.model.surveyunit.Mode; import fr.insee.genesis.infrastructure.document.context.DataProcessingContextDocument; import fr.insee.genesis.infrastructure.repository.DataProcessingContextMongoDBRepository; import org.junit.jupiter.api.DisplayName; @@ -18,9 +22,9 @@ import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; -import java.io.IOException; import java.time.LocalDateTime; import java.util.List; +import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -237,93 +241,92 @@ void count_empty_shouldReturnZero() { assertThat(adapter.count()).isZero(); } } - @Nested @DisplayName("removeExpiredSchedules() tests") class RemoveExpiredSchedulesTests { @Test - @DisplayName("Should return empty list when no schedule is expired") - void removeExpiredSchedules_noExpired_shouldReturnEmptyList() throws IOException { - //GIVEN - KraftwerkExecutionSchedule future = buildSchedule(LocalDateTime.now().plusDays(1)); - DataProcessingContextModel model = buildModelWithSchedules(List.of(future)); + @DisplayName("Should return empty result when no schedule is expired") + void removeExpiredSchedules_noExpired_shouldReturnEmptyResult() { + KraftwerkExecutionScheduleV2 future = buildScheduleV2(LocalDateTime.now().plusDays(1)); + DataProcessingContextModel model = buildModelWithSchedulesV2(List.of(future)); - //WHEN - List removed = adapter.removeExpiredSchedules(model); + DeletedExpiredSchedules removed = adapter.removeExpiredSchedules(model); - //THEN - assertThat(removed).isEmpty(); + assertThat(removed.isEmpty()).isTrue(); + assertThat(removed.v1Schedules()).isEmpty(); + assertThat(removed.v2Schedules()).isEmpty(); verifyNoInteractions(mongoTemplate); } @Test - @DisplayName("Should return expired schedules and call updateMulti by collectionInstrumentId when set") - void removeExpiredSchedules_withCollectionInstrumentId_shouldUpdateByCollectionInstrumentId() throws IOException { - //GIVEN - KraftwerkExecutionSchedule expired = buildSchedule(LocalDateTime.now().minusDays(1)); - DataProcessingContextModel model = buildModelWithSchedules(List.of(expired)); + @DisplayName("Should return expired V2 schedules and call updateMulti") + void removeExpiredSchedules_withExpiredV2_shouldUpdateByCollectionInstrumentId() { + KraftwerkExecutionScheduleV2 expired = buildScheduleV2(LocalDateTime.now().minusDays(1)); + DataProcessingContextModel model = buildModelWithSchedulesV2(List.of(expired)); - //WHEN - List removed = adapter.removeExpiredSchedules(model); + DeletedExpiredSchedules removed = adapter.removeExpiredSchedules(model); - //THEN - assertThat(removed).hasSize(1).containsExactly(expired); + assertThat(removed.v1Schedules()).isEmpty(); + assertThat(removed.v2Schedules()).hasSize(1).containsExactly(expired); ArgumentCaptor queryCaptor = ArgumentCaptor.forClass(Query.class); + verify(mongoTemplate).updateMulti( queryCaptor.capture(), any(Update.class), - eq(Constants.MONGODB_SCHEDULE_COLLECTION_NAME) + eq(Constants.MONGODB_CONTEXT_COLLECTION_NAME) ); - // The outer query must filter by collectionInstrumentId + assertThat(queryCaptor.getValue().toString()).contains("collectionInstrumentId"); } @Test - @DisplayName("Should remove only expired schedules from a mixed list") - void removeExpiredSchedules_mixedList_shouldRemoveOnlyExpired() throws IOException { - //GIVEN - KraftwerkExecutionSchedule expired = buildSchedule(LocalDateTime.now().minusDays(1)); - KraftwerkExecutionSchedule future = buildSchedule(LocalDateTime.now().plusDays(1)); - DataProcessingContextModel model = buildModelWithSchedules(List.of(expired, future)); + @DisplayName("Should remove only expired V2 schedules from a mixed V2 list") + void removeExpiredSchedules_mixedV2List_shouldRemoveOnlyExpired() { + KraftwerkExecutionScheduleV2 expired = buildScheduleV2(LocalDateTime.now().minusDays(1)); + KraftwerkExecutionScheduleV2 future = buildScheduleV2(LocalDateTime.now().plusDays(1)); + DataProcessingContextModel model = buildModelWithSchedulesV2(List.of(expired, future)); - //WHEN - List removed = adapter.removeExpiredSchedules(model); + DeletedExpiredSchedules removed = adapter.removeExpiredSchedules(model); - //THEN - assertThat(removed).hasSize(1).containsExactly(expired); - // updateMulti called once, only for the expired one - verify(mongoTemplate, times(1)).updateMulti(any(), any(), any(String.class)); + assertThat(removed.v1Schedules()).isEmpty(); + assertThat(removed.v2Schedules()).hasSize(1).containsExactly(expired); + + verify(mongoTemplate, times(1)) + .updateMulti(any(), any(), eq(Constants.MONGODB_CONTEXT_COLLECTION_NAME)); } @Test - @DisplayName("Should call updateMulti once per expired schedule") - void removeExpiredSchedules_multipleExpired_shouldCallUpdateMultiForEach() throws IOException { - //GIVEN - KraftwerkExecutionSchedule expired1 = buildSchedule(LocalDateTime.now().minusDays(1)); - KraftwerkExecutionSchedule expired2 = buildSchedule(LocalDateTime.now().minusDays(2)); - DataProcessingContextModel model = buildModelWithSchedules(List.of(expired1, expired2)); + @DisplayName("Should call updateMulti once per expired V2 schedule") + void removeExpiredSchedules_multipleExpiredV2_shouldCallUpdateMultiForEach() { + KraftwerkExecutionScheduleV2 expired1 = buildScheduleV2(LocalDateTime.now().minusDays(1)); + KraftwerkExecutionScheduleV2 expired2 = buildScheduleV2(LocalDateTime.now().minusDays(2)); + DataProcessingContextModel model = buildModelWithSchedulesV2(List.of(expired1, expired2)); - //WHEN - List removed = adapter.removeExpiredSchedules(model); + DeletedExpiredSchedules removed = adapter.removeExpiredSchedules(model); - //THEN - assertThat(removed).hasSize(2); - verify(mongoTemplate, times(2)).updateMulti(any(), any(), any(String.class)); + assertThat(removed.v1Schedules()).isEmpty(); + assertThat(removed.v2Schedules()).hasSize(2); + + verify(mongoTemplate, times(2)) + .updateMulti(any(), any(), eq(Constants.MONGODB_CONTEXT_COLLECTION_NAME)); } @Test - @DisplayName("Should return empty list when schedule list is empty") - void removeExpiredSchedules_emptyScheduleList_shouldReturnEmptyList() throws IOException { - //GIVEN - DataProcessingContextModel model = buildModelWithSchedules(List.of()); - - //WHEN - List removed = adapter.removeExpiredSchedules(model); - - //THEN - assertThat(removed).isEmpty(); + @DisplayName("Should return empty result when schedule lists are empty") + void removeExpiredSchedules_emptyScheduleLists_shouldReturnEmptyResult() { + DataProcessingContextModel model = DataProcessingContextModel.builder() + .collectionInstrumentId(DataProcessingContextMongoAdapterTest.COLLECTION_INSTRUMENT_ID) + .kraftwerkExecutionScheduleList(List.of()) + .kraftwerkExecutionScheduleV2List(List.of()) + .build(); + + DeletedExpiredSchedules removed = adapter.removeExpiredSchedules(model); + + assertThat(removed.isEmpty()).isTrue(); + assertThat(removed.v1Schedules()).isEmpty(); + assertThat(removed.v2Schedules()).isEmpty(); verifyNoInteractions(mongoTemplate); } } @@ -403,21 +406,29 @@ void findAllByReview_emptyRepository_shouldReturnEmptyList() { } //UTILS - private KraftwerkExecutionSchedule buildSchedule(LocalDateTime endDate) { - return new KraftwerkExecutionSchedule( - null, + private KraftwerkExecutionScheduleV2 buildScheduleV2(LocalDateTime endDate) { + return new KraftwerkExecutionScheduleV2( + UUID.randomUUID().toString(), "0 10 * * *", - ServiceToCall.GENESIS, + ExportType.JSON, LocalDateTime.now(), endDate, - null + Mode.WEB, + DestinationType.APPLISHARE, + false, + "string", + false, + null, + 100 ); } - private DataProcessingContextModel buildModelWithSchedules(List schedules) { + private DataProcessingContextModel buildModelWithSchedulesV2( + List schedules + ) { return DataProcessingContextModel.builder() .collectionInstrumentId(DataProcessingContextMongoAdapterTest.COLLECTION_INSTRUMENT_ID) - .kraftwerkExecutionScheduleList(schedules) + .kraftwerkExecutionScheduleV2List(schedules) .build(); }