diff --git a/src/main/java/de/ozgcloud/aggregation/AggregationManagerRunner.java b/src/main/java/de/ozgcloud/aggregation/AggregationManagerRunner.java index ccfe40139c90b6f80ff097426b707ad7515f42ce..3cd83a1ba1130ed10ce21e2466db8abc71e47955 100644 --- a/src/main/java/de/ozgcloud/aggregation/AggregationManagerRunner.java +++ b/src/main/java/de/ozgcloud/aggregation/AggregationManagerRunner.java @@ -23,37 +23,19 @@ */ package de.ozgcloud.aggregation; -import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.UUID; -import java.util.function.Function; -import java.util.function.IntFunction; -import java.util.function.IntPredicate; -import java.util.function.Predicate; -import java.util.function.UnaryOperator; -import java.util.stream.IntStream; -import java.util.stream.Stream; -import org.apache.logging.log4j.CloseableThreadContext; import org.apache.logging.log4j.ThreadContext; +import org.springframework.beans.factory.annotation.Lookup; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; -import de.ozgcloud.aggregation.transformation.AggregationMapping.FormIdentifier; +import de.ozgcloud.aggregation.transformation.AggregationMapping; import de.ozgcloud.aggregation.transformation.Transformation; import de.ozgcloud.aggregation.transformation.TransformationException; import de.ozgcloud.aggregation.transformation.TransformationService; -import de.ozgcloud.aggregation.transformation.VorgangMapper; -import de.ozgcloud.aggregation.warehouse.DocumentEntry; -import de.ozgcloud.aggregation.warehouse.WarehouseRepository; -import de.ozgcloud.apilib.vorgang.OzgCloudVorgang; -import de.ozgcloud.apilib.vorgang.OzgCloudVorgangQuery; -import de.ozgcloud.apilib.vorgang.OzgCloudVorgangQuery.FormIdentification; -import de.ozgcloud.apilib.vorgang.OzgCloudVorgangService; -import de.ozgcloud.apilib.vorgang.OzgCloudVorgangStub; -import de.ozgcloud.apilib.vorgang.Page; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; @@ -62,133 +44,54 @@ import lombok.extern.log4j.Log4j2; @RequiredArgsConstructor @Log4j2 @ConditionalOnProperty(prefix = "ozgcloud.command.line.runner", value = "enabled", havingValue = "true", matchIfMissing = true) -public class AggregationManagerRunner implements CommandLineRunner { +public abstract class AggregationManagerRunner implements CommandLineRunner { private static final String MDC_EXECUTION = "execution"; - private static final String MDC_VORGANG = "vorgang"; - private static final Predicate<Batch> HAS_NEXT_BATCH = x -> !x.items.isEmpty(); - - private final OzgCloudVorgangService vorgangService; private final AggregationManagerConfiguration config; - private final TransformationProperties transformationProperties; - private final TransformationService transformationService; - private final WarehouseRepository repository; - private final VorgangMapper vorgangMapper; @Override public void run(String... args) throws TransformationException { var identifier = transformationProperties.getIdentifier(); var aggregationMappings = transformationProperties.getAggregationMappings(); if (Objects.isNull(aggregationMappings) || aggregationMappings.isEmpty()) { - runWithTransformation(transformationService.load(identifier, null), Optional.empty()); + runWithDefaultTransformation(transformationService.load(identifier, null)); } else { aggregationMappings.stream() .forEach(aggregationMapping -> runWithTransformation(transformationService.load(identifier, aggregationMapping), - Optional.of(aggregationMapping.getFormIdentifier()))); + aggregationMapping)); } } - void runWithTransformation(Transformation transformation, Optional<FormIdentifier> formIdentifier) { + void runWithDefaultTransformation(Transformation transformation) { + runWithTransformation(transformation, null); + } + + void runWithTransformation(Transformation transformation, AggregationMapping aggregationMapping) { try (Execution execution = new Execution(transformation)) { ThreadContext.put(MDC_EXECUTION, execution.id.toString()); - loadVorgaengeIntoRepository(Stream.concat( - extractBatchesOfVorgaengeFromDataSource(execution, formIdentifier), - extractBatchesOfDeletedVorgaengeFromDataSource(execution, formIdentifier))); + prepareAggregator(execution, aggregationMapping).aggregate(); } finally { ThreadContext.remove(MDC_EXECUTION); } } - void loadVorgaengeIntoRepository(Stream<Batch> batches) { - repository.deleteAll(); - batches.map(this::transformBatchToDocumentEntries).forEach(this::loadDocumentEntriesIntoRepository); - } - - Stream<Batch> extractBatchesOfVorgaengeFromDataSource(Execution execution, Optional<FormIdentifier> formIdentifier) { - return extractBatchesFromDataSource(execution, page -> getVorgaengeFromDataSource(page, formIdentifier)); - } - - List<OzgCloudVorgang> getVorgaengeFromDataSource(Page page, Optional<FormIdentifier> formIdentifier) { - return vorgangService.find(buildFindByFormEngineQuery(formIdentifier), page).stream() - .map(vorgangStub -> vorgangService.getById(vorgangStub.getId())) - .toList(); - } - - OzgCloudVorgangQuery buildFindByFormEngineQuery(Optional<FormIdentifier> formIdentifier) { - return OzgCloudVorgangQuery.builder() - .form(mapToFormIdentification(formIdentifier)) - .build(); - } - - private Optional<FormIdentification> mapToFormIdentification(Optional<FormIdentifier> formIdentifier) { - return formIdentifier - .map(identifier -> FormIdentification.builder() - .formId(identifier.getFormId()) - .formEngineName(identifier.getFormEngineName()) - .build()); - } - - Stream<Batch> extractBatchesOfDeletedVorgaengeFromDataSource(Execution execution, Optional<FormIdentifier> formIdentifier) { - return formIdentifier.isEmpty() ? extractBatchesFromDataSource(execution, getPagedDeletedVorgaenge(vorgangService.findDeleted())) - : Stream.empty(); + Aggregator prepareAggregator(Execution execution, AggregationMapping aggregationMapping) { + return createAggregator() + .withExecution(execution) + .withAggregationMapping(aggregationMapping) + .withBatchSize(config.getFetchingBatchSize()); } - Function<Page, List<OzgCloudVorgang>> getPagedDeletedVorgaenge(Stream<OzgCloudVorgangStub> allDeletedVorgaenge) { - var it = allDeletedVorgaenge.iterator(); - IntPredicate hasNextVorgangStub = ignored -> it.hasNext(); - IntFunction<OzgCloudVorgangStub> nextVorgangStub = ignored -> it.next(); - return page -> IntStream.range(page.getOffset(), page.getOffset() + page.getLimit()) - .takeWhile(hasNextVorgangStub) - .mapToObj(nextVorgangStub) - .map(vorgangMapper::fromVorgangStub) - .toList(); - } - - Stream<Batch> extractBatchesFromDataSource(Execution execution, Function<Page, List<OzgCloudVorgang>> getFromDataSource) { - var fetchSize = config.getFetchingBatchSize(); - var initialBatch = createBatch(execution, Page.builder().offset(0).limit(fetchSize).build(), getFromDataSource); - UnaryOperator<Batch> nextBatch = x -> createBatch( - execution, - Page.builder().offset(x.page.getOffset() + fetchSize).limit(fetchSize).build(), - getFromDataSource); - return Stream.iterate(initialBatch, HAS_NEXT_BATCH, nextBatch).filter(x -> !x.items.isEmpty()); - } - - private Batch createBatch(Execution execution, Page page, Function<Page, List<OzgCloudVorgang>> getFromDataSource) { - return new Batch(execution, createBatchUUID(), page, getFromDataSource.apply(page)); - } - - UUID createBatchUUID() { - return UUID.randomUUID(); - } - - void loadDocumentEntriesIntoRepository(Stream<DocumentEntry> entries) { - final List<DocumentEntry> documents = entries.toList(); - LOG.atDebug().log("store documents: {}", () -> documents.stream().map(DocumentEntry::getId).toList()); - repository.saveAll(documents); - } - - Stream<DocumentEntry> transformBatchToDocumentEntries(Batch batch) { - return batch.items.stream().map(vorgang -> transformWithinBatch(batch, vorgang)).filter(Objects::nonNull); - } - - DocumentEntry transformWithinBatch(Batch batch, OzgCloudVorgang vorgang) { - try (var instance = CloseableThreadContext.put(MDC_VORGANG, vorgang.getId().toString())) { - return batch.execution.transformation.apply(vorgang); - } catch (TransformationException e) { - LOG.atError().withThrowable(e).log("failed to transform vorgang [{}] in batch [{}] of execution [{}]", - vorgang::getId, () -> batch.id, () -> batch.execution.id); - return null; - } - } + @Lookup + protected abstract Aggregator createAggregator(); + @Getter @RequiredArgsConstructor protected static class Execution implements AutoCloseable { private final UUID id = UUID.randomUUID(); - @Getter private final Transformation transformation; @Override @@ -197,7 +100,4 @@ public class AggregationManagerRunner implements CommandLineRunner { } } - record Batch(Execution execution, UUID id, Page page, List<OzgCloudVorgang> items) { - } - } diff --git a/src/main/java/de/ozgcloud/aggregation/Aggregator.java b/src/main/java/de/ozgcloud/aggregation/Aggregator.java new file mode 100644 index 0000000000000000000000000000000000000000..79cf481fe8871d780ceec13bb1b7564037d2fb0c --- /dev/null +++ b/src/main/java/de/ozgcloud/aggregation/Aggregator.java @@ -0,0 +1,160 @@ +package de.ozgcloud.aggregation; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.IntPredicate; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.apache.logging.log4j.CloseableThreadContext; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import de.ozgcloud.aggregation.AggregationManagerRunner.Execution; +import de.ozgcloud.aggregation.transformation.AggregationMapping; +import de.ozgcloud.aggregation.transformation.AggregationMapping.FormIdentifier; +import de.ozgcloud.aggregation.transformation.TransformationException; +import de.ozgcloud.aggregation.transformation.VorgangMapper; +import de.ozgcloud.aggregation.warehouse.DocumentEntry; +import de.ozgcloud.aggregation.warehouse.WarehouseRepository; +import de.ozgcloud.apilib.vorgang.OzgCloudVorgang; +import de.ozgcloud.apilib.vorgang.OzgCloudVorgangQuery; +import de.ozgcloud.apilib.vorgang.OzgCloudVorgangQuery.FormIdentification; +import de.ozgcloud.apilib.vorgang.OzgCloudVorgangService; +import de.ozgcloud.apilib.vorgang.OzgCloudVorgangStub; +import de.ozgcloud.apilib.vorgang.Page; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; + +@Component +@Scope("prototype") +@RequiredArgsConstructor +@Log4j2 +class Aggregator { + + private static final String MDC_VORGANG = "vorgang"; + private static final Predicate<Batch> HAS_NEXT_BATCH = x -> !x.items.isEmpty(); + + private final OzgCloudVorgangService vorgangService; + private final WarehouseRepository repository; + private final VorgangMapper vorgangMapper; + + private Execution execution; + private FormIdentifier formIdentifier; + private String collectionName = DocumentEntry.COLLECTION; + private int batchSize = 100; + + public Aggregator withExecution(Execution execution) { + this.execution = execution; + return this; + } + + public Aggregator withAggregationMapping(AggregationMapping aggregationMapping) { + if (Objects.nonNull(aggregationMapping)) { + this.formIdentifier = aggregationMapping.getFormIdentifier(); + this.collectionName = aggregationMapping.getName(); + } + return this; + } + + public Aggregator withBatchSize(int batchSize) { + this.batchSize = batchSize; + return this; + } + + public void aggregate() { + loadVorgaengeIntoRepository(Stream.concat(extractBatchesOfVorgaengeFromDataSource(), extractBatchesOfDeletedVorgaengeFromDataSource())); + } + + Stream<Batch> extractBatchesOfVorgaengeFromDataSource() { + return extractBatchesFromDataSource(this::getVorgaengeFromDataSource); + } + + List<OzgCloudVorgang> getVorgaengeFromDataSource(Page page) { + return vorgangService.find(buildFindByFormEngineQuery(), page).stream() + .map(vorgangStub -> vorgangService.getById(vorgangStub.getId())) + .toList(); + } + + OzgCloudVorgangQuery buildFindByFormEngineQuery() { + return OzgCloudVorgangQuery.builder() + .form(mapToFormIdentification()) + .build(); + } + + private Optional<FormIdentification> mapToFormIdentification() { + return Optional.ofNullable(formIdentifier) + .map(identifier -> FormIdentification.builder() + .formId(identifier.getFormId()) + .formEngineName(identifier.getFormEngineName()) + .build()); + } + + Stream<Batch> extractBatchesOfDeletedVorgaengeFromDataSource() { + return Objects.isNull(formIdentifier) ? extractBatchesFromDataSource(getPagedDeletedVorgaenge(vorgangService.findDeleted())) + : Stream.empty(); + } + + Function<Page, List<OzgCloudVorgang>> getPagedDeletedVorgaenge(Stream<OzgCloudVorgangStub> allDeletedVorgaenge) { + var it = allDeletedVorgaenge.iterator(); + IntPredicate hasNextVorgangStub = ignored -> it.hasNext(); + IntFunction<OzgCloudVorgangStub> nextVorgangStub = ignored -> it.next(); + return page -> IntStream.range(page.getOffset(), page.getOffset() + page.getLimit()) + .takeWhile(hasNextVorgangStub) + .mapToObj(nextVorgangStub) + .map(vorgangMapper::fromVorgangStub) + .toList(); + } + + Stream<Batch> extractBatchesFromDataSource(Function<Page, List<OzgCloudVorgang>> getFromDataSource) { + var initialBatch = createBatch(execution, Page.builder().offset(0).limit(batchSize).build(), getFromDataSource); + UnaryOperator<Batch> nextBatch = x -> createBatch( + execution, + Page.builder().offset(x.page.getOffset() + batchSize).limit(batchSize).build(), + getFromDataSource); + return Stream.iterate(initialBatch, HAS_NEXT_BATCH, nextBatch).filter(x -> !x.items.isEmpty()); + } + + private Batch createBatch(Execution execution, Page page, Function<Page, List<OzgCloudVorgang>> getFromDataSource) { + return new Batch(execution, createBatchUUID(), page, getFromDataSource.apply(page)); + } + + UUID createBatchUUID() { + return UUID.randomUUID(); + } + + void loadVorgaengeIntoRepository(Stream<Batch> batches) { + repository.clearCollection(collectionName); + batches.map(this::transformBatchToDocumentEntries).forEach(this::loadDocumentEntriesIntoRepository); + } + + List<DocumentEntry> transformBatchToDocumentEntries(Batch batch) { + return batch.items.stream().map(vorgang -> transformWithinBatch(batch, vorgang)).filter(Objects::nonNull).toList(); + } + + DocumentEntry transformWithinBatch(Batch batch, OzgCloudVorgang vorgang) { + try (var instance = CloseableThreadContext.put(MDC_VORGANG, vorgang.getId().toString())) { + return batch.execution.getTransformation().apply(vorgang); + } catch (TransformationException e) { + LOG.error( + "failed to transform vorgang [%s] in batch [%s] of execution [%s]".formatted(vorgang.getId(), batch.id, batch.execution.getId()), + e); + return null; + } + } + + void loadDocumentEntriesIntoRepository(List<DocumentEntry> entries) { + LOG.atDebug().log("store documents: {}", () -> entries.stream().map(DocumentEntry::getId).toList()); + repository.saveAllInCollection(entries, collectionName); + } + + record Batch(Execution execution, UUID id, Page page, List<OzgCloudVorgang> items) { + } + +} diff --git a/src/main/java/de/ozgcloud/aggregation/TransformationProperties.java b/src/main/java/de/ozgcloud/aggregation/TransformationProperties.java index b639b4bd91893f94bef366a4f16122a7a8265103..aadc1cac0a74c6ceec10de99559e7c6862b2a84c 100644 --- a/src/main/java/de/ozgcloud/aggregation/TransformationProperties.java +++ b/src/main/java/de/ozgcloud/aggregation/TransformationProperties.java @@ -25,8 +25,11 @@ package de.ozgcloud.aggregation; import java.util.List; +import jakarta.validation.Valid; + import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; +import org.springframework.validation.annotation.Validated; import de.ozgcloud.aggregation.transformation.AggregationMapping; import lombok.Getter; @@ -35,6 +38,7 @@ import lombok.extern.log4j.Log4j2; @ConfigurationProperties(prefix = "ozgcloud.aggregation") @Configuration +@Validated @Getter @Setter @Log4j2 @@ -44,6 +48,7 @@ public class TransformationProperties { * List of field mapping definitions with a form specification to which the * field mappings should be applied */ + @Valid private List<AggregationMapping> aggregationMappings; /* diff --git a/src/main/java/de/ozgcloud/aggregation/transformation/AggregationMapping.java b/src/main/java/de/ozgcloud/aggregation/transformation/AggregationMapping.java index d98c2350ef04f3920dc8d30f8ea4100236991d65..958811b512292fb3388433275fa7dde64b8eb167 100644 --- a/src/main/java/de/ozgcloud/aggregation/transformation/AggregationMapping.java +++ b/src/main/java/de/ozgcloud/aggregation/transformation/AggregationMapping.java @@ -25,6 +25,10 @@ package de.ozgcloud.aggregation.transformation; import java.util.List; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + import lombok.Builder; import lombok.Getter; import lombok.Singular; @@ -35,22 +39,31 @@ import lombok.ToString; @ToString public class AggregationMapping { + @NotBlank + private String name; + @NotNull + @Valid private FormIdentifier formIdentifier; @Singular + @Valid private List<FieldMapping> fieldMappings; @Getter @Builder public static class FormIdentifier { + @NotBlank private String formEngineName; + @NotBlank private String formId; } @Getter @Builder public static class FieldMapping { + @NotBlank private String sourcePath; + @NotBlank private String targetPath; } } diff --git a/src/main/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepository.java b/src/main/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..0a312165a18ef10eab5ce9178e3c4ba6f4efc396 --- /dev/null +++ b/src/main/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepository.java @@ -0,0 +1,12 @@ +package de.ozgcloud.aggregation.warehouse; + +import java.util.List; + +interface CustomWarehouseRepository { + + DocumentEntry saveInCollection(DocumentEntry documentEntry, String collectionName); + + List<DocumentEntry> saveAllInCollection(Iterable<DocumentEntry> documentEntries, String collectionName); + + void clearCollection(String collectionName); +} diff --git a/src/main/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepositoryImpl.java b/src/main/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepositoryImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..a5169d606f6a6055943538d8ac02f8a0477716b1 --- /dev/null +++ b/src/main/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepositoryImpl.java @@ -0,0 +1,30 @@ +package de.ozgcloud.aggregation.warehouse; + +import java.util.List; +import java.util.stream.StreamSupport; + +import org.springframework.data.mongodb.core.MongoTemplate; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +class CustomWarehouseRepositoryImpl implements CustomWarehouseRepository { + + private final MongoTemplate mongoTemplate; + + @Override + public List<DocumentEntry> saveAllInCollection(Iterable<DocumentEntry> documentEntries, String collectionName) { + return StreamSupport.stream(documentEntries.spliterator(), false).map(entry -> saveInCollection(entry, collectionName)).toList(); + } + + @Override + public DocumentEntry saveInCollection(DocumentEntry documentEntry, String collectionName) { + return mongoTemplate.save(documentEntry, collectionName); + } + + @Override + public void clearCollection(String collectionName) { + mongoTemplate.dropCollection(collectionName); + } + +} diff --git a/src/main/java/de/ozgcloud/aggregation/warehouse/DocumentEntry.java b/src/main/java/de/ozgcloud/aggregation/warehouse/DocumentEntry.java index 0b17e778ea3b1874c261d40a2110961a21dd0029..8f368ac3845ce1d37eedfc408cf1e0b0b2ab7da3 100644 --- a/src/main/java/de/ozgcloud/aggregation/warehouse/DocumentEntry.java +++ b/src/main/java/de/ozgcloud/aggregation/warehouse/DocumentEntry.java @@ -40,7 +40,7 @@ import lombok.Getter; @TypeAlias("Vorgang") public class DocumentEntry { - static final String COLLECTION = "vorgang"; + public static final String COLLECTION = "vorgang"; @Id private String id; diff --git a/src/main/java/de/ozgcloud/aggregation/warehouse/WarehouseRepository.java b/src/main/java/de/ozgcloud/aggregation/warehouse/WarehouseRepository.java index f8bef952cdeeda6c19e7df8f928932f002a2ecbd..c31564a9a30e98564b181b174e3589510be7c12c 100644 --- a/src/main/java/de/ozgcloud/aggregation/warehouse/WarehouseRepository.java +++ b/src/main/java/de/ozgcloud/aggregation/warehouse/WarehouseRepository.java @@ -27,6 +27,6 @@ import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; @Repository -public interface WarehouseRepository extends MongoRepository<DocumentEntry, String> { +public interface WarehouseRepository extends MongoRepository<DocumentEntry, String>, CustomWarehouseRepository { } diff --git a/src/test/java/de/ozgcloud/aggregation/AggregationManagerRunnerTest.java b/src/test/java/de/ozgcloud/aggregation/AggregationManagerRunnerTest.java index 1e578c804c4a8caa7022cce046ec08f37c059fcc..ef2a1f26bb2fdfbcdf47a5dcd7d2f6292f63b661 100644 --- a/src/test/java/de/ozgcloud/aggregation/AggregationManagerRunnerTest.java +++ b/src/test/java/de/ozgcloud/aggregation/AggregationManagerRunnerTest.java @@ -29,42 +29,26 @@ import static org.mockito.Mockito.*; import java.util.Collections; import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.function.Function; -import java.util.stream.Stream; +import org.apache.commons.lang3.RandomUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; -import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; import com.thedeanda.lorem.LoremIpsum; -import de.ozgcloud.aggregation.AggregationManagerRunner.Batch; import de.ozgcloud.aggregation.AggregationManagerRunner.Execution; import de.ozgcloud.aggregation.transformation.AggregationMapping; -import de.ozgcloud.aggregation.transformation.AggregationMapping.FormIdentifier; import de.ozgcloud.aggregation.transformation.AggregationMappingTestFactory; -import de.ozgcloud.aggregation.transformation.FormIdentifierTestFactory; import de.ozgcloud.aggregation.transformation.Transformation; import de.ozgcloud.aggregation.transformation.TransformationService; import de.ozgcloud.aggregation.transformation.VorgangMapper; -import de.ozgcloud.aggregation.warehouse.DocumentEntry; -import de.ozgcloud.aggregation.warehouse.DocumentEntryTestFactory; import de.ozgcloud.aggregation.warehouse.WarehouseRepository; -import de.ozgcloud.apilib.vorgang.OzgCloudVorgang; -import de.ozgcloud.apilib.vorgang.OzgCloudVorgangQuery; import de.ozgcloud.apilib.vorgang.OzgCloudVorgangService; -import de.ozgcloud.apilib.vorgang.OzgCloudVorgangStub; -import de.ozgcloud.apilib.vorgang.OzgCloudVorgangStubTestFactory; -import de.ozgcloud.apilib.vorgang.OzgCloudVorgangTestFactory; -import de.ozgcloud.apilib.vorgang.Page; class AggregationManagerRunnerTest { @@ -80,9 +64,11 @@ class AggregationManagerRunnerTest { private WarehouseRepository repository; @Mock private VorgangMapper vorgangMapper; + @Mock + private static Aggregator aggregator; @Spy @InjectMocks - private AggregationManagerRunner runner; + private AggregationManagerRunnerImpl runner; @Nested class TestRun { @@ -135,8 +121,8 @@ class AggregationManagerRunnerTest { void shouldRunWithTransformationForEachTransformation() { runner.run(); - verify(runner).runWithTransformation(firstTransformation, Optional.of(AggregationMappingTestFactory.FORM_IDENTIFIER)); - verify(runner).runWithTransformation(secondTransformation, Optional.of(AggregationMappingTestFactory.FORM_IDENTIFIER)); + verify(runner).runWithTransformation(firstTransformation, aggregationMappings.get(0)); + verify(runner).runWithTransformation(secondTransformation, aggregationMappings.get(1)); } } @@ -159,10 +145,10 @@ class AggregationManagerRunnerTest { } @Test - void shouldCallRunWithTransformation() { + void shouldCallRunWithDefaultTransformation() { runner.run(); - verify(runner).runWithTransformation(transformation, Optional.empty()); + verify(runner).runWithDefaultTransformation(transformation); } } @@ -185,386 +171,124 @@ class AggregationManagerRunnerTest { } @Test - void shouldCallRunWithTransformation() { + void shouldCallRunWithDefaultTransformation() { runner.run(); - verify(runner).runWithTransformation(transformation, Optional.empty()); + verify(runner).runWithDefaultTransformation(transformation); } } } @Nested - class TestRunWithTransformation { + class TestRunWithDefaultTransformation { @Mock private Transformation transformation; - private final ArgumentMatcher<Execution> hasTransformation = execution -> execution.getTransformation().equals(transformation); - private final Optional<FormIdentifier> formIdentifier = Optional.of(FormIdentifierTestFactory.create()); - @Mock - private Batch batchOfVorgaenge; - @Mock - private Batch batchOfDeletedVorgaenge; - @Captor - private ArgumentCaptor<Stream<Batch>> batchStreamCaptor; - - @BeforeEach - void init() { - doReturn(Stream.of(batchOfVorgaenge)).when(runner).extractBatchesOfVorgaengeFromDataSource(any(), any()); - doReturn(Stream.of(batchOfDeletedVorgaenge)).when(runner).extractBatchesOfDeletedVorgaengeFromDataSource(any(), any()); - doNothing().when(runner).loadVorgaengeIntoRepository(any()); - } @Test - void shouldExtractBatchesOfVorgaengeFromDataSource() { - runner.runWithTransformation(transformation, formIdentifier); - - verify(runner).extractBatchesOfVorgaengeFromDataSource(argThat(hasTransformation), eq(formIdentifier)); - } + void shouldCallRunWithTransformation() { + doNothing().when(runner).runWithTransformation(any(), any()); - @Test - void shouldExtractBatchesOfDeletedVorgaengeFromDataSource() { - runner.runWithTransformation(transformation, formIdentifier); + runner.runWithDefaultTransformation(transformation); - verify(runner).extractBatchesOfDeletedVorgaengeFromDataSource(argThat(hasTransformation), eq(formIdentifier)); - } - - @Test - void shouldLoadVorgaengeIntoRepository() { - runner.runWithTransformation(transformation, formIdentifier); - - verify(runner).loadVorgaengeIntoRepository(batchStreamCaptor.capture()); - assertThat(batchStreamCaptor.getValue()).containsExactly(batchOfVorgaenge, batchOfDeletedVorgaenge); + verify(runner).runWithTransformation(eq(transformation), isNull()); } } @Nested - class TestLoadVorgaengeIntoRepository { + class TestRunWithTransformation { @Mock - private Execution execution; - @Mock - private Batch batch; - private final DocumentEntry documentEntry = DocumentEntryTestFactory.create(); - @Captor - private ArgumentCaptor<Stream<DocumentEntry>> documentEntriesCaptor; + private Transformation transformation; + private final AggregationMapping aggregationMapping = AggregationMappingTestFactory.create(); + private final ArgumentMatcher<Execution> hasTransformation = execution -> execution.getTransformation().equals(transformation); @BeforeEach - void init() { - doReturn(Stream.of(documentEntry)).when(runner).transformBatchToDocumentEntries(any()); - doNothing().when(runner).loadDocumentEntriesIntoRepository(any()); - } - - @Test - void shouldDropCollection() { - loadVorgaengeIntoRepository(); - - verify(repository).deleteAll(); + void mock() { + doReturn(aggregator).when(runner).prepareAggregator(any(), any()); } @Test - void shouldTransform() { - loadVorgaengeIntoRepository(); + void shouldCallPrepareAggregator() { + runWithTransformation(); - verify(runner).transformBatchToDocumentEntries(batch); + verify(runner).prepareAggregator(argThat(hasTransformation), eq(aggregationMapping)); } @Test - void shouldLoadIntoRepository() { - loadVorgaengeIntoRepository(); + void shouldLoadAggregation() { + runWithTransformation(); - verify(runner).loadDocumentEntriesIntoRepository(documentEntriesCaptor.capture()); - assertThat(documentEntriesCaptor.getValue()).containsExactly(documentEntry); + verify(aggregator).aggregate(); } - private void loadVorgaengeIntoRepository() { - runner.loadVorgaengeIntoRepository(Stream.of(batch)); + private void runWithTransformation() { + runner.runWithTransformation(transformation, aggregationMapping); } } @Nested - class TestExtractBatchesOfVorgaengeFromDataSource { + class TestPrepareAggregator { - private final Optional<FormIdentifier> formIdentifier = Optional.of(FormIdentifierTestFactory.create()); @Mock private Execution execution; - @Captor - private ArgumentCaptor<Function<Page, List<OzgCloudVorgang>>> functionToRetrieveDataCaptor; - @Mock - private Batch batch; - @Mock - private Page page; + private final AggregationMapping aggregationMapping = AggregationMappingTestFactory.create(); + private final int batchSize = RandomUtils.insecure().randomInt(); @BeforeEach - void init() { - doReturn(Stream.of(batch)).when(runner).extractBatchesFromDataSource(any(), any()); - } - - @Test - void shouldExtract() { - runner.extractBatchesOfVorgaengeFromDataSource(execution, formIdentifier); - - verify(runner).extractBatchesFromDataSource(eq(execution), any()); - } - - @Test - void shouldExtractWithDataRetrievalFunction() { - runner.extractBatchesOfVorgaengeFromDataSource(execution, formIdentifier); - - verify(runner).extractBatchesFromDataSource(eq(execution), functionToRetrieveDataCaptor.capture()); - functionToRetrieveDataCaptor.getValue().apply(page); - verify(runner).getVorgaengeFromDataSource(page, formIdentifier); - } - - @Test - void shouldReturnExtractedBatches() { - var extracted = runner.extractBatchesOfVorgaengeFromDataSource(execution, formIdentifier); - - assertThat(extracted).containsExactly(batch); - } - } - - @Nested - class TestGetVorgaengeFromDataSource { - - private final Page page = Page.builder().offset(10).limit(2).build(); - private final Optional<FormIdentifier> formIdentifier = Optional.of(FormIdentifierTestFactory.create()); - private final OzgCloudVorgangQuery query = OzgCloudVorgangQuery.builder().build(); - - @BeforeEach - void init() { - doReturn(query).when(runner).buildFindByFormEngineQuery(any()); - when(vorgangService.find(any(), any())).thenReturn(List.of(OzgCloudVorgangStubTestFactory.create())); - when(vorgangService.getById(any())).thenReturn(OzgCloudVorgangTestFactory.create()); - } - - @Test - void shouldCallBuildFindByFormEngineQuery() { - getVorgaengeFromDataSource(); - - verify(runner).buildFindByFormEngineQuery(formIdentifier); + void mock() { + when(config.getFetchingBatchSize()).thenReturn(batchSize); + when(aggregator.withExecution(any())).thenReturn(aggregator); + when(aggregator.withAggregationMapping(any())).thenReturn(aggregator); + when(aggregator.withBatchSize(anyInt())).thenReturn(aggregator); } @Test - void shouldCallVorgangService() { - getVorgaengeFromDataSource(); + void shouldGetBatchSize() { + runner.prepareAggregator(execution, aggregationMapping); - verify(vorgangService).find(query, page); + verify(config).getFetchingBatchSize(); } @Test - void shouldGetVorgangDetails() { - getVorgaengeFromDataSource(); + void shouldSetExecution() { + runner.prepareAggregator(execution, aggregationMapping); - verify(vorgangService).getById(OzgCloudVorgangTestFactory.ID); + verify(aggregator).withExecution(execution); } @Test - void shouldReturnVorgangDetails() { - var vorgaenge = getVorgaengeFromDataSource(); - - assertThat(vorgaenge).usingRecursiveFieldByFieldElementComparator().containsExactly(OzgCloudVorgangTestFactory.create()); - } - - private List<OzgCloudVorgang> getVorgaengeFromDataSource() { - return runner.getVorgaengeFromDataSource(page, formIdentifier); - } - } - - @Nested - class TestBuildFindByFormEngineQuery { - - @Nested - class TestOnEmptyFormIdentifier { - - @Test - void shouldReturnFindAllQueryOnEmptyFormIdentifier() { - var query = runner.buildFindByFormEngineQuery(Optional.empty()); - - assertThat(query).usingRecursiveComparison().isEqualTo(OzgCloudVorgangQuery.builder().build()); - } - } - - @Nested - class TestOnFormIdentifierNotEmpty { - - private final Optional<FormIdentifier> formIdentifier = Optional.of(FormIdentifierTestFactory.create()); - - @Test - void shouldSetFormIdInQuery() { - var query = runner.buildFindByFormEngineQuery(formIdentifier); - - assertThat(query.getForm()).get().extracting("formId").isEqualTo(FormIdentifierTestFactory.FORM_ID); - } - - @Test - void shouldSetFormEngineNameInQuery() { - var query = runner.buildFindByFormEngineQuery(formIdentifier); + void shouldSetAggregationMapping() { + runner.prepareAggregator(execution, aggregationMapping); - assertThat(query.getForm()).get().extracting("formEngineName").isEqualTo(FormIdentifierTestFactory.FORM_ENGINE_NAME); - } - } - } - - @Nested - class TestExtractBatchesOfDeletedVorgaengeFromDataSource { - - @Mock - private Execution execution; - @Mock - private Function<Page, List<OzgCloudVorgang>> functionToRetrieveData; - private final List<OzgCloudVorgangStub> deletedVorgaenge = List.of(OzgCloudVorgangStubTestFactory.create()); - @Captor - private ArgumentCaptor<Stream<OzgCloudVorgangStub>> deletedVorgaengeCaptor; - @Mock - private Batch batch; - - @Nested - class TestOnEmptyFormIdentifier { - - @BeforeEach - void init() { - when(vorgangService.findDeleted()).thenReturn(deletedVorgaenge.stream()); - doReturn(functionToRetrieveData).when(runner).getPagedDeletedVorgaenge(any()); - doReturn(Stream.of(batch)).when(runner).extractBatchesFromDataSource(any(), any()); - } - - @Test - void shouldFindDeleted() { - runner.extractBatchesOfDeletedVorgaengeFromDataSource(execution, Optional.empty()); - - verify(vorgangService).findDeleted(); - } - - @Test - void shouldGetPagedDeletedVorgaenge() { - runner.extractBatchesOfDeletedVorgaengeFromDataSource(execution, Optional.empty()); - - verify(runner).getPagedDeletedVorgaenge(deletedVorgaengeCaptor.capture()); - assertThat(deletedVorgaengeCaptor.getValue()).usingRecursiveFieldByFieldElementComparator() - .containsExactlyElementsOf(deletedVorgaenge); - } - - @Test - void shouldExtractWithDataRetrievalFunction() { - runner.extractBatchesOfDeletedVorgaengeFromDataSource(execution, Optional.empty()); - - verify(runner).extractBatchesFromDataSource(execution, functionToRetrieveData); - } - - @Test - void shouldReturnExtractedBatches() { - var extracted = runner.extractBatchesOfDeletedVorgaengeFromDataSource(execution, Optional.empty()); - - assertThat(extracted).containsExactly(batch); - } + verify(aggregator).withAggregationMapping(aggregationMapping); } - @Nested - class TestOnFormIdentifierNotEmpty { - - private final Optional<FormIdentifier> formIdentifier = Optional.of(FormIdentifierTestFactory.create()); - - @Test - void shouldReturnEmptyStream() { - var extracted = runner.extractBatchesOfDeletedVorgaengeFromDataSource(execution, formIdentifier); - - assertThat(extracted).isEmpty(); - } - } - } - - @Nested - class TestGetPagedDeletedVorgaenge { - - private static final int PAGE_SIZE = 2; - - private final Stream<OzgCloudVorgangStub> vorgangStubs = Stream.generate(OzgCloudVorgangStubTestFactory::create).limit(PAGE_SIZE + 1); - @Test - void shouldReturnFirstFullPage() { - var pagingFunction = runner.getPagedDeletedVorgaenge(vorgangStubs); - - var vorgaenge = getFirstPage(pagingFunction); + void shouldSetBatchSize() { + runner.prepareAggregator(execution, aggregationMapping); - assertThat(vorgaenge).hasSize(PAGE_SIZE); + verify(aggregator).withBatchSize(batchSize); } @Test - void shouldReturnSecondPageWithOneElement() { - var pagingFunction = runner.getPagedDeletedVorgaenge(vorgangStubs); - getFirstPage(pagingFunction); - - var vorgaenge = getSecondPage(pagingFunction); - - assertThat(vorgaenge).hasSize(1); - } + void shouldReturnAggregator() { + var result = runner.prepareAggregator(execution, aggregationMapping); - private List<OzgCloudVorgang> getFirstPage(Function<Page, List<OzgCloudVorgang>> pagingFunction) { - return pagingFunction.apply(Page.builder().offset(0).limit(PAGE_SIZE).build()); - } - - private List<OzgCloudVorgang> getSecondPage(Function<Page, List<OzgCloudVorgang>> pagingFunction) { - return pagingFunction.apply(Page.builder().offset(PAGE_SIZE).limit(PAGE_SIZE).build()); + assertThat(result).isEqualTo(aggregator); } } - @Nested - class TestExtractBatchesFromDataSource { - - private static final int BATCH_SIZE = 2; - private static final List<OzgCloudVorgang> VORGAENGE_1 = List.of(OzgCloudVorgangTestFactory.create(), OzgCloudVorgangTestFactory.create()); - private static final List<OzgCloudVorgang> VORGAENGE_2 = List.of(OzgCloudVorgangTestFactory.create()); - private static final UUID BATCH_1_UUID = UUID.randomUUID(); - private static final UUID BATCH_2_UUID = UUID.randomUUID(); - - @Mock - private Execution execution; - @Mock - private Function<Page, List<OzgCloudVorgang>> functionToRetrieveData; - - @BeforeEach - void init() { - when(config.getFetchingBatchSize()).thenReturn(BATCH_SIZE); - when(functionToRetrieveData.apply(any())).thenReturn(VORGAENGE_1, VORGAENGE_2, List.of()); - when(runner.createBatchUUID()).thenReturn(BATCH_1_UUID, BATCH_2_UUID); - } - - @Test - void shouldCallFunctionToRetrieveDataForFirstPage() { - extractBatchesFromDataSource(); - - verify(functionToRetrieveData).apply(argThat(hasExpectedOffsetAndConfiguredLimit(0))); - } - - @Test - void shouldCallFunctionToRetrieveDataForSecondPage() { - extractBatchesFromDataSource().toList(); - - verify(functionToRetrieveData).apply(argThat(hasExpectedOffsetAndConfiguredLimit(BATCH_SIZE))); - } - - @Test - void shouldCallFunctionToRetrieveDataForThirdPage() { - extractBatchesFromDataSource().toList(); - - verify(functionToRetrieveData).apply(argThat(hasExpectedOffsetAndConfiguredLimit(BATCH_SIZE * 2))); - } - - @Test - void shouldReturnBatches() { - var batches = extractBatchesFromDataSource().toList(); - - assertThat(batches).hasSize(2).usingRecursiveFieldByFieldElementComparator().containsExactly( - new Batch(execution, BATCH_1_UUID, Page.builder().offset(0).limit(BATCH_SIZE).build(), VORGAENGE_1), - new Batch(execution, BATCH_2_UUID, Page.builder().offset(BATCH_SIZE).limit(BATCH_SIZE).build(), VORGAENGE_2)); - } + static class AggregationManagerRunnerImpl extends AggregationManagerRunner { - private ArgumentMatcher<Page> hasExpectedOffsetAndConfiguredLimit(int expectedOffset) { - return page -> page.getOffset() == expectedOffset && page.getLimit() == BATCH_SIZE; + public AggregationManagerRunnerImpl(AggregationManagerConfiguration config, TransformationProperties transformationProperties, + TransformationService transformationService) { + super(config, transformationProperties, transformationService); } - private Stream<Batch> extractBatchesFromDataSource() { - return runner.extractBatchesFromDataSource(execution, functionToRetrieveData); + @Override + protected Aggregator createAggregator() { + return aggregator; } } } diff --git a/src/test/java/de/ozgcloud/aggregation/AggregatorTest.java b/src/test/java/de/ozgcloud/aggregation/AggregatorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2d66f74b8f51b0eb4abbd382b62aa780b98dce54 --- /dev/null +++ b/src/test/java/de/ozgcloud/aggregation/AggregatorTest.java @@ -0,0 +1,551 @@ +package de.ozgcloud.aggregation; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.apache.commons.lang3.RandomUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.springframework.test.util.ReflectionTestUtils; + +import de.ozgcloud.aggregation.AggregationManagerRunner.Execution; +import de.ozgcloud.aggregation.Aggregator.Batch; +import de.ozgcloud.aggregation.transformation.AggregationMapping; +import de.ozgcloud.aggregation.transformation.AggregationMapping.FormIdentifier; +import de.ozgcloud.aggregation.transformation.AggregationMappingTestFactory; +import de.ozgcloud.aggregation.transformation.FormIdentifierTestFactory; +import de.ozgcloud.aggregation.transformation.Transformation; +import de.ozgcloud.aggregation.transformation.VorgangMapper; +import de.ozgcloud.aggregation.warehouse.DocumentEntry; +import de.ozgcloud.aggregation.warehouse.DocumentEntryTestFactory; +import de.ozgcloud.aggregation.warehouse.WarehouseRepository; +import de.ozgcloud.apilib.vorgang.OzgCloudVorgang; +import de.ozgcloud.apilib.vorgang.OzgCloudVorgangQuery; +import de.ozgcloud.apilib.vorgang.OzgCloudVorgangService; +import de.ozgcloud.apilib.vorgang.OzgCloudVorgangStub; +import de.ozgcloud.apilib.vorgang.OzgCloudVorgangStubTestFactory; +import de.ozgcloud.apilib.vorgang.OzgCloudVorgangTestFactory; +import de.ozgcloud.apilib.vorgang.Page; + +class AggregatorTest { + + @InjectMocks + @Spy + private Aggregator aggregator; + @Mock + private Transformation transformation; + private final Execution execution = new Execution(transformation); + @Mock + private WarehouseRepository repository; + @Mock + private VorgangMapper vorgangMapper; + @Mock + private OzgCloudVorgangService vorgangService; + + @Nested + class TestWithExecution { + + @Test + void shouldReturnSelf() { + var result = aggregator.withExecution(execution); + + assertThat(result).isSameAs(aggregator); + } + + @Test + void shouldSetExecution() { + var result = aggregator.withExecution(execution); + + assertThat(ReflectionTestUtils.getField(result, "execution")).isEqualTo(execution); + } + } + + @Nested + class TestWithAggregationMapping { + + @Nested + class TestOnNonNullAggregationMapping { + + private final AggregationMapping aggregationMapping = AggregationMappingTestFactory.create(); + private final FormIdentifier formIdentifier = AggregationMappingTestFactory.FORM_IDENTIFIER; + private final String collectionName = AggregationMappingTestFactory.NAME; + + @Test + void shouldReturnSelf() { + var result = aggregator.withAggregationMapping(aggregationMapping); + + assertThat(result).isSameAs(aggregator); + } + + @Test + void shouldSetFormIdentifier() { + var result = aggregator.withAggregationMapping(aggregationMapping); + + assertThat(ReflectionTestUtils.getField(result, "formIdentifier")).isEqualTo(formIdentifier); + } + + @Test + void shouldSetCollectionName() { + var result = aggregator.withAggregationMapping(aggregationMapping); + + assertThat(ReflectionTestUtils.getField(result, "collectionName")).isEqualTo(collectionName); + } + } + + @Nested + class TestOnNullAggregationMapping { + + @Test + void shouldReturnSelf() { + var result = aggregator.withAggregationMapping(null); + + assertThat(result).isSameAs(aggregator); + } + + @Test + void shouldNotSetFormIdentifier() { + var result = aggregator.withAggregationMapping(null); + + assertThat(ReflectionTestUtils.getField(result, "formIdentifier")).isNull(); + } + + @Test + void shouldNotSetCollectionName() { + var result = aggregator.withAggregationMapping(null); + + assertThat(ReflectionTestUtils.getField(result, "collectionName")).isEqualTo(DocumentEntry.COLLECTION); + } + } + } + + @Nested + class TestWithBatchSize { + + private final int batchSize = RandomUtils.insecure().randomInt(); + + @Test + void shouldReturnSelf() { + var result = aggregator.withBatchSize(batchSize); + + assertThat(result).isSameAs(aggregator); + } + + @Test + void shouldSetBatchSize() { + var result = aggregator.withBatchSize(batchSize); + + assertThat(ReflectionTestUtils.getField(result, "batchSize")).isEqualTo(batchSize); + } + } + + @Nested + class TestAggregate { + @Mock + private Batch batchOfVorgaenge; + @Mock + private Batch batchOfDeletedVorgaenge; + @Captor + private ArgumentCaptor<Stream<Batch>> batchStreamCaptor; + + @BeforeEach + void setUp() { + doReturn(Stream.of(batchOfVorgaenge)).when(aggregator).extractBatchesOfVorgaengeFromDataSource(); + doReturn(Stream.of(batchOfDeletedVorgaenge)).when(aggregator).extractBatchesOfDeletedVorgaengeFromDataSource(); + doNothing().when(aggregator).loadVorgaengeIntoRepository(any()); + } + + @Test + void shouldExtractBatchesOfVorgaengeFromDataSource() { + aggregator.aggregate(); + + verify(aggregator).extractBatchesOfVorgaengeFromDataSource(); + } + + @Test + void shouldExtractBatchesOfDeletedVorgaengeFromDataSource() { + aggregator.aggregate(); + + verify(aggregator).extractBatchesOfDeletedVorgaengeFromDataSource(); + } + + @Test + void shouldLoadVorgaengeIntoRepository() { + aggregator.aggregate(); + + verify(aggregator).loadVorgaengeIntoRepository(batchStreamCaptor.capture()); + assertThat(batchStreamCaptor.getValue()).containsExactly(batchOfVorgaenge, batchOfDeletedVorgaenge); + } + } + + @Nested + class TestExtractBatchesOfVorgaengeFromDataSource { + @Captor + private ArgumentCaptor<Function<Page, List<OzgCloudVorgang>>> functionToRetrieveDataCaptor; + @Mock + private Batch batch; + @Mock + private Page page; + + @BeforeEach + void init() { + doReturn(Stream.of(batch)).when(aggregator).extractBatchesFromDataSource(any()); + } + + @Test + void shouldExtractWithDataRetrievalFunction() { + doReturn(Collections.emptyList()).when(aggregator).getVorgaengeFromDataSource(any()); + + aggregator.extractBatchesOfVorgaengeFromDataSource(); + + verify(aggregator).extractBatchesFromDataSource(functionToRetrieveDataCaptor.capture()); + functionToRetrieveDataCaptor.getValue().apply(page); + verify(aggregator).getVorgaengeFromDataSource(page); + } + + @Test + void shouldReturnExtractedBatches() { + var extracted = aggregator.extractBatchesOfVorgaengeFromDataSource(); + + assertThat(extracted).containsExactly(batch); + } + } + + @Nested + class TestGetVorgaengeFromDataSource { + + private final Page page = Page.builder().offset(10).limit(2).build(); + private final OzgCloudVorgangQuery query = OzgCloudVorgangQuery.builder().build(); + + @BeforeEach + void init() { + doReturn(query).when(aggregator).buildFindByFormEngineQuery(); + when(vorgangService.find(any(), any())).thenReturn(List.of(OzgCloudVorgangStubTestFactory.create())); + when(vorgangService.getById(any())).thenReturn(OzgCloudVorgangTestFactory.create()); + } + + @Test + void shouldCallBuildFindByFormEngineQuery() { + getVorgaengeFromDataSource(); + + verify(aggregator).buildFindByFormEngineQuery(); + } + + @Test + void shouldCallVorgangService() { + getVorgaengeFromDataSource(); + + verify(vorgangService).find(query, page); + } + + @Test + void shouldGetVorgangDetails() { + getVorgaengeFromDataSource(); + + verify(vorgangService).getById(OzgCloudVorgangTestFactory.ID); + } + + @Test + void shouldReturnVorgangDetails() { + var vorgaenge = getVorgaengeFromDataSource(); + + assertThat(vorgaenge).usingRecursiveFieldByFieldElementComparator().containsExactly(OzgCloudVorgangTestFactory.create()); + } + + private List<OzgCloudVorgang> getVorgaengeFromDataSource() { + return aggregator.getVorgaengeFromDataSource(page); + } + } + + @Nested + class TestBuildFindByFormEngineQuery { + + @Nested + class TestOnFormIdentifierNull { + + @Test + void shouldReturnFindAllQueryOnNullFormIdentifier() { + var query = aggregator.buildFindByFormEngineQuery(); + + assertThat(query).usingRecursiveComparison().isEqualTo(OzgCloudVorgangQuery.builder().build()); + } + } + + @Nested + class TestOnFormIdentifierNotNull { + + private final AggregationMapping aggregationMapping = AggregationMappingTestFactory.create(); + + @BeforeEach + void init() { + aggregator = aggregator.withAggregationMapping(aggregationMapping); + } + + @Test + void shouldSetFormIdInQuery() { + var query = aggregator.buildFindByFormEngineQuery(); + + assertThat(query.getForm()).get().extracting("formId").isEqualTo(FormIdentifierTestFactory.FORM_ID); + } + + @Test + void shouldSetFormEngineNameInQuery() { + var query = aggregator.buildFindByFormEngineQuery(); + + assertThat(query.getForm()).get().extracting("formEngineName").isEqualTo(FormIdentifierTestFactory.FORM_ENGINE_NAME); + } + } + } + + @Nested + class TestExtractBatchesOfDeletedVorgaengeFromDataSource { + + @Mock + private Execution execution; + @Mock + private Function<Page, List<OzgCloudVorgang>> functionToRetrieveData; + private final List<OzgCloudVorgangStub> deletedVorgaenge = List.of(OzgCloudVorgangStubTestFactory.create()); + @Captor + private ArgumentCaptor<Stream<OzgCloudVorgangStub>> deletedVorgaengeCaptor; + @Mock + private Batch batch; + + @Nested + class TestOnFormIdentifierNull { + + @BeforeEach + void init() { + when(vorgangService.findDeleted()).thenReturn(deletedVorgaenge.stream()); + doReturn(functionToRetrieveData).when(aggregator).getPagedDeletedVorgaenge(any()); + doReturn(Stream.of(batch)).when(aggregator).extractBatchesFromDataSource(any()); + } + + @Test + void shouldFindDeleted() { + aggregator.extractBatchesOfDeletedVorgaengeFromDataSource(); + + verify(vorgangService).findDeleted(); + } + + @Test + void shouldGetPagedDeletedVorgaenge() { + aggregator.extractBatchesOfDeletedVorgaengeFromDataSource(); + + verify(aggregator).getPagedDeletedVorgaenge(deletedVorgaengeCaptor.capture()); + assertThat(deletedVorgaengeCaptor.getValue()).usingRecursiveFieldByFieldElementComparator() + .containsExactlyElementsOf(deletedVorgaenge); + } + + @Test + void shouldExtractWithDataRetrievalFunction() { + aggregator.extractBatchesOfDeletedVorgaengeFromDataSource(); + + verify(aggregator).extractBatchesFromDataSource(functionToRetrieveData); + } + + @Test + void shouldReturnExtractedBatches() { + var extracted = aggregator.extractBatchesOfDeletedVorgaengeFromDataSource(); + + assertThat(extracted).containsExactly(batch); + } + } + + @Nested + class TestOnFormIdentifierNotNull { + + private final AggregationMapping aggregationMapping = AggregationMappingTestFactory.create(); + + @BeforeEach + void init() { + aggregator = aggregator.withAggregationMapping(aggregationMapping); + } + + @Test + void shouldNotFindDeleted() { + aggregator.extractBatchesOfDeletedVorgaengeFromDataSource(); + + verify(vorgangService, never()).findDeleted(); + } + + @Test + void shouldReturnEmptyStream() { + var extracted = aggregator.extractBatchesOfDeletedVorgaengeFromDataSource(); + + assertThat(extracted).isEmpty(); + } + } + } + + @Nested + class TestGetPagedDeletedVorgaenge { + + private static final int PAGE_SIZE = 2; + + private final Stream<OzgCloudVorgangStub> vorgangStubs = Stream.generate(OzgCloudVorgangStubTestFactory::create).limit(PAGE_SIZE + 1); + + @Test + void shouldReturnFirstFullPage() { + var pagingFunction = aggregator.getPagedDeletedVorgaenge(vorgangStubs); + + var vorgaenge = getFirstPage(pagingFunction); + + assertThat(vorgaenge).hasSize(PAGE_SIZE); + } + + @Test + void shouldReturnSecondPageWithOneElement() { + var pagingFunction = aggregator.getPagedDeletedVorgaenge(vorgangStubs); + getFirstPage(pagingFunction); + + var vorgaenge = getSecondPage(pagingFunction); + + assertThat(vorgaenge).hasSize(1); + } + + private List<OzgCloudVorgang> getFirstPage(Function<Page, List<OzgCloudVorgang>> pagingFunction) { + return pagingFunction.apply(Page.builder().offset(0).limit(PAGE_SIZE).build()); + } + + private List<OzgCloudVorgang> getSecondPage(Function<Page, List<OzgCloudVorgang>> pagingFunction) { + return pagingFunction.apply(Page.builder().offset(PAGE_SIZE).limit(PAGE_SIZE).build()); + } + } + + @Nested + class TestExtractBatchesFromDataSource { + + private static final int BATCH_SIZE = 2; + private static final List<OzgCloudVorgang> VORGAENGE_1 = List.of(OzgCloudVorgangTestFactory.create(), OzgCloudVorgangTestFactory.create()); + private static final List<OzgCloudVorgang> VORGAENGE_2 = List.of(OzgCloudVorgangTestFactory.create()); + private static final UUID BATCH_1_UUID = UUID.randomUUID(); + private static final UUID BATCH_2_UUID = UUID.randomUUID(); + + @Mock + private Function<Page, List<OzgCloudVorgang>> functionToRetrieveData; + + @SuppressWarnings("unchecked") + @BeforeEach + void init() { + aggregator = aggregator.withBatchSize(BATCH_SIZE).withExecution(execution); + when(functionToRetrieveData.apply(any())).thenReturn(VORGAENGE_1, VORGAENGE_2, Collections.emptyList()); + when(aggregator.createBatchUUID()).thenReturn(BATCH_1_UUID, BATCH_2_UUID); + } + + @Test + void shouldCallFunctionToRetrieveDataForFirstPage() { + extractBatchesFromDataSource(); + + verify(functionToRetrieveData).apply(argThat(hasExpectedOffsetAndConfiguredLimit(0))); + } + + @Test + void shouldCallFunctionToRetrieveDataForSecondPage() { + extractBatchesFromDataSource().toList(); + + verify(functionToRetrieveData).apply(argThat(hasExpectedOffsetAndConfiguredLimit(BATCH_SIZE))); + } + + @Test + void shouldCallFunctionToRetrieveDataForThirdPage() { + extractBatchesFromDataSource().toList(); + + verify(functionToRetrieveData).apply(argThat(hasExpectedOffsetAndConfiguredLimit(BATCH_SIZE * 2))); + } + + @Test + void shouldReturnBatches() { + var batches = extractBatchesFromDataSource().toList(); + + assertThat(batches).hasSize(2).usingRecursiveFieldByFieldElementComparator().containsExactly( + new Batch(execution, BATCH_1_UUID, Page.builder().offset(0).limit(BATCH_SIZE).build(), VORGAENGE_1), + new Batch(execution, BATCH_2_UUID, Page.builder().offset(BATCH_SIZE).limit(BATCH_SIZE).build(), VORGAENGE_2)); + } + + private ArgumentMatcher<Page> hasExpectedOffsetAndConfiguredLimit(int expectedOffset) { + return page -> page.getOffset() == expectedOffset && page.getLimit() == BATCH_SIZE; + } + + private Stream<Batch> extractBatchesFromDataSource() { + return aggregator.extractBatchesFromDataSource(functionToRetrieveData); + } + } + + @Nested + class TestLoadVorgaengeIntoRepository { + + @Mock + private Execution execution; + @Mock + private Batch batch; + private final DocumentEntry documentEntry = DocumentEntryTestFactory.create(); + private final AggregationMapping aggregationMapping = AggregationMappingTestFactory.create(); + @Captor + private ArgumentCaptor<List<DocumentEntry>> documentEntriesCaptor; + + @BeforeEach + void init() { + aggregator = aggregator.withAggregationMapping(aggregationMapping); + doReturn(List.of(documentEntry)).when(aggregator).transformBatchToDocumentEntries(any()); + doNothing().when(aggregator).loadDocumentEntriesIntoRepository(any()); + } + + @Test + void shouldDropCollection() { + loadVorgaengeIntoRepository(); + + verify(repository).clearCollection(AggregationMappingTestFactory.NAME); + } + + @Test + void shouldTransform() { + loadVorgaengeIntoRepository(); + + verify(aggregator).transformBatchToDocumentEntries(batch); + } + + @Test + void shouldLoadIntoRepository() { + loadVorgaengeIntoRepository(); + + verify(aggregator).loadDocumentEntriesIntoRepository(documentEntriesCaptor.capture()); + assertThat(documentEntriesCaptor.getValue()).containsExactly(documentEntry); + } + + private void loadVorgaengeIntoRepository() { + aggregator.loadVorgaengeIntoRepository(Stream.of(batch)); + } + } + + @Nested + class TestLoadDocumentEntriesIntoRepository { + + private final List<DocumentEntry> documentEntries = List.of(DocumentEntryTestFactory.create()); + private final AggregationMapping aggregationMapping = AggregationMappingTestFactory.create(); + + @BeforeEach + void init() { + aggregator = aggregator.withAggregationMapping(aggregationMapping); + } + + @Test + void shouldSaveDocumentEntriesInCollection() { + aggregator.loadDocumentEntriesIntoRepository(documentEntries); + + verify(repository).saveAllInCollection(documentEntries, AggregationMappingTestFactory.NAME); + } + } +} diff --git a/src/test/java/de/ozgcloud/aggregation/transformation/AggregationMappingTestFactory.java b/src/test/java/de/ozgcloud/aggregation/transformation/AggregationMappingTestFactory.java index 4be105f1284cafaef138f184ae54625242ad50e5..44a5c25ce128edf4625a5b95ab27d772bb621c46 100644 --- a/src/test/java/de/ozgcloud/aggregation/transformation/AggregationMappingTestFactory.java +++ b/src/test/java/de/ozgcloud/aggregation/transformation/AggregationMappingTestFactory.java @@ -23,6 +23,8 @@ */ package de.ozgcloud.aggregation.transformation; +import com.thedeanda.lorem.LoremIpsum; + import de.ozgcloud.aggregation.transformation.AggregationMapping.AggregationMappingBuilder; import de.ozgcloud.aggregation.transformation.AggregationMapping.FieldMapping; import de.ozgcloud.aggregation.transformation.AggregationMapping.FormIdentifier; @@ -31,6 +33,7 @@ public class AggregationMappingTestFactory { public static final FormIdentifier FORM_IDENTIFIER = FormIdentifierTestFactory.create(); public static final FieldMapping MAPPING = FieldMappingTestFactory.create(); + public static final String NAME = LoremIpsum.getInstance().getWords(1); public static AggregationMapping create() { return createBuilder().build(); @@ -39,6 +42,7 @@ public class AggregationMappingTestFactory { public static AggregationMappingBuilder createBuilder() { return AggregationMapping.builder() .formIdentifier(FORM_IDENTIFIER) - .fieldMapping(MAPPING); + .fieldMapping(MAPPING) + .name(NAME); } } diff --git a/src/test/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepositoryImplTest.java b/src/test/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepositoryImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..194a372bc2330c39c339baab737c0bf879fba26e --- /dev/null +++ b/src/test/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepositoryImplTest.java @@ -0,0 +1,88 @@ +package de.ozgcloud.aggregation.warehouse; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.util.List; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.springframework.data.mongodb.core.MongoTemplate; + +import com.thedeanda.lorem.LoremIpsum; + +class CustomWarehouseRepositoryImplTest { + + @Spy + @InjectMocks + private CustomWarehouseRepositoryImpl repository; + + @Mock + private MongoTemplate mongoTemplate; + + @Nested + class TestSaveAllInCollection { + + private final DocumentEntry documentEntry = DocumentEntryTestFactory.create(); + private final List<DocumentEntry> documentEntries = List.of(documentEntry); + private final String collectionName = LoremIpsum.getInstance().getWords(1); + + @Test + void shouldCallSaveInCollection() { + repository.saveAllInCollection(documentEntries, collectionName); + + verify(repository).saveInCollection(documentEntry, collectionName); + } + + @Test + void shouldReturnSavedDocumentEntry() { + var savedDocumentEntry = DocumentEntryTestFactory.create(); + doReturn(savedDocumentEntry).when(repository).saveInCollection(any(), any()); + + var returnedDocumentEntries = repository.saveAllInCollection(documentEntries, collectionName); + + assertThat(returnedDocumentEntries).containsExactly(savedDocumentEntry); + } + } + + @Nested + class TestSaveInCollection { + + private final DocumentEntry documentEntry = DocumentEntryTestFactory.create(); + private final String collectionName = LoremIpsum.getInstance().getWords(1); + + @Test + void testSaveInCollection() { + repository.saveInCollection(documentEntry, collectionName); + + verify(mongoTemplate).save(documentEntry, collectionName); + } + + @Test + void shouldReturnSavedDocumentEntry() { + var savedDocumentEntry = DocumentEntryTestFactory.create(); + when(mongoTemplate.save(documentEntry, collectionName)).thenReturn(savedDocumentEntry); + + var returnedDocumentEntry = repository.saveInCollection(documentEntry, collectionName); + + assertThat(returnedDocumentEntry).isEqualTo(savedDocumentEntry); + } + } + + @Nested + class TestDeleteCollection { + + private final String collectionName = LoremIpsum.getInstance().getWords(1); + + @Test + void shouldDropCollection() { + repository.clearCollection(collectionName); + + verify(mongoTemplate).dropCollection(collectionName); + } + } +} diff --git a/src/test/java/de/ozgcloud/aggregation/warehouse/WarehouseRepositoryITCase.java b/src/test/java/de/ozgcloud/aggregation/warehouse/WarehouseRepositoryITCase.java index 18a3ab40118388b784fee2bfe17873ae5ec811d7..ad1c860d0f6006f1c13a5ca3cf5c349863bf136d 100644 --- a/src/test/java/de/ozgcloud/aggregation/warehouse/WarehouseRepositoryITCase.java +++ b/src/test/java/de/ozgcloud/aggregation/warehouse/WarehouseRepositoryITCase.java @@ -32,6 +32,8 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoOperations; +import com.thedeanda.lorem.LoremIpsum; + import de.ozgcloud.common.test.DataITCase; @DataITCase @@ -57,7 +59,33 @@ class WarehouseRepositoryITCase { var foundDocument = mongoOperations.findById(savedDocument.getId(), Document.class, DocumentEntry.COLLECTION); assertThat(foundDocument.getString("_class")).isEqualTo("Vorgang"); } + } + + @Nested + class TestSaveInCollection { + private final String collectionName = LoremIpsum.getInstance().getWords(1); + + @Test + void shouldSaveInCollection() { + repository.saveInCollection(DocumentEntryTestFactory.create(), collectionName); + + assertThat(mongoOperations.getCollection(collectionName).countDocuments()).isOne(); + } } + @Nested + class TestClearCollection { + + private final String collectionName = LoremIpsum.getInstance().getWords(1); + + @Test + void shouldClearCollection() { + mongoOperations.save(DocumentEntryTestFactory.create(), collectionName); + + repository.clearCollection(collectionName); + + assertThat(mongoOperations.getCollection(collectionName).countDocuments()).isZero(); + } + } }