From 6f975cee213f2202c61215e27d1eac4ac8e34d29 Mon Sep 17 00:00:00 2001 From: Jan Zickermann <jan.zickermann@dataport.de> Date: Mon, 17 Feb 2025 17:17:24 +0100 Subject: [PATCH] OZG-4097 send-attachment: Delete attachments on unsuccessful chunk upload --- .../osiv2/transfer/Osi2QuarantineService.java | 49 ++-- .../osiv2/transfer/Osi2ResponseMapper.java | 8 + .../transfer/PostfachApiFacadeService.java | 8 +- .../osiv2/OsiPostfachRemoteServiceITCase.java | 219 +-------------- ...OsiPostfachRemoteServicePrimaryITCase.java | 216 +++++++++++++++ ...iPostfachRemoteServiceSecondaryITCase.java | 31 +++ .../ConcatInputStreamTest.java | 5 +- .../LimitedInputStreamTest.java | 3 +- .../factory/FileChunkInfoTestFactory.java | 1 - .../Osi2FileUploadTestFactory.java | 2 +- .../QuarantineFileResultTestFactory.java | 19 ++ .../transfer/Osi2PostfachServiceTest.java | 2 +- .../transfer/Osi2QuarantineServiceTest.java | 252 +++++++++++++----- .../osiv2/transfer/Osi2RequestMapperTest.java | 5 +- .../transfer/Osi2ResponseMapperTest.java | 31 +++ .../PostfachApiFacadeServiceTest.java | 13 + 16 files changed, 555 insertions(+), 309 deletions(-) create mode 100644 src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServicePrimaryITCase.java create mode 100644 src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceSecondaryITCase.java rename src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/{storage => attachment}/ConcatInputStreamTest.java (92%) rename src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/{storage => attachment}/LimitedInputStreamTest.java (90%) rename src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/{transfer => factory}/Osi2FileUploadTestFactory.java (95%) create mode 100644 src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/QuarantineFileResultTestFactory.java diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2QuarantineService.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2QuarantineService.java index 619c408..2c5253e 100644 --- a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2QuarantineService.java +++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2QuarantineService.java @@ -12,11 +12,11 @@ import java.util.stream.Stream; import de.ozgcloud.apilib.file.OzgCloudFile; import de.ozgcloud.nachrichten.postfach.osiv2.ServiceIfOsi2Enabled; +import de.ozgcloud.nachrichten.postfach.osiv2.attachment.Osi2AttachmentFileService; import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2RuntimeException; import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2UploadException; import de.ozgcloud.nachrichten.postfach.osiv2.model.FileChunkInfo; import de.ozgcloud.nachrichten.postfach.osiv2.model.Osi2FileUpload; -import de.ozgcloud.nachrichten.postfach.osiv2.attachment.Osi2AttachmentFileService; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; @@ -31,37 +31,52 @@ public class Osi2QuarantineService { static final Duration POLLING_TIMEOUT = Duration.ofMinutes(5); public List<Osi2FileUpload> uploadAttachments(List<String> attachmentIds) { - var ozgCloudFiles = binaryFileService.getFileMetadataOfIds(attachmentIds); - var uploadFiles = uploadFilesToQuarantineWithLargestFileFirst(ozgCloudFiles); - waitForVirusScan(uploadFiles); - return uploadFiles; + return uploadFiles(binaryFileService.getFileMetadataOfIds(attachmentIds)); + } + + List<Osi2FileUpload> uploadFiles(List<OzgCloudFile> ozgCloudFiles) { + var sortedUploadFiles = deriveSortedUploadFiles(ozgCloudFiles); + try { + tryUploadSortedFiles(sortedUploadFiles); + return sortedUploadFiles; + } catch (RuntimeException e) { + deleteAttachments(sortedUploadFiles); + throw e; + } + } + + void tryUploadSortedFiles(List<Osi2FileUpload> sortedUploadFiles) { + uploadFilesToQuarantine(sortedUploadFiles); + waitForVirusScan(sortedUploadFiles); } - List<Osi2FileUpload> uploadFilesToQuarantineWithLargestFileFirst(List<OzgCloudFile> ozgCloudFiles) { + List<Osi2FileUpload> deriveSortedUploadFiles(List<OzgCloudFile> ozgCloudFiles) { return ozgCloudFiles.stream() .sorted(Comparator.comparing(OzgCloudFile::getSize).reversed()) - .map(this::uploadFileToQuarantine) + .map(Osi2FileUpload::from) .toList(); } - Osi2FileUpload uploadFileToQuarantine(OzgCloudFile file) { - try (var fileInputStream = binaryFileService.downloadFileContent(file.getId().toString())) { - return uploadInputStreamToQuarantine(file, fileInputStream); + void uploadFilesToQuarantine(List<Osi2FileUpload> uploads) { + uploads.forEach(this::uploadFileToQuarantine); + } + + void uploadFileToQuarantine(Osi2FileUpload upload) { + try (var fileInputStream = binaryFileService.downloadFileContent(upload.file().getId().toString())) { + uploadInputStreamToQuarantine(upload, fileInputStream); } catch (IOException e) { throw new Osi2RuntimeException("Failed to close upload input stream!", e); } } - Osi2FileUpload uploadInputStreamToQuarantine(OzgCloudFile file, InputStream fileInputStream) { - var fileUpload = Osi2FileUpload.from(file); - streamFileChunkInfos(fileUpload) + void uploadInputStreamToQuarantine(Osi2FileUpload upload, InputStream fileInputStream) { + streamFileChunkInfos(upload) .forEachOrdered(chunkInfo -> postfachApiFacadeService.uploadChunk( chunkInfo, chunkInfo.createUploadResource(fileInputStream) ) ); - return fileUpload; } Stream<FileChunkInfo> streamFileChunkInfos(Osi2FileUpload upload) { @@ -95,8 +110,10 @@ public class Osi2QuarantineService { } } - public void deleteAttachments(List<Osi2FileUpload> osi2FileMetadata) { - // TODO delete on exception + void deleteAttachments(List<Osi2FileUpload> uploads) { + uploads.stream() + .map(Osi2FileUpload::guid) + .forEach(postfachApiFacadeService::deleteFileUpload); } } diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapper.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapper.java index f64b1b1..a578105 100644 --- a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapper.java +++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapper.java @@ -21,6 +21,7 @@ import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2RuntimeException; import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2UploadException; import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.MessageExchangeReceiveMessage; import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.MessageExchangeReceiveMessagesResponse; +import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.QuarantineFileResult; import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.QuarantineStatus; import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.V1ReplyBehavior; import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.V1ReplyMessage; @@ -117,6 +118,13 @@ public interface Osi2ResponseMapper { }; } + default void checkChunkUploadSuccess(QuarantineFileResult quarantineFileResult) { + if (!Boolean.TRUE.equals(quarantineFileResult.getSuccess())) { + throw new Osi2RuntimeException( + "Chunk-Upload of file %s failed: %s".formatted(quarantineFileResult.getFileUid(), quarantineFileResult.getError()), null); + } + } + @Builder @Getter class StringBasedIdentifier implements PostfachAddressIdentifier { diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeService.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeService.java index c27b95d..c38e730 100644 --- a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeService.java +++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeService.java @@ -37,12 +37,12 @@ public class PostfachApiFacadeService { } public <T extends AbstractResource> void uploadChunk(FileChunkInfo chunkInfo, T chunkResource) { - quarantineApi.uploadChunk( + responseMapper.checkChunkUploadSuccess(quarantineApi.uploadChunk( requestMapper.mapDomainChunkMetaData(chunkInfo), apiConfiguration.getTenant(), apiConfiguration.getNameIdentifier(), chunkResource - ); + )); } public boolean checkUploadSuccessful(final String messageId) throws Osi2UploadException { @@ -65,4 +65,8 @@ public class PostfachApiFacadeService { public void deleteMessage(final String messageId) { messageExchangeApi.deleteMessage(UUID.fromString(messageId)); } + + public void deleteFileUpload(String fileUploadId) { + quarantineApi.deleteUpload(UUID.fromString(fileUploadId)); + } } diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceITCase.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceITCase.java index 34c70d8..71a645f 100644 --- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceITCase.java +++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceITCase.java @@ -1,17 +1,9 @@ package de.ozgcloud.nachrichten.postfach.osiv2; -import static com.github.tomakehurst.wiremock.client.WireMock.*; import static de.ozgcloud.nachrichten.NachrichtenManagerConfiguration.*; import static de.ozgcloud.nachrichten.postfach.osiv2.factory.JwtFactory.*; -import static org.assertj.core.api.Assertions.*; - -import java.time.OffsetDateTime; -import java.util.List; -import java.util.UUID; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -21,22 +13,10 @@ import org.springframework.test.context.DynamicPropertySource; import org.springframework.test.context.TestPropertySource; import com.github.tomakehurst.wiremock.WireMockServer; -import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; -import de.ozgcloud.nachrichten.postfach.PostfachMessageCode; -import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2PostfachException; -import de.ozgcloud.nachrichten.postfach.osiv2.extension.AttachmentExampleUploadUtil; -import de.ozgcloud.nachrichten.postfach.osiv2.extension.Jwt; +import de.ozgcloud.nachrichten.postfach.osiv2.attachment.Osi2AttachmentFileService; import de.ozgcloud.nachrichten.postfach.osiv2.extension.OsiMockServerExtension; import de.ozgcloud.nachrichten.postfach.osiv2.extension.VorgangManagerServerExtension; -import de.ozgcloud.nachrichten.postfach.osiv2.factory.JsonUtil; -import de.ozgcloud.nachrichten.postfach.osiv2.factory.MessageExchangeReceiveMessagesResponseTestFactory; -import de.ozgcloud.nachrichten.postfach.osiv2.factory.MessageExchangeSendMessageResponseTestFactory; -import de.ozgcloud.nachrichten.postfach.osiv2.factory.PostfachNachrichtTestFactory; -import de.ozgcloud.nachrichten.postfach.osiv2.factory.V1ReplyMessageTestFactory; -import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.QuarantineFileResult; -import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.QuarantineStatus; -import de.ozgcloud.nachrichten.postfach.osiv2.attachment.Osi2AttachmentFileService; import lombok.SneakyThrows; @SpringBootTest(classes = TestApplication.class) @@ -53,9 +33,9 @@ class OsiPostfachRemoteServiceITCase { static final VorgangManagerServerExtension VORGANG_MANAGER_SERVER_EXTENSION = new VorgangManagerServerExtension(); @Autowired - private OsiPostfachRemoteService osiPostfachRemoteService; + protected OsiPostfachRemoteService osiPostfachRemoteService; @Autowired - private Osi2AttachmentFileService osi2AttachmentFileService; + protected Osi2AttachmentFileService osi2AttachmentFileService; @DynamicPropertySource static void dynamicProperties(DynamicPropertyRegistry registry) { @@ -68,202 +48,11 @@ class OsiPostfachRemoteServiceITCase { registry.add("grpc.client." + GRPC_FILE_MANAGER_NAME + ".negotiationType", () -> "PLAINTEXT"); } - private WireMockServer postfachFacadeMockServer; + protected WireMockServer postfachFacadeMockServer; @BeforeEach @SneakyThrows public void setup() { postfachFacadeMockServer = OSI_MOCK_SERVER_EXTENSION.getPostfachFacadeMockServer(); } - - @DisplayName("should send request with jwt") - @Test - @SneakyThrows - void shouldSendRequestWithJwt() { - var postfachNachricht = PostfachNachrichtTestFactory.create(); - mockSendResponse(); - - osiPostfachRemoteService.sendMessage(postfachNachricht); - - var requests = postfachFacadeMockServer.findAll( - postRequestedFor(urlPathTemplate("/MessageExchange/v1/Send/{mailboxId}"))); - assertThat(requests).hasSize(1); - var jwt = Jwt.parseAuthHeaderValue(requests.getFirst().getHeader("Authorization")); - assertThat(jwt.body().read("$.client_id", String.class)).isEqualTo(CLIENT_ID); - assertThat(jwt.body().read("$.aud", String.class)).isEqualTo(RESOURCE_URN); - } - - @DisplayName("should throw postfach exception with connection error code") - @Test - void shouldThrowPostfachExceptionWithConnectionErrorCode() { - var postfachNachricht = PostfachNachrichtTestFactory.create(); - postfachFacadeMockServer.stop(); - - assertThatThrownBy(() -> osiPostfachRemoteService.sendMessage(postfachNachricht)) - .isInstanceOf(Osi2PostfachException.class) - .hasFieldOrPropertyWithValue("messageCode", PostfachMessageCode.SERVER_CONNECTION_FAILED_MESSAGE_CODE); - } - - @DisplayName("should send message with attachment") - @Test - void shouldSendMessageWithAttachment() { - var textFileId = AttachmentExampleUploadUtil.uploadTextFile(osi2AttachmentFileService); - postfachFacadeMockServer.stubFor(post(urlPathTemplate("/Quarantine/v1/Upload/Chunked")) - .willReturn(okJsonObj(QuarantineFileResult.builder() - .success(true) - .build())) - ); - postfachFacadeMockServer.stubFor(get(urlPathTemplate("/Quarantine/v1/Upload/{guid}")) - .willReturn(okJsonObj(QuarantineStatus.SAFE)) - ); - var postfachNachrichtWithAttachment = PostfachNachrichtTestFactory.createBuilder() - .attachments(List.of(textFileId)) - .build(); - mockSendResponse(); - - osiPostfachRemoteService.sendMessage(postfachNachrichtWithAttachment); - - postfachFacadeMockServer.verify( - exactly(1), - postRequestedFor(urlPathTemplate("/Quarantine/v1/Upload/Chunked")) - ); - postfachFacadeMockServer.verify( - exactly(1), - postRequestedFor(urlPathTemplate("/Quarantine/v1/Upload/{guid}")) - ); - postfachFacadeMockServer.verify( - exactly(1), - postRequestedFor(urlPathTemplate("/MessageExchange/v1/Send/{mailboxId}")) - ); - } - - @DisplayName("should delete attachments on upload exception") - @Test - void shouldDeleteAttachmentsOnUploadException() { - var textFileId = AttachmentExampleUploadUtil.uploadTextFile(osi2AttachmentFileService); - postfachFacadeMockServer.stubFor(post(urlPathTemplate("/Quarantine/v1/Upload/Chunked")) - .willReturn(okJsonObj(QuarantineFileResult.builder() - .success(false) - .error("Upload failure") - .build())) - ); - postfachFacadeMockServer.stubFor(delete(urlPathTemplate("/Quarantine/v1/Upload/{guid}")) - .willReturn(ok()) - ); - var postfachNachrichtWithAttachment = PostfachNachrichtTestFactory.createBuilder() - .attachments(List.of(textFileId)) - .build(); - - osiPostfachRemoteService.sendMessage(postfachNachrichtWithAttachment); - - postfachFacadeMockServer.verify( - exactly(1), - deleteRequestedFor(urlPathTemplate("/Quarantine/v1/Upload/{guid}")) - ); - } - - @DisplayName("should delete attachments on scan exception") - @Test - void shouldDeleteAttachmentsOnScanException() { - var textFileId = AttachmentExampleUploadUtil.uploadTextFile(osi2AttachmentFileService); - postfachFacadeMockServer.stubFor(post(urlPathTemplate("/Quarantine/v1/Upload/Chunked")) - .willReturn(okJsonObj(QuarantineFileResult.builder() - .success(true) - .build())) - ); - postfachFacadeMockServer.stubFor(get(urlPathTemplate("/Quarantine/v1/Upload/{guid}")) - .willReturn(okJsonObj(QuarantineStatus.UNSAFE)) - ); - postfachFacadeMockServer.stubFor(delete(urlPathTemplate("/Quarantine/v1/Upload/{guid}")) - .willReturn(ok()) - ); - var postfachNachrichtWithAttachment = PostfachNachrichtTestFactory.createBuilder() - .attachments(List.of(textFileId)) - .build(); - - osiPostfachRemoteService.sendMessage(postfachNachrichtWithAttachment); - - postfachFacadeMockServer.verify( - exactly(1), - deleteRequestedFor(urlPathTemplate("/Quarantine/v1/Upload/{guid}")) - ); - } - - private void mockSendResponse() { - // Stub message send response (MessageExchangeApi::sendMessage) - postfachFacadeMockServer.stubFor(post(urlPathTemplate("/MessageExchange/v1/Send/{mailboxId}")) - .willReturn( - okJsonObj( - MessageExchangeSendMessageResponseTestFactory.create() - .messageId(UUID.randomUUID()) - ) - ) - ); - } - - @DisplayName("should receive one messages") - @Test - @SneakyThrows - void shouldReceiveMessages() { - mockPostfachMessageAndResponse("00000000-0000-0000-0000-000000000000"); - - var messageList = osiPostfachRemoteService.getAllMessages().toList(); - - assertThat(messageList).hasSize(1); - } - - @DisplayName("should receive two messages") - @Test - @SneakyThrows - void shouldReceiveTwoMessages() { - mockPostfachMessageAndResponse("00000000-0000-0000-0000-000000000000", "00000000-0000-0000-0000-000000000001"); - - var messageList = osiPostfachRemoteService.getAllMessages().toList(); - - assertThat(messageList).hasSize(2); - } - - private void mockPostfachMessageAndResponse(final String... uuids) { - // Stub message listing response (MessageExchangeApi::receiveMessages) - postfachFacadeMockServer.stubFor(get(urlPathEqualTo("/MessageExchange/v1/Receive")) - .withQueryParam("take", equalTo("100")) - .withQueryParam("skip", equalTo("0")) - .willReturn(okJsonObj(MessageExchangeReceiveMessagesResponseTestFactory.create(uuids))) - ); - for (String uuid : uuids) { - // Stub individual response for message (MessageExchangeApi::getMessage) - postfachFacadeMockServer.stubFor(get(urlPathTemplate("/MessageExchange/v1/Receive/{messageId}")) - .withPathParam("messageId", equalTo(uuid)) - .willReturn(okJsonObj( - V1ReplyMessageTestFactory.create() - .messageBox(UUID.fromString(uuid)) - .responseTime(OffsetDateTime.now()) - )) - ); - } - } - - private ResponseDefinitionBuilder okJsonObj(final Object body) { - return okJson(JsonUtil.toJson(body)); - } - - @DisplayName("should delete message") - @Test - @SneakyThrows - void shouldDeleteMessage() { - var messageId = "00000000-0000-0000-0000-000000000000"; - // Stub delete message response (MessageExchangeApi::deleteMessage) - postfachFacadeMockServer.stubFor(delete(urlPathTemplate("/MessageExchange/v1/Delete/{messageId}")) - .willReturn(ok()) - ); - - osiPostfachRemoteService.deleteMessage(messageId); - - postfachFacadeMockServer.verify( - exactly(1), - deleteRequestedFor(urlPathTemplate("/MessageExchange/v1/Delete/{messageId}")) - .withPathParam("messageId", equalTo(messageId)) - ); - } - } diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServicePrimaryITCase.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServicePrimaryITCase.java new file mode 100644 index 0000000..0b9b9b5 --- /dev/null +++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServicePrimaryITCase.java @@ -0,0 +1,216 @@ +package de.ozgcloud.nachrichten.postfach.osiv2; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static de.ozgcloud.nachrichten.postfach.osiv2.factory.JwtFactory.*; +import static org.assertj.core.api.Assertions.*; + +import java.time.OffsetDateTime; +import java.util.List; +import java.util.UUID; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; + +import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2PostfachException; +import de.ozgcloud.nachrichten.postfach.osiv2.extension.AttachmentExampleUploadUtil; +import de.ozgcloud.nachrichten.postfach.osiv2.extension.Jwt; +import de.ozgcloud.nachrichten.postfach.osiv2.factory.JsonUtil; +import de.ozgcloud.nachrichten.postfach.osiv2.factory.MessageExchangeReceiveMessagesResponseTestFactory; +import de.ozgcloud.nachrichten.postfach.osiv2.factory.MessageExchangeSendMessageResponseTestFactory; +import de.ozgcloud.nachrichten.postfach.osiv2.factory.PostfachNachrichtTestFactory; +import de.ozgcloud.nachrichten.postfach.osiv2.factory.V1ReplyMessageTestFactory; +import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.QuarantineFileResult; +import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.QuarantineStatus; +import lombok.SneakyThrows; + +class OsiPostfachRemoteServicePrimaryITCase extends OsiPostfachRemoteServiceITCase { + + @DisplayName("should send request with jwt") + @Test + @SneakyThrows + void shouldSendRequestWithJwt() { + var postfachNachricht = PostfachNachrichtTestFactory.create(); + mockSendResponse(); + + osiPostfachRemoteService.sendMessage(postfachNachricht); + + var requests = postfachFacadeMockServer.findAll( + postRequestedFor(urlPathTemplate("/MessageExchange/v1/Send/{mailboxId}"))); + assertThat(requests).hasSize(1); + var jwt = Jwt.parseAuthHeaderValue(requests.getFirst().getHeader("Authorization")); + assertThat(jwt.body().read("$.client_id", String.class)).isEqualTo(CLIENT_ID); + assertThat(jwt.body().read("$.aud", String.class)).isEqualTo(RESOURCE_URN); + } + + @DisplayName("should send message with attachment") + @Test + void shouldSendMessageWithAttachment() { + var textFileId = AttachmentExampleUploadUtil.uploadTextFile(osi2AttachmentFileService); + postfachFacadeMockServer.stubFor(post(urlPathTemplate("/Quarantine/v1/Upload/Chunked")) + .willReturn(okJsonObj(QuarantineFileResult.builder() + .success(true) + .build())) + ); + postfachFacadeMockServer.stubFor(get(urlPathTemplate("/Quarantine/v1/Upload/{guid}")) + .willReturn(okJsonObj(QuarantineStatus.SAFE)) + ); + var postfachNachrichtWithAttachment = PostfachNachrichtTestFactory.createBuilder() + .attachments(List.of(textFileId)) + .build(); + mockSendResponse(); + + osiPostfachRemoteService.sendMessage(postfachNachrichtWithAttachment); + + postfachFacadeMockServer.verify( + exactly(1), + postRequestedFor(urlPathTemplate("/Quarantine/v1/Upload/Chunked")) + ); + postfachFacadeMockServer.verify( + exactly(1), + postRequestedFor(urlPathTemplate("/Quarantine/v1/Upload/{guid}")) + ); + postfachFacadeMockServer.verify( + exactly(1), + postRequestedFor(urlPathTemplate("/MessageExchange/v1/Send/{mailboxId}")) + ); + } + + @DisplayName("should delete attachments on upload exception") + @Test + void shouldDeleteAttachmentsOnUploadException() { + var textFileId = AttachmentExampleUploadUtil.uploadTextFile(osi2AttachmentFileService); + postfachFacadeMockServer.stubFor(post(urlPathTemplate("/Quarantine/v1/Upload/Chunked")) + .willReturn(okJsonObj(QuarantineFileResult.builder() + .success(false) + .error("Upload failure") + .build())) + ); + postfachFacadeMockServer.stubFor(delete(urlPathTemplate("/Quarantine/v1/Upload/{guid}")) + .willReturn(ok()) + ); + var postfachNachrichtWithAttachment = PostfachNachrichtTestFactory.createBuilder() + .attachments(List.of(textFileId)) + .build(); + + try { + osiPostfachRemoteService.sendMessage(postfachNachrichtWithAttachment); + } catch (Osi2PostfachException e) { + // ignore + } + + postfachFacadeMockServer.verify( + exactly(1), + deleteRequestedFor(urlPathTemplate("/Quarantine/v1/Upload/{guid}")) + ); + } + + @DisplayName("should delete attachments on scan exception") + @Test + void shouldDeleteAttachmentsOnScanException() { + var textFileId = AttachmentExampleUploadUtil.uploadTextFile(osi2AttachmentFileService); + postfachFacadeMockServer.stubFor(post(urlPathTemplate("/Quarantine/v1/Upload/Chunked")) + .willReturn(okJsonObj(QuarantineFileResult.builder() + .success(true) + .build())) + ); + postfachFacadeMockServer.stubFor(get(urlPathTemplate("/Quarantine/v1/Upload/{guid}")) + .willReturn(okJsonObj(QuarantineStatus.UNSAFE)) + ); + postfachFacadeMockServer.stubFor(delete(urlPathTemplate("/Quarantine/v1/Upload/{guid}")) + .willReturn(ok()) + ); + var postfachNachrichtWithAttachment = PostfachNachrichtTestFactory.createBuilder() + .attachments(List.of(textFileId)) + .build(); + + try { + osiPostfachRemoteService.sendMessage(postfachNachrichtWithAttachment); + } catch (Osi2PostfachException e) { + // ignore + } + + postfachFacadeMockServer.verify( + exactly(1), + deleteRequestedFor(urlPathTemplate("/Quarantine/v1/Upload/{guid}")) + ); + } + + private void mockSendResponse() { + // Stub message send response (MessageExchangeApi::sendMessage) + postfachFacadeMockServer.stubFor(post(urlPathTemplate("/MessageExchange/v1/Send/{mailboxId}")) + .willReturn( + okJsonObj( + MessageExchangeSendMessageResponseTestFactory.create() + .messageId(UUID.randomUUID()) + ) + ) + ); + } + + @DisplayName("should receive one messages") + @Test + @SneakyThrows + void shouldReceiveMessages() { + mockPostfachMessageAndResponse("00000000-0000-0000-0000-000000000000"); + + var messageList = osiPostfachRemoteService.getAllMessages().toList(); + + assertThat(messageList).hasSize(1); + } + + @DisplayName("should receive two messages") + @Test + @SneakyThrows + void shouldReceiveTwoMessages() { + mockPostfachMessageAndResponse("00000000-0000-0000-0000-000000000000", "00000000-0000-0000-0000-000000000001"); + + var messageList = osiPostfachRemoteService.getAllMessages().toList(); + + assertThat(messageList).hasSize(2); + } + + private void mockPostfachMessageAndResponse(final String... uuids) { + // Stub message listing response (MessageExchangeApi::receiveMessages) + postfachFacadeMockServer.stubFor(get(urlPathEqualTo("/MessageExchange/v1/Receive")) + .withQueryParam("take", equalTo("100")) + .withQueryParam("skip", equalTo("0")) + .willReturn(okJsonObj(MessageExchangeReceiveMessagesResponseTestFactory.create(uuids))) + ); + for (String uuid : uuids) { + // Stub individual response for message (MessageExchangeApi::getMessage) + postfachFacadeMockServer.stubFor(get(urlPathTemplate("/MessageExchange/v1/Receive/{messageId}")) + .withPathParam("messageId", equalTo(uuid)) + .willReturn(okJsonObj( + V1ReplyMessageTestFactory.create() + .messageBox(UUID.fromString(uuid)) + .responseTime(OffsetDateTime.now()) + )) + ); + } + } + + private ResponseDefinitionBuilder okJsonObj(final Object body) { + return okJson(JsonUtil.toJson(body)); + } + + @DisplayName("should delete message") + @Test + @SneakyThrows + void shouldDeleteMessage() { + var messageId = "00000000-0000-0000-0000-000000000000"; + // Stub delete message response (MessageExchangeApi::deleteMessage) + postfachFacadeMockServer.stubFor(delete(urlPathTemplate("/MessageExchange/v1/Delete/{messageId}")) + .willReturn(ok()) + ); + + osiPostfachRemoteService.deleteMessage(messageId); + + postfachFacadeMockServer.verify( + exactly(1), + deleteRequestedFor(urlPathTemplate("/MessageExchange/v1/Delete/{messageId}")) + .withPathParam("messageId", equalTo(messageId)) + ); + } +} diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceSecondaryITCase.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceSecondaryITCase.java new file mode 100644 index 0000000..33f22c4 --- /dev/null +++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceSecondaryITCase.java @@ -0,0 +1,31 @@ +package de.ozgcloud.nachrichten.postfach.osiv2; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import de.ozgcloud.nachrichten.postfach.PostfachMessageCode; +import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2PostfachException; +import de.ozgcloud.nachrichten.postfach.osiv2.factory.PostfachNachrichtTestFactory; + +class OsiPostfachRemoteServiceSecondaryITCase extends OsiPostfachRemoteServiceITCase { + + @BeforeEach + void mock() { + // Stopping the mock server + // Note that a restarting the server will configure a new unknown port. + postfachFacadeMockServer.stop(); + } + + @DisplayName("should throw postfach exception with connection error code") + @Test + void shouldThrowPostfachExceptionWithConnectionErrorCode() { + var postfachNachricht = PostfachNachrichtTestFactory.create(); + + assertThatThrownBy(() -> osiPostfachRemoteService.sendMessage(postfachNachricht)) + .isInstanceOf(Osi2PostfachException.class) + .hasFieldOrPropertyWithValue("messageCode", PostfachMessageCode.SERVER_CONNECTION_FAILED_MESSAGE_CODE); + } +} diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/storage/ConcatInputStreamTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/attachment/ConcatInputStreamTest.java similarity index 92% rename from src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/storage/ConcatInputStreamTest.java rename to src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/attachment/ConcatInputStreamTest.java index 43509c4..9340fe2 100644 --- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/storage/ConcatInputStreamTest.java +++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/attachment/ConcatInputStreamTest.java @@ -1,4 +1,4 @@ -package de.ozgcloud.nachrichten.postfach.osiv2.storage; +package de.ozgcloud.nachrichten.postfach.osiv2.attachment; import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; @@ -10,7 +10,6 @@ import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import de.ozgcloud.nachrichten.postfach.osiv2.attachment.ConcatInputStream; import lombok.SneakyThrows; class ConcatInputStreamTest { @@ -70,7 +69,7 @@ class ConcatInputStreamTest { } private ConcatInputStream createExampleConcatInputStream() { - var streams = Stream.of("1234","567","", "89") + var streams = Stream.of("1234", "567", "", "89") .map(s -> new ByteArrayInputStream(s.getBytes())) .toList(); return new ConcatInputStream(streams.iterator()); diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/storage/LimitedInputStreamTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/attachment/LimitedInputStreamTest.java similarity index 90% rename from src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/storage/LimitedInputStreamTest.java rename to src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/attachment/LimitedInputStreamTest.java index b9eae5d..880c800 100644 --- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/storage/LimitedInputStreamTest.java +++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/attachment/LimitedInputStreamTest.java @@ -1,4 +1,4 @@ -package de.ozgcloud.nachrichten.postfach.osiv2.storage; +package de.ozgcloud.nachrichten.postfach.osiv2.attachment; import static org.junit.jupiter.api.Assertions.*; @@ -8,7 +8,6 @@ import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import de.ozgcloud.nachrichten.postfach.osiv2.attachment.LimitedInputStream; import lombok.SneakyThrows; class LimitedInputStreamTest { diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/FileChunkInfoTestFactory.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/FileChunkInfoTestFactory.java index 56d8f10..f720d9b 100644 --- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/FileChunkInfoTestFactory.java +++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/FileChunkInfoTestFactory.java @@ -1,7 +1,6 @@ package de.ozgcloud.nachrichten.postfach.osiv2.factory; import de.ozgcloud.nachrichten.postfach.osiv2.model.FileChunkInfo; -import de.ozgcloud.nachrichten.postfach.osiv2.transfer.Osi2FileUploadTestFactory; public class FileChunkInfoTestFactory { diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2FileUploadTestFactory.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/Osi2FileUploadTestFactory.java similarity index 95% rename from src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2FileUploadTestFactory.java rename to src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/Osi2FileUploadTestFactory.java index b9cf5d2..1951488 100644 --- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2FileUploadTestFactory.java +++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/Osi2FileUploadTestFactory.java @@ -1,4 +1,4 @@ -package de.ozgcloud.nachrichten.postfach.osiv2.transfer; +package de.ozgcloud.nachrichten.postfach.osiv2.factory; import static de.ozgcloud.nachrichten.postfach.osiv2.model.Osi2FileUpload.*; diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/QuarantineFileResultTestFactory.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/QuarantineFileResultTestFactory.java new file mode 100644 index 0000000..c50c823 --- /dev/null +++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/QuarantineFileResultTestFactory.java @@ -0,0 +1,19 @@ +package de.ozgcloud.nachrichten.postfach.osiv2.factory; + +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.QuarantineFileResult; + +public class QuarantineFileResultTestFactory { + + public static final String ERROR_STRING = LoremIpsum.getInstance().getWords(10); + + public static QuarantineFileResult create() { + return createBuilder().build(); + } + + public static QuarantineFileResult.Builder createBuilder() { + return QuarantineFileResult.builder() + .success(true); + } +} diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2PostfachServiceTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2PostfachServiceTest.java index a078451..1a70dd9 100644 --- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2PostfachServiceTest.java +++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2PostfachServiceTest.java @@ -15,6 +15,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import de.ozgcloud.nachrichten.postfach.PostfachNachricht; +import de.ozgcloud.nachrichten.postfach.osiv2.factory.Osi2FileUploadTestFactory; import de.ozgcloud.nachrichten.postfach.osiv2.factory.PostfachNachrichtTestFactory; import de.ozgcloud.nachrichten.postfach.osiv2.model.Osi2FileUpload; @@ -88,7 +89,6 @@ class Osi2PostfachServiceTest { private Stream<PostfachNachricht> receiveMessages() { return osi2PostfachService.receiveMessages(); } - } } \ No newline at end of file diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2QuarantineServiceTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2QuarantineServiceTest.java index 37039f8..0b90398 100644 --- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2QuarantineServiceTest.java +++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2QuarantineServiceTest.java @@ -1,7 +1,7 @@ package de.ozgcloud.nachrichten.postfach.osiv2.transfer; +import static de.ozgcloud.nachrichten.postfach.osiv2.factory.Osi2FileUploadTestFactory.*; import static de.ozgcloud.nachrichten.postfach.osiv2.model.Osi2FileUpload.*; -import static de.ozgcloud.nachrichten.postfach.osiv2.transfer.Osi2FileUploadTestFactory.*; import static de.ozgcloud.nachrichten.postfach.osiv2.transfer.Osi2QuarantineService.*; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; @@ -25,12 +25,13 @@ import org.mockito.Spy; import de.ozgcloud.apilib.file.OzgCloudFile; import de.ozgcloud.apilib.file.OzgCloudFileId; import de.ozgcloud.apilib.file.OzgCloudFileTestFactory; +import de.ozgcloud.nachrichten.postfach.osiv2.attachment.Osi2AttachmentFileService; import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2RuntimeException; import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2UploadException; +import de.ozgcloud.nachrichten.postfach.osiv2.factory.Osi2FileUploadTestFactory; import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.QuarantineStatus; import de.ozgcloud.nachrichten.postfach.osiv2.model.FileChunkInfo; import de.ozgcloud.nachrichten.postfach.osiv2.model.Osi2FileUpload; -import de.ozgcloud.nachrichten.postfach.osiv2.attachment.Osi2AttachmentFileService; import lombok.SneakyThrows; class Osi2QuarantineServiceTest { @@ -45,18 +46,35 @@ class Osi2QuarantineServiceTest { @Mock private Osi2AttachmentFileService binaryFileService; + private final OzgCloudFile file1 = OzgCloudFileTestFactory.createBuilder() + .id(new OzgCloudFileId(UPLOAD_FILE_ID)) + .size(1000L) + .build(); + private final OzgCloudFile file2 = OzgCloudFileTestFactory.createBuilder() + .size(2000L) + .build(); + + private final Osi2FileUpload upload1 = Osi2FileUploadTestFactory.createBuilder() + .file(file1) + .build(); + private final Osi2FileUpload upload2 = Osi2FileUploadTestFactory.createBuilder() + .guid(UUID.randomUUID().toString()) + .file(file2) + .build(); + private final Osi2FileUpload upload3 = Osi2FileUploadTestFactory.createBuilder() + .guid(UUID.randomUUID().toString()) + .build(); + + private final List<Osi2FileUpload> uploads = List.of(upload1, upload2, upload3); + @DisplayName("upload attachments") @Nested class TestUploadAttachments { - private final OzgCloudFile file = OzgCloudFileTestFactory.create(); - private final Osi2FileUpload uploadFile = Osi2FileUploadTestFactory.create(); - @BeforeEach void mock() { - when(binaryFileService.getFileMetadataOfIds(any())).thenReturn(List.of(file)); - doReturn(List.of(uploadFile)).when(service).uploadFilesToQuarantineWithLargestFileFirst(any()); - doNothing().when(service).waitForVirusScan(any()); + when(binaryFileService.getFileMetadataOfIds(any())).thenReturn(List.of(file1)); + doReturn(List.of(upload1)).when(service).uploadFiles(any()); } @DisplayName("should call getFileMetadataOfIds") @@ -67,20 +85,12 @@ class Osi2QuarantineServiceTest { verify(binaryFileService).getFileMetadataOfIds(List.of(UPLOAD_FILE_ID)); } - @DisplayName("should call uploadFilesToQuarantineWithLargestFileFirst") + @DisplayName("should call uploadFiles") @Test void shouldCallUploadFilesToQuarantineWithLargestFileFirst() { uploadAttachments(); - verify(service).uploadFilesToQuarantineWithLargestFileFirst(List.of(file)); - } - - @DisplayName("should call waitForVirusScan") - @Test - void shouldCallWaitForVirusScan() { - uploadAttachments(); - - verify(service).waitForVirusScan(List.of(uploadFile)); + verify(service).uploadFiles(List.of(file1)); } @DisplayName("should return") @@ -88,7 +98,7 @@ class Osi2QuarantineServiceTest { void shouldReturn() { var result = uploadAttachments(); - assertThat(result).containsExactly(uploadFile); + assertThat(result).containsExactly(upload1); } List<Osi2FileUpload> uploadAttachments() { @@ -96,36 +106,155 @@ class Osi2QuarantineServiceTest { } } - @DisplayName("upload files to quarantine with largest file first") + @DisplayName("upload files") @Nested - class TestUploadFilesToQuarantineWithLargestFileFirst { + class TestUploadFiles { - private final OzgCloudFile file1 = OzgCloudFileTestFactory.createBuilder() - .id(new OzgCloudFileId(UPLOAD_FILE_ID)) - .size(1000L) - .build(); - private final OzgCloudFile file2 = OzgCloudFileTestFactory.createBuilder() - .size(2000L) - .build(); - private final Osi2FileUpload uploadFile1 = Osi2FileUploadTestFactory.createBuilder().file(file1).build(); - private final Osi2FileUpload uploadFile2 = Osi2FileUploadTestFactory.createBuilder().file(file2).build(); + @BeforeEach + void mock() { + doReturn(uploads).when(service).deriveSortedUploadFiles(any()); + } + + @DisplayName("without exception") + @Nested + class TestWithoutException { + @BeforeEach + void mock() { + doNothing().when(service).tryUploadSortedFiles(any()); + } + + @DisplayName("should call deriveSortedUploadFiles") + @Test + void shouldCallDeriveSortedUploadFiles() { + uploadFiles(); + + verify(service).deriveSortedUploadFiles(List.of(file1)); + } + + @DisplayName("should call uploadFiles") + @Test + void shouldCallUploadFilesToQuarantineWithLargestFileFirst() { + uploadFiles(); + + verify(service).tryUploadSortedFiles(uploads); + } + + @DisplayName("should return") + @Test + void shouldReturn() { + var result = uploadFiles(); + + assertThat(result).containsExactly(upload1, upload2, upload3); + } + } + + @DisplayName("with exception") + @Nested + class TestWithException { + + private final RuntimeException exception = new RuntimeException("test"); + + @BeforeEach + void mock() { + doThrow(exception).when(service).tryUploadSortedFiles(any()); + } + + @DisplayName("should delete attachments") + @Test + void shouldDeleteAttachments() { + try { + uploadFiles(); + } catch (RuntimeException e) { + // ignore + } + + verify(service).deleteAttachments(uploads); + } + + @DisplayName("should rethrow exception") + @Test + void shouldRethrowException() { + assertThatThrownBy(TestUploadFiles.this::uploadFiles) + .isEqualTo(exception); + } + } + + List<Osi2FileUpload> uploadFiles() { + return service.uploadFiles(List.of(file1)); + } + } + + @DisplayName("derive sorted upload files") + @Nested + class TestDeriveSortedUploadFiles { + + @DisplayName("should return largest file first") + @Test + void shouldReturnLargestFileFirst() { + var result = service.deriveSortedUploadFiles(List.of(file1, file2)); + + assertThat(result) + .extracting(Osi2FileUpload::file) + .containsExactly(file2, file1); + } + + @DisplayName("should have random upload guid") + @Test + void shouldHaveRandomUploadGuid() { + var result = service.deriveSortedUploadFiles(List.of(file1, file2)); + + assertThat(result) + .extracting(Osi2FileUpload::guid) + .extracting(String::length) + .containsExactly(36, 36); + } + } + + @DisplayName("try upload sorted files") + @Nested + class TestTryUploadSortedFiles { @BeforeEach void mock() { - doReturn(uploadFile1).when(service).uploadFileToQuarantine(file1); - doReturn(uploadFile2).when(service).uploadFileToQuarantine(file2); + doNothing().when(service).uploadFilesToQuarantine(any()); + doNothing().when(service).waitForVirusScan(any()); } - @DisplayName("should return") + @DisplayName("should call uploadFilesToQuarantine") @Test - void shouldReturn() { - var result = uploadFilesToQuarantineWithLargestFileFirst(); + void shouldCallUploadFilesToQuarantine() { + tryUploadSortedFiles(); - assertThat(result).containsExactly(uploadFile2, uploadFile1); + verify(service).uploadFilesToQuarantine(uploads); } - List<Osi2FileUpload> uploadFilesToQuarantineWithLargestFileFirst() { - return service.uploadFilesToQuarantineWithLargestFileFirst(List.of(file1, file2)); + @DisplayName("should call waitForVirusScan") + @Test + void shouldCallWaitForVirusScan() { + tryUploadSortedFiles(); + + verify(service).waitForVirusScan(uploads); + } + + void tryUploadSortedFiles() { + service.tryUploadSortedFiles(uploads); + } + } + + @DisplayName("upload files to quarantine") + @Nested + class TestUploadFilesToQuarantine { + + @DisplayName("should call uploadFileToQuarantine for each file") + @Test + void shouldCallUploadFileToQuarantineForEachFile() { + doNothing().when(service).uploadFileToQuarantine(any()); + + service.uploadFilesToQuarantine(uploads); + + verify(service).uploadFileToQuarantine(upload1); + verify(service).uploadFileToQuarantine(upload2); + verify(service).uploadFileToQuarantine(upload3); } } @@ -136,13 +265,10 @@ class Osi2QuarantineServiceTest { @Mock private InputStream fileInputStream; - private final OzgCloudFile file = OzgCloudFileTestFactory.create(); - private final Osi2FileUpload uploadFile = Osi2FileUploadTestFactory.create(); - @BeforeEach void mock() { when(binaryFileService.downloadFileContent(any())).thenReturn(fileInputStream); - doReturn(uploadFile).when(service).uploadInputStreamToQuarantine(any(), any()); + doNothing().when(service).uploadInputStreamToQuarantine(any(), any()); } @DisplayName("should call streamFileContent") @@ -150,7 +276,7 @@ class Osi2QuarantineServiceTest { void shouldCallStreamFileContent() { uploadFileToQuarantine(); - verify(binaryFileService).downloadFileContent(OzgCloudFileTestFactory.ID_STR); + verify(binaryFileService).downloadFileContent(UPLOAD_FILE_ID); } @DisplayName("should call uploadInputStreamToQuarantine") @@ -158,15 +284,7 @@ class Osi2QuarantineServiceTest { void shouldCallUploadInputStreamToQuarantine() { uploadFileToQuarantine(); - verify(service).uploadInputStreamToQuarantine(file, fileInputStream); - } - - @DisplayName("should return") - @Test - void shouldReturn() { - var result = uploadFileToQuarantine(); - - assertThat(result).isEqualTo(uploadFile); + verify(service).uploadInputStreamToQuarantine(upload1, fileInputStream); } @DisplayName("should close inputStream") @@ -188,8 +306,8 @@ class Osi2QuarantineServiceTest { .isInstanceOf(Osi2RuntimeException.class); } - Osi2FileUpload uploadFileToQuarantine() { - return service.uploadFileToQuarantine(file); + void uploadFileToQuarantine() { + service.uploadFileToQuarantine(upload1); } } @@ -225,16 +343,6 @@ class Osi2QuarantineServiceTest { @Nested class TestCheckVirusScanCompleted { - private final Osi2FileUpload upload1 = Osi2FileUploadTestFactory.create(); - private final Osi2FileUpload upload2 = Osi2FileUploadTestFactory.createBuilder() - .guid(UUID.randomUUID().toString()) - .build(); - private final Osi2FileUpload upload3 = Osi2FileUploadTestFactory.createBuilder() - .guid(UUID.randomUUID().toString()) - .build(); - - private final List<Osi2FileUpload> uploads = List.of(upload1, upload2, upload3); - @DisplayName("should return true if all virus scans completed") @Test void shouldReturnTrueIfAllVirusScansCompleted() { @@ -302,8 +410,6 @@ class Osi2QuarantineServiceTest { @DisplayName("wait for virus scan") @Nested class TestWaitForVirusScan { - private final Osi2FileUpload upload1 = Osi2FileUploadTestFactory.create(); - private final List<Osi2FileUpload> uploads = List.of(upload1); @DisplayName("should call waitUntil") @Test @@ -339,4 +445,18 @@ class Osi2QuarantineServiceTest { } } + @DisplayName("delete attachments") + @Nested + class TestDeleteAttachments { + @DisplayName("should call deleteAttachment for uploads") + @Test + void shouldCallDeleteAttachmentForUploads() { + service.deleteAttachments(uploads); + + verify(postfachApiFacadeService).deleteFileUpload(upload1.guid()); + verify(postfachApiFacadeService).deleteFileUpload(upload2.guid()); + verify(postfachApiFacadeService).deleteFileUpload(upload3.guid()); + } + } + } \ No newline at end of file diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2RequestMapperTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2RequestMapperTest.java index eef22fd..b43843d 100644 --- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2RequestMapperTest.java +++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2RequestMapperTest.java @@ -1,8 +1,8 @@ package de.ozgcloud.nachrichten.postfach.osiv2.transfer; +import static de.ozgcloud.nachrichten.postfach.osiv2.factory.Osi2FileUploadTestFactory.*; import static de.ozgcloud.nachrichten.postfach.osiv2.factory.PostfachAddressTestFactory.*; import static de.ozgcloud.nachrichten.postfach.osiv2.factory.PostfachNachrichtTestFactory.*; -import static de.ozgcloud.nachrichten.postfach.osiv2.transfer.Osi2FileUploadTestFactory.*; import static java.util.Collections.*; import static org.assertj.core.api.Assertions.*; @@ -23,6 +23,7 @@ import de.ozgcloud.nachrichten.postfach.PostfachAddress; import de.ozgcloud.nachrichten.postfach.PostfachAddressIdentifier; import de.ozgcloud.nachrichten.postfach.PostfachNachricht; import de.ozgcloud.nachrichten.postfach.osiv2.factory.FileChunkInfoTestFactory; +import de.ozgcloud.nachrichten.postfach.osiv2.factory.Osi2FileUploadTestFactory; import de.ozgcloud.nachrichten.postfach.osiv2.factory.PostfachAddressTestFactory; import de.ozgcloud.nachrichten.postfach.osiv2.factory.PostfachNachrichtTestFactory; import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.DomainChunkMetaData; @@ -303,7 +304,7 @@ class Osi2RequestMapperTest { void shouldMapTotalChunks() { var result = doMapping(); - assertThat(result.getTotalChunks()).isEqualTo(NUMBER_OF_CHUNKS); + assertThat(result.getTotalChunks()).isEqualTo((int) NUMBER_OF_CHUNKS); } @DisplayName("should map total file size") diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapperTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapperTest.java index 0bc446f..6001100 100644 --- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapperTest.java +++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2ResponseMapperTest.java @@ -1,5 +1,6 @@ package de.ozgcloud.nachrichten.postfach.osiv2.transfer; +import static de.ozgcloud.nachrichten.postfach.osiv2.factory.QuarantineFileResultTestFactory.*; import static de.ozgcloud.nachrichten.postfach.osiv2.factory.V1ReplyMessageTestFactory.*; import static org.assertj.core.api.Assertions.*; @@ -19,6 +20,8 @@ import de.ozgcloud.nachrichten.postfach.PostfachNachricht; import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2RuntimeException; import de.ozgcloud.nachrichten.postfach.osiv2.exception.Osi2UploadException; import de.ozgcloud.nachrichten.postfach.osiv2.factory.MessageExchangeReceiveMessagesResponseTestFactory; +import de.ozgcloud.nachrichten.postfach.osiv2.factory.Osi2FileUploadTestFactory; +import de.ozgcloud.nachrichten.postfach.osiv2.factory.QuarantineFileResultTestFactory; import de.ozgcloud.nachrichten.postfach.osiv2.factory.V1ReplyMessageTestFactory; import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.QuarantineStatus; import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.V1ReplyBehavior; @@ -230,4 +233,32 @@ class Osi2ResponseMapperTest { assertThat(result).isTrue(); } } + + @DisplayName("check chunk upload success") + @Nested + class TestCheckChunkUploadSuccess { + + @DisplayName("should return") + @Test + void shouldReturn() { + var quarantineFileResult = QuarantineFileResultTestFactory.create(); + + mapper.checkChunkUploadSuccess(quarantineFileResult); + } + + @DisplayName("should throw on failed upload") + @Test + void shouldThrowOnFailedUpload() { + var quarantineFileResult = QuarantineFileResultTestFactory.createBuilder() + .success(false) + .error(ERROR_STRING) + .fileUid(Osi2FileUploadTestFactory.UPLOAD_GUID) + .build(); + + assertThatThrownBy(() -> mapper.checkChunkUploadSuccess(quarantineFileResult)) + .isInstanceOf(Osi2RuntimeException.class) + .hasMessageContaining(ERROR_STRING) + .hasMessageContaining(Osi2FileUploadTestFactory.UPLOAD_GUID); + } + } } diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeServiceTest.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeServiceTest.java index 6117bc3..95a4ab6 100644 --- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeServiceTest.java +++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeServiceTest.java @@ -22,6 +22,7 @@ import de.ozgcloud.nachrichten.postfach.PostfachNachricht; import de.ozgcloud.nachrichten.postfach.osiv2.config.Osi2PostfachProperties; import de.ozgcloud.nachrichten.postfach.osiv2.factory.FileChunkInfoTestFactory; import de.ozgcloud.nachrichten.postfach.osiv2.factory.MessageExchangeReceiveMessagesResponseTestFactory; +import de.ozgcloud.nachrichten.postfach.osiv2.factory.Osi2FileUploadTestFactory; import de.ozgcloud.nachrichten.postfach.osiv2.factory.PostfachNachrichtTestFactory; import de.ozgcloud.nachrichten.postfach.osiv2.gen.api.MessageExchangeApi; import de.ozgcloud.nachrichten.postfach.osiv2.gen.api.QuarantineApi; @@ -306,4 +307,16 @@ class PostfachApiFacadeServiceTest { } } + @DisplayName("delete file upload") + @Nested + class TestDeleteFileUpload { + @DisplayName("should call deleteUpload") + @Test + void shouldCallDeleteUpload() { + service.deleteFileUpload(MESSAGE_ID_1); + + verify(quarantineApi).deleteUpload(UUID.fromString(MESSAGE_ID_1)); + } + } + } \ No newline at end of file -- GitLab