diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/export/ExportRemoteService.java b/alfa-service/src/main/java/de/ozgcloud/alfa/export/ExportRemoteService.java similarity index 56% rename from alfa-service/src/test/java/de/ozgcloud/alfa/export/ExportRemoteService.java rename to alfa-service/src/main/java/de/ozgcloud/alfa/export/ExportRemoteService.java index af08570cfa2d9c5baa25e2f4bd434f6ad6260829..c650c035b18bf00f3327d01cc2155db719f4ad1b 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/export/ExportRemoteService.java +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/export/ExportRemoteService.java @@ -1,11 +1,10 @@ package de.ozgcloud.alfa.export; -import java.io.OutputStream; - import org.springframework.stereotype.Service; import de.ozgcloud.alfa.common.GrpcUtil; import de.ozgcloud.archive.grpc.export.ExportServiceGrpc.ExportServiceBlockingStub; +import de.ozgcloud.archive.grpc.export.GrpcExportVorgangRequest; import net.devh.boot.grpc.client.inject.GrpcClient; @Service @@ -14,7 +13,8 @@ class ExportRemoteService { @GrpcClient(GrpcUtil.FILE_MANAGER_GRPC_CLIENT) private ExportServiceBlockingStub exportServiceStub; - public void exportVorgang(String vorgangId, OutputStream archiveOut) { - + public ExportedVorgangFile exportVorgang(String vorgangId) { + var responseIterator = exportServiceStub.exportVorgang(GrpcExportVorgangRequest.newBuilder().setVorgangId(vorgangId).build()); + return new StreamedExportedVorgangFile(responseIterator); } } diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/export/ExportVorgangController.java b/alfa-service/src/main/java/de/ozgcloud/alfa/export/ExportVorgangController.java index 568a2b6ec17ed7bf14ad074210305add9f529550..abd43c474649b916f33e8992f21d44784bb332ff 100644 --- a/alfa-service/src/main/java/de/ozgcloud/alfa/export/ExportVorgangController.java +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/export/ExportVorgangController.java @@ -1,9 +1,5 @@ package de.ozgcloud.alfa.export; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Iterator; - import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -13,39 +9,23 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; -import de.ozgcloud.alfa.common.GrpcUtil; -import de.ozgcloud.archive.grpc.export.ExportServiceGrpc.ExportServiceBlockingStub; -import de.ozgcloud.archive.grpc.export.GrpcExportVorgangRequest; -import de.ozgcloud.archive.grpc.export.GrpcExportVorgangResponse; -import net.devh.boot.grpc.client.inject.GrpcClient; +import lombok.RequiredArgsConstructor; @RestController @RequestMapping(ExportVorgangController.PATH) +@RequiredArgsConstructor public class ExportVorgangController { static final String PATH = "/api/vorgangs"; // NOSONAR - private static final String EXPORT_FILENAME_TEMPLATE = "%s_Abgabe.Abgabe.0401.xdomea"; - - @GrpcClient(GrpcUtil.VORGANG_MANAGER_GRPC_CLIENT) - private ExportServiceBlockingStub grpcService; + private final ExportRemoteService exportRemoteService; @GetMapping(value = "{vorgangId}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) - public ResponseEntity<StreamingResponseBody> exportToXdomea(@PathVariable String vorgangId) { - var response = grpcService.exportVorgang(GrpcExportVorgangRequest.newBuilder().setVorgangId(vorgangId).build()); + public ResponseEntity<StreamingResponseBody> exportVorgang(@PathVariable String vorgangId) { + var exportedVorgangFile = exportRemoteService.exportVorgang(vorgangId); return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=%s", response.next().getVorgangFile().getFileName())) + .header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=%s", exportedVorgangFile.getFileName())) .contentType(MediaType.APPLICATION_OCTET_STREAM) - .body(out -> writeResponse(response, out)); - } - - String buildZipFilename(String filenameId) { - return String.format(EXPORT_FILENAME_TEMPLATE, filenameId); - } - - void writeResponse(Iterator<GrpcExportVorgangResponse> response, OutputStream outputStream) throws IOException { - while (response.hasNext()) { - outputStream.write(response.next().getVorgangFile().getFileContent().toByteArray()); - } + .body(exportedVorgangFile::writeToOutputStream); } } diff --git a/alfa-xdomea/src/main/java/de/ozgcloud/alfa/export/ExportVorgangProcessor.java b/alfa-service/src/main/java/de/ozgcloud/alfa/export/ExportVorgangProcessor.java similarity index 96% rename from alfa-xdomea/src/main/java/de/ozgcloud/alfa/export/ExportVorgangProcessor.java rename to alfa-service/src/main/java/de/ozgcloud/alfa/export/ExportVorgangProcessor.java index b7621e342cb52f19593b07ee03384b25285feb6e..8c570cff79bdad0796683d6ecaf92888c841138f 100644 --- a/alfa-xdomea/src/main/java/de/ozgcloud/alfa/export/ExportVorgangProcessor.java +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/export/ExportVorgangProcessor.java @@ -33,7 +33,7 @@ class ExportVorgangProcessor implements RepresentationModelProcessor<EntityModel return ModelBuilder.fromModel(model) .ifMatch(IS_VORGANG_ABGESCHLOSSEN) - .addLink(linkTo(methodOn(ExportVorgangController.class).exportToXdomea(vorgang.getId())).withRel(REL_EXPORT)) + .addLink(linkTo(methodOn(ExportVorgangController.class).exportVorgang(vorgang.getId())).withRel(REL_EXPORT)) .buildModel(); } } diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/export/ExportedVorgangFile.java b/alfa-service/src/main/java/de/ozgcloud/alfa/export/ExportedVorgangFile.java new file mode 100644 index 0000000000000000000000000000000000000000..e778a5a905885776809922c99ca8c27a5f595f92 --- /dev/null +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/export/ExportedVorgangFile.java @@ -0,0 +1,11 @@ +package de.ozgcloud.alfa.export; + +import java.io.IOException; +import java.io.OutputStream; + +interface ExportedVorgangFile { + + String getFileName(); + + void writeToOutputStream(OutputStream outputStream) throws IOException; +} diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/export/StreamedExportedVorgangFile.java b/alfa-service/src/main/java/de/ozgcloud/alfa/export/StreamedExportedVorgangFile.java new file mode 100644 index 0000000000000000000000000000000000000000..b51f2653ad632ddb9dd2bbe503550b73cf09239e --- /dev/null +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/export/StreamedExportedVorgangFile.java @@ -0,0 +1,30 @@ +package de.ozgcloud.alfa.export; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Iterator; + +import de.ozgcloud.archive.grpc.export.GrpcExportVorgangResponse; +import de.ozgcloud.common.errorhandling.TechnicalException; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +class StreamedExportedVorgangFile implements ExportedVorgangFile { + + private final Iterator<GrpcExportVorgangResponse> responseIterator; + + @Override + public String getFileName() { + if (!responseIterator.hasNext()) { + throw new TechnicalException("Response is empty"); + } + return responseIterator.next().getVorgangFile().getFileName(); + } + + @Override + public void writeToOutputStream(OutputStream outputStream) throws IOException { + while (responseIterator.hasNext()) { + outputStream.write(responseIterator.next().getVorgangFile().getFileContent().toByteArray()); + } + } +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/export/ExportRemoteServiceTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/export/ExportRemoteServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7347a68634b5633a45b909f02b9e13a572bb3ef8 --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/export/ExportRemoteServiceTest.java @@ -0,0 +1,58 @@ +package de.ozgcloud.alfa.export; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.Iterator; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; + +import de.ozgcloud.alfa.vorgang.VorgangHeaderTestFactory; +import de.ozgcloud.archive.grpc.export.ExportServiceGrpc.ExportServiceBlockingStub; +import de.ozgcloud.archive.grpc.export.GrpcExportVorgangRequest; +import de.ozgcloud.archive.grpc.export.GrpcExportVorgangResponse; + +class ExportRemoteServiceTest { + + @Spy + @InjectMocks + private ExportRemoteService service; + @Mock + private ExportServiceBlockingStub exportServiceStub; + + @Nested + class TestExportVorgang { + + public static final String VORGANG_ID = VorgangHeaderTestFactory.ID; + + private static final ArgumentMatcher<GrpcExportVorgangRequest> HAS_VORGANG_ID = request -> request.getVorgangId().equals(VorgangHeaderTestFactory.ID); + + @Mock + private Iterator<GrpcExportVorgangResponse> responseIterator; + + @BeforeEach + void init() { + when(exportServiceStub.exportVorgang(any(GrpcExportVorgangRequest.class))).thenReturn(responseIterator); + } + + @Test + void shouldExportVorgang() { + service.exportVorgang(VORGANG_ID); + + verify(exportServiceStub).exportVorgang(argThat(HAS_VORGANG_ID)); + } + + @Test + void shouldReturnExportedVorgangFile() { + var exportedVorgangFile = service.exportVorgang(VORGANG_ID); + + assertThat(exportedVorgangFile).isInstanceOf(StreamedExportedVorgangFile.class).extracting("responseIterator").isEqualTo(responseIterator); + } + } +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/export/ExportVorgangControllerTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/export/ExportVorgangControllerTest.java index c4a5b3b4147b3222d476d7835ed42b5705898873..57f0063280880e67b654368c8c68f081d7dd5a3f 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/export/ExportVorgangControllerTest.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/export/ExportVorgangControllerTest.java @@ -1,17 +1,15 @@ package de.ozgcloud.alfa.export; -import static org.assertj.core.api.AssertionsForClassTypes.*; import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.io.OutputStream; import java.util.UUID; 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.Spy; @@ -21,7 +19,6 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import de.ozgcloud.alfa.common.AlfaTestUtils; import de.ozgcloud.alfa.vorgang.VorgangHeaderTestFactory; class ExportVorgangControllerTest { @@ -29,6 +26,8 @@ class ExportVorgangControllerTest { @Spy @InjectMocks private ExportVorgangController controller; + @Mock + private ExportRemoteService exportRemoteService; private MockMvc mockMvc; @@ -38,58 +37,50 @@ class ExportVorgangControllerTest { } @Nested - class TestBuildZipFilename { + class TestExportVorgang { + + public static final String VORGANG_ID = VorgangHeaderTestFactory.ID; + public static final String EXPORTED_VORGANG_FILENAME = UUID.randomUUID().toString(); + + @Mock + private ExportedVorgangFile exportedVorgangFile; + + @BeforeEach + void init() { + when(exportedVorgangFile.getFileName()).thenReturn(EXPORTED_VORGANG_FILENAME); + when(exportRemoteService.exportVorgang(VORGANG_ID)).thenReturn(exportedVorgangFile); + } @Test - void shouldMatchPattern() { - var filename = controller.buildZipFilename(UUID.randomUUID().toString()); + void shouldExportVorgang() throws Exception { + doRequest(); - assertThat(filename).matches(AlfaTestUtils.uuidRegexWithSuffix("_Abgabe.Abgabe.0401.xdomea")); + verify(exportRemoteService).exportVorgang(VORGANG_ID); } - } -// @Nested -// class TestExportToXdomea { -// -// private static final String VORGANG_EXPORT_FILENAME = "00000000-0000-0000-0000-000000000000_Abgabe.Abgabe.0401.xml"; -// -// @Captor -// private ArgumentCaptor<String> filenameIdArgumentCaptor; -// -// @BeforeEach -// void init() { -// doReturn(VORGANG_EXPORT_FILENAME).when(controller).buildZipFilename(anyString()); -// } -// -// @Test -// void shouldHaveContentDispositonHeader() throws Exception { -// doRequest().andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + VORGANG_EXPORT_FILENAME)); -// } -// -// @Test -// void shouldCallXdomeaService() throws Exception { -// doRequest(); -// -// verify(xDomeaService).writeExport(eq(VorgangHeaderTestFactory.ID), filenameIdArgumentCaptor.capture(), any()); -// assertThat(filenameIdArgumentCaptor.getValue()).matches(AlfaTestUtils.UUID_REGEX); -// } -// -// @Test -// void shouldUseUUIDAsFilenameId() throws Exception { -// doRequest(); -// -// verify(controller).buildZipFilename(filenameIdArgumentCaptor.capture()); -// -// assertThat(filenameIdArgumentCaptor.getValue()).matches(AlfaTestUtils.UUID_REGEX); -// -// } -// -// private ResultActions doRequest() throws Exception { -// return mockMvc.perform(asyncDispatch( -// mockMvc.perform(get(ExportVorgangController.PATH + "/" + VorgangHeaderTestFactory.ID) -// .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_OCTET_STREAM_VALUE)).andReturn())) -// .andExpect(status().isOk()); -// } -// } + @Test + void shouldHaveContentDispositonHeader() throws Exception { + doRequest().andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + EXPORTED_VORGANG_FILENAME)); + } + + @Test + void shouldHaveContentTypeHeader() throws Exception { + doRequest().andExpect(header().string(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM.toString())); + } + + @Test + void shouldWriteFileToOutputStream() throws Exception { + doRequest(); + + verify(exportedVorgangFile).writeToOutputStream(any(OutputStream.class)); + } + + private ResultActions doRequest() throws Exception { + return mockMvc.perform(asyncDispatch( + mockMvc.perform(get(ExportVorgangController.PATH + "/" + VorgangHeaderTestFactory.ID) + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_OCTET_STREAM_VALUE)).andReturn())) + .andExpect(status().isOk()); + } + } } \ No newline at end of file diff --git a/alfa-xdomea/src/test/java/de/ozgcloud/alfa/export/ExportVorgangProcessorTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/export/ExportVorgangProcessorTest.java similarity index 100% rename from alfa-xdomea/src/test/java/de/ozgcloud/alfa/export/ExportVorgangProcessorTest.java rename to alfa-service/src/test/java/de/ozgcloud/alfa/export/ExportVorgangProcessorTest.java diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/export/GrpcExportVorgangResponseTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/export/GrpcExportVorgangResponseTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..6fb86287921347fa6d9eaecdb12e20ca5450a3bb --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/export/GrpcExportVorgangResponseTestFactory.java @@ -0,0 +1,22 @@ +package de.ozgcloud.alfa.export; + +import de.ozgcloud.archive.grpc.export.GrpcExportVorgangResponse; +import de.ozgcloud.archive.grpc.export.GrpcFile; + +class GrpcExportVorgangResponseTestFactory { + + public static final GrpcFile VORGANG_FILE_WITH_NAME = GrpcFileTestFactory.createWithName(); + public static final GrpcFile VORGANG_FILE_WITH_CONTENT = GrpcFileTestFactory.createWithContent(); + + public static GrpcExportVorgangResponse createWithName() { + return createBuilder().setVorgangFile(VORGANG_FILE_WITH_NAME).build(); + } + + public static GrpcExportVorgangResponse createWithContent() { + return createBuilder().setVorgangFile(VORGANG_FILE_WITH_CONTENT).build(); + } + + public static GrpcExportVorgangResponse.Builder createBuilder() { + return GrpcExportVorgangResponse.newBuilder(); + } +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/export/GrpcFileTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/export/GrpcFileTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..2d07f97a0e54a5d6484ecd24c6a872037124b517 --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/export/GrpcFileTestFactory.java @@ -0,0 +1,24 @@ +package de.ozgcloud.alfa.export; + +import com.google.protobuf.ByteString; +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.archive.grpc.export.GrpcFile; + +class GrpcFileTestFactory { + + public static final String FILE_NAME = LoremIpsum.getInstance().getName(); + public static final ByteString FILE_CONTENT = ByteString.copyFromUtf8(LoremIpsum.getInstance().getWords(10)); + + public static GrpcFile createWithName() { + return createBuilder().setFileName(FILE_NAME).build(); + } + + public static GrpcFile createWithContent() { + return createBuilder().setFileContent(FILE_CONTENT).build(); + } + + public static GrpcFile.Builder createBuilder() { + return GrpcFile.newBuilder(); + } +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/export/StreamedExportedVorgangFileTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/export/StreamedExportedVorgangFileTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8ff133a927a8c5359d0824a9621611d2ec01519c --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/export/StreamedExportedVorgangFileTest.java @@ -0,0 +1,112 @@ +package de.ozgcloud.alfa.export; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Iterator; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import com.google.protobuf.ByteString; +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.archive.grpc.export.GrpcExportVorgangResponse; +import de.ozgcloud.common.errorhandling.TechnicalException; + +class StreamedExportedVorgangFileTest { + + @Mock + private Iterator<GrpcExportVorgangResponse> responseIterator; + @InjectMocks + private StreamedExportedVorgangFile exportedVorgangFile; + + @Nested + class TestGetFileName { + + @Nested + class OnEmptyResponse { + + @BeforeEach + void init() { + when(responseIterator.hasNext()).thenReturn(false); + } + + @Test + void shouldThrowException() { + assertThatExceptionOfType(TechnicalException.class).isThrownBy(exportedVorgangFile::getFileName); + } + } + + @Nested + class OnNotEmptyResponse { + + @BeforeEach + void init() { + when(responseIterator.hasNext()).thenReturn(true); + when(responseIterator.next()).thenReturn(GrpcExportVorgangResponseTestFactory.createWithName()); + } + + @Test + void shouldCallHasNext() { + exportedVorgangFile.getFileName(); + + verify(responseIterator).hasNext(); + } + + @Test + void shouldCallNextOnce() { + exportedVorgangFile.getFileName(); + + verify(responseIterator, times(1)).next(); + } + + @Test + void shouldReturnFileName() { + var fileName = exportedVorgangFile.getFileName(); + + assertThat(fileName).isEqualTo(GrpcFileTestFactory.FILE_NAME); + } + } + } + + @Nested + class TestWriteToOutputStream { + + public static final ByteString FILE_CONTENT_1 = GrpcFileTestFactory.FILE_CONTENT; + public static final ByteString FILE_CONTENT_2 = ByteString.copyFromUtf8(LoremIpsum.getInstance().getWords(8)); + + @Mock + private OutputStream outputStream; + + @BeforeEach + void init() { + when(responseIterator.hasNext()) + .thenReturn(true) + .thenReturn(true) + .thenReturn(false); + when(responseIterator.next()) + .thenReturn(GrpcExportVorgangResponseTestFactory.createWithContent()) + .thenReturn(GrpcExportVorgangResponseTestFactory.createBuilder().setVorgangFile( + GrpcFileTestFactory.createBuilder().setFileContent(FILE_CONTENT_2).build() + ).build()); + } + + @Test + void shouldWriteFileContentInOrder() throws IOException { + var orderVerifier = inOrder(outputStream); + + exportedVorgangFile.writeToOutputStream(outputStream); + + orderVerifier.verify(outputStream).write(FILE_CONTENT_1.toByteArray()); + orderVerifier.verify(outputStream).write(FILE_CONTENT_2.toByteArray()); + } + + + } +}