diff --git a/vorgang-manager-interface/src/main/protobuf/vorgang.proto b/vorgang-manager-interface/src/main/protobuf/vorgang.proto index 2dbba69a45b32a58ba87a368eba75bf1e03102a9..37a64301daf83dd7d8729e620a1109d260b8edc1 100644 --- a/vorgang-manager-interface/src/main/protobuf/vorgang.proto +++ b/vorgang-manager-interface/src/main/protobuf/vorgang.proto @@ -47,6 +47,9 @@ service VorgangService { rpc CreateCollaborationVorgang(GrpcCreateCollaborationVorgangRequest) returns (GrpcCreateCollaborationVorgangResponse) { } + + rpc FindDeletedVorgang(GrpcFindDeletedVorgangRequest) returns (stream GrpcFindDeletedVorgangResponse) { + } } message GrpcCreateVorgangRequest { @@ -107,4 +110,11 @@ message GrpcFinishCreationRequest { message GrpcFinishCreationResponse { string message = 1; +} + +message GrpcFindDeletedVorgangRequest { +} + +message GrpcFindDeletedVorgangResponse { + repeated GrpcVorgangHeader vorgang = 1; } \ No newline at end of file diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/grpc/GrpcResponseBatchStreamer.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/grpc/GrpcResponseBatchStreamer.java new file mode 100644 index 0000000000000000000000000000000000000000..675fec5463e8ec08cc48430318a069a961960c10 --- /dev/null +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/grpc/GrpcResponseBatchStreamer.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.common.grpc; + +import java.util.function.Supplier; + +import io.grpc.stub.StreamObserver; +import lombok.Setter; + +public class GrpcResponseBatchStreamer<E, T> { + + public static final int DEFAULT_BATCH_SIZE = 100; + + @Setter + private int batchSize = DEFAULT_BATCH_SIZE; + + private final Supplier<GrpcResponseBuilder<E, T>> responseBuilderSupplier; + private final StreamObserver<T> responseObserver; + private GrpcResponseBuilder<E, T> responseBuilder; + + public static <E, T> GrpcResponseBatchStreamer<E, T> create(Supplier<GrpcResponseBuilder<E, T>> responseBuilderSupplier, + StreamObserver<T> responseObserver) { + return new GrpcResponseBatchStreamer<>(responseBuilderSupplier, responseObserver); + } + + private GrpcResponseBatchStreamer(Supplier<GrpcResponseBuilder<E, T>> responseBuilderSupplier, + StreamObserver<T> responseObserver) { + this.responseBuilderSupplier = responseBuilderSupplier; + this.responseObserver = responseObserver; + responseBuilder = responseBuilderSupplier.get(); + } + + public void send(E element) { + responseBuilder.addElement(element); + if (batchIsFull()) { + sendResponse(); + responseBuilder = responseBuilderSupplier.get(); + } + } + + private boolean batchIsFull() { + return responseBuilder.getElementCount() >= batchSize; + } + + public void finish() { + if (!batchIsEmpty()) { + sendResponse(); + } + responseObserver.onCompleted(); + } + + private boolean batchIsEmpty() { + return responseBuilder.getElementCount() == 0; + } + + private void sendResponse() { + responseObserver.onNext(responseBuilder.build()); + } +} diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/grpc/GrpcResponseBuilder.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/grpc/GrpcResponseBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..1651ab8808b0a0a6a5d2182310c3fd383566596f --- /dev/null +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/grpc/GrpcResponseBuilder.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.common.grpc; + +public interface GrpcResponseBuilder<E, T> { + + int getElementCount(); + + void addElement(E elem); + + T build(); +} diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/GrpcFindDeletedVorgangResponseBuilder.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/GrpcFindDeletedVorgangResponseBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..e827425674555744882a4ed8d19b51666af8ed95 --- /dev/null +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/GrpcFindDeletedVorgangResponseBuilder.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.vorgang; + +import de.ozgcloud.vorgang.common.grpc.GrpcResponseBuilder; + +class GrpcFindDeletedVorgangResponseBuilder implements GrpcResponseBuilder<GrpcVorgangHeader, GrpcFindDeletedVorgangResponse> { + + private final GrpcFindDeletedVorgangResponse.Builder delegate = GrpcFindDeletedVorgangResponse.newBuilder(); + + @Override + public int getElementCount() { + return delegate.getVorgangCount(); + } + + @Override + public void addElement(GrpcVorgangHeader elem) { + delegate.addVorgang(elem); + } + + @Override + public GrpcFindDeletedVorgangResponse build() { + return delegate.build(); + } +} diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangGrpcService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangGrpcService.java index 82270add8423926bfb236532c940880c9c85ab5e..daa2f758bdfc53001ec2bf67c9b236c1423c9daa 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangGrpcService.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangGrpcService.java @@ -29,6 +29,7 @@ import de.ozgcloud.vorgang.collaboration.CollaborationService; import de.ozgcloud.vorgang.collaboration.CreateCollaborationVorgangBadRequestException; import de.ozgcloud.vorgang.collaboration.CreateCollaborationVorgangRequest; import de.ozgcloud.vorgang.collaboration.CreateCollaborationVorgangRequestMapper; +import de.ozgcloud.vorgang.common.grpc.GrpcResponseBatchStreamer; import io.grpc.stub.StreamObserver; import lombok.RequiredArgsConstructor; import net.devh.boot.grpc.server.service.GrpcService; @@ -48,6 +49,7 @@ class VorgangGrpcService extends VorgangServiceGrpc.VorgangServiceImplBase { private final IncomingFileGroupMapper incomingFileGroupMapper; private final CollaborationService collaborationService; private final CreateCollaborationVorgangRequestMapper createCollaborationVorgangRequestMapper; + private final VorgangStubMapper vorgangStubMapper; @Override public void startCreation(GrpcCreateVorgangRequest request, StreamObserver<GrpcCreateVorgangResponse> responseObserver) { @@ -138,4 +140,11 @@ class VorgangGrpcService extends VorgangServiceGrpc.VorgangServiceImplBase { GrpcCreateCollaborationVorgangResponse buildCreateCollaborationVorgangResponse(Vorgang vorgang) { return GrpcCreateCollaborationVorgangResponse.newBuilder().setVorgangId(vorgang.getId()).build(); } + + @Override + public void findDeletedVorgang(GrpcFindDeletedVorgangRequest request, StreamObserver<GrpcFindDeletedVorgangResponse> responseObserver) { + var responseStreamer = GrpcResponseBatchStreamer.create(GrpcFindDeletedVorgangResponseBuilder::new, responseObserver); + vorgangService.findDeleted().map(vorgangStubMapper::toGrpcVorgangHeader).forEach(responseStreamer::send); + responseStreamer.finish(); + } } \ No newline at end of file diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangRepository.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangRepository.java index 125315d1d5dbf4b03b9cdddc8f95e1d9bdf9992b..4363b1e02331dd3fc92a5de2de17a51c1d43e744 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangRepository.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangRepository.java @@ -27,6 +27,7 @@ import static de.ozgcloud.vorgang.common.db.CriteriaUtil.*; import static org.springframework.data.mongodb.core.query.Query.*; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Stream; @@ -127,4 +128,8 @@ class VorgangRepository { return new Criteria().andOperator(CriteriaUtil.isId(vorgangId), CriteriaUtil.isNotDeleted(), Criteria.where(VorgangService.KEY_HEADER_LOCK).ne(null)); } + + public Stream<VorgangStub> findDeleted() { + return mongoOperations.stream(query(CriteriaUtil.vorgangInStatus(List.of(Vorgang.Status.DELETED))), VorgangStub.class); + } } \ No newline at end of file diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangService.java index bd1b375f8556d3c29615e7e85fc171e1c73a81a8..3cbc342a0f46aad3c4131a1d2033996f0d372eb2 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangService.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangService.java @@ -33,8 +33,6 @@ import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; -import jakarta.validation.Valid; - import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; @@ -53,6 +51,7 @@ import de.ozgcloud.vorgang.clientattribute.ClientAttributeReadPermitted; import de.ozgcloud.vorgang.clientattribute.ClientAttributesMap; import de.ozgcloud.vorgang.common.errorhandling.NotFoundException; import de.ozgcloud.vorgang.servicekonto.ServiceKonto; +import jakarta.validation.Valid; import lombok.NonNull; import lombok.RequiredArgsConstructor; @@ -275,4 +274,7 @@ public class VorgangService { return lock.map(Lock::getClientName).filter(lockingClient -> !lockingClient.equals(clientName)).isPresent(); } + public Stream<VorgangStub> findDeleted() { + return repository.findDeleted(); + } } \ No newline at end of file diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangStub.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangStub.java index a7562d92ed862323042a5b57ea63195a0268cbfe..0268e82bc6391ce102f71829d3d53388e41a7358 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangStub.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangStub.java @@ -36,7 +36,7 @@ import lombok.Getter; @Getter @Document(collection = Vorgang.COLLECTION_NAME) @TypeAlias("VorgangStub") -class VorgangStub { +public class VorgangStub { @Id private String id; diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangStubMapper.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangStubMapper.java index 098b34a18b1156ce2f2e87117b97a3ede918ff6b..80dfc8645eb65d5a304bc3d1f49458c347d0ac98 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangStubMapper.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangStubMapper.java @@ -60,4 +60,10 @@ interface VorgangStubMapper { } } + @Mapping(target = "aktenzeichen", ignore = true) + @Mapping(target = "assignedTo", ignore = true) + @Mapping(target = "nummer", ignore = true) + @Mapping(target = "clientAttributesList", ignore = true) + @Mapping(target = "formEngineName", ignore = true) + GrpcVorgangHeader toGrpcVorgangHeader(VorgangStub vorgangStub); } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/grpc/GrpcResponseBatchStreamerTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/grpc/GrpcResponseBatchStreamerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e984c0a13f9e782db23a674ca8ea4039e47eb703 --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/grpc/GrpcResponseBatchStreamerTest.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.common.grpc; + +import static org.mockito.Mockito.*; + +import java.util.function.Supplier; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mock; + +import io.grpc.stub.StreamObserver; + +class GrpcResponseBatchStreamerTest { + + private static final int BATCH_SIZE = 2; + + @Mock + private GrpcResponseBuilder<DummyElement, DummyResponse> responseBuilder; + @Mock + private GrpcResponseBuilder<DummyElement, DummyResponse> responseBuilder2; + @Mock + private Supplier<GrpcResponseBuilder<DummyElement, DummyResponse>> responseBuilderSupplier; + @Mock + private StreamObserver<DummyResponse> responseObserver; + private GrpcResponseBatchStreamer<DummyElement, DummyResponse> batchStreamer; + + @BeforeEach + void init() { + when(responseBuilderSupplier.get()).thenReturn(responseBuilder, responseBuilder2); + batchStreamer = GrpcResponseBatchStreamer.create(responseBuilderSupplier, responseObserver); + batchStreamer.setBatchSize(BATCH_SIZE); + } + + @Test + void shouldGetNewResponseBuilderDuringInitialization() { + verify(responseBuilderSupplier).get(); + } + + @Nested + class TestSend { + + private final DummyElement element = new DummyElement(); + + @Test + void shouldGetElementCount() { + batchStreamer.send(element); + + verify(responseBuilder).getElementCount(); + } + + @Nested + class BatchIsNotFull { + + @BeforeEach + void init() { + when(responseBuilder.getElementCount()).thenReturn(BATCH_SIZE - 1); + } + + @Test + void shouldAddElementToResponse() { + batchStreamer.send(element); + + verify(responseBuilder).addElement(element); + } + + @Test + void shouldNotSendResponse() { + batchStreamer.send(element); + + verifyNoInteractions(responseObserver); + } + } + + @Nested + class BatchIsFull { + + private final DummyResponse response = new DummyResponse(); + + @BeforeEach + void init() { + when(responseBuilder.getElementCount()).thenReturn(BATCH_SIZE); + when(responseBuilder.build()).thenReturn(response); + } + + @Test + void shouldAddElementToResponse() { + batchStreamer.send(element); + + verify(responseBuilder).addElement(element); + } + + @Test + void shouldSendResponse() { + batchStreamer.send(element); + + verify(responseObserver).onNext(response); + } + + @Test + void shouldGetNewResponseBuilder() { + batchStreamer.send(element); + + // first time during initialization, second time in send() + verify(responseBuilderSupplier, times(2)).get(); + } + + @Test + void shouldUseNewResponseBuilderNextTime() { + batchStreamer.send(element); + + batchStreamer.send(element); + + verify(responseBuilder2).addElement(element); + } + } + } + + @Nested + class TestFinish { + + private final DummyResponse response = new DummyResponse(); + + @Test + void shouldGetElementCount() { + batchStreamer.finish(); + + verify(responseBuilder).getElementCount(); + } + + @Test + void shouldNotSendResponseIfBatchIsEmpty() { + when(responseBuilder.getElementCount()).thenReturn(0); + + batchStreamer.finish(); + + verify(responseObserver, never()).onNext(any()); + } + + @Test + void shouldSendResponseIfBatchIsNotEmpty() { + when(responseBuilder.getElementCount()).thenReturn(1); + when(responseBuilder.build()).thenReturn(response); + + batchStreamer.finish(); + + verify(responseObserver).onNext(response); + } + + @ParameterizedTest + @ValueSource(ints = {0, 1}) + void shouldCallOnCompleted(int batchSize) { + when(responseBuilder.getElementCount()).thenReturn(batchSize); + + batchStreamer.finish(); + + verify(responseObserver).onCompleted(); + } + } + + private static class DummyResponse { + } + + private static class DummyElement { + } +} diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/GrpcFindDeletedVorgangRequestTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/GrpcFindDeletedVorgangRequestTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..91d47686cb2356fb5df9e4cd0920cb81baa3c62e --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/GrpcFindDeletedVorgangRequestTestFactory.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.vorgang; + +class GrpcFindDeletedVorgangRequestTestFactory { + + public static GrpcFindDeletedVorgangRequest create() { + return createBuilder().build(); + } + + public static GrpcFindDeletedVorgangRequest.Builder createBuilder() { + return GrpcFindDeletedVorgangRequest.newBuilder(); + } +} diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangGrpcServiceITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangGrpcServiceITCase.java index 97e3b6f0a91e1b10f8afe785fc84c5f4ba95ebe9..24d8152f703322dd571094e5b4eefd73463fbc87 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangGrpcServiceITCase.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangGrpcServiceITCase.java @@ -23,6 +23,7 @@ */ package de.ozgcloud.vorgang.vorgang; +import static de.ozgcloud.vorgang.common.grpc.GrpcResponseBatchStreamer.*; import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @@ -31,6 +32,8 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.stream.IntStream; +import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -121,8 +124,8 @@ class VorgangGrpcServiceITCase { @SuppressWarnings("unchecked") @Test void shouldKeepFieldsOrderInSubForm() { - var grpcFormData = GrpcFormDataTestFactory.createBuilder().addForm( - GrpcSubFormTestFactory.createBuilder().clearField().clearSubForm().setTitle(TITLE_SUBFORM_1).addAllField(formFields)) + var grpcFormData = GrpcFormDataTestFactory.createBuilder() + .addForm(GrpcSubFormTestFactory.createBuilder().clearField().clearSubForm().setTitle(TITLE_SUBFORM_1).addAllField(formFields)) .build(); var formData = (Map<String, Object>) startCreation(grpcFormData).get(TITLE_SUBFORM_1); @@ -144,9 +147,9 @@ class VorgangGrpcServiceITCase { @Test void shouldKeepOrderInSubForm() { var grpcFormData = GrpcFormDataTestFactory.createBuilder().addForm( - GrpcSubFormTestFactory.createBuilder().setTitle(TITLE_SUBFORM_1).clearField().clearSubForm() - .addSubForm(GrpcSubFormTestFactory.createBuilder().setTitle(TITLE_SUBFORM_3)) - .addSubForm(GrpcSubFormTestFactory.createBuilder().setTitle(TITLE_SUBFORM_2))) + GrpcSubFormTestFactory.createBuilder().setTitle(TITLE_SUBFORM_1).clearField().clearSubForm() + .addSubForm(GrpcSubFormTestFactory.createBuilder().setTitle(TITLE_SUBFORM_3)) + .addSubForm(GrpcSubFormTestFactory.createBuilder().setTitle(TITLE_SUBFORM_2))) .build(); var formData = (Map<String, Object>) startCreation(grpcFormData).get(TITLE_SUBFORM_1); @@ -235,4 +238,34 @@ class VorgangGrpcServiceITCase { return findVorgangResponseCaptor.getValue().getVorgangWithEingang().getEingang().getFormData(); } } + + @Nested + class TestFindDeletedVorgang { + + private static final int INCOMPLETE_BATCH_SIZE = DEFAULT_BATCH_SIZE - 1; + private static final int DELETED_VORGANG_COUNT = DEFAULT_BATCH_SIZE + INCOMPLETE_BATCH_SIZE; + + @Mock + private StreamObserver<GrpcFindDeletedVorgangResponse> streamObserver; + @Captor + private ArgumentCaptor<GrpcFindDeletedVorgangResponse> findVorgangResponseCaptor; + + @BeforeEach + void init() { + when(service.findDeleted()).thenReturn(generateVorgangStubs()); + } + + @Test + void shouldSendResponses() { + grpcVorgangService.findDeletedVorgang(GrpcFindDeletedVorgangRequestTestFactory.create(), streamObserver); + + verify(streamObserver, times(2)).onNext(findVorgangResponseCaptor.capture()); + var responses = findVorgangResponseCaptor.getAllValues(); + assertThat(responses).extracting(response -> response.getVorgangList().size()).containsExactly(DEFAULT_BATCH_SIZE, INCOMPLETE_BATCH_SIZE); + } + + private Stream<VorgangStub> generateVorgangStubs() { + return IntStream.range(0, DELETED_VORGANG_COUNT).mapToObj(idx -> VorgangStubTestFactory.create()); + } + } } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangGrpcServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangGrpcServiceTest.java index 1fcfd3b108caaf4decd0382b0c0274edd5fb1ee1..aea3a5e9472def6b896648cb395e4137ced56514 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangGrpcServiceTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangGrpcServiceTest.java @@ -29,7 +29,9 @@ import static org.mockito.Mockito.*; import java.util.Collections; import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -39,9 +41,11 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.Spy; import org.springframework.data.domain.Page; +import de.ozgcloud.nachrichten.common.vorgang.GrpcVorgangHeaderTestFactory; import de.ozgcloud.vorgang.collaboration.CollaborationService; import de.ozgcloud.vorgang.collaboration.CreateCollaborationVorgangBadRequestException; import de.ozgcloud.vorgang.collaboration.CreateCollaborationVorgangRequest; @@ -49,6 +53,7 @@ import de.ozgcloud.vorgang.collaboration.CreateCollaborationVorgangRequestMapper import de.ozgcloud.vorgang.collaboration.CreateCollaborationVorgangRequestTestFactory; import de.ozgcloud.vorgang.collaboration.GrpcCreateCollaborationRequestDataTestFactory; import de.ozgcloud.vorgang.collaboration.GrpcCreateCollaborationVorgangResponseTestFactory; +import de.ozgcloud.vorgang.common.grpc.GrpcResponseBatchStreamer; import io.grpc.stub.StreamObserver; class VorgangGrpcServiceTest { @@ -66,6 +71,8 @@ class VorgangGrpcServiceTest { private VorgangHeaderService headerService; @Mock private VorgangHeaderMapper vorgangHeaderMapper; + @Mock + private VorgangStubMapper vorgangStubMapper; @Mock private EingangMapper eingangMapper; @@ -510,4 +517,78 @@ class VorgangGrpcServiceTest { assertThat(result.getVorgangId()).isEqualTo(VorgangTestFactory.ID); } } + + @Nested + class TestFindDeletedVorgang { + + private final GrpcFindDeletedVorgangRequest request = GrpcFindDeletedVorgangRequestTestFactory.create(); + private MockedStatic<GrpcResponseBatchStreamer> mockedStatic; + @Mock + private GrpcResponseBatchStreamer<GrpcVorgangHeader, GrpcFindVorgangResponse> batchStreamer; + @Mock + private StreamObserver<GrpcFindDeletedVorgangResponse> responseObserver; + + private final VorgangStub vorgangStub = VorgangStubTestFactory.create(); + private final GrpcVorgangHeader grpcVorgangHeader = GrpcVorgangHeaderTestFactory.create(); + + @BeforeEach + void init() { + mockedStatic = mockStatic(GrpcResponseBatchStreamer.class); + mockedStatic.when(() -> GrpcResponseBatchStreamer.create(any(), any())).thenReturn(batchStreamer); + when(vorgangService.findDeleted()).thenReturn(Stream.of(vorgangStub)); + when(vorgangStubMapper.toGrpcVorgangHeader(vorgangStub)).thenReturn(grpcVorgangHeader); + } + + @AfterEach + void cleanup() { + mockedStatic.close(); + } + + @Test + void shouldFindDeleted() { + findDeletedVorgang(); + + verify(vorgangService).findDeleted(); + } + + @Test + void shouldMapToGrpc() { + findDeletedVorgang(); + + verify(vorgangStubMapper).toGrpcVorgangHeader(vorgangStub); + } + + @Test + void shouldSendToBatchStreamer() { + findDeletedVorgang(); + + verify(batchStreamer).send(grpcVorgangHeader); + } + + @Test + void shouldCallFinish() { + findDeletedVorgang(); + + verify(batchStreamer).finish(); + } + + @Test + void shouldNotCallStreamObserverDirectly() { + findDeletedVorgang(); + + verifyNoInteractions(responseObserver); + } + + @Test + void shouldNotCatchExceptions() { + var exception = new RuntimeException(); + when(vorgangStubMapper.toGrpcVorgangHeader(vorgangStub)).thenThrow(exception); + + assertThatThrownBy(this::findDeletedVorgang).isSameAs(exception); + } + + private void findDeletedVorgang() { + service.findDeletedVorgang(request, responseObserver); + } + } } \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangHeaderServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangHeaderServiceTest.java index 719ae2ead5bfda1dda98281e146e0a3850b478a8..74d47f18f4bca9929b2de9497a7bef7658c31f71 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangHeaderServiceTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangHeaderServiceTest.java @@ -141,7 +141,7 @@ class VorgangHeaderServiceTest { } @Nested - class TestGEtById { + class TestGetById { @Test void shouldCallFindById() { diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangRepositoryITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangRepositoryITCase.java index 5244ce68773a36de27d72361380c0946266b9774..8f81b6fd4323fda2e60732eab71b5d7833bbf84f 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangRepositoryITCase.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangRepositoryITCase.java @@ -23,6 +23,7 @@ */ package de.ozgcloud.vorgang.vorgang; +import static de.ozgcloud.vorgang.vorgang.Vorgang.Status.*; import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; @@ -41,6 +42,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 @@ -255,7 +258,7 @@ class VorgangRepositoryITCase { @Test void shouldNotExistsDELETEDVorgang() { - mongoOperations.save(VorgangTestFactory.createBuilder().status(Vorgang.Status.DELETED).build()); + mongoOperations.save(VorgangTestFactory.createBuilder().status(DELETED).build()); var exists = repository.exists(VorgangTestFactory.ID, Collections.singleton(ZustaendigeStelleTestFactory.ORGANISATIONSEINHEIT_ID)); @@ -435,4 +438,20 @@ class VorgangRepositoryITCase { return mongoOperations.findById(VorgangTestFactory.ID, Vorgang.class); } } + + @Nested + class TestFindDeleted { + + private static final String DELETED_VORGANG_NAME = LoremIpsum.getInstance().getWords(3); + + @Test + void shouldReturnOnlyWhereStatusIsDeleted() { + mongoOperations.save(VorgangStubTestFactory.createBuilder().build()); + mongoOperations.save(VorgangStubTestFactory.createBuilder().name(DELETED_VORGANG_NAME).status(DELETED).build()); + + var deleted = repository.findDeleted().toList(); + + assertThat(deleted).hasSize(1).extracting(VorgangStub::getName).contains(DELETED_VORGANG_NAME); + } + } } \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangServiceTest.java index 53df957e740b3479162ea94de6f2dcee7c16f80c..3449659b130bca698edbf9b3b88daf73fe867efc 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangServiceTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangServiceTest.java @@ -24,7 +24,6 @@ package de.ozgcloud.vorgang.vorgang; import static org.assertj.core.api.Assertions.*; -import static org.assertj.core.api.InstanceOfAssertFactories.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @@ -35,7 +34,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import org.assertj.core.api.InstanceOfAssertFactories; +import org.assertj.core.api.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -760,4 +759,26 @@ class VorgangServiceTest { assertThat(result).isFalse(); } } + + + @Nested + class TestFindDeleted { + + @Test + void shouldCallRepository() { + service.findDeleted(); + + verify(repository).findDeleted(); + } + + @Test + void shouldReturnRepositoryResult() { + var repositoryResult = List.of(VorgangStubTestFactory.create()); + when(repository.findDeleted()).thenReturn(repositoryResult.stream()); + + var serviceResult = service.findDeleted().toList(); + + assertThat(serviceResult).isEqualTo(repositoryResult); + } + } } \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangStubMapperTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangStubMapperTest.java index c1d2c25865eeb820b8a1f1d5528881688d656004..087abf2267bccc8efe37ddb0d27a46a156c09312 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangStubMapperTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangStubMapperTest.java @@ -64,4 +64,22 @@ class VorgangStubMapperTest { } } + @Nested + class TestToGrpcVorgangHeader { + + @Test + void shouldMapFields() { + var vorgangStub = VorgangStubTestFactory.createBuilder().status(Vorgang.Status.DELETED).build(); + var expectedGrpcVorgangHeader = GrpcVorgangHeader.newBuilder() + .setId(VorgangStubTestFactory.ID) + .setName(VorgangStubTestFactory.NAME) + .setCreatedAt(VorgangStubTestFactory.CREATED_AT_STR) + .setStatus(Vorgang.Status.DELETED.name()) + .build(); + + var mappedGrpcVorgangHeader = mapper.toGrpcVorgangHeader(vorgangStub); + + assertThat(mappedGrpcVorgangHeader).isEqualTo(expectedGrpcVorgangHeader); + } + } } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangStubTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangStubTestFactory.java index 1a5b3eff69bb7beeef81a7d84e0f15b34e4dfef6..8833e98ec2f1a0074d9fc3b89bb736ee3c63b76a 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangStubTestFactory.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangStubTestFactory.java @@ -23,8 +23,15 @@ */ package de.ozgcloud.vorgang.vorgang; +import java.time.ZonedDateTime; + public class VorgangStubTestFactory { + public static final String ID = VorgangTestFactory.ID; + public static final String NAME = VorgangTestFactory.NAME; + public static final String CREATED_AT_STR = VorgangTestFactory.CREATED_AT_STR; + public static final ZonedDateTime CREATED_AT = VorgangTestFactory.CREATED_AT; + public static VorgangStub create() { return createBuilder().build(); } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangTestFactory.java index 6c7e4c40b0f9f508a90eb8ddb37e3c13dcb7c75c..1f95514a96ce44e06fe9ba8cc348d19a767dc11e 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangTestFactory.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangTestFactory.java @@ -30,9 +30,9 @@ import org.bson.types.ObjectId; import com.thedeanda.lorem.LoremIpsum; import de.ozgcloud.vorgang.callcontext.UserTestFactory; -import de.ozgcloud.vorgang.vorgang.Vorgang.Status; import de.ozgcloud.vorgang.clientattribute.ClientAttributesMap; import de.ozgcloud.vorgang.clientattribute.ClientAttributesMapTestFactory; +import de.ozgcloud.vorgang.vorgang.Vorgang.Status; public class VorgangTestFactory { @@ -44,7 +44,8 @@ public class VorgangTestFactory { public static final String INITIAL_DATE = "2021-01-10"; public static final String INITIAL_TIME = "10:30:00"; public static final String INITIAL_DATE_STR = "%sT%sZ".formatted(INITIAL_DATE, INITIAL_TIME); - public static final ZonedDateTime CREATED_AT = ZonedDateTime.parse(INITIAL_DATE_STR); + public static final String CREATED_AT_STR = INITIAL_DATE_STR; + public static final ZonedDateTime CREATED_AT = ZonedDateTime.parse(CREATED_AT_STR); public static final String VORGANG_NUMMER = "VOR-GANG-NUMMER-1"; public static final String AKTENZEICHEN = LoremIpsum.getInstance().getWords(1);