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 1694d6db9409d53ecd4e0991c068a40bcb3ba956..7938fa52c7d6b5715e34dc65c1574b9dd290cbac 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 @@ -1,5 +1,7 @@ package de.ozgcloud.nachrichten.postfach.osiv2.transfer; +import java.io.IOException; +import java.io.InputStream; import java.time.Duration; import java.util.Comparator; import java.util.List; @@ -9,10 +11,12 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.BooleanSupplier; +import java.util.stream.Stream; import de.ozgcloud.apilib.file.OzgCloudFile; import de.ozgcloud.nachrichten.postfach.osiv2.OsiPostfachException; import de.ozgcloud.nachrichten.postfach.osiv2.ServiceIfOsi2Enabled; +import de.ozgcloud.nachrichten.postfach.osiv2.model.FileChunkInfo; import de.ozgcloud.nachrichten.postfach.osiv2.model.Osi2FileUpload; import de.ozgcloud.nachrichten.postfach.osiv2.storage.Osi2BinaryFileRemoteService; import lombok.RequiredArgsConstructor; @@ -27,6 +31,7 @@ public class Osi2QuarantineService { static final Duration POLLING_INTERVAL = Duration.ofSeconds(1); static final Duration POLLING_TIMEOUT = Duration.ofMinutes(5); + static final int CHUNK_SIZE = 100 * (2 << 10); public List<Osi2FileUpload> uploadAttachments(List<String> attachmentIds) { var ozgCloudFiles = binaryFileService.getFileMetadataOfIds(attachmentIds); @@ -44,14 +49,20 @@ public class Osi2QuarantineService { Osi2FileUpload uploadFileToQuarantine(OzgCloudFile file) { -// binaryFileService.streamFileContent(file.getId().toString()) -// .forEachRemaining(chunk -> postfachApiFacadeService.uploadChunk(file.getId(), chunk.getFileContent().toByteArray())); + try (var fileInputStream = binaryFileService.streamFileContent(file.getId().toString())) { + return uploadInputStreamToQuarantine(file, fileInputStream); + } catch (IOException e) { + throw new OsiPostfachException("Failed to upload file to quarantine!", e); + } + } + + Osi2FileUpload uploadInputStreamToQuarantine(OzgCloudFile file, InputStream fileInputStream) { + // TODO + return null; + } + + Stream<FileChunkInfo> streamFileChunkInfos(OzgCloudFile file) { // TODO - // var request = createRequestWithFileId(attachmentId); - // var metadata = binaryFileService.findBinaryFilesMetaData(GrpcBinaryFilesRequest.newBuilder().) - // var content = binaryFileService.getBinaryFileContent(request); - // - // content.next().getFileContent().writeTo(); return null; } 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 e4d6b9b9802b41b45c8f1bdf26f48af22ae06479..bd43b7de6be09deaf45a1a7fbb9cdc6254293c3b 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 @@ -5,13 +5,14 @@ import static de.ozgcloud.nachrichten.postfach.osiv2.transfer.Osi2RequestMapper. import java.util.List; import java.util.UUID; +import org.springframework.core.io.AbstractResource; + import de.ozgcloud.nachrichten.postfach.PostfachNachricht; import de.ozgcloud.nachrichten.postfach.osiv2.ServiceIfOsi2Enabled; import de.ozgcloud.nachrichten.postfach.osiv2.config.Osi2PostfachProperties; import de.ozgcloud.nachrichten.postfach.osiv2.gen.api.MessageExchangeApi; import de.ozgcloud.nachrichten.postfach.osiv2.gen.api.QuarantineApi; import de.ozgcloud.nachrichten.postfach.osiv2.model.FileChunkInfo; -import de.ozgcloud.nachrichten.postfach.osiv2.model.FileChunkResource; import de.ozgcloud.nachrichten.postfach.osiv2.model.Osi2FileUpload; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; @@ -34,14 +35,13 @@ public class PostfachApiFacadeService { ); } - public void uploadChunk(FileChunkInfo chunkInfo, FileChunkResource chunkResource) { - // TODO - // quarantineApi.uploadChunk( - // requestMapper.mapDomainChunkMetaData(chunkInfo), - // apiConfiguration.getTenant(), - // apiConfiguration.getNameIdentifier(), - // chunkResource - // ); + public <T extends AbstractResource> void uploadChunk(FileChunkInfo chunkInfo, T chunkResource) { + quarantineApi.uploadChunk( + requestMapper.mapDomainChunkMetaData(chunkInfo), + apiConfiguration.getTenant(), + apiConfiguration.getNameIdentifier(), + chunkResource + ); } public List<String> fetchPendingMessageIds() { 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 new file mode 100644 index 0000000000000000000000000000000000000000..5f746028712cb62267f59af913d324654528fe9a --- /dev/null +++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/FileChunkInfoTestFactory.java @@ -0,0 +1,30 @@ +package de.ozgcloud.nachrichten.postfach.osiv2.factory; + +import java.util.UUID; + +import de.ozgcloud.nachrichten.postfach.osiv2.model.FileChunkInfo; + +public class FileChunkInfoTestFactory { + + static final String FILE_UPLOAD_GUID = UUID.randomUUID().toString(); + static final String FILE_NAME = "test-file-name"; + static final String CONTENT_TYPE = "test-content-type"; + static final Integer CHUNK_INDEX = 1; + static final Integer TOTAL_CHUNKS = 10; + static final Long TOTAL_FILE_SIZE = 1000L; + + + public static FileChunkInfo create() { + return createBuilder().build(); + } + + public static FileChunkInfo.FileChunkInfoBuilder createBuilder() { + return FileChunkInfo.builder() + .guid(FILE_UPLOAD_GUID) + .fileName(FILE_NAME) + .contentType(CONTENT_TYPE) + .chunkIndex(CHUNK_INDEX) + .totalChunks(TOTAL_CHUNKS) + .totalFileSize(TOTAL_FILE_SIZE); + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..f0c34e89abd63e6a577c80d4ef30c774528f2df0 --- /dev/null +++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/Osi2QuarantineServiceTest.java @@ -0,0 +1,167 @@ +package de.ozgcloud.nachrichten.postfach.osiv2.transfer; + +import static de.ozgcloud.nachrichten.postfach.osiv2.transfer.Osi2FileUploadTestFactory.*; +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.io.InputStream; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; + +import de.ozgcloud.apilib.file.OzgCloudFile; +import de.ozgcloud.apilib.file.OzgCloudFileId; +import de.ozgcloud.apilib.file.OzgCloudFileTestFactory; +import de.ozgcloud.nachrichten.postfach.osiv2.model.Osi2FileUpload; +import de.ozgcloud.nachrichten.postfach.osiv2.storage.Osi2BinaryFileRemoteService; +import lombok.SneakyThrows; + +class Osi2QuarantineServiceTest { + + @InjectMocks + @Spy + private Osi2QuarantineService service; + + @Mock + private PostfachApiFacadeService postfachApiFacadeService; + + @Mock + private Osi2BinaryFileRemoteService binaryFileService; + + @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()); + } + + @DisplayName("should call getFileMetadataOfIds") + @Test + void shouldCallGetFileMetadataOfIds() { + uploadAttachments(); + + verify(binaryFileService).getFileMetadataOfIds(List.of(UPLOAD_FILE_ID)); + } + + @DisplayName("should call uploadFilesToQuarantineWithLargestFileFirst") + @Test + void shouldCallUploadFilesToQuarantineWithLargestFileFirst() { + uploadAttachments(); + + verify(service).uploadFilesToQuarantineWithLargestFileFirst(List.of(file)); + } + + @DisplayName("should call waitForVirusScan") + @Test + void shouldCallWaitForVirusScan() { + uploadAttachments(); + + verify(service).waitForVirusScan(List.of(uploadFile)); + } + + @DisplayName("should return") + @Test + void shouldReturn() { + var result = uploadAttachments(); + + assertThat(result).containsExactly(uploadFile); + } + + List<Osi2FileUpload> uploadAttachments() { + return service.uploadAttachments(List.of(UPLOAD_FILE_ID)); + } + } + + @DisplayName("upload files to quarantine with largest file first") + @Nested + class TestUploadFilesToQuarantineWithLargestFileFirst { + + 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(uploadFile1).when(service).uploadFileToQuarantine(file1); + doReturn(uploadFile2).when(service).uploadFileToQuarantine(file2); + } + + @DisplayName("should return") + @Test + void shouldReturn() { + var result = uploadFilesToQuarantineWithLargestFileFirst(); + + assertThat(result).containsExactly(uploadFile2, uploadFile1); + } + + List<Osi2FileUpload> uploadFilesToQuarantineWithLargestFileFirst() { + return service.uploadFilesToQuarantineWithLargestFileFirst(List.of(file1, file2)); + } + } + + @DisplayName("upload file to quarantine") + @Nested + class TestUploadFileToQuarantine { + + @Mock + private InputStream fileInputStream; + + private final OzgCloudFile file = OzgCloudFileTestFactory.create(); + private final Osi2FileUpload uploadFile = Osi2FileUploadTestFactory.create(); + + @BeforeEach + void mock() { + when(binaryFileService.streamFileContent(any())).thenReturn(fileInputStream); + doReturn(uploadFile).when(service).uploadInputStreamToQuarantine(any(), any()); + } + + @DisplayName("should call streamFileContent") + @Test + void shouldCallStreamFileContent() { + uploadFileToQuarantine(); + + verify(binaryFileService).streamFileContent(OzgCloudFileTestFactory.ID_STR); + } + + @DisplayName("should call uploadInputStreamToQuarantine") + @Test + void shouldCallUploadInputStreamToQuarantine() { + uploadFileToQuarantine(); + + verify(service).uploadInputStreamToQuarantine(file, fileInputStream); + } + + @DisplayName("should return") + @Test + void shouldReturn() { + var result = uploadFileToQuarantine(); + + assertThat(result).isEqualTo(uploadFile); + } + + Osi2FileUpload uploadFileToQuarantine() { + return service.uploadFileToQuarantine(file); + } + } + +} \ No newline at end of file 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 faf012b0373acb1ea8d98e359de78379df7b8852..0f9b57d824813196948f83a8117147c43ebd81aa 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 @@ -18,14 +18,20 @@ import org.mockito.Mock; import org.mockito.Spy; 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.PostfachNachrichtTestFactory; import de.ozgcloud.nachrichten.postfach.osiv2.gen.api.MessageExchangeApi; +import de.ozgcloud.nachrichten.postfach.osiv2.gen.api.QuarantineApi; +import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.DomainChunkMetaData; import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.MessageExchangeDeleteMessageResponse; import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.MessageExchangeReceiveMessagesResponse; import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.MessageExchangeSendMessageResponse; import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.OutSendMessageRequestV2; import de.ozgcloud.nachrichten.postfach.osiv2.gen.model.V1ReplyMessage; +import de.ozgcloud.nachrichten.postfach.osiv2.model.FileChunkInfo; +import de.ozgcloud.nachrichten.postfach.osiv2.model.FileChunkResource; import de.ozgcloud.nachrichten.postfach.osiv2.model.Osi2FileUpload; class PostfachApiFacadeServiceTest { @@ -37,12 +43,18 @@ class PostfachApiFacadeServiceTest { @Mock MessageExchangeApi messageExchangeApi; + @Mock + QuarantineApi quarantineApi; + @Mock Osi2RequestMapper osi2RequestMapper; @Mock Osi2ResponseMapper osi2ResponseMapper; + @Mock + Osi2PostfachProperties.ApiConfiguration apiConfiguration; + @DisplayName("send message") @Nested class TestSendMessage { @@ -179,6 +191,44 @@ class PostfachApiFacadeServiceTest { } } + @DisplayName("upload chunk") + @Nested + class TestUploadChunk { + @Mock + FileChunkResource chunkResource; + + @Mock + DomainChunkMetaData domainChunkMetaData; + + private static final String TENANT = "tenant"; + private static final String NAME_IDENTIFIER = "nameIdentifier"; + + private final FileChunkInfo info = FileChunkInfoTestFactory.create(); + + @BeforeEach + void mock() { + when(apiConfiguration.getTenant()).thenReturn(TENANT); + when(apiConfiguration.getNameIdentifier()).thenReturn(NAME_IDENTIFIER); + when(osi2RequestMapper.mapDomainChunkMetaData(any())).thenReturn(domainChunkMetaData); + } + + @DisplayName("should call mapDomainChunkMetadata") + @Test + void shouldCallMapDomainChunkMetadata() { + postfachApiFacadeService.uploadChunk(info, chunkResource); + + verify(osi2RequestMapper).mapDomainChunkMetaData(info); + } + + @DisplayName("should call uploadChunk") + @Test + void shouldCallUploadChunk() { + postfachApiFacadeService.uploadChunk(info, chunkResource); + + verify(quarantineApi).uploadChunk(domainChunkMetaData, TENANT, NAME_IDENTIFIER, chunkResource); + } + } + @DisplayName("Delete Message") @Nested class TestDeleteMessage {