From 0f7d5bacf17b933b5bc92547203811ef143d1534 Mon Sep 17 00:00:00 2001 From: Felix Reichenbach <felix.reichenbach@mgm-tp.com> Date: Wed, 19 Mar 2025 15:15:02 +0100 Subject: [PATCH] OZG-7573 OZG-7930 forward attachments to eingang-manger --- vorgang-manager-server/pom.xml | 14 +- .../vorgang/vorgang/IncomingFileMapper.java | 15 + .../redirect/ForwardingRemoteService.java | 87 +++- .../vorgang/vorgang/EingangTestFactory.java | 4 +- .../vorgang/IncomingFileMapperTest.java | 12 + .../redirect/ForwardingRemoteServiceTest.java | 424 +++++++++++++++++- .../GrpcAttachmentFileTestFactory.java | 48 ++ .../redirect/GrpcAttachmentTestFactory.java | 47 ++ .../redirect/GrpcFileContentTestFactory.java | 52 +++ ...GrpcRouteForwardingRequestTestFactory.java | 16 + 10 files changed, 710 insertions(+), 9 deletions(-) create mode 100644 vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/GrpcAttachmentFileTestFactory.java create mode 100644 vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/GrpcAttachmentTestFactory.java create mode 100644 vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/GrpcFileContentTestFactory.java diff --git a/vorgang-manager-server/pom.xml b/vorgang-manager-server/pom.xml index b25865e77..30ca31258 100644 --- a/vorgang-manager-server/pom.xml +++ b/vorgang-manager-server/pom.xml @@ -32,7 +32,7 @@ <parent> <groupId>de.ozgcloud.common</groupId> <artifactId>ozgcloud-common-parent</artifactId> - <version>4.11.0</version> + <version>4.12.0-OZG-7573-GrpcUpload-utils-SNAPSHOT</version> <relativePath /> </parent> @@ -51,7 +51,7 @@ <spring-boot.build-image.imageName>docker.ozg-sh.de/vorgang-manager:build-latest</spring-boot.build-image.imageName> <zufi-manager-interface.version>1.6.0</zufi-manager-interface.version> - + <common-lib.version>4.12.0-OZG-7573-GrpcUpload-utils-SNAPSHOT</common-lib.version> <user-manager-interface.version>2.12.0</user-manager-interface.version> <processor-manager.version>0.5.0</processor-manager.version> <nachrichten-manager.version>2.18.0</nachrichten-manager.version> @@ -60,7 +60,7 @@ <collaboration-manager.version>0.7.0</collaboration-manager.version> <archive-manager.version>0.3.0</archive-manager.version> <document-manager.version>1.2.0</document-manager.version> - <eingang-manager-interface.version>2.19.0-SNAPSHOT</eingang-manager-interface.version> + <eingang-manager-interface.version>2.19.0-OZG-7573-forward-vorgang-SNAPSHOT</eingang-manager-interface.version> <zip.version>2.11.5</zip.version> <jsoup.version>1.15.3</jsoup.version> @@ -82,6 +82,11 @@ <version>${project.version}</version> </dependency> + <dependency> + <groupId>de.ozgcloud.common</groupId> + <artifactId>ozgcloud-common-lib</artifactId> + <version>${common-lib.version}</version> + </dependency> <dependency> <groupId>de.ozgcloud.nachrichten</groupId> <artifactId>nachrichten-manager-server</artifactId> @@ -402,7 +407,8 @@ <env> <BPE_DELIM_JAVA_TOOL_OPTIONS xml:space="preserve"> </BPE_DELIM_JAVA_TOOL_OPTIONS> <BPE_APPEND_JAVA_TOOL_OPTIONS>-Dfile.encoding=UTF-8</BPE_APPEND_JAVA_TOOL_OPTIONS> - <BPE_APPEND_JAVA_TOOL_OPTIONS>-Dio.grpc.netty.shaded.io.netty.maxDirectMemory=0</BPE_APPEND_JAVA_TOOL_OPTIONS> + <BPE_APPEND_JAVA_TOOL_OPTIONS> + -Dio.grpc.netty.shaded.io.netty.maxDirectMemory=0</BPE_APPEND_JAVA_TOOL_OPTIONS> <BPE_APPEND_JAVA_TOOL_OPTIONS>-XX:MaxDirectMemorySize=512m</BPE_APPEND_JAVA_TOOL_OPTIONS> <BPE_APPEND_LC_ALL>en_US.UTF-8</BPE_APPEND_LC_ALL> </env> diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/IncomingFileMapper.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/IncomingFileMapper.java index aefc6d34e..2e8f8d7ca 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/IncomingFileMapper.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/IncomingFileMapper.java @@ -24,9 +24,11 @@ package de.ozgcloud.vorgang.vorgang; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import com.google.protobuf.ByteString; +import de.ozgcloud.eingang.forwarding.GrpcAttachmentFile; import de.ozgcloud.vorgang.files.FileIdMapper; @Mapper(uses = FileIdMapper.class) @@ -37,4 +39,17 @@ public interface IncomingFileMapper { default byte[] map(ByteString value) { return value.toByteArray(); } + + @Mapping(target = "mergeFrom", ignore = true) + @Mapping(target = "clearField", ignore = true) + @Mapping(target = "clearOneof", ignore = true) + @Mapping(target = "mergeUnknownFields", ignore = true) + @Mapping(target = "contentTypeBytes", ignore = true) + @Mapping(target = "fileNameBytes", ignore = true) + @Mapping(target = "groupNameBytes", ignore = true) + @Mapping(target = "unknownFields", ignore = true) + @Mapping(target = "vendorIdBytes", ignore = true) + @Mapping(target = "allFields", ignore = true) + @Mapping(target = "fileName", source = "file.name") + GrpcAttachmentFile toAttachmentFile(String groupName, IncomingFile file); } \ No newline at end of file diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRemoteService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRemoteService.java index 416b80956..c615f1720 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRemoteService.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRemoteService.java @@ -23,18 +23,35 @@ */ package de.ozgcloud.vorgang.vorgang.redirect; +import java.io.InputStream; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Function; +import org.apache.commons.io.IOUtils; import org.springframework.stereotype.Service; +import com.google.protobuf.ByteString; + +import de.ozgcloud.common.binaryfile.GrpcFileUploadUtils; +import de.ozgcloud.common.binaryfile.GrpcFileUploadUtils.FileSender; import de.ozgcloud.common.errorhandling.TechnicalException; import de.ozgcloud.eingang.forwarder.RouteForwardingServiceGrpc; +import de.ozgcloud.eingang.forwarding.GrpcAttachment; +import de.ozgcloud.eingang.forwarding.GrpcFileContent; import de.ozgcloud.eingang.forwarding.GrpcRouteForwardingRequest; import de.ozgcloud.eingang.forwarding.GrpcRouteForwardingResponse; import de.ozgcloud.vorgang.callcontext.VorgangManagerClientCallContextAttachingInterceptor; +import de.ozgcloud.vorgang.files.FileService; import de.ozgcloud.vorgang.vorgang.Eingang; +import de.ozgcloud.vorgang.vorgang.IncomingFile; +import de.ozgcloud.vorgang.vorgang.IncomingFileGroup; +import de.ozgcloud.vorgang.vorgang.IncomingFileMapper; import de.ozgcloud.vorgang.vorgang.VorgangService; +import io.grpc.stub.CallStreamObserver; import io.grpc.stub.StreamObserver; import lombok.RequiredArgsConstructor; import net.devh.boot.grpc.client.inject.GrpcClient; @@ -43,10 +60,13 @@ import net.devh.boot.grpc.client.inject.GrpcClient; @RequiredArgsConstructor class ForwardingRemoteService { + private static final int TIMEOUT_MINUTES = 2; private final VorgangService vorgangService; private final ForwardingRequestMapper forwardingRequestMapper; @GrpcClient("forwarder") private final RouteForwardingServiceGrpc.RouteForwardingServiceStub serviceStub; + private final FileService fileService; + private final IncomingFileMapper incomingFileMapper; public void forward(ForwardingRequest request) { CompletableFuture<Void> responseFuture = new CompletableFuture<>(); @@ -69,7 +89,7 @@ class ForwardingRemoteService { void sendEingang(ForwardingRequest request, StreamObserver<GrpcRouteForwardingRequest> requestStreamObserver) { var eingang = vorgangService.getById(request.getVorgangId()).getEingangs().getFirst(); requestStreamObserver.onNext(buildRouteForwardingRequest(request, eingang)); - + sendAttachments(eingang.getAttachments(), requestStreamObserver); } GrpcRouteForwardingRequest buildRouteForwardingRequest(ForwardingRequest request, Eingang eingang) { @@ -77,6 +97,71 @@ class ForwardingRemoteService { return GrpcRouteForwardingRequest.newBuilder().setRouteForwarding(routeForwarding).build(); } + void sendAttachments(List<IncomingFileGroup> attachments, StreamObserver<GrpcRouteForwardingRequest> requestStreamObserver) { + for (var attachment : attachments) { + var groupName = attachment.getName(); + attachment.getFiles().forEach(file -> sendAttachmentFile(requestStreamObserver, groupName, file)); + } + } + + private void sendAttachmentFile(StreamObserver<GrpcRouteForwardingRequest> requestStreamObserver, String groupName, IncomingFile file) { + var fileContentStream = fileService.getUploadedFileStream(file.getId()); + var fileSender = createFileSender(requestStreamObserver, groupName, file, fileContentStream).send(); + waitForFinishedFileUpload(fileSender, fileContentStream); + } + + FileSender<GrpcRouteForwardingRequest, GrpcRouteForwardingResponse> createFileSender( + StreamObserver<GrpcRouteForwardingRequest> requestStreamObserver, String groupName, IncomingFile file, InputStream fileContentStream) { + return GrpcFileUploadUtils + .createSender(this::buildAttachmentChunk, fileContentStream, requestCallStreamObserverProvider(requestStreamObserver), false) + .withMetaData(buildGrpcAttachmentFile(groupName, file)); + } + + private Function<StreamObserver<GrpcRouteForwardingResponse>, CallStreamObserver<GrpcRouteForwardingRequest>> requestCallStreamObserverProvider( + StreamObserver<GrpcRouteForwardingRequest> requestStreamObserver) { + return response -> (CallStreamObserver<GrpcRouteForwardingRequest>) requestStreamObserver; + } + + GrpcRouteForwardingRequest buildAttachmentChunk(byte[] chunk, int length) { + var fileContentBuilder = GrpcFileContent.newBuilder(); + if (length <= 0) { + fileContentBuilder.setIsEndOfFile(true); + } else { + fileContentBuilder.setContent(ByteString.copyFrom(chunk)); + } + return GrpcRouteForwardingRequest.newBuilder() + .setAttachment(GrpcAttachment.newBuilder() + .setContent(fileContentBuilder) + .build()) + .build(); + } + + GrpcRouteForwardingRequest buildGrpcAttachmentFile(String name, IncomingFile file) { + return GrpcRouteForwardingRequest.newBuilder() + .setAttachment(GrpcAttachment.newBuilder() + .setFile(incomingFileMapper.toAttachmentFile(name, file)) + .build()) + .build(); + } + + void waitForFinishedFileUpload(FileSender<GrpcRouteForwardingRequest, GrpcRouteForwardingResponse> fileSender, InputStream inputStream) { + try { + fileSender.getResultFuture().get(TIMEOUT_MINUTES, TimeUnit.MINUTES); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + fileSender.cancelOnError(e); + throw new TechnicalException("Waiting for finishing file upload was interrupted.", e); + } catch (ExecutionException e) { + fileSender.cancelOnError(e); + throw new TechnicalException("Error on uploading file content.", e); + } catch (TimeoutException e) { + fileSender.cancelOnTimeout(); + throw new TechnicalException("Timeout on uploading file content.", e); + } finally { + IOUtils.closeQuietly(inputStream); + } + } + private static void waitForCompletion(CompletableFuture<Void> responseFuture) { try { responseFuture.get(); diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/EingangTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/EingangTestFactory.java index 9fa8813dd..b12ea789a 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/EingangTestFactory.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/EingangTestFactory.java @@ -49,6 +49,8 @@ public class EingangTestFactory { public static final Map<String, Object> SUBFORM_FLAT = Map.of(SUBFORM_FIELD_NAME, SUBFORM_FIELD); public static final Map<String, Object> FORM_DATA = Map.of(SINGLE_FIELD_NAME, SINGLE_FIELD, SUBFORM_NAME, SUBFORM); + public static final IncomingFileGroup ATTACHMENT = IncomingFileGroupTestFactory.create(); + public static Eingang create() { return createBuilder().build(); } @@ -59,7 +61,7 @@ public class EingangTestFactory { .header(EingangHeaderTestFactory.create()) .antragsteller(AntragstellerTestFactory.create()) .formData(FORM_DATA) - .attachment(IncomingFileGroupTestFactory.create()) + .attachment(ATTACHMENT) .representation(IncomingFileTestFactory.create()) .zustaendigeStelle(ZustaendigeStelleTestFactory.create()); } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/IncomingFileMapperTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/IncomingFileMapperTest.java index e59250ca0..d6f41ddfe 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/IncomingFileMapperTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/IncomingFileMapperTest.java @@ -32,6 +32,7 @@ import org.mockito.InjectMocks; import org.mockito.Spy; import de.ozgcloud.vorgang.files.FileIdMapper; +import de.ozgcloud.vorgang.vorgang.redirect.GrpcAttachmentFileTestFactory; class IncomingFileMapperTest { @@ -50,4 +51,15 @@ class IncomingFileMapperTest { assertThat(result).isNotNull().usingRecursiveComparison().isEqualTo(IncomingFileTestFactory.create()); } } + + @Nested + class TestToAttachmentFile { + + @Test + void shouldMapToGrpcAttachmentFile() { + var result = mapper.toAttachmentFile(GrpcAttachmentFileTestFactory.GROUP_NAME, IncomingFileTestFactory.create()); + + assertThat(result).isEqualTo(GrpcAttachmentFileTestFactory.create()); + } + } } \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRemoteServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRemoteServiceTest.java index df3940325..ccebcdf91 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRemoteServiceTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRemoteServiceTest.java @@ -24,34 +24,67 @@ package de.ozgcloud.vorgang.vorgang.redirect; import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import java.io.InputStream; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.BiFunction; +import java.util.function.Function; + +import org.apache.commons.lang3.RandomUtils; +import org.junit.jupiter.api.AfterEach; 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.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.Spy; +import de.ozgcloud.common.binaryfile.GrpcFileUploadUtils; +import de.ozgcloud.common.binaryfile.GrpcFileUploadUtils.FileSender; +import de.ozgcloud.common.errorhandling.TechnicalException; import de.ozgcloud.eingang.forwarder.RouteForwardingServiceGrpc; import de.ozgcloud.eingang.forwarding.GrpcRouteForwardingRequest; +import de.ozgcloud.eingang.forwarding.GrpcRouteForwardingResponse; import de.ozgcloud.vorgang.callcontext.VorgangManagerClientCallContextAttachingInterceptor; +import de.ozgcloud.vorgang.files.FileService; +import de.ozgcloud.vorgang.vorgang.EingangTestFactory; +import de.ozgcloud.vorgang.vorgang.IncomingFile; +import de.ozgcloud.vorgang.vorgang.IncomingFileGroup; +import de.ozgcloud.vorgang.vorgang.IncomingFileGroupTestFactory; +import de.ozgcloud.vorgang.vorgang.IncomingFileMapper; +import de.ozgcloud.vorgang.vorgang.IncomingFileTestFactory; import de.ozgcloud.vorgang.vorgang.VorgangService; import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; import de.ozgcloud.vorgang.vorgang.redirect.ForwardingRemoteService.ForwardingResponseObserver; +import io.grpc.stub.CallStreamObserver; import io.grpc.stub.StreamObserver; +import lombok.SneakyThrows; class ForwardingRemoteServiceTest { + @Spy + @InjectMocks + private ForwardingRemoteService service; @Mock private VorgangService vorgangService; @Mock private ForwardingRemoteService forwardingRemoteService; @Mock private RouteForwardingServiceGrpc.RouteForwardingServiceStub serviceStub; - @Spy - @InjectMocks - private ForwardingRemoteService service; + @Mock + private FileService fileService; + @Mock + private IncomingFileMapper incomingFileMapper; @Mock private StreamObserver<GrpcRouteForwardingRequest> requestObserver; @@ -160,6 +193,7 @@ class ForwardingRemoteServiceTest { void init() { when(vorgangService.getById(any())).thenReturn(VorgangTestFactory.create()); doReturn(GrpcRouteForwardingRequestTestFactory.create()).when(service).buildRouteForwardingRequest(any(), any()); + doNothing().when(service).sendAttachments(any(), any()); } @Test @@ -169,8 +203,392 @@ class ForwardingRemoteServiceTest { verify(vorgangService).getById(VorgangTestFactory.ID); } + @Test + void shouldCallSendAttachments() { + sendEingang(); + + verify(service).sendAttachments(List.of(EingangTestFactory.ATTACHMENT), requestObserver); + } + private void sendEingang() { service.sendEingang(request, requestObserver); } } + + @Nested + class TestSendAttachments { + + @Mock + private FileSender<GrpcRouteForwardingRequest, GrpcRouteForwardingResponse> fileSender; + @Mock + private InputStream inputStream; + + private final IncomingFileGroup attachment = IncomingFileGroupTestFactory.create(); + + @BeforeEach + void init() { + when(fileService.getUploadedFileStream(any())).thenReturn(inputStream); + doReturn(fileSender).when(service).createFileSender(any(), any(), any(), any()); + when(fileSender.send()).thenReturn(fileSender); + } + + @Test + void shouldGetUploadedFileContent() { + sendAttachments(); + + verify(fileService).getUploadedFileStream(IncomingFileTestFactory.ID); + } + + @Test + void shouldCallCreateFileSender() { + sendAttachments(); + + verify(service).createFileSender(requestObserver, IncomingFileGroupTestFactory.NAME, IncomingFileGroupTestFactory.FILE, + inputStream); + } + + @Test + void shouldSend() { + sendAttachments(); + + verify(fileSender).send(); + } + + @Test + void shouldCallWaitForFinishedFileUploadAfterSend() { + var inOrder = inOrder(fileSender, service); + + sendAttachments(); + + inOrder.verify(fileSender).send(); + inOrder.verify(service).waitForFinishedFileUpload(fileSender, inputStream); + } + + private void sendAttachments() { + service.sendAttachments(List.of(attachment), requestObserver); + } + } + + @Nested + class TestCreateFileSender { + + private MockedStatic<GrpcFileUploadUtils> grpcFileUploadUtilsMock; + @Mock + private CallStreamObserver<GrpcRouteForwardingRequest> requestCallStreamObserver; + @Mock + private FileSender<GrpcRouteForwardingRequest, GrpcRouteForwardingResponse> fileSender; + @Mock + private FileSender<GrpcRouteForwardingRequest, GrpcRouteForwardingResponse> fileSenderWithMetadata; + @Mock + private StreamObserver<GrpcRouteForwardingResponse> responseObserver; + @Mock + private InputStream inputStream; + @Captor + private ArgumentCaptor<BiFunction<byte[], Integer, GrpcRouteForwardingRequest>> chunkBuilderCaptor; + @Captor + private ArgumentCaptor<Function<StreamObserver<GrpcRouteForwardingResponse>, CallStreamObserver<GrpcRouteForwardingRequest>>> reqObserverBuilderCaptor; + + private final byte[] chunk = RandomUtils.insecure().randomBytes(5); + private final GrpcRouteForwardingRequest metadataRequest = GrpcRouteForwardingRequestTestFactory.create(); + + @BeforeEach + void init() { + grpcFileUploadUtilsMock = mockStatic(GrpcFileUploadUtils.class); + grpcFileUploadUtilsMock.when(() -> GrpcFileUploadUtils.createSender(any(), any(), any(), anyBoolean())).thenReturn(fileSender); + doReturn(metadataRequest).when(service).buildGrpcAttachmentFile(any(), any()); + when(fileSender.withMetaData(any())).thenReturn(fileSenderWithMetadata); + } + + @AfterEach + void tearDown() { + grpcFileUploadUtilsMock.close(); + } + + @Test + void shouldCreateFileSenderWithChunkBuilder() { + createFileSender(); + + grpcFileUploadUtilsMock.verify(() -> GrpcFileUploadUtils.createSender(chunkBuilderCaptor.capture(), any(), any(), anyBoolean())); + chunkBuilderCaptor.getValue().apply(chunk, chunk.length); + verify(service).buildAttachmentChunk(chunk, chunk.length); + } + + @Test + void shouldCreateFileSenderWithInputStream() { + createFileSender(); + + grpcFileUploadUtilsMock.verify(() -> GrpcFileUploadUtils.createSender(any(), eq(inputStream), any(), anyBoolean())); + } + + @Test + void shouldCreateFileSenderWithRequestObserverBuilder() { + createFileSender(); + + grpcFileUploadUtilsMock.verify(() -> GrpcFileUploadUtils.createSender(any(), any(), reqObserverBuilderCaptor.capture(), anyBoolean())); + assertThat(reqObserverBuilderCaptor.getValue().apply(responseObserver)).isSameAs(requestCallStreamObserver); + } + + @Test + void shouldCreateFileSenderWithNotCompleteOnFileSent() { + createFileSender(); + + grpcFileUploadUtilsMock.verify(() -> GrpcFileUploadUtils.createSender(any(), any(), any(), eq(false))); + } + + @Test + void shouldCallBuildGrpcAttachmentFile() { + createFileSender(); + + verify(service).buildGrpcAttachmentFile(IncomingFileGroupTestFactory.NAME, IncomingFileGroupTestFactory.FILE); + } + + @Test + void shouldSetMetaData() { + createFileSender(); + + verify(fileSender).withMetaData(metadataRequest); + } + + @Test + void shouldReturnBuiltFileSender() { + var returnedFileSender = createFileSender(); + + assertThat(returnedFileSender).isSameAs(fileSenderWithMetadata); + } + + private FileSender<GrpcRouteForwardingRequest, GrpcRouteForwardingResponse> createFileSender() { + return service.createFileSender(requestCallStreamObserver, IncomingFileGroupTestFactory.NAME, IncomingFileGroupTestFactory.FILE, + inputStream); + } + } + + @Nested + class TestBuildAttachmentChunk { + + @Nested + class TestOnEndOfFile { + + @Test + void shouldBuildEndOfFileChunk() { + var fileContent = service.buildAttachmentChunk(new byte[0], -1).getAttachment().getContent(); + + assertThat(fileContent).isEqualTo(GrpcFileContentTestFactory.createEndOfFile()); + } + } + + @Nested + class TestOnContentProvided { + + @Test + void shouldBuildEndOfFileChunk() { + var fileContent = service.buildAttachmentChunk(GrpcFileContentTestFactory.CONTENT, GrpcFileContentTestFactory.CONTENT.length) + .getAttachment().getContent(); + + assertThat(fileContent).isEqualTo(GrpcFileContentTestFactory.create()); + } + } + } + + @Nested + class TestBuildGrpcAttachmentFile { + + private IncomingFile file = IncomingFileTestFactory.create(); + + @BeforeEach + void mock() { + when(incomingFileMapper.toAttachmentFile(any(), any())).thenReturn(GrpcAttachmentFileTestFactory.create()); + } + + @Test + void shouldCallIncomingFileMapper() { + service.buildGrpcAttachmentFile(IncomingFileGroupTestFactory.NAME, file); + + verify(incomingFileMapper).toAttachmentFile(IncomingFileGroupTestFactory.NAME, file); + } + + @Test + void shoudlReturnAttachmentMetadataRequest() { + var result = service.buildGrpcAttachmentFile(IncomingFileGroupTestFactory.NAME, file); + + assertThat(result).isEqualTo(GrpcRouteForwardingRequestTestFactory.createWithAttachmentMetadata()); + } + } + + @Nested + class TestWaitForFinishedFileUpload { + + @Mock + private FileSender<GrpcRouteForwardingRequest, GrpcRouteForwardingResponse> fileSender; + @Mock + private InputStream inputStream; + @Mock + private CompletableFuture<GrpcRouteForwardingResponse> resultFuture; + + @BeforeEach + void mock() { + when(fileSender.getResultFuture()).thenReturn(resultFuture); + } + + @Test + void shouldGetResultFuture() { + waitForFinishedFileUpload(); + + verify(fileSender).getResultFuture(); + } + + @Test + @SneakyThrows + void shouldGetResultFromFuture() { + waitForFinishedFileUpload(); + + verify(resultFuture).get(2, TimeUnit.MINUTES); + } + + @Test + @SneakyThrows + void shouldCloseInputStream() { + try { + waitForFinishedFileUpload(); + } catch (TechnicalException e) { + // expected + } + + verify(inputStream).close(); + } + + @Nested + class TestOnInterruptedException { + + private InterruptedException exception = new InterruptedException(); + + @BeforeEach + @SneakyThrows + void mock() { + when(resultFuture.get(anyLong(), any())).thenThrow(exception); + } + + @Test + void shouldThrowTechnicalException() { + assertThrows(TechnicalException.class, () -> waitForFinishedFileUpload()); + } + + @Test + void shouldInterruptThread() { + try { + waitForFinishedFileUpload(); + } catch (TechnicalException e) { + // expected + } + + assertThat(Thread.currentThread().isInterrupted()).isTrue(); + } + + @Test + void shouldCancelOnError() { + try { + waitForFinishedFileUpload(); + } catch (TechnicalException e) { + // expected + } + + verify(fileSender).cancelOnError(exception); + } + + @Test + @SneakyThrows + void shouldCloseInputStream() { + try { + waitForFinishedFileUpload(); + } catch (TechnicalException e) { + // expected + } + + verify(inputStream).close(); + } + } + + @Nested + class TestOnExecutionException { + + private ExecutionException exception = new ExecutionException(new Exception()); + + @BeforeEach + @SneakyThrows + void mock() { + when(resultFuture.get(anyLong(), any())).thenThrow(exception); + } + + @Test + void shouldThrowTechnicalException() { + assertThrows(TechnicalException.class, () -> waitForFinishedFileUpload()); + } + + @Test + void shouldCancelOnError() { + try { + waitForFinishedFileUpload(); + } catch (TechnicalException e) { + // expected + } + + verify(fileSender).cancelOnError(exception); + } + + @Test + @SneakyThrows + void shouldCloseInputStream() { + try { + waitForFinishedFileUpload(); + } catch (TechnicalException e) { + // expected + } + + verify(inputStream).close(); + } + } + + @Nested + class TestOnTimeoutException { + + private TimeoutException exception = new TimeoutException(); + + @BeforeEach + @SneakyThrows + void mock() { + when(resultFuture.get(anyLong(), any())).thenThrow(exception); + } + + @Test + void shouldThrowTechnicalException() { + assertThrows(TechnicalException.class, () -> waitForFinishedFileUpload()); + } + + @Test + void shouldCancelOnTimeout() { + try { + waitForFinishedFileUpload(); + } catch (TechnicalException e) { + // expected + } + + verify(fileSender).cancelOnTimeout(); + } + + @Test + @SneakyThrows + void shouldCloseInputStream() { + try { + waitForFinishedFileUpload(); + } catch (TechnicalException e) { + // expected + } + + verify(inputStream).close(); + } + } + + private void waitForFinishedFileUpload() { + service.waitForFinishedFileUpload(fileSender, inputStream); + } + } } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/GrpcAttachmentFileTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/GrpcAttachmentFileTestFactory.java new file mode 100644 index 000000000..8c954a3e1 --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/GrpcAttachmentFileTestFactory.java @@ -0,0 +1,48 @@ +/* + * 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.redirect; + +import de.ozgcloud.eingang.forwarding.GrpcAttachmentFile; +import de.ozgcloud.eingang.forwarding.GrpcAttachmentFile.Builder; +import de.ozgcloud.vorgang.vorgang.IncomingFileGroupTestFactory; +import de.ozgcloud.vorgang.vorgang.IncomingFileTestFactory; + +public class GrpcAttachmentFileTestFactory { + + public static final String GROUP_NAME = IncomingFileGroupTestFactory.NAME; + + public static GrpcAttachmentFile create() { + return createBuilder().build(); + } + + public static Builder createBuilder() { + return GrpcAttachmentFile.newBuilder() + .setGroupName(GROUP_NAME) + .setFileName(IncomingFileTestFactory.NAME) + .setContentType(IncomingFileTestFactory.CONTENT_TYPE_STR) + .setVendorId(IncomingFileTestFactory.VENDOR_ID) + .setSize(IncomingFileTestFactory.SIZE); + } + +} diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/GrpcAttachmentTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/GrpcAttachmentTestFactory.java new file mode 100644 index 000000000..4e0e278a9 --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/GrpcAttachmentTestFactory.java @@ -0,0 +1,47 @@ +/* + * 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.redirect; + +import de.ozgcloud.eingang.forwarding.GrpcAttachment; +import de.ozgcloud.eingang.forwarding.GrpcAttachmentFile; +import de.ozgcloud.eingang.forwarding.GrpcFileContent; + +public class GrpcAttachmentTestFactory { + + public static final GrpcAttachmentFile FILE = GrpcAttachmentFileTestFactory.create(); + public static final GrpcFileContent CONTENT = GrpcFileContentTestFactory.create(); + + public static GrpcAttachment createWithFile() { + return GrpcAttachment.newBuilder() + .setFile(FILE) + .build(); + } + + public static GrpcAttachment createWithContent() { + return GrpcAttachment.newBuilder() + .setContent(CONTENT) + .build(); + } + +} diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/GrpcFileContentTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/GrpcFileContentTestFactory.java new file mode 100644 index 000000000..c37286a4d --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/GrpcFileContentTestFactory.java @@ -0,0 +1,52 @@ +/* + * 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.redirect; + +import org.apache.commons.lang3.RandomUtils; + +import com.google.protobuf.ByteString; + +import de.ozgcloud.eingang.forwarding.GrpcFileContent; +import de.ozgcloud.eingang.forwarding.GrpcFileContent.Builder; + +public class GrpcFileContentTestFactory { + + public static final boolean IS_END_OF_FILE = false; + public static final byte[] CONTENT = RandomUtils.insecure().randomBytes(10);; + + public static GrpcFileContent create() { + return createBuilder().build(); + } + + public static Builder createBuilder() { + return GrpcFileContent.newBuilder() + .setContent(ByteString.copyFrom(CONTENT)) + .setIsEndOfFile(IS_END_OF_FILE); + } + + public static GrpcFileContent createEndOfFile() { + return GrpcFileContent.newBuilder().setIsEndOfFile(true).build(); + } + +} diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/GrpcRouteForwardingRequestTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/GrpcRouteForwardingRequestTestFactory.java index d09b7af52..58b939a41 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/GrpcRouteForwardingRequestTestFactory.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/GrpcRouteForwardingRequestTestFactory.java @@ -23,10 +23,14 @@ */ package de.ozgcloud.vorgang.vorgang.redirect; +import de.ozgcloud.eingang.forwarding.GrpcAttachment; import de.ozgcloud.eingang.forwarding.GrpcRouteForwardingRequest; class GrpcRouteForwardingRequestTestFactory { + public static final GrpcAttachment ATTACHMENT_METADATA = GrpcAttachmentTestFactory.createWithFile(); + public static final GrpcAttachment ATTACHMENT_COTNENT = GrpcAttachmentTestFactory.createWithContent(); + public static GrpcRouteForwardingRequest create() { return createBuilder().build(); } @@ -34,4 +38,16 @@ class GrpcRouteForwardingRequestTestFactory { public static GrpcRouteForwardingRequest.Builder createBuilder() { return GrpcRouteForwardingRequest.newBuilder(); } + + public static GrpcRouteForwardingRequest createWithAttachmentMetadata() { + return GrpcRouteForwardingRequest.newBuilder() + .setAttachment(ATTACHMENT_METADATA) + .build(); + } + + public static GrpcRouteForwardingRequest createWithAttachmentContent() { + return GrpcRouteForwardingRequest.newBuilder() + .setAttachment(ATTACHMENT_METADATA) + .build(); + } } -- GitLab