diff --git a/archive-manager-interface/src/main/protobuf/export.model.proto b/archive-manager-interface/src/main/protobuf/export.model.proto new file mode 100644 index 0000000000000000000000000000000000000000..2a3b69d589e0982621a566d97fafa6ef7895b085 --- /dev/null +++ b/archive-manager-interface/src/main/protobuf/export.model.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +package de.ozgcloud.archive.grpc.export; + +option java_multiple_files = true; +option java_package = "de.ozgcloud.archive.grpc.export"; +option java_outer_classname = "ExportModelProto"; + +message GrpcExportVorgangRequest { + string vorgangId = 1; +} + +message GrpcExportVorgangResponse { + GrpcFile vorgangFile = 1; +} + +message GrpcFile { + oneof file { + string fileName = 1; + bytes fileContent = 2; + } +} \ No newline at end of file diff --git a/archive-manager-interface/src/main/protobuf/export.proto b/archive-manager-interface/src/main/protobuf/export.proto new file mode 100644 index 0000000000000000000000000000000000000000..f6a99ab3b81a72e8c8351f8c7c982a939f6f5c90 --- /dev/null +++ b/archive-manager-interface/src/main/protobuf/export.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package de.ozgcloud.archive.grpc.export; + +import "export.model.proto"; + +option java_multiple_files = true; +option java_package = "de.ozgcloud.archive.grpc.export"; +option java_outer_classname = "ExportProto"; + +service ExportService{ + rpc ExportVorgang(GrpcExportVorgangRequest) returns (stream GrpcExportVorgangResponse); +} \ No newline at end of file diff --git a/archive-manager-server/pom.xml b/archive-manager-server/pom.xml index 637cd30bc844060b6c7fcaa6eba161fcb8c78721..b1091bf5a6a58eaaeff32a8e3b486e970997da01 100644 --- a/archive-manager-server/pom.xml +++ b/archive-manager-server/pom.xml @@ -37,6 +37,11 @@ <artifactId>grpc-inprocess</artifactId> </dependency> <!-- OZG cloud projects --> + <dependency> + <groupId>de.ozgcloud.archive</groupId> + <artifactId>archive-manager-interface</artifactId> + <version>${project.version}</version> + </dependency> <dependency> <groupId>de.ozgcloud.vorgang</groupId> <artifactId>vorgang-manager-interface</artifactId> @@ -49,6 +54,10 @@ <groupId>de.ozgcloud.api-lib</groupId> <artifactId>api-lib-core</artifactId> </dependency> + <dependency> + <groupId>de.ozgcloud.common</groupId> + <artifactId>ozgcloud-common-lib</artifactId> + </dependency> <dependency> <groupId>de.ozgcloud.api-lib</groupId> <artifactId>api-lib-core</artifactId> diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/ArchiveManagerConfiguration.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/ArchiveManagerConfiguration.java index debcdff08821b4ca35010f590e12d415fffbf677..32bd3b1164367ae2715a4e81501e79e87b9b37fd 100644 --- a/archive-manager-server/src/main/java/de/ozgcloud/archive/ArchiveManagerConfiguration.java +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/ArchiveManagerConfiguration.java @@ -3,7 +3,7 @@ package de.ozgcloud.archive; import org.springframework.context.annotation.Configuration; @Configuration -public class ArchiveManagerConfiguration { //NOSONAR +public class ArchiveManagerConfiguration { // NOSONAR public static final String VORGANG_REMOTE_MANAGER_SERVICE_NAME = "archive_VorgangRemoteService"; public static final String VORGANG_WITH_EINGANG_MAPPER_NAME = "archive_VorgangWithEingangMapper"; @@ -20,10 +20,15 @@ public class ArchiveManagerConfiguration { //NOSONAR public static final String BINARY_FILE_SERVICE_NAME = "archive_BinaryFileService"; public static final String BINARY_FILE_REMOTE_SERVICE_NAME = "archive_BinaryFileRemoteService"; + public static final String OZGCLOUD_FILE_SERVICE_NAME = "archive_OzgCloudFileService"; public static final String FILE_ID_MAPPER_NAME = "archive_FileIdMapper"; public static final String BESCHEID_REMOTE_SERVICE_NAME = "archive_BescheidRemoteService"; public static final String BESCHEID_MAPPER_NAME = "archive_BescheidMapper"; + public static final String EXCEPTION_HANDLER_NAME = "archive_ExceptionHandler"; + + public static final String OZGCLOUD_CALL_CONTEXT_PROVIDER_NAME = "archive_CallContextProvider"; + } diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/CallContextConfiguration.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/CallContextConfiguration.java deleted file mode 100644 index 1ed06ab15a44fce06e98b5f337b73d0ce2c18afc..0000000000000000000000000000000000000000 --- a/archive-manager-server/src/main/java/de/ozgcloud/archive/CallContextConfiguration.java +++ /dev/null @@ -1,20 +0,0 @@ -package de.ozgcloud.archive; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Scope; -import org.springframework.context.annotation.ScopedProxyMode; - -import de.ozgcloud.archive.common.callcontext.ArchiveCallContext; -import net.devh.boot.grpc.server.scope.GrpcRequestScope; - -@Configuration -class CallContextConfiguration { - - @Bean - @Scope(scopeName = GrpcRequestScope.GRPC_REQUEST_SCOPE_NAME, proxyMode = ScopedProxyMode.TARGET_CLASS) - ArchiveCallContext archiveCallContext() { - return ArchiveCallContext.builder().build(); - } - -} diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/OzgcloudConfiguration.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/OzgcloudConfiguration.java index b5210709ed4f0ef25009246f8bcacd0fa4c3d41b..9dac15d605629f09e837399d7f0ea769567c88ba 100644 --- a/archive-manager-server/src/main/java/de/ozgcloud/archive/OzgcloudConfiguration.java +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/OzgcloudConfiguration.java @@ -1,12 +1,9 @@ package de.ozgcloud.archive; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import de.ozgcloud.apilib.common.callcontext.DefaultOzgCloudCallContextProvider; -import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextProvider; +import de.ozgcloud.apilib.file.OzgCloudFileService; import de.ozgcloud.apilib.file.grpc.GrpcOzgCloudFileService; import de.ozgcloud.apilib.file.grpc.OzgCloudFileMapper; import de.ozgcloud.apilib.user.GrpcOzgCloudUserProfileService; @@ -17,15 +14,11 @@ import de.ozgcloud.archive.common.callcontext.CallContextProvider; import de.ozgcloud.user.grpc.userprofile.UserProfileServiceGrpc.UserProfileServiceBlockingStub; import de.ozgcloud.vorgang.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceBlockingStub; import de.ozgcloud.vorgang.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceStub; -import de.ozgcloud.vorgang.vorgang.VorgangServiceGrpc.VorgangServiceBlockingStub; import net.devh.boot.grpc.client.inject.GrpcClient; @Configuration class OzgcloudConfiguration { - @GrpcClient(GrpcUtil.VORGANG_MANAGER_GRPC_CLIENT) - private VorgangServiceBlockingStub vorgangServiceStub; - @GrpcClient(GrpcUtil.USER_MANAGER_GRPC_CLIENT) private UserProfileServiceBlockingStub userProfileServiceBlockingStub; @@ -35,20 +28,14 @@ class OzgcloudConfiguration { @GrpcClient(GrpcUtil.FILE_MANAGER_GRPC_CLIENT) private BinaryFileServiceStub binaryFileServiceStub; - @Bean - @ConditionalOnMissingBean - OzgCloudCallContextProvider callContextProvider(ApplicationContext ctxt) { - return new DefaultOzgCloudCallContextProvider(ctxt); - } - - @Bean - GrpcOzgCloudFileService grpcOzgCloudFileService(OzgCloudCallContextProvider contextProvider, OzgCloudFileMapper mapper) { + @Bean(ArchiveManagerConfiguration.OZGCLOUD_FILE_SERVICE_NAME) + OzgCloudFileService ozgCloudFileService(CallContextProvider contextProvider, OzgCloudFileMapper mapper) { return new GrpcOzgCloudFileService(binaryFileServiceBlockingStub, binaryFileServiceStub, contextProvider, mapper); } @Bean(ArchiveManagerConfiguration.OZGCLOUD_USER_PROFILE_SERVICE_NAME) // NOSONAR - OzgCloudUserProfileService grpcOzgCloudUserProfileService(CallContextProvider ozgCloudCallContextProvider, UserProfileMapper userProfileMapper) { - return new GrpcOzgCloudUserProfileService(userProfileServiceBlockingStub, userProfileMapper, ozgCloudCallContextProvider); + OzgCloudUserProfileService grpcOzgCloudUserProfileService(CallContextProvider callContextProvider, UserProfileMapper userProfileMapper) { + return new GrpcOzgCloudUserProfileService(userProfileServiceBlockingStub, userProfileMapper, callContextProvider); } } diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/bescheid/BescheidRemoteService.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/bescheid/BescheidRemoteService.java index 30440017204f2c57bf27bc6e3a79c1e49083be99..bd691e9f0de34f7219aba5488e6774f6a04b57a1 100644 --- a/archive-manager-server/src/main/java/de/ozgcloud/archive/bescheid/BescheidRemoteService.java +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/bescheid/BescheidRemoteService.java @@ -6,6 +6,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextAttachingInterceptor; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextProvider; import de.ozgcloud.archive.ArchiveManagerConfiguration; import de.ozgcloud.archive.common.GrpcUtil; import de.ozgcloud.bescheid.BescheidServiceGrpc.BescheidServiceBlockingStub; @@ -17,7 +19,9 @@ class BescheidRemoteService { @GrpcClient(GrpcUtil.VORGANG_MANAGER_GRPC_CLIENT) private BescheidServiceBlockingStub grpcService; - + @Autowired + @Qualifier(ArchiveManagerConfiguration.OZGCLOUD_CALL_CONTEXT_PROVIDER_NAME) + private OzgCloudCallContextProvider contextProvider; @Autowired @Qualifier(ArchiveManagerConfiguration.BESCHEID_MAPPER_NAME) // NOSONAR private BescheidMapper bescheidMapper; @@ -25,7 +29,7 @@ class BescheidRemoteService { public Stream<Bescheid> findByVorgangId(String vorgangId) { var request = buildGetAllBescheidRequest(vorgangId); - var response = grpcService.getAll(request); + var response = getGrpcServiceWithInterceptor().getAll(request); return response.getBescheidList().stream().map(grpcBescheid -> bescheidMapper.fromGrpc(grpcBescheid, vorgangId)); } @@ -33,4 +37,8 @@ class BescheidRemoteService { GrpcGetAllBescheidRequest buildGetAllBescheidRequest(String vorgangId) { return GrpcGetAllBescheidRequest.newBuilder().setVorgangId(vorgangId).build(); } + + BescheidServiceBlockingStub getGrpcServiceWithInterceptor() { + return grpcService.withInterceptors(new OzgCloudCallContextAttachingInterceptor(contextProvider)); + } } diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/bescheid/DocumentRemoteService.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/bescheid/DocumentRemoteService.java index 77e62802086c1d86facff5292dac322567e0df5f..1eead2e35a5cef9783cf6f9bbb465179a619e001 100644 --- a/archive-manager-server/src/main/java/de/ozgcloud/archive/bescheid/DocumentRemoteService.java +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/bescheid/DocumentRemoteService.java @@ -4,6 +4,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextAttachingInterceptor; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextProvider; import de.ozgcloud.archive.ArchiveManagerConfiguration; import de.ozgcloud.archive.common.GrpcUtil; import de.ozgcloud.common.binaryfile.FileId; @@ -19,10 +21,16 @@ class DocumentRemoteService { @Autowired @Qualifier(ArchiveManagerConfiguration.DOCUMENT_MAPPER_NAME) // NOSONAR private DocumentMapper documentMapper; + @Autowired + @Qualifier(ArchiveManagerConfiguration.OZGCLOUD_CALL_CONTEXT_PROVIDER_NAME) + private OzgCloudCallContextProvider contextProvider; public FileId getDocument(String documentId) { - var response = grpcService.getDocument(GrpcGetDocumentRequest.newBuilder().setId(documentId).build()); + var response = getGrpcServiceWithInterceptor().getDocument(GrpcGetDocumentRequest.newBuilder().setId(documentId).build()); return documentMapper.fromGrpc(response.getDocument()); } + DocumentServiceBlockingStub getGrpcServiceWithInterceptor() { + return grpcService.withInterceptors(new OzgCloudCallContextAttachingInterceptor(contextProvider)); + } } diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/common/binaryfile/BinaryFileRemoteService.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/binaryfile/BinaryFileRemoteService.java index 11c53d93fe3bf9a89c6feea096ca523edf2e6621..81d450f5df26ba8bb5eb6f02e0e49a5477929bd1 100644 --- a/archive-manager-server/src/main/java/de/ozgcloud/archive/common/binaryfile/BinaryFileRemoteService.java +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/binaryfile/BinaryFileRemoteService.java @@ -8,7 +8,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; -import de.ozgcloud.apilib.file.grpc.GrpcOzgCloudFileService; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextAttachingInterceptor; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextProvider; +import de.ozgcloud.apilib.file.OzgCloudFileService; import de.ozgcloud.archive.ArchiveManagerConfiguration; import de.ozgcloud.archive.common.GrpcUtil; import de.ozgcloud.archive.file.OzgFile; @@ -25,16 +27,20 @@ class BinaryFileRemoteService { @GrpcClient(GrpcUtil.VORGANG_MANAGER_GRPC_CLIENT) private BinaryFileServiceBlockingStub grpcService; @Autowired - private GrpcOzgCloudFileService grpcOzgCloudFileService; + @Qualifier(ArchiveManagerConfiguration.OZGCLOUD_FILE_SERVICE_NAME) + private OzgCloudFileService ozgCloudFileService; @Autowired private OzgFileMapper ozgFileMapper; + @Autowired + @Qualifier(ArchiveManagerConfiguration.OZGCLOUD_CALL_CONTEXT_PROVIDER_NAME) + private OzgCloudCallContextProvider contextProvider; @Autowired @Qualifier(ArchiveManagerConfiguration.FILE_ID_MAPPER_NAME) // NOSONAR private FileIdMapper fileIdMapper; public Stream<OzgFile> getFiles(List<FileId> fileIds) { - var response = grpcService.findBinaryFilesMetaData(buildBinaryFilesRequest(fileIds)); + var response = getGrpcServiceWithInterceptor().findBinaryFilesMetaData(buildBinaryFilesRequest(fileIds)); return mapGrpcFindFilesRespone(response); } @@ -54,10 +60,14 @@ class BinaryFileRemoteService { } public OzgFile getFile(FileId fileId) { - return ozgFileMapper.fromOzgCloudFile(grpcOzgCloudFileService.getFile(fileIdMapper.toOzgCloudFileId(fileId))); + return ozgFileMapper.fromOzgCloudFile(ozgCloudFileService.getFile(fileIdMapper.toOzgCloudFileId(fileId))); } public void writeFileContent(FileId fileId, OutputStream outputStream) { - grpcOzgCloudFileService.writeFileDataToStream(fileIdMapper.toOzgCloudFileId(fileId), outputStream); + ozgCloudFileService.writeFileDataToStream(fileIdMapper.toOzgCloudFileId(fileId), outputStream); + } + + BinaryFileServiceBlockingStub getGrpcServiceWithInterceptor() { + return grpcService.withInterceptors(new OzgCloudCallContextAttachingInterceptor(contextProvider)); } } diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/common/binaryfile/FileIdMapper.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/binaryfile/FileIdMapper.java index 8e316d2da813f4be399f25b889e9f5d543d834da..c6289388448ed2650014fb500cf9ffe48f150df6 100644 --- a/archive-manager-server/src/main/java/de/ozgcloud/archive/common/binaryfile/FileIdMapper.java +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/binaryfile/FileIdMapper.java @@ -1,6 +1,7 @@ package de.ozgcloud.archive.common.binaryfile; import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; import org.mapstruct.Mapper; import org.springframework.stereotype.Component; @@ -9,7 +10,7 @@ import de.ozgcloud.archive.ArchiveManagerConfiguration; import de.ozgcloud.common.binaryfile.FileId; @Mapper -@AnnotateWith(value = Component.class, elements = @AnnotateWith.Element(strings = ArchiveManagerConfiguration.FILE_ID_MAPPER_NAME)) +@AnnotateWith(value = Component.class, elements = @Element(strings = ArchiveManagerConfiguration.FILE_ID_MAPPER_NAME)) public interface FileIdMapper { default FileId fromString(String fileId) { diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/common/callcontext/ArchiveCallContext.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/callcontext/ArchiveCallContext.java index 392f9fb9427d1edb215dc0dbac3a4d5a9b828a19..20d128669fb7401b5339119bee5b092da1ffa5f9 100644 --- a/archive-manager-server/src/main/java/de/ozgcloud/archive/common/callcontext/ArchiveCallContext.java +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/callcontext/ArchiveCallContext.java @@ -7,7 +7,7 @@ import lombok.Builder; import lombok.Getter; import lombok.Singular; -@Builder(toBuilder = true) +@Builder @Getter public class ArchiveCallContext { diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/common/callcontext/CallContextConfiguration.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/callcontext/CallContextConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..ffaabe7c2236d88f41f561056f620ac9306817cb --- /dev/null +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/callcontext/CallContextConfiguration.java @@ -0,0 +1,16 @@ +package de.ozgcloud.archive.common.callcontext; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextProvider; +import de.ozgcloud.archive.ArchiveManagerConfiguration; + +@Configuration +public class CallContextConfiguration { + + @Bean(ArchiveManagerConfiguration.OZGCLOUD_CALL_CONTEXT_PROVIDER_NAME) + OzgCloudCallContextProvider callContextProvider(ArchiveCallContextMapper mapper) { + return new CallContextProvider(mapper, CallContextInterceptor.CONTEXT_KEY_CALL_CONTEXT::get); + } +} diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/common/callcontext/CallContextInterceptor.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/callcontext/CallContextInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..f2736787df9f53f3bb11ced8b6af76b9d2d7d24e --- /dev/null +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/callcontext/CallContextInterceptor.java @@ -0,0 +1,40 @@ +package de.ozgcloud.archive.common.callcontext; + +import static de.ozgcloud.common.grpc.GrpcUtil.*; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import io.grpc.Context; +import io.grpc.Contexts; +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.ServerCall.Listener; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class CallContextInterceptor implements ServerInterceptor { + + static final String KEY_USER_ID = "USER_ID-bin"; + static final String KEY_ACCESS_LIMITED = "ACCESS_LIMITED-bin"; + static final String KEY_ACCESS_LIMITED_ORGAID = "ACCESS_LIMITED_TO_ORGANISATORISCHEEINHEITENID-bin"; + + public static final Context.Key<ArchiveCallContext> CONTEXT_KEY_CALL_CONTEXT = Context.key("archiveCallContext"); + + @Override + public <A, B> Listener<A> interceptCall(ServerCall<A, B> call, Metadata headers, ServerCallHandler<A, B> next) { + var newContext = Context.current().withValue(CONTEXT_KEY_CALL_CONTEXT, createArchiveCallContext(headers)); + return Contexts.interceptCall(newContext, call, headers, next); + } + + ArchiveCallContext createArchiveCallContext(Metadata headers) { + return ArchiveCallContext.builder() + .userId(getFromHeaders(KEY_USER_ID, headers).orElse(StringUtils.EMPTY)) + .accessLimited(getFromHeaders(KEY_ACCESS_LIMITED, headers).map(isLimited -> isLimited.equals("true")).orElse(false)) + .accessLimitedToOrgaIds(getCollection(KEY_ACCESS_LIMITED_ORGAID, headers)) + .build(); + } +} diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/common/callcontext/CallContextProvider.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/callcontext/CallContextProvider.java index 17bb65f08ab942cc205e86e3005fcf1054861f6e..5ce75ee17473a0f65c2483dc18cc1c2ec7f8a718 100644 --- a/archive-manager-server/src/main/java/de/ozgcloud/archive/common/callcontext/CallContextProvider.java +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/callcontext/CallContextProvider.java @@ -1,20 +1,18 @@ package de.ozgcloud.archive.common.callcontext; -import org.springframework.stereotype.Component; - import de.ozgcloud.apilib.common.callcontext.CallContext; import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextProvider; import lombok.RequiredArgsConstructor; -@Component @RequiredArgsConstructor public class CallContextProvider implements OzgCloudCallContextProvider { - private final ArchiveCallContext archiveCallContext; private final ArchiveCallContextMapper mapper; + private final GrpcCallContextSupplier contextSupplier; @Override public CallContext provideContext() { + var archiveCallContext = contextSupplier.get(); return mapper.toOzgCloudCallContext(archiveCallContext); } } diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/common/callcontext/GrpcCallContextSupplier.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/callcontext/GrpcCallContextSupplier.java new file mode 100644 index 0000000000000000000000000000000000000000..a5237a7877c7805d6724a480b3062a257028dadb --- /dev/null +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/callcontext/GrpcCallContextSupplier.java @@ -0,0 +1,7 @@ +package de.ozgcloud.archive.common.callcontext; + +import java.util.function.Supplier; + +interface GrpcCallContextSupplier extends Supplier<ArchiveCallContext> { + +} diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/common/command/CommandMapper.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/command/CommandMapper.java index 83b566525aea12ba40f79f621d0a408e38cc39b0..5a43480617a6f567e436223f50d54777e97b356c 100644 --- a/archive-manager-server/src/main/java/de/ozgcloud/archive/common/command/CommandMapper.java +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/command/CommandMapper.java @@ -4,6 +4,7 @@ import java.time.ZonedDateTime; import org.apache.commons.lang3.StringUtils; import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.NullValueCheckStrategy; @@ -14,7 +15,7 @@ import de.ozgcloud.vorgang.common.grpc.GrpcObjectMapper; import de.ozgcloud.vorgang.grpc.command.GrpcCommand; @Mapper(uses = { GrpcObjectMapper.class }, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) -@AnnotateWith(value = Component.class, elements = @AnnotateWith.Element(strings = ArchiveManagerConfiguration.COMMAND_MAPPER_NAME)) +@AnnotateWith(value = Component.class, elements = @Element(strings = ArchiveManagerConfiguration.COMMAND_MAPPER_NAME)) interface CommandMapper { @Mapping(target = "body", source = "bodyObj") diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/common/command/CommandRemoteService.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/command/CommandRemoteService.java index 7696eb1d92ccacc7a3341afd299c8c2083032abe..1e54516036e69e23b8e188f45b26826ed19d9b00 100644 --- a/archive-manager-server/src/main/java/de/ozgcloud/archive/common/command/CommandRemoteService.java +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/command/CommandRemoteService.java @@ -7,6 +7,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextAttachingInterceptor; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextProvider; import de.ozgcloud.archive.ArchiveManagerConfiguration; import de.ozgcloud.archive.common.GrpcUtil; import de.ozgcloud.vorgang.grpc.command.CommandServiceGrpc.CommandServiceBlockingStub; @@ -21,9 +23,12 @@ class CommandRemoteService { @Autowired @Qualifier(ArchiveManagerConfiguration.COMMAND_MAPPER_NAME) // NOSONAR private CommandMapper mapper; + @Autowired + @Qualifier(ArchiveManagerConfiguration.OZGCLOUD_CALL_CONTEXT_PROVIDER_NAME) + private OzgCloudCallContextProvider contextProvider; public Stream<Command> findCommands(String vorgangId, Optional<String> status, Optional<String> order) { - var response = grpcService.findCommands(buildFindCommandsRequest(vorgangId, status, order)); + var response = getGrpcServiceWithInterceptor().findCommands(buildFindCommandsRequest(vorgangId, status, order)); return response.getCommandList().stream().map(mapper::fromGrpc); } @@ -38,4 +43,7 @@ class CommandRemoteService { return builder.build(); } + CommandServiceBlockingStub getGrpcServiceWithInterceptor() { + return grpcService.withInterceptors(new OzgCloudCallContextAttachingInterceptor(contextProvider)); + } } diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/common/errorhandling/ExceptionHandler.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/errorhandling/ExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..d3f901d08e453ff436c3ae1bb29bad942e483a4e --- /dev/null +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/errorhandling/ExceptionHandler.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 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.archive.common.errorhandling; + +import java.util.Objects; +import java.util.UUID; + +import org.springframework.stereotype.Component; + +import de.ozgcloud.archive.ArchiveManagerConfiguration; +import de.ozgcloud.common.errorhandling.ExceptionUtil; +import io.grpc.Metadata; +import io.grpc.Metadata.Key; +import io.grpc.Status; +import io.grpc.StatusException; +import io.grpc.StatusRuntimeException; +import lombok.extern.log4j.Log4j2; +import net.devh.boot.grpc.server.advice.GrpcAdvice; +import net.devh.boot.grpc.server.advice.GrpcExceptionHandler; + +@Log4j2 +@GrpcAdvice +@Component(ArchiveManagerConfiguration.EXCEPTION_HANDLER_NAME) +public class ExceptionHandler { + + static final String STATUS_RUNTIME_EXCEPTION_MESSAGE = "Error on calling Grpc server"; + + static final Key<String> KEY_EXCEPTION_ID = Key.of("EXCEPTION_ID", Metadata.ASCII_STRING_MARSHALLER); + + @GrpcExceptionHandler + public StatusException handleStatusRuntimeException(StatusRuntimeException e) { + var exceptionId = createExceptionId(); + var messageWithExceptionId = ExceptionUtil.formatMessageWithExceptionId(STATUS_RUNTIME_EXCEPTION_MESSAGE, exceptionId); + LOG.error("Error on call Grpc server: {}", messageWithExceptionId, e); + return createStatusException(expandStatus(e, messageWithExceptionId), buildMetadataFromStatusRuntimeException(e, exceptionId)); + } + + Status expandStatus(StatusRuntimeException e, String messageWithExceptionId) { + return e.getStatus().augmentDescription(messageWithExceptionId); + } + + Metadata buildMetadataFromStatusRuntimeException(StatusRuntimeException e, String exceptionId) { + var metadata = Objects.requireNonNullElse(e.getTrailers(), new Metadata()); + metadata.put(KEY_EXCEPTION_ID, exceptionId); + return metadata; + } + + String createExceptionId() { + return UUID.randomUUID().toString(); + } + + StatusException createStatusException(Status status, Metadata metadata) { + return new StatusException(status, metadata); + } + +} \ No newline at end of file diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/common/user/UserProfileMapper.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/user/UserProfileMapper.java index 91feb820adf2cb8374c013661b4ef590154298f9..d7fdd2a1cb50dbf1bac8570256899ac1645c0674 100644 --- a/archive-manager-server/src/main/java/de/ozgcloud/archive/common/user/UserProfileMapper.java +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/common/user/UserProfileMapper.java @@ -1,6 +1,7 @@ package de.ozgcloud.archive.common.user; import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; import org.mapstruct.Mapper; import org.springframework.stereotype.Component; @@ -8,7 +9,7 @@ import de.ozgcloud.apilib.user.OzgCloudUserProfile; import de.ozgcloud.archive.ArchiveManagerConfiguration; @Mapper -@AnnotateWith(value = Component.class, elements = @AnnotateWith.Element(strings = ArchiveManagerConfiguration.USER_PROFILE_MAPPER_NAME)) +@AnnotateWith(value = Component.class, elements = @Element(strings = ArchiveManagerConfiguration.USER_PROFILE_MAPPER_NAME)) public interface UserProfileMapper { UserProfile fromOzgUserProfile(OzgCloudUserProfile userProfile); diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/export/ExportGrpcService.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/export/ExportGrpcService.java new file mode 100644 index 0000000000000000000000000000000000000000..48f92d0a8105bdd771405e74c845dc5093ee3755 --- /dev/null +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/export/ExportGrpcService.java @@ -0,0 +1,67 @@ +package de.ozgcloud.archive.export; + +import java.util.UUID; + +import org.springframework.core.task.TaskExecutor; + +import com.google.protobuf.ByteString; + +import de.ozgcloud.archive.common.callcontext.CallContextInterceptor; +import de.ozgcloud.archive.grpc.export.ExportServiceGrpc.ExportServiceImplBase; +import de.ozgcloud.archive.grpc.export.GrpcExportVorgangRequest; +import de.ozgcloud.archive.grpc.export.GrpcExportVorgangResponse; +import de.ozgcloud.archive.grpc.export.GrpcFile; +import de.ozgcloud.common.binaryfile.GrpcBinaryFileServerDownloader; +import io.grpc.stub.CallStreamObserver; +import io.grpc.stub.StreamObserver; +import lombok.RequiredArgsConstructor; +import net.devh.boot.grpc.server.service.GrpcService; + +@GrpcService(interceptors = { CallContextInterceptor.class }) +@RequiredArgsConstructor +class ExportGrpcService extends ExportServiceImplBase { + + private static final String EXPORT_FILENAME_TEMPLATE = "%s_Abgabe.Abgabe.0401.xdomea"; + + static final int CHUNK_SIZE = 256 * 1024; + + private final ExportService exportService; + private final TaskExecutor taskExecutor; + + @Override + public void exportVorgang(GrpcExportVorgangRequest request, StreamObserver<GrpcExportVorgangResponse> responseObserver) { + var fileNameId = createFileNameId(); + sendFileName(responseObserver, fileNameId); + buildExportDownloader(request.getVorgangId(), fileNameId, responseObserver).start(); + } + + String createFileNameId() { + return UUID.randomUUID().toString(); + } + + void sendFileName(StreamObserver<GrpcExportVorgangResponse> responseObserver, String fileNameId) { + responseObserver.onNext(GrpcExportVorgangResponse.newBuilder() + .setVorgangFile(GrpcFile.newBuilder().setFileName(buildXdomeaFileName(fileNameId)).build()) + .build()); + } + + String buildXdomeaFileName(String fileNameId) { + return String.format(EXPORT_FILENAME_TEMPLATE, fileNameId); + } + + GrpcBinaryFileServerDownloader<GrpcExportVorgangResponse> buildExportDownloader(String vorgangId, String fileNameId, + StreamObserver<GrpcExportVorgangResponse> responseObserver) { + return GrpcBinaryFileServerDownloader.<GrpcExportVorgangResponse>builder() + .callObserver((CallStreamObserver<GrpcExportVorgangResponse>) responseObserver) + .taskExecutor(taskExecutor) + .downloadConsumer(outputStream -> exportService.writeXdomeaFileContent(vorgangId, fileNameId, outputStream)) + .chunkBuilder(this::buildExportVorgangChunkResponse) + .build(); + } + + GrpcExportVorgangResponse buildExportVorgangChunkResponse(ByteString chunk) { + return GrpcExportVorgangResponse.newBuilder() + .setVorgangFile(GrpcFile.newBuilder().setFileContent(chunk).build()) + .build(); + } +} diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/export/ExportService.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/export/ExportService.java index 0b00f824bf82ed111a83b5775037992866acb7c0..c2a4f346f0c4cc13d9cfbaa5d07550b692971280 100644 --- a/archive-manager-server/src/main/java/de/ozgcloud/archive/export/ExportService.java +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/export/ExportService.java @@ -1,8 +1,5 @@ package de.ozgcloud.archive.export; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; @@ -28,7 +25,6 @@ import de.ozgcloud.archive.vorgang.Eingang; import de.ozgcloud.archive.vorgang.EingangHeader; import de.ozgcloud.archive.vorgang.ExportVorgangService; import de.ozgcloud.archive.vorgang.VorgangWithEingang; -import de.ozgcloud.common.binaryfile.TempFileUtils; import de.ozgcloud.common.errorhandling.TechnicalException; import de.xoev.xdomea.AbgabeAbgabe0401; import lombok.RequiredArgsConstructor; @@ -49,10 +45,9 @@ class ExportService { private final ExportNachrichtService exportNachrichtService; private final ExportBescheidService exportBescheidService; - public void writeExport(String vorgangId, String filenameId, OutputStream out) { + public void writeXdomeaFileContent(String vorgangId, String filenameId, OutputStream outputStream) { var exportData = collectExportData(vorgangId, filenameId); - var zipFile = createZipFile(exportData); - writeZipFileContent(zipFile, out); + writeZipFile(exportData, outputStream); } ExportData collectExportData(String vorgangId, String filenameId) { @@ -98,12 +93,10 @@ class ExportService { return xDomeaXmlMarshaller.marshal(abgabe); } - File createZipFile(ExportData exportData) { - var file = TempFileUtils.createTmpFile().toFile(); - try (var zipOutputStream = new ZipOutputStream(new FileOutputStream(file))) { + void writeZipFile(ExportData exportData, OutputStream outputStream) { + try (var zipOutputStream = new ZipOutputStream(outputStream)) { putZipEntry(exportData.getExportFilename(), createXmlContent(exportData.getAbgabe()), zipOutputStream); putFilesIntoZip(exportData.getExportFiles(), zipOutputStream); - return file; } catch (Exception e) { throw new TechnicalException("Error creating xdomea zip file", e); } @@ -130,12 +123,4 @@ class ExportService { throw new TechnicalException("Cannot add file to ZIP.", e); } } - - void writeZipFileContent(File file, OutputStream outputStream) { - try (var fileInputStream = new FileInputStream(file)) { - fileInputStream.transferTo(outputStream); - } catch (Exception e) { - throw new TechnicalException("Error writing xdomea zip file to output stream", e); - } - } } diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/file/OzgFileRemoteService.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/file/OzgFileRemoteService.java index 6728ac5cbfbca671c3bde32ad2c0b77746a893f8..7ad5f233eb966f5ca4c9588281ea582f6d44d790 100644 --- a/archive-manager-server/src/main/java/de/ozgcloud/archive/file/OzgFileRemoteService.java +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/file/OzgFileRemoteService.java @@ -3,8 +3,12 @@ package de.ozgcloud.archive.file; import java.util.stream.Stream; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextAttachingInterceptor; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextProvider; +import de.ozgcloud.archive.ArchiveManagerConfiguration; import de.ozgcloud.archive.common.GrpcUtil; import de.ozgcloud.vorgang.grpc.file.FileServiceGrpc.FileServiceBlockingStub; import de.ozgcloud.vorgang.grpc.file.GrpcGetAttachmentsRequest; @@ -12,15 +16,19 @@ import de.ozgcloud.vorgang.grpc.file.GrpcGetRepresentationsRequest; import net.devh.boot.grpc.client.inject.GrpcClient; @Service -public class OzgFileRemoteService { +class OzgFileRemoteService { @GrpcClient(GrpcUtil.VORGANG_MANAGER_GRPC_CLIENT) private FileServiceBlockingStub grpcService; @Autowired private OzgFileMapper fileMapper; + @Autowired + @Qualifier(ArchiveManagerConfiguration.OZGCLOUD_CALL_CONTEXT_PROVIDER_NAME) + private OzgCloudCallContextProvider contextProvider; public Stream<OzgFile> getAttachmentsByEingang(String eingangId) { - return grpcService.getAttachments(buildGrpcGetAttachmentsRequest(eingangId)).getFileList().stream().map(fileMapper::fromGrpc); + return getGrpcServiceWithInterceptor().getAttachments(buildGrpcGetAttachmentsRequest(eingangId)).getFileList().stream() + .map(fileMapper::fromGrpc); } GrpcGetAttachmentsRequest buildGrpcGetAttachmentsRequest(String eingangId) { @@ -28,10 +36,15 @@ public class OzgFileRemoteService { } public Stream<OzgFile> getRepresentationsByEingang(String eingangId) { - return grpcService.getRepresentations(buildGrpcGetRepresentationsRequest(eingangId)).getFileList().stream().map(fileMapper::fromGrpc); + return getGrpcServiceWithInterceptor().getRepresentations(buildGrpcGetRepresentationsRequest(eingangId)).getFileList().stream() + .map(fileMapper::fromGrpc); } GrpcGetRepresentationsRequest buildGrpcGetRepresentationsRequest(String eingangId) { return GrpcGetRepresentationsRequest.newBuilder().setEingangId(eingangId).build(); } + + FileServiceBlockingStub getGrpcServiceWithInterceptor() { + return grpcService.withInterceptors(new OzgCloudCallContextAttachingInterceptor(contextProvider)); + } } diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/kommentar/KommentarRemoteService.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/kommentar/KommentarRemoteService.java index 546406b10b51b8b323eb00bd372509e69af34341..0fc1e206b70090a2ab26a0714d44139c5c1bad26 100644 --- a/archive-manager-server/src/main/java/de/ozgcloud/archive/kommentar/KommentarRemoteService.java +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/kommentar/KommentarRemoteService.java @@ -26,8 +26,12 @@ package de.ozgcloud.archive.kommentar; import java.util.stream.Stream; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextAttachingInterceptor; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextProvider; +import de.ozgcloud.archive.ArchiveManagerConfiguration; import de.ozgcloud.archive.common.GrpcUtil; import de.ozgcloud.vorgang.vorgangAttachedItem.GrpcFindVorgangAttachedItemRequest; import de.ozgcloud.vorgang.vorgangAttachedItem.VorgangAttachedItemServiceGrpc.VorgangAttachedItemServiceBlockingStub; @@ -42,9 +46,12 @@ class KommentarRemoteService { private VorgangAttachedItemServiceBlockingStub grpcService; @Autowired private KommentarMapper mapper; + @Autowired + @Qualifier(ArchiveManagerConfiguration.OZGCLOUD_CALL_CONTEXT_PROVIDER_NAME) + private OzgCloudCallContextProvider contextProvider; public Stream<Kommentar> findByVorgangId(String vorgangId) { - var response = grpcService.find(buildFindRequest(vorgangId)); + var response = getGrpcServiceWithInterceptor().find(buildFindRequest(vorgangId)); return response.getVorgangAttachedItemsList().stream().map(mapper::fromItem); } @@ -55,4 +62,8 @@ class KommentarRemoteService { .setItemName(ITEM_NAME) .build(); } + + VorgangAttachedItemServiceBlockingStub getGrpcServiceWithInterceptor() { + return grpcService.withInterceptors(new OzgCloudCallContextAttachingInterceptor(contextProvider)); + } } diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/vorgang/VorgangRemoteService.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/vorgang/VorgangRemoteService.java index aa56369097249ef16bb52967589c56a003616016..f78836c848ab2d65bb92f2717a1578421053d224 100644 --- a/archive-manager-server/src/main/java/de/ozgcloud/archive/vorgang/VorgangRemoteService.java +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/vorgang/VorgangRemoteService.java @@ -5,29 +5,31 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextAttachingInterceptor; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextProvider; import de.ozgcloud.archive.ArchiveManagerConfiguration; import de.ozgcloud.archive.common.GrpcUtil; -import de.ozgcloud.archive.common.callcontext.CallContextProvider; import de.ozgcloud.vorgang.vorgang.GrpcFindVorgangWithEingangRequest; import de.ozgcloud.vorgang.vorgang.VorgangServiceGrpc.VorgangServiceBlockingStub; +import lombok.RequiredArgsConstructor; import net.devh.boot.grpc.client.inject.GrpcClient; @Service(ArchiveManagerConfiguration.VORGANG_REMOTE_MANAGER_SERVICE_NAME) // NOSONAR +@RequiredArgsConstructor class VorgangRemoteService { @GrpcClient(GrpcUtil.VORGANG_MANAGER_GRPC_CLIENT) private VorgangServiceBlockingStub grpcService; - @Autowired @Qualifier(ArchiveManagerConfiguration.VORGANG_WITH_EINGANG_MAPPER_NAME) // NOSONAR private VorgangWithEingangMapper vorgangWithEingangMapper; - @Autowired - private CallContextProvider contextProvider; + @Qualifier(ArchiveManagerConfiguration.OZGCLOUD_CALL_CONTEXT_PROVIDER_NAME) + private OzgCloudCallContextProvider contextProvider; public VorgangWithEingang findVorgangWithEingang(String vorgangId) { return vorgangWithEingangMapper - .fromGrpc(getGrpcService().findVorgangWithEingang(buildFindVorgangWithEingangRequest(vorgangId)) + .fromGrpc(getGrpcServiceWithInterceptor() + .findVorgangWithEingang(buildFindVorgangWithEingangRequest(vorgangId)) .getVorgangWithEingang()); } @@ -37,7 +39,7 @@ class VorgangRemoteService { .build(); } - VorgangServiceBlockingStub getGrpcService() { + VorgangServiceBlockingStub getGrpcServiceWithInterceptor() { return grpcService.withInterceptors(new OzgCloudCallContextAttachingInterceptor(contextProvider)); } } diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/vorgang/VorgangWithEingangMapper.java b/archive-manager-server/src/main/java/de/ozgcloud/archive/vorgang/VorgangWithEingangMapper.java index db23db6c1b8793a2f9daa000e151fc1fc0d7b860..eb0227bb957814cf923ac4bd14c8be30c0ef754d 100644 --- a/archive-manager-server/src/main/java/de/ozgcloud/archive/vorgang/VorgangWithEingangMapper.java +++ b/archive-manager-server/src/main/java/de/ozgcloud/archive/vorgang/VorgangWithEingangMapper.java @@ -1,6 +1,7 @@ package de.ozgcloud.archive.vorgang; import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; import org.mapstruct.Mapper; import org.springframework.stereotype.Component; @@ -9,7 +10,7 @@ import de.ozgcloud.vorgang.common.grpc.GrpcFormDataMapper; import de.ozgcloud.vorgang.vorgang.GrpcVorgangWithEingang; @Mapper(uses = GrpcFormDataMapper.class) -@AnnotateWith(value = Component.class, elements = @AnnotateWith.Element(strings = ArchiveManagerConfiguration.VORGANG_WITH_EINGANG_MAPPER_NAME)) +@AnnotateWith(value = Component.class, elements = @Element(strings = ArchiveManagerConfiguration.VORGANG_WITH_EINGANG_MAPPER_NAME)) interface VorgangWithEingangMapper { VorgangWithEingang fromGrpc(GrpcVorgangWithEingang grpcVorgangWithEingang); diff --git a/archive-manager-server/src/main/java/de/ozgcloud/archive/ArchiveManagerServerApplication.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/ArchiveManagerServerApplication.java similarity index 99% rename from archive-manager-server/src/main/java/de/ozgcloud/archive/ArchiveManagerServerApplication.java rename to archive-manager-server/src/test/java/de/ozgcloud/archive/ArchiveManagerServerApplication.java index 3924138c32eea931007ce74621aa0eed48d5a3f2..a036d2df513da2748060c040d7a3515edad4039c 100644 --- a/archive-manager-server/src/main/java/de/ozgcloud/archive/ArchiveManagerServerApplication.java +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/ArchiveManagerServerApplication.java @@ -11,4 +11,5 @@ public class ArchiveManagerServerApplication { public static void main(String[] args) { SpringApplication.run(ArchiveManagerServerApplication.class, args); } + } diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/OzgcloudTestConfiguration.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/OzgcloudTestConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..b7b9f9c88a1b1816735750a7dfe1ded355890c1c --- /dev/null +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/OzgcloudTestConfiguration.java @@ -0,0 +1,46 @@ +package de.ozgcloud.archive; + +import org.mapstruct.factory.Mappers; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import de.ozgcloud.apilib.file.grpc.OzgCloudFileMapper; +import de.ozgcloud.apilib.user.UserProfileMapper; +import de.ozgcloud.apilib.vorgang.OzgCloudVorgangIdMapper; +import de.ozgcloud.apilib.vorgang.grpc.OzgCloudVorgangMapper; +import de.ozgcloud.apilib.vorgang.grpc.OzgCloudVorgangStubMapper; + +@Configuration +class OzgcloudTestConfiguration { + + @Bean + @ConditionalOnMissingBean + OzgCloudVorgangMapper ozgCloudVorgangMapper() { + return Mappers.getMapper(OzgCloudVorgangMapper.class); + } + + @Bean + @ConditionalOnMissingBean + OzgCloudVorgangStubMapper ozgCloudVorgangStubMapper() { + return Mappers.getMapper(OzgCloudVorgangStubMapper.class); + } + + @Bean + @ConditionalOnMissingBean + OzgCloudVorgangIdMapper ozgCloudVorgangIdMapper() { + return Mappers.getMapper(OzgCloudVorgangIdMapper.class); + } + + @Bean + @ConditionalOnMissingBean + UserProfileMapper ozgCloudUserProfileMapper() { + return Mappers.getMapper(UserProfileMapper.class); + } + + @Bean + @ConditionalOnMissingBean + OzgCloudFileMapper fileMapper() { + return Mappers.getMapper(OzgCloudFileMapper.class); + } +} diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/bescheid/BescheidRemoteServiceTest.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/bescheid/BescheidRemoteServiceTest.java index aa8986ab2521ef5adf0fd8fa7c7ef6dadbfd3d9c..dccdac6fb88e507627d82191aab09f74b02def4c 100644 --- a/archive-manager-server/src/test/java/de/ozgcloud/archive/bescheid/BescheidRemoteServiceTest.java +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/bescheid/BescheidRemoteServiceTest.java @@ -4,13 +4,20 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import java.util.stream.Stream; + +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.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedConstruction; import org.mockito.Spy; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextAttachingInterceptor; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextProvider; +import de.ozgcloud.archive.common.callcontext.CallContextProvider; import de.ozgcloud.archive.vorgang.VorgangWithEingangTestFactory; import de.ozgcloud.bescheid.BescheidServiceGrpc.BescheidServiceBlockingStub; import de.ozgcloud.bescheid.GrpcGetAllBescheidRequest; @@ -24,6 +31,8 @@ class BescheidRemoteServiceTest { private BescheidServiceBlockingStub grpcService; @Mock private BescheidMapper bescheidMapper; + @Mock + private CallContextProvider contextProvider; @Nested class TestFindByVorgangId { @@ -33,30 +42,41 @@ class BescheidRemoteServiceTest { private final Bescheid bescheid = BescheidTestFactory.create(); + @Mock + private BescheidServiceBlockingStub grpcServiceWithInterceptor; + @BeforeEach void setUp() { doReturn(request).when(service).buildGetAllBescheidRequest(VorgangWithEingangTestFactory.ID); - when(grpcService.getAll(any())).thenReturn(response); + doReturn(grpcServiceWithInterceptor).when(service).getGrpcServiceWithInterceptor(); + when(grpcServiceWithInterceptor.getAll(any())).thenReturn(response); } @Test void shouldBuildGetAllRequest() { - service.findByVorgangId(VorgangWithEingangTestFactory.ID); + callService(); verify(service).buildGetAllBescheidRequest(VorgangWithEingangTestFactory.ID); } + @Test + void shouldGetGrpcServiceWithInterceptor() { + callService(); + + verify(service).getGrpcServiceWithInterceptor(); + } + @Test void shouldCallGrpcService() { - service.findByVorgangId(VorgangWithEingangTestFactory.ID); + callService(); - verify(grpcService).getAll(request); + verify(grpcServiceWithInterceptor).getAll(request); } @Test void shouldMapBescheid() { - service.findByVorgangId(VorgangWithEingangTestFactory.ID).toList(); + callService().toList(); verify(bescheidMapper).fromGrpc(GrpcGetAllBescheidResponseTestFactory.GRPC_BESCHEID, VorgangWithEingangTestFactory.ID); } @@ -65,10 +85,69 @@ class BescheidRemoteServiceTest { void shouldReturnStreamOfBescheide() { when(bescheidMapper.fromGrpc(any(), any())).thenReturn(bescheid); - var bescheide = service.findByVorgangId(VorgangWithEingangTestFactory.ID); + var bescheide = callService(); assertThat(bescheide).containsExactly(bescheid); } + + private Stream<Bescheid> callService() { + return service.findByVorgangId(VorgangWithEingangTestFactory.ID); + } } + @Nested + class TestGetGrpcServiceWithInterceptor { + + private MockedConstruction<OzgCloudCallContextAttachingInterceptor> mockedConstructionInterceptor; + private OzgCloudCallContextProvider argumentContextProvider; + @Mock + private OzgCloudCallContextAttachingInterceptor interceptor; + + @BeforeEach + void setUpMocks() { + mockedConstructionInterceptor = mockConstruction(OzgCloudCallContextAttachingInterceptor.class, (mock, context) -> { + argumentContextProvider = (OzgCloudCallContextProvider) context.arguments().get(0); + interceptor = mock; + }); + } + + @AfterEach + void cleanUp() { + mockedConstructionInterceptor.close(); + } + + @Test + void shoudCreateOzgCloudCallContextAttachingInterceptor() { + callService(); + + assertThat(mockedConstructionInterceptor.constructed()).hasSize(1); + } + + @Test + void shoudCreateOzgCloudCallContextAttachingInterceptorWithContextProvider() { + callService(); + + assertThat(argumentContextProvider).isEqualTo(contextProvider); + } + + @Test + void shouldSetInterceptor() { + callService(); + + verify(grpcService).withInterceptors(interceptor); + } + + @Test + void shouldReturnServiceStub() { + when(grpcService.withInterceptors(any(OzgCloudCallContextAttachingInterceptor.class))).thenReturn(grpcService); + + var returnedServiceStub = callService(); + + assertThat(returnedServiceStub).isEqualTo(grpcService); + } + + private BescheidServiceBlockingStub callService() { + return service.getGrpcServiceWithInterceptor(); + } + } } diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/bescheid/DocumentRemoteServiceTest.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/bescheid/DocumentRemoteServiceTest.java index 07c9a76882c21436009c1469453c0a0e200bb4cb..e709b76a3daf7f6a090add5bccb379455d4541a7 100644 --- a/archive-manager-server/src/test/java/de/ozgcloud/archive/bescheid/DocumentRemoteServiceTest.java +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/bescheid/DocumentRemoteServiceTest.java @@ -4,27 +4,34 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +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.ArgumentMatcher; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedConstruction; import org.mockito.Spy; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextAttachingInterceptor; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextProvider; +import de.ozgcloud.archive.common.callcontext.CallContextProvider; import de.ozgcloud.common.binaryfile.FileId; import de.ozgcloud.document.DocumentServiceGrpc.DocumentServiceBlockingStub; import de.ozgcloud.document.GrpcGetDocumentRequest; class DocumentRemoteServiceTest { + @Spy + @InjectMocks + private DocumentRemoteService service; @Mock private DocumentServiceBlockingStub grpcService; @Mock private DocumentMapper documentMapper; - @Spy - @InjectMocks - private DocumentRemoteService service; + @Mock + private CallContextProvider contextProvider; @Nested class TestGetDocument { @@ -34,17 +41,28 @@ class DocumentRemoteServiceTest { private final FileId documentFileId = FileId.from(BescheidTestFactory.BESCHEID_DOCUMENT); + @Mock + private DocumentServiceBlockingStub grpcServiceWithInterceptor; + @BeforeEach void init() { - when(grpcService.getDocument(argThat(REQUEST_MATCHER))).thenReturn(GrpcGetDocumentResponseTestFactory.create()); + doReturn(grpcServiceWithInterceptor).when(service).getGrpcServiceWithInterceptor(); + when(grpcServiceWithInterceptor.getDocument(argThat(REQUEST_MATCHER))).thenReturn(GrpcGetDocumentResponseTestFactory.create()); when(documentMapper.fromGrpc(GrpcGetDocumentResponseTestFactory.GRPC_DOCUMENT)).thenReturn(documentFileId); } + @Test + void shouldGetGrpcServiceWithInterceptor() { + callService(); + + verify(service).getGrpcServiceWithInterceptor(); + } + @Test void shouldCallGrpcService() { callService(); - verify(grpcService).getDocument(argThat(REQUEST_MATCHER)); + verify(grpcServiceWithInterceptor).getDocument(argThat(REQUEST_MATCHER)); } @Test @@ -65,4 +83,61 @@ class DocumentRemoteServiceTest { return service.getDocument(BescheidTestFactory.BESCHEID_DOCUMENT); } } + + @Nested + class TestGetGrpcServiceWithInterceptor { + + private MockedConstruction<OzgCloudCallContextAttachingInterceptor> mockedConstructionInterceptor; + private OzgCloudCallContextProvider argumentContextProvider; + @Mock + private OzgCloudCallContextAttachingInterceptor interceptor; + + @BeforeEach + void setUpMocks() { + mockedConstructionInterceptor = mockConstruction(OzgCloudCallContextAttachingInterceptor.class, (mock, context) -> { + argumentContextProvider = (OzgCloudCallContextProvider) context.arguments().get(0); + interceptor = mock; + }); + } + + @AfterEach + void cleanUp() { + mockedConstructionInterceptor.close(); + } + + @Test + void shoudCreateOzgCloudCallContextAttachingInterceptor() { + callService(); + + assertThat(mockedConstructionInterceptor.constructed()).hasSize(1); + } + + @Test + void shoudCreateOzgCloudCallContextAttachingInterceptorWithContextProvider() { + callService(); + + assertThat(argumentContextProvider).isEqualTo(contextProvider); + } + + @Test + void shouldSetInterceptor() { + callService(); + + verify(grpcService).withInterceptors(interceptor); + } + + @Test + void shouldReturnServiceStub() { + when(grpcService.withInterceptors(any(OzgCloudCallContextAttachingInterceptor.class))).thenReturn(grpcService); + + var returnedServiceStub = callService(); + + assertThat(returnedServiceStub).isEqualTo(grpcService); + } + + private DocumentServiceBlockingStub callService() { + return service.getGrpcServiceWithInterceptor(); + } + } + } diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/common/binaryfile/BinaryFileRemoteServiceTest.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/common/binaryfile/BinaryFileRemoteServiceTest.java index 2720fd1569d9e156df6b7eb1d0621cbfb5adcbb3..d9431f674e3777c2d080c7432040d31b5c9b3411 100644 --- a/archive-manager-server/src/test/java/de/ozgcloud/archive/common/binaryfile/BinaryFileRemoteServiceTest.java +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/common/binaryfile/BinaryFileRemoteServiceTest.java @@ -1,6 +1,7 @@ package de.ozgcloud.archive.common.binaryfile; import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.io.OutputStream; @@ -8,17 +9,22 @@ import java.util.List; import java.util.UUID; import java.util.stream.Stream; +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.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedConstruction; import org.mockito.Spy; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextAttachingInterceptor; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextProvider; import de.ozgcloud.apilib.file.OzgCloudFile; import de.ozgcloud.apilib.file.OzgCloudFileId; import de.ozgcloud.apilib.file.OzgCloudFileTestFactory; import de.ozgcloud.apilib.file.grpc.GrpcOzgCloudFileService; +import de.ozgcloud.archive.common.callcontext.CallContextProvider; import de.ozgcloud.archive.file.OzgFile; import de.ozgcloud.archive.file.OzgFileMapper; import de.ozgcloud.archive.file.OzgFileTestFactory; @@ -31,19 +37,17 @@ class BinaryFileRemoteServiceTest { @InjectMocks @Spy - private BinaryFileRemoteService remoteService; - + private BinaryFileRemoteService service; @Mock private BinaryFileServiceBlockingStub grpcService; - @Mock private GrpcOzgCloudFileService grpcOzgCloudFileService; - @Mock private OzgFileMapper ozgFileMapper; - @Mock private FileIdMapper fileIdMapper; + @Mock + private CallContextProvider contextProvider; @Nested class TestGetFiles { @@ -53,38 +57,49 @@ class BinaryFileRemoteServiceTest { private final FileId fileId1 = FileId.from(UUID.randomUUID().toString()); private final FileId fileId2 = FileId.from(UUID.randomUUID().toString()); + @Mock + private BinaryFileServiceBlockingStub grpcServiceWithInterceptor; + @BeforeEach void mock() { - doReturn(request).when(remoteService).buildBinaryFilesRequest(List.of(fileId1, fileId2)); - when(grpcService.findBinaryFilesMetaData(request)).thenReturn(response); + doReturn(grpcServiceWithInterceptor).when(service).getGrpcServiceWithInterceptor(); + doReturn(request).when(service).buildBinaryFilesRequest(List.of(fileId1, fileId2)); + when(grpcServiceWithInterceptor.findBinaryFilesMetaData(request)).thenReturn(response); + } + + @Test + void shouldGetGrpcServiceWithInterceptor() { + callService(); + + verify(service).getGrpcServiceWithInterceptor(); } @Test void shouldBuildBinaryFilesRequest() { callService(); - verify(remoteService).buildBinaryFilesRequest(List.of(fileId1, fileId2)); + verify(service).buildBinaryFilesRequest(List.of(fileId1, fileId2)); } @Test void shouldCallGrpcService() { callService(); - verify(grpcService).findBinaryFilesMetaData(request); + verify(grpcServiceWithInterceptor).findBinaryFilesMetaData(request); } @Test void shouldMapGrpcFindFilesRespone() { callService(); - verify(remoteService).mapGrpcFindFilesRespone(response); + verify(service).mapGrpcFindFilesRespone(response); } @Test void shouldReturnMappedOzgFiles() { var mappedOzgFile1 = OzgFileTestFactory.create(); var mappedOzgFile2 = OzgFileTestFactory.create(); - doReturn(Stream.of(mappedOzgFile1, mappedOzgFile2)).when(remoteService).mapGrpcFindFilesRespone(response); + doReturn(Stream.of(mappedOzgFile1, mappedOzgFile2)).when(service).mapGrpcFindFilesRespone(response); var ozgFiles = callService(); @@ -92,7 +107,7 @@ class BinaryFileRemoteServiceTest { } private Stream<OzgFile> callService() { - return remoteService.getFiles(List.of(fileId1, fileId2)); + return service.getFiles(List.of(fileId1, fileId2)); } } @@ -119,7 +134,7 @@ class BinaryFileRemoteServiceTest { } private Stream<OzgFile> callService() { - return remoteService.mapGrpcFindFilesRespone(GrpcFindFilesResponseTestFactory.create()); + return service.mapGrpcFindFilesRespone(GrpcFindFilesResponseTestFactory.create()); } } @@ -136,7 +151,7 @@ class BinaryFileRemoteServiceTest { } private GrpcBinaryFilesRequest callService() { - return remoteService.buildBinaryFilesRequest(List.of(fileId1, fileId2)); + return service.buildBinaryFilesRequest(List.of(fileId1, fileId2)); } } @@ -186,7 +201,7 @@ class BinaryFileRemoteServiceTest { } private OzgFile callService() { - return remoteService.getFile(OzgFileTestFactory.ID); + return service.getFile(OzgFileTestFactory.ID); } } @@ -216,7 +231,63 @@ class BinaryFileRemoteServiceTest { } private void callService() { - remoteService.writeFileContent(fileId, outputStream); + service.writeFileContent(fileId, outputStream); + } + } + + @Nested + class TestGetGrpcServiceWithInterceptor { + + private MockedConstruction<OzgCloudCallContextAttachingInterceptor> mockedConstructionInterceptor; + private OzgCloudCallContextProvider argumentContextProvider; + @Mock + private OzgCloudCallContextAttachingInterceptor interceptor; + + @BeforeEach + void setUpMocks() { + mockedConstructionInterceptor = mockConstruction(OzgCloudCallContextAttachingInterceptor.class, (mock, context) -> { + argumentContextProvider = (OzgCloudCallContextProvider) context.arguments().get(0); + interceptor = mock; + }); + } + + @AfterEach + void cleanUp() { + mockedConstructionInterceptor.close(); + } + + @Test + void shoudCreateOzgCloudCallContextAttachingInterceptor() { + callService(); + + assertThat(mockedConstructionInterceptor.constructed()).hasSize(1); + } + + @Test + void shoudCreateOzgCloudCallContextAttachingInterceptorWithContextProvider() { + callService(); + + assertThat(argumentContextProvider).isEqualTo(contextProvider); + } + + @Test + void shouldSetInterceptor() { + callService(); + + verify(grpcService).withInterceptors(interceptor); + } + + @Test + void shouldReturnServiceStub() { + when(grpcService.withInterceptors(any(OzgCloudCallContextAttachingInterceptor.class))).thenReturn(grpcService); + + var returnedServiceStub = callService(); + + assertThat(returnedServiceStub).isEqualTo(grpcService); + } + + private BinaryFileServiceBlockingStub callService() { + return service.getGrpcServiceWithInterceptor(); } } } diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/common/callcontext/ArchiveCallContextTestFactory.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/common/callcontext/ArchiveCallContextTestFactory.java index 19ee24380258bd641e1b9aaf9a3c40980a142259..004f98698822d130b8b91a17d0eb99025b04eed7 100644 --- a/archive-manager-server/src/test/java/de/ozgcloud/archive/common/callcontext/ArchiveCallContextTestFactory.java +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/common/callcontext/ArchiveCallContextTestFactory.java @@ -1,20 +1,22 @@ package de.ozgcloud.archive.common.callcontext; -import java.util.Collection; +import static de.ozgcloud.common.grpc.GrpcUtil.*; + import java.util.List; import java.util.UUID; import com.thedeanda.lorem.LoremIpsum; import de.ozgcloud.archive.common.callcontext.ArchiveCallContext.ArchiveCallContextBuilder; +import io.grpc.Metadata; public class ArchiveCallContextTestFactory { public static final String REQUEST_ID = UUID.randomUUID().toString(); public static final String USER_ID = UUID.randomUUID().toString(); public static final String CLIENT_NAME = LoremIpsum.getInstance().getName(); - public static final Collection<String> ORGANISATIONS_EINHEITEN_IDS = List.of(UUID.randomUUID().toString()); - public static final boolean ACCESS_LIMITED = true; + public static final List<String> ORGANISATIONS_EINHEITEN_IDS = List.of(UUID.randomUUID().toString()); + public static final Boolean ACCESS_LIMITED = true; public static ArchiveCallContext create() { return createBuilder() @@ -29,4 +31,12 @@ public class ArchiveCallContextTestFactory { .accessLimited(ACCESS_LIMITED) .accessLimitedToOrgaIds(ORGANISATIONS_EINHEITEN_IDS); } + + public static Metadata createMetadata() { + var metadata = new Metadata(); + metadata.put(createKeyOf(CallContextInterceptor.KEY_ACCESS_LIMITED), ACCESS_LIMITED.toString().getBytes()); + metadata.put(createKeyOf(CallContextInterceptor.KEY_ACCESS_LIMITED_ORGAID), ORGANISATIONS_EINHEITEN_IDS.get(0).getBytes()); + metadata.put(createKeyOf(CallContextInterceptor.KEY_USER_ID), USER_ID.getBytes()); + return metadata; + } } diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/common/callcontext/CallContextInterceptorTest.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/common/callcontext/CallContextInterceptorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..644c95dfbb8a9b4161bf1ce709589240635c9f4b --- /dev/null +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/common/callcontext/CallContextInterceptorTest.java @@ -0,0 +1,122 @@ +package de.ozgcloud.archive.common.callcontext; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +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.Mock; +import org.mockito.MockedStatic; +import org.mockito.Spy; + +import io.grpc.Context; +import io.grpc.Contexts; +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.ServerCall.Listener; +import io.grpc.ServerCallHandler; + +class CallContextInterceptorTest { + + @Spy + private CallContextInterceptor interceptor; + + @Nested + class TestInterceptCall { + + @Mock + private ServerCall<Object, Object> call; + @Mock + private Metadata headers; + @Mock + private ServerCallHandler<Object, Object> next; + @Mock + private Context grpcContext; + @Mock + private Context newGrpcContext; + @Mock + private Listener<Object> listener; + + private MockedStatic<Context> mockedStaticContext; + + private final ArchiveCallContext archiveCallContext = ArchiveCallContextTestFactory.create(); + + @BeforeEach + void setUpMock() { + doReturn(archiveCallContext).when(interceptor).createArchiveCallContext(headers); + mockedStaticContext = mockStatic(Context.class); + mockedStaticContext.when(Context::current).thenReturn(grpcContext); + when(grpcContext.withValue(CallContextInterceptor.CONTEXT_KEY_CALL_CONTEXT, archiveCallContext)).thenReturn(newGrpcContext); + } + + @AfterEach + void closeMock() { + mockedStaticContext.close(); + } + + @Test + void shouldCreateArchiveCallContext() { + intercept(); + + verify(interceptor).createArchiveCallContext(headers); + } + + @Test + void shouldSetArchiveCallContextInGrpcContext() { + intercept(); + + verify(grpcContext).withValue(CallContextInterceptor.CONTEXT_KEY_CALL_CONTEXT, archiveCallContext); + } + + @Test + void shouldAttachNewGrpcContextToCall() { + try (var mockedStaticContexts = mockStatic(Contexts.class)) { + intercept(); + + mockedStaticContexts.verify(() -> Contexts.interceptCall(newGrpcContext, call, headers, next)); + } + } + + @Test + void shouldReturnListenerWithAttachedGrpcContext() { + try (var mockedStaticContexts = mockStatic(Contexts.class)) { + mockedStaticContexts.when(() -> Contexts.interceptCall(newGrpcContext, call, headers, next)).thenReturn(listener); + + var returnedListener = intercept(); + + assertThat(returnedListener).isEqualTo(listener); + } + } + + private Listener<Object> intercept() { + return interceptor.interceptCall(call, headers, next); + } + } + + @Nested + class TestCreateArchiveCallContext { + + @Test + void shouldCreateArchiveCallContext() { + var archiveCallContext = callInterceptor(); + + assertThat(archiveCallContext).usingRecursiveComparison().ignoringFields("requestId") + .isEqualTo(ArchiveCallContextTestFactory.createBuilder() + .clientName(ArchiveCallContext.CLIENT_NAME) + .build()); + } + + @Test + void shouldHaveRequestId() { + var archiveCallContext = callInterceptor(); + + assertThat(archiveCallContext.getRequestId()).isNotBlank(); + } + + private ArchiveCallContext callInterceptor() { + return interceptor.createArchiveCallContext(ArchiveCallContextTestFactory.createMetadata()); + } + } +} diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/common/callcontext/CallContextProviderTest.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/common/callcontext/CallContextProviderTest.java index 2cf686fea26179a98a09b041cbd3f7931d4c24be..6abea45385afc05903fad1155b64e3908e5db0b6 100644 --- a/archive-manager-server/src/test/java/de/ozgcloud/archive/common/callcontext/CallContextProviderTest.java +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/common/callcontext/CallContextProviderTest.java @@ -3,6 +3,7 @@ package de.ozgcloud.archive.common.callcontext; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; @@ -17,15 +18,28 @@ class CallContextProviderTest { @InjectMocks private CallContextProvider contextProvider; - @Mock - private ArchiveCallContext archiveCallContext; - @Mock private ArchiveCallContextMapper mapper; + @Mock + private GrpcCallContextSupplier contextSupplier; @Nested class TestProvideContext { + private final ArchiveCallContext archiveCallContext = ArchiveCallContextTestFactory.create(); + + @BeforeEach + void mock() { + when(contextSupplier.get()).thenReturn(archiveCallContext); + } + + @Test + void shouldGetArchiveCallContextFromGrpcContext() { + callProvider(); + + verify(contextSupplier).get(); + } + @Test void shouldMapArchiveCallContext() { callProvider(); diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/common/command/CommandRemoteServiceTest.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/common/command/CommandRemoteServiceTest.java index 39618f8c32ed30782c17f74d0831594758372be3..3aeccc092624d2368c7a7dc4d8ef92e3546d6da2 100644 --- a/archive-manager-server/src/test/java/de/ozgcloud/archive/common/command/CommandRemoteServiceTest.java +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/common/command/CommandRemoteServiceTest.java @@ -7,6 +7,7 @@ import static org.mockito.Mockito.*; import java.util.Collections; import java.util.Optional; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -14,8 +15,12 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedConstruction; import org.mockito.Spy; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextAttachingInterceptor; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextProvider; +import de.ozgcloud.archive.common.callcontext.CallContextProvider; import de.ozgcloud.archive.vorgang.VorgangWithEingangTestFactory; import de.ozgcloud.vorgang.grpc.command.CommandServiceGrpc.CommandServiceBlockingStub; import de.ozgcloud.vorgang.grpc.command.GrpcCommand; @@ -23,6 +28,7 @@ import de.ozgcloud.vorgang.grpc.command.GrpcCommandsResponse; import de.ozgcloud.vorgang.grpc.command.GrpcFindCommandsRequest; class CommandRemoteServiceTest { + @Spy @InjectMocks private CommandRemoteService service; @@ -30,6 +36,8 @@ class CommandRemoteServiceTest { private CommandServiceBlockingStub grpcService; @Mock private CommandMapper mapper; + @Mock + private CallContextProvider contextProvider; @Nested class TestFindCommands { @@ -39,26 +47,37 @@ class CommandRemoteServiceTest { private final GrpcCommand grpcCommand = GrpcCommand.newBuilder().build(); private final GrpcCommandsResponse response = GrpcCommandsResponse.newBuilder().addAllCommand(Collections.singleton(grpcCommand)).build(); - @Captor // NOSONAR + @Mock + private CommandServiceBlockingStub grpcServiceWithInterceptor; + + @Captor private ArgumentCaptor<GrpcFindCommandsRequest> requestCaptor; @BeforeEach void init() { - when(grpcService.findCommands(any())).thenReturn(response); + doReturn(grpcServiceWithInterceptor).when(service).getGrpcServiceWithInterceptor(); + when(grpcServiceWithInterceptor.findCommands(any())).thenReturn(response); + } + + @Test + void shouldGetGrpcServiceWithInterceptor() { + service.findCommands(VorgangWithEingangTestFactory.ID, Optional.empty(), Optional.empty()); + + verify(service).getGrpcServiceWithInterceptor(); } @Test void shouldCallGrpcService() { service.findCommands(VorgangWithEingangTestFactory.ID, Optional.empty(), Optional.empty()); - verify(grpcService).findCommands(notNull()); + verify(grpcServiceWithInterceptor).findCommands(notNull()); } @Test void shouldRequestWithVorgangId() { service.findCommands(VorgangWithEingangTestFactory.ID, Optional.empty(), Optional.empty()); - verify(grpcService).findCommands(requestCaptor.capture()); + verify(grpcServiceWithInterceptor).findCommands(requestCaptor.capture()); assertThat(requestCaptor.getValue().getVorgangId()).isEqualTo(VorgangWithEingangTestFactory.ID); } @@ -67,7 +86,7 @@ class CommandRemoteServiceTest { void shouldRequestWithStatus() { service.findCommands(VorgangWithEingangTestFactory.ID, Optional.of(COMMAND_STATUS_FINISHED), Optional.empty()); - verify(grpcService).findCommands(requestCaptor.capture()); + verify(grpcServiceWithInterceptor).findCommands(requestCaptor.capture()); assertThat(requestCaptor.getValue().getStatusList()).contains(COMMAND_STATUS_FINISHED); } @@ -76,7 +95,7 @@ class CommandRemoteServiceTest { void shouldRequestWithOrder() { service.findCommands(VorgangWithEingangTestFactory.ID, Optional.empty(), Optional.of(REDIRECT_VORGANG_ORDER)); - verify(grpcService).findCommands(requestCaptor.capture()); + verify(grpcServiceWithInterceptor).findCommands(requestCaptor.capture()); assertThat(requestCaptor.getValue().getOrderString()).isEqualTo(REDIRECT_VORGANG_ORDER); } @@ -89,4 +108,60 @@ class CommandRemoteServiceTest { } } + @Nested + class TestGetGrpcServiceWithInterceptor { + + private MockedConstruction<OzgCloudCallContextAttachingInterceptor> mockedConstructionInterceptor; + private OzgCloudCallContextProvider argumentContextProvider; + @Mock + private OzgCloudCallContextAttachingInterceptor interceptor; + + @BeforeEach + void setUpMocks() { + mockedConstructionInterceptor = mockConstruction(OzgCloudCallContextAttachingInterceptor.class, (mock, context) -> { + argumentContextProvider = (OzgCloudCallContextProvider) context.arguments().get(0); + interceptor = mock; + }); + } + + @AfterEach + void cleanUp() { + mockedConstructionInterceptor.close(); + } + + @Test + void shoudCreateOzgCloudCallContextAttachingInterceptor() { + callService(); + + assertThat(mockedConstructionInterceptor.constructed()).hasSize(1); + } + + @Test + void shoudCreateOzgCloudCallContextAttachingInterceptorWithContextProvider() { + callService(); + + assertThat(argumentContextProvider).isEqualTo(contextProvider); + } + + @Test + void shouldSetInterceptor() { + callService(); + + verify(grpcService).withInterceptors(interceptor); + } + + @Test + void shouldReturnServiceStub() { + when(grpcService.withInterceptors(any(OzgCloudCallContextAttachingInterceptor.class))).thenReturn(grpcService); + + var returnedServiceStub = callService(); + + assertThat(returnedServiceStub).isEqualTo(grpcService); + } + + private CommandServiceBlockingStub callService() { + return service.getGrpcServiceWithInterceptor(); + } + } + } diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/common/errorhandling/ExceptionHandlerTest.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/common/errorhandling/ExceptionHandlerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..bd34dde45f64c69fa26b396cf19509979ff7e6cf --- /dev/null +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/common/errorhandling/ExceptionHandlerTest.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2024 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.archive.common.errorhandling; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.UUID; + +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.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.InjectMocks; +import org.mockito.MockedStatic; +import org.mockito.Spy; + +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.common.errorhandling.ExceptionUtil; +import io.grpc.Metadata; +import io.grpc.Status; +import io.grpc.StatusException; +import io.grpc.StatusRuntimeException; + +class ExceptionHandlerTest { + + @Spy + @InjectMocks + private ExceptionHandler handler; + + @Nested + class TestHandleStatusRuntimeException { + + private final Metadata metadata = MetadataTestFactory.create(); + private final String exceptionId = UUID.randomUUID().toString(); + private final String messageWithExceptionId = LoremIpsum.getInstance().getWords(5); + private final StatusRuntimeException exception = new StatusRuntimeException(Status.INTERNAL, metadata); + + private MockedStatic<ExceptionUtil> mockedStaticExceptionUtil; + + @BeforeEach + void mockExceptionId() { + doReturn(exceptionId).when(handler).createExceptionId(); + } + + @BeforeEach + void setUpMocks() { + mockedStaticExceptionUtil = mockStatic(ExceptionUtil.class); + mockedStaticExceptionUtil + .when(() -> ExceptionUtil.formatMessageWithExceptionId(ExceptionHandler.STATUS_RUNTIME_EXCEPTION_MESSAGE, exceptionId)) + .thenReturn(messageWithExceptionId); + doReturn(Status.ABORTED).when(handler).expandStatus(exception, messageWithExceptionId); + doReturn(metadata).when(handler).buildMetadataFromStatusRuntimeException(exception, exceptionId); + } + + @AfterEach + void closeStaticMock() { + mockedStaticExceptionUtil.close(); + } + + @Test + void shouldCreateExceptionId() { + handleException(Status.INTERNAL); + + verify(handler).createExceptionId(); + } + + @Test + void shouldFormatMessageWithExceptionId() { + handleException(Status.INTERNAL); + + mockedStaticExceptionUtil + .verify(() -> ExceptionUtil.formatMessageWithExceptionId(ExceptionHandler.STATUS_RUNTIME_EXCEPTION_MESSAGE, exceptionId)); + } + + @Test + void shouldExpandStatus() { + handleException(Status.INTERNAL); + + verify(handler).expandStatus(exception, messageWithExceptionId); + } + + @Test + void shouldBuildMetadataFromStatusRuntimeException() { + handleException(Status.INTERNAL); + + verify(handler).buildMetadataFromStatusRuntimeException(exception, exceptionId); + } + + @Test + void shouldCreateStatusException() { + handleException(Status.INTERNAL); + + verify(handler).createStatusException(Status.ABORTED, metadata); + } + + @Test + void shouldReturnStatusException() { + var statusException = new StatusException(Status.CANCELLED); + doReturn(statusException).when(handler).createStatusException(Status.ABORTED, metadata); + + var returnedStatusException = handleException(Status.INTERNAL); + + assertThat(returnedStatusException).isEqualTo(statusException); + } + + private StatusException handleException(Status status) { + return handler.handleStatusRuntimeException(exception); + } + } + + @Nested + class TestExpandStatus { + + private final String messageWithExceptionId = LoremIpsum.getInstance().getWords(5); + + @ParameterizedTest + @EnumSource + void shouldPropagateStatusCode(Status.Code statusCode) { + var status = callHandler(Status.fromCode(statusCode)); + + assertThat(status.getCode()).isEqualTo(statusCode); + } + + @Test + void shouldHaveMessageInDescription() { + var status = callHandler(Status.INTERNAL); + + assertThat(status.getDescription()).contains(messageWithExceptionId); + } + + private Status callHandler(Status status) { + return handler.expandStatus(new StatusRuntimeException(status), messageWithExceptionId); + } + } + + @Nested + class TestBuildMetadataFromStatusRuntimeException { + + private final Metadata metadata = MetadataTestFactory.create(); + private final String exceptionId = UUID.randomUUID().toString(); + + @Test + void shouldAddExceptionIdToMetadata() { + var metadata = callHandler(); + + assertThat(metadata.get(ExceptionHandler.KEY_EXCEPTION_ID)).isEqualTo(exceptionId); + } + + @Test + void shouldHandleNullMetadata() { + assertDoesNotThrow(() -> callHandler()); + + } + + @Test + void shouldPropagateMetadata() { + var metadata = callHandler(); + + assertThat(metadata.keys()).contains(MetadataTestFactory.KEY_STRING_1, MetadataTestFactory.KEY_STRING_2); + } + + private Metadata callHandler() { + return handler.buildMetadataFromStatusRuntimeException(new StatusRuntimeException(Status.ABORTED, metadata), exceptionId); + } + } +} \ No newline at end of file diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/common/errorhandling/MetadataTestFactory.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/common/errorhandling/MetadataTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..8ff81b793d53bcec602a0f17b9d71ab5c134cb38 --- /dev/null +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/common/errorhandling/MetadataTestFactory.java @@ -0,0 +1,23 @@ +package de.ozgcloud.archive.common.errorhandling; + +import com.thedeanda.lorem.LoremIpsum; + +import io.grpc.Metadata; +import io.grpc.Metadata.Key; + +public class MetadataTestFactory { + + public static final String KEY_STRING_1 = LoremIpsum.getInstance().getWords(1); + public static final String KEY_STRING_2 = LoremIpsum.getInstance().getWords(1); + public static final Key<String> KEY_1 = Key.of(KEY_STRING_1, Metadata.ASCII_STRING_MARSHALLER); + public static final Key<String> KEY_2 = Key.of(KEY_STRING_2, Metadata.ASCII_STRING_MARSHALLER); + public static final String VALUE_1 = LoremIpsum.getInstance().getWords(1); + public static final String VALUE_2 = LoremIpsum.getInstance().getWords(1); + + public static Metadata create() { + var metadata = new Metadata(); + metadata.put(KEY_1, VALUE_1); + metadata.put(KEY_2, VALUE_2); + return metadata; + } +} diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/export/ExportGrpcServiceITCase.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/export/ExportGrpcServiceITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..ec897cfb7b5e8d3d1dbfd42578fbac8fe733d21a --- /dev/null +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/export/ExportGrpcServiceITCase.java @@ -0,0 +1,216 @@ +package de.ozgcloud.archive.export; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Iterator; +import java.util.stream.Stream; +import java.util.zip.ZipInputStream; + +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.archive.bescheid.BescheidExportData; +import de.ozgcloud.archive.bescheid.ExportBescheidService; +import de.ozgcloud.archive.common.binaryfile.BinaryFileService; +import de.ozgcloud.archive.common.user.UserProfileTestFactory; +import de.ozgcloud.archive.common.user.UserService; +import de.ozgcloud.archive.file.ExportFileService; +import de.ozgcloud.archive.grpc.export.ExportServiceGrpc; +import de.ozgcloud.archive.grpc.export.GrpcExportVorgangRequest; +import de.ozgcloud.archive.grpc.export.GrpcExportVorgangResponse; +import de.ozgcloud.archive.historie.ExportHistorieService; +import de.ozgcloud.archive.kommentar.ExportKommentarService; +import de.ozgcloud.archive.kommentar.KommentarTestFactory; +import de.ozgcloud.archive.kommentar.KommentarsExportData; +import de.ozgcloud.archive.kommentar.KommentarsExportDataTestFactory; +import de.ozgcloud.archive.postfach.ExportNachrichtService; +import de.ozgcloud.archive.postfach.PostfachMailExportData; +import de.ozgcloud.archive.vorgang.ExportVorgangService; +import de.ozgcloud.archive.vorgang.VorgangTypeTestFactory; +import de.ozgcloud.archive.vorgang.VorgangWithEingang; +import de.ozgcloud.archive.vorgang.VorgangWithEingangTestFactory; +import de.ozgcloud.common.test.ITCase; +import lombok.SneakyThrows; +import net.devh.boot.grpc.client.inject.GrpcClient; + +@SpringBootTest(properties = { + "grpc.server.inProcessName=GrpcExportService-test", + "grpc.client.inProcess.address=in-process:GrpcExportService-test" +}) +@SpringJUnitConfig(classes = { GrpcIntegrationTestConfiguration.class }) +@ITCase +class ExportGrpcServiceITCase { + + @GrpcClient("inProcess") + private ExportServiceGrpc.ExportServiceBlockingStub clientService; + + @MockBean + private ExportFileService exportFileService; + @MockBean + private ExportVorgangService exportVorgangService; + @MockBean + private ExportHistorieService exportHistorieService; + @MockBean + private ExportKommentarService exportKommentarService; + @MockBean + private BinaryFileService binaryFileService; + @MockBean + private ExportNachrichtService exportNachrichtService; + @MockBean + private ExportBescheidService exportBescheidService; + @MockBean + private UserService userService; + + @Nested + class TestExportVorgang { + + private final GrpcExportVorgangRequest request = GrpcExportVorgangRequestTestFactory.create(); + private final VorgangWithEingang vorgang = VorgangWithEingangTestFactory.create(); + + @BeforeEach + void setUpCommonMocks() { + when(exportVorgangService.getVorgang(VorgangWithEingangTestFactory.ID)).thenReturn(vorgang); + when(exportNachrichtService.createExportData(vorgang)).thenReturn(PostfachMailExportData.builder().build()); + when(exportBescheidService.createExportData(vorgang)).thenReturn(BescheidExportData.builder().build()); + when(exportFileService.getRepresentations(vorgang)).thenReturn(Stream.empty()); + when(exportFileService.getAttachments(vorgang)).thenReturn(Stream.empty()); + + when(exportVorgangService.createKopf(vorgang)).thenReturn(NkAbgabeTypeTestFactory.create()); + when(exportVorgangService.createVorgangType(vorgang)).thenReturn(VorgangTypeTestFactory.create()); + when(exportVorgangService.createAkteType(vorgang)).thenReturn(AkteTypeTestFactory.create()); + + } + + @Nested + class OnMinimalVorgang { + + @BeforeEach + void setUpMock() { + when(exportKommentarService.createExportData(vorgang)).thenReturn(KommentarsExportData.builder().build()); + } + + @Test + @SneakyThrows + void shouldHaveOneZipEntry() { + var response = callService(); + + int numEntry; + try (var outputStream = new ByteArrayOutputStream()) { + collectResponseInStream(response, outputStream); + numEntry = countZipEntries(outputStream); + } + + assertThat(numEntry).isEqualTo(1); + } + + @Test + @SneakyThrows + void shouldContainVorgangKopfDataInXml() { + var response = callService(); + + var content = new String[2]; + try (var outputStream = new ByteArrayOutputStream()) { + collectResponseInStream(response, outputStream); + writeZipEntryContentToString(outputStream, content); + } + + assertThat(content[0]).isEqualToIgnoringWhitespace(VorgangWithEingangTestFactory.buildMinimalVorgangAsXml()); + } + } + + @Nested + class OnVorgangWithKommentarAndAttachment { + private final String attachmentContent = LoremIpsum.getInstance().getWords(5); + + @BeforeEach + void setUpMocks() { + + when(exportKommentarService.createExportData(vorgang)).thenReturn(KommentarsExportDataTestFactory.create()); + + doAnswer(invocation -> { + OutputStream outputStream = invocation.getArgument(1); + outputStream.write(attachmentContent.getBytes()); + return null; + }).when(exportFileService).writeOzgFile(any(), any()); + + when(userService.getById(KommentarTestFactory.CREATED_BY)).thenReturn(UserProfileTestFactory.create()); + } + + @Test + @SneakyThrows + void shouldHaveTwoZipEntries() { + var response = callService(); + + int numEntry; + try (var outputStream = new ByteArrayOutputStream()) { + collectResponseInStream(response, outputStream); + numEntry = countZipEntries(outputStream); + } + + assertThat(numEntry).isEqualTo(2); + } + + @Test + @SneakyThrows + void shouldHaveKommentarAttachment() { + var response = callService(); + + String[] content = new String[2]; + try (var outputStream = new ByteArrayOutputStream()) { + collectResponseInStream(response, outputStream); + writeZipEntryContentToString(outputStream, content); + } + + assertThat(content[1]).isEqualTo(attachmentContent); + } + } + + @SneakyThrows + private void collectResponseInStream(Iterator<GrpcExportVorgangResponse> response, ByteArrayOutputStream outputStream) { + while (response.hasNext()) { + outputStream.write(response.next().getVorgangFile().getFileContent().toByteArray()); + } + } + + @SneakyThrows + private int countZipEntries(ByteArrayOutputStream outputStream) { + var numEntry = 0; + try (var inputStream = new ByteArrayInputStream(outputStream.toByteArray()); + var zipInputStream = new ZipInputStream(inputStream)) { + while ((zipInputStream.getNextEntry()) != null) { + numEntry++; + } + } + return numEntry; + } + + @SneakyThrows + private void writeZipEntryContentToString(ByteArrayOutputStream outputStream, String[] content) { + try (var byteArrayInputStream = new ByteArrayInputStream(outputStream.toByteArray()); + var zipInputStream = new ZipInputStream(byteArrayInputStream)) { + var numEntry = 0; + while ((zipInputStream.getNextEntry()) != null) { + content[numEntry] = IOUtils.toString(zipInputStream, StandardCharsets.UTF_8); + numEntry++; + } + } + } + + private Iterator<GrpcExportVorgangResponse> callService() { + return clientService.exportVorgang(request); + } + } +} diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/export/ExportGrpcServiceTest.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/export/ExportGrpcServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..25bc633502404e4408e3227b3bc86bb354fa0991 --- /dev/null +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/export/ExportGrpcServiceTest.java @@ -0,0 +1,252 @@ +package de.ozgcloud.archive.export; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.io.OutputStream; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Function; + +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.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedConstruction; +import org.mockito.Spy; +import org.springframework.core.task.TaskExecutor; + +import com.google.protobuf.ByteString; +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.archive.grpc.export.GrpcExportVorgangRequest; +import de.ozgcloud.archive.grpc.export.GrpcExportVorgangResponse; +import de.ozgcloud.archive.grpc.export.GrpcFile; +import de.ozgcloud.archive.vorgang.VorgangWithEingangTestFactory; +import de.ozgcloud.common.binaryfile.GrpcBinaryFileServerDownloader; +import io.grpc.stub.CallStreamObserver; +import io.grpc.stub.StreamObserver; + +class ExportGrpcServiceTest { + + @InjectMocks + @Spy + private ExportGrpcService service; + @Mock + private ExportService exportService; + @Mock + private TaskExecutor taskExecutor; + + @Nested + class TestExportVorgang { + + @Mock + private StreamObserver<GrpcExportVorgangResponse> responseObserver; + @Mock + private GrpcBinaryFileServerDownloader<GrpcExportVorgangResponse> downloader; + + private final GrpcExportVorgangRequest request = GrpcExportVorgangRequestTestFactory.create(); + private final String fileNameId = UUID.randomUUID().toString(); + + @BeforeEach + void setUpMocks() { + doReturn(fileNameId).when(service).createFileNameId(); + doReturn(downloader).when(service).buildExportDownloader(VorgangWithEingangTestFactory.ID, fileNameId, responseObserver); + } + + @Test + void shouldCreateFileNameId() { + callService(); + + verify(service).createFileNameId(); + } + + @Test + void shouldSendFileName() { + callService(); + + verify(service).sendFileName(responseObserver, fileNameId); + } + + @Test + void shouldBuildExportDownloader() { + callService(); + + verify(service).buildExportDownloader(VorgangWithEingangTestFactory.ID, fileNameId, responseObserver); + } + + @Test + void shouldStartDownloader() { + callService(); + + verify(downloader).start(); + } + + private void callService() { + service.exportVorgang(request, responseObserver); + } + } + + @Nested + class TestCreateFileNameId { + + private final UUID uuid = UUID.randomUUID(); + + @Test + void shouldReturnRandomUUID() { + try (var mockedStaticUUID = mockStatic(UUID.class)) { + mockedStaticUUID.when(UUID::randomUUID).thenReturn(uuid); + + var returnedFileNameId = service.createFileNameId(); + + assertThat(returnedFileNameId).isEqualTo(uuid.toString()); + } + } + } + + @Nested + class TestSendFileName { + + @Mock + private StreamObserver<GrpcExportVorgangResponse> responseObserver; + @Mock + private GrpcBinaryFileServerDownloader<GrpcExportVorgangResponse> downloader; + + private final String fileNameId = UUID.randomUUID().toString(); + private final String fileName = LoremIpsum.getInstance().getName(); + + @Test + void shouldCallBuildXdomeaFileName() { + sendFileName(); + + verify(service).buildXdomeaFileName(fileNameId); + } + + @Test + void shouldSendFileName() { + doReturn(fileName).when(service).buildXdomeaFileName(fileNameId); + + sendFileName(); + + verify(responseObserver).onNext(argThat((response) -> response.getVorgangFile().getFileName().equals(fileName))); + } + + private void sendFileName() { + service.sendFileName(responseObserver, fileNameId); + } + } + + @Nested + class TestBuildExportDownloader { + + @Mock + private CallStreamObserver<GrpcExportVorgangResponse> responseObserver; + + private final String fileNameId = UUID.randomUUID().toString(); + @Mock + private OutputStream outputStream; + + private MockedConstruction<GrpcBinaryFileServerDownloader> downloaderMockedConstruction; // NOSONAR + private GrpcBinaryFileServerDownloader<GrpcExportVorgangResponse> downloader; + private StreamObserver<GrpcExportVorgangResponse> setResponseObserver; + private TaskExecutor setTaskExecutor; + private Consumer<OutputStream> setDownloadConsumer; + private Function<ByteString, GrpcExportVorgangResponse> setChunkBuilder; + + private final ByteString chunk = ByteString.copyFromUtf8(LoremIpsum.getInstance().getWords(5)); + + @SuppressWarnings("unchecked") + @BeforeEach + void mock() { + downloaderMockedConstruction = mockConstruction(GrpcBinaryFileServerDownloader.class, (downloader, context) -> { + setResponseObserver = (StreamObserver<GrpcExportVorgangResponse>) context.arguments().get(0); + setChunkBuilder = (Function<ByteString, GrpcExportVorgangResponse>) context.arguments().get(1); + setDownloadConsumer = (Consumer<OutputStream>) context.arguments().get(2); + setTaskExecutor = (TaskExecutor) context.arguments().get(3); + this.downloader = downloader; + }); + } + + @AfterEach + void closeMock() { + downloaderMockedConstruction.close(); + } + + @Test + void shouldSetResponseObserver() { + buildExportDownloader(); + + assertThat(setResponseObserver).isEqualTo(responseObserver); + } + + @Test + void shouldSetTaskExecutor() { + buildExportDownloader(); + + assertThat(setTaskExecutor).isEqualTo(taskExecutor); + } + + @Test + void shouldSetDownloadConsumer() { + buildExportDownloader(); + + setDownloadConsumer.accept(outputStream); + + verify(exportService).writeXdomeaFileContent(VorgangWithEingangTestFactory.ID, fileNameId, outputStream); + } + + @Test + void shouldSetChunkBuilder() { + var response = GrpcExportVorgangResponse.newBuilder().setVorgangFile(GrpcFile.newBuilder().setFileContent(chunk).build()).build(); + doReturn(response).when(service).buildExportVorgangChunkResponse(chunk); + buildExportDownloader(); + + var result = setChunkBuilder.apply(chunk); + + assertThat(result).isEqualTo(response); + } + + @Test + void shouldReturnDownloader() { + var returnedDownloader = buildExportDownloader(); + + assertThat(returnedDownloader).isEqualTo(downloader); + } + + private GrpcBinaryFileServerDownloader<GrpcExportVorgangResponse> buildExportDownloader() { + return service.buildExportDownloader(VorgangWithEingangTestFactory.ID, fileNameId, responseObserver); + } + } + + @Nested + class TestBuildExportVorgangChunkResponse { + + private final ByteString chunk = ByteString.copyFromUtf8(LoremIpsum.getInstance().getWords(5)); + + @Test + void shouldReturnResponse() { + var expectedResponse = GrpcExportVorgangResponse.newBuilder().setVorgangFile(GrpcFile.newBuilder().setFileContent(chunk).build()).build(); + + var result = service.buildExportVorgangChunkResponse(chunk); + + assertThat(result).isEqualTo(expectedResponse); + + } + } + + @Nested + class TestBuildXdomeaFileName { + + private final String id = UUID.randomUUID().toString(); + + @Test + void shouldReturnFileName() { + var fileName = service.buildXdomeaFileName(id); + + assertThat(fileName).isEqualTo(id + "_Abgabe.Abgabe.0401.xdomea"); + } + } +} diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/export/ExportServiceITCase.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/export/ExportServiceITCase.java index b5323e98f8e4286305599f3d753afd7dcd4b8df5..d085f3d41f19d41bcaa323dbe984f95cebb739f0 100644 --- a/archive-manager-server/src/test/java/de/ozgcloud/archive/export/ExportServiceITCase.java +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/export/ExportServiceITCase.java @@ -81,8 +81,7 @@ class ExportServiceITCase { @Test void shouldNotThrowException() { assertDoesNotThrow( - () -> exportService.writeExport(VorgangWithEingangTestFactory.ID, userId, - new ByteArrayOutputStream())); + () -> exportService.writeXdomeaFileContent(VorgangWithEingangTestFactory.ID, userId, new ByteArrayOutputStream())); } } } diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/export/ExportServiceTest.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/export/ExportServiceTest.java index 7bf183568a88725fc2d9ee3fded0c99312032587..4e6039ba992fe23de31c6d77faedc1a44efdc11b 100644 --- a/archive-manager-server/src/test/java/de/ozgcloud/archive/export/ExportServiceTest.java +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/export/ExportServiceTest.java @@ -5,9 +5,8 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -import java.io.ByteArrayOutputStream; -import java.io.File; import java.io.IOException; +import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.UUID; @@ -79,22 +78,20 @@ class ExportServiceTest { @Mock private ExportBescheidService exportBescheidService; - @DisplayName("Write exportToXdomea") + @DisplayName("Write Xdomea File") @Nested - class TestWriteExport { + class TestWriteXdomeaFileContent { private static final String FILENAME_ID = UUID.randomUUID().toString(); - private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); private final ExportData exportData = ExportDataTestFactory.create(); @Mock - private File zipFile; + private OutputStream outputStream; @BeforeEach void setUp() { doReturn(exportData).when(service).collectExportData(VorgangWithEingangTestFactory.ID, FILENAME_ID); - doReturn(zipFile).when(service).createZipFile(exportData); - doNothing().when(service).writeZipFileContent(zipFile, outputStream); + doNothing().when(service).writeZipFile(exportData, outputStream); } @Test @@ -105,21 +102,15 @@ class ExportServiceTest { } @Test - void shouldCreateZipFile() { + void shouldWriteZipFile() { callService(); - verify(service).createZipFile(exportData); - } - - @Test - void shouldWriteZipFileContentToOutputStream() { - callService(); - - verify(service).writeZipFileContent(zipFile, outputStream); + verify(service).writeZipFile(exportData, outputStream); } + @SneakyThrows private void callService() { - service.writeExport(VorgangWithEingangTestFactory.ID, FILENAME_ID, outputStream); + service.writeXdomeaFileContent(VorgangWithEingangTestFactory.ID, FILENAME_ID, outputStream); } } @@ -192,7 +183,8 @@ class ExportServiceTest { stream -> when(exportFileService.createDokumentTypes(representations, EingangHeaderTestFactory.FORM_ENGINE_NAME)) .thenReturn(stream)); mockStreamToList(attachmentsDokumentTypes, - stream -> when(exportFileService.createDokumentTypes(attachments, EingangHeaderTestFactory.FORM_ENGINE_NAME)).thenReturn(stream)); + stream -> when(exportFileService.createDokumentTypes(attachments, EingangHeaderTestFactory.FORM_ENGINE_NAME)) + .thenReturn(stream)); } private void setUpExportHistorieService() { @@ -431,15 +423,20 @@ class ExportServiceTest { } @Nested - class TestCreateZipFile { + class TestWriteZipFile { private static final String XML_FILE_CONTENT = "<xml></xml>"; private static final String XML_STRING = "<xml>"; private final ExportData exportData = ExportDataTestFactory.create(); + @Mock + private OutputStream outputStream; + @Captor private ArgumentCaptor<ZipOutputStream> zipOutputStreamArgumentCaptor; + private OutputStream writtenOnOutputStream; + @SneakyThrows @BeforeEach void setUp() { @@ -447,6 +444,17 @@ class ExportServiceTest { doNothing().when(service).putOzgFileIntoZip(any(OzgFile.class), any(ZipOutputStream.class)); } + @Test + void shouldConstructZipOutputStream() { + try (var mockedConstructionZipOutputStream = mockConstruction(ZipOutputStream.class, (mock, context) -> { + writtenOnOutputStream = (OutputStream) context.arguments().get(0); + })) { + callService(); + + assertThat(writtenOnOutputStream).isEqualTo(outputStream); + } + } + @Test void shouldCreateXmlStringContent() { callService(); @@ -464,18 +472,14 @@ class ExportServiceTest { @Test void shouldCreateZipOutputStream() throws IOException { - callService(); - - verify(service).putZipEntry(eq(ExportDataTestFactory.EXPORT_FILENAME), eq(XML_FILE_CONTENT), - zipOutputStreamArgumentCaptor.capture()); - assertThat(zipOutputStreamArgumentCaptor.getValue()).isInstanceOf(ZipOutputStream.class); - } + try (var mockedConstructionZipOutputStream = mockConstruction(ZipOutputStream.class, (mock, context) -> { + writtenOnOutputStream = (OutputStream) context.arguments().get(0); + })) { + callService(); - @Test - void shouldWriteBytes() { - var file = callService(); - - assertThat(file).isNotEmpty().content().hasSizeGreaterThan(100); + verify(service).putZipEntry(ExportDataTestFactory.EXPORT_FILENAME, XML_FILE_CONTENT, + mockedConstructionZipOutputStream.constructed().get(0)); + } } @SneakyThrows @@ -493,8 +497,8 @@ class ExportServiceTest { assertThatThrownBy(this::callService).isInstanceOf(TechnicalException.class); } - private File callService() { - return service.createZipFile(exportData); + private void callService() { + service.writeZipFile(exportData, outputStream); } } diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/export/FileContentTestFactory.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/export/FileContentTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..20c6f2d8010c1cff0df30766576259526edf45f1 --- /dev/null +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/export/FileContentTestFactory.java @@ -0,0 +1,16 @@ +package de.ozgcloud.archive.export; + +import java.util.Random; + +public class FileContentTestFactory { + + public static byte[] createContentInByte(int fileSize) { + Random r = new Random(); + byte[] chunk = new byte[fileSize]; + for (int i = 0; i < chunk.length; i++) { + chunk[i] = (byte) (r.nextInt('z' - 'a') + 'a'); + } + return chunk; + } + +} diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/export/GrpcExportVorgangRequestTestFactory.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/export/GrpcExportVorgangRequestTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..ff92705270befa0a14093da8340049d8162a4dea --- /dev/null +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/export/GrpcExportVorgangRequestTestFactory.java @@ -0,0 +1,21 @@ +package de.ozgcloud.archive.export; + +import de.ozgcloud.archive.grpc.export.GrpcExportVorgangRequest; +import de.ozgcloud.archive.grpc.export.GrpcExportVorgangRequest.Builder; +import de.ozgcloud.archive.vorgang.VorgangWithEingangTestFactory; + +public class GrpcExportVorgangRequestTestFactory { + + public static final String VORGANG_ID = VorgangWithEingangTestFactory.ID; + + public static GrpcExportVorgangRequest create() { + return createBuilder() + .build(); + } + + public static Builder createBuilder() { + return GrpcExportVorgangRequest.newBuilder() + .setVorgangId(VORGANG_ID); + } + +} diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/export/GrpcIntegrationTestConfiguration.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/export/GrpcIntegrationTestConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..cff6b42443baf1733b5d089d3ad5f86397d25a68 --- /dev/null +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/export/GrpcIntegrationTestConfiguration.java @@ -0,0 +1,26 @@ +package de.ozgcloud.archive.export; + +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.TaskExecutor; + +import net.devh.boot.grpc.client.autoconfigure.GrpcClientAutoConfiguration; +import net.devh.boot.grpc.server.autoconfigure.GrpcServerAutoConfiguration; +import net.devh.boot.grpc.server.autoconfigure.GrpcServerFactoryAutoConfiguration; + +@Configuration +@ImportAutoConfiguration({ + GrpcServerAutoConfiguration.class, + GrpcServerFactoryAutoConfiguration.class, + GrpcClientAutoConfiguration.class +}) +@ComponentScan({ "de.ozgcloud.*" }) +public class GrpcIntegrationTestConfiguration { + + @Bean + ExportGrpcService grpcExportService(ExportService exportService, TaskExecutor taskExecutor) { + return new ExportGrpcService(exportService, taskExecutor); + } +} diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/file/OzgFileRemoteServiceTest.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/file/OzgFileRemoteServiceTest.java index 253040a0a6f6bcd11eebdad30cd93f3893717594..39538ac67f3b6d4cd02c949730ffb4cd6ee8d8af 100644 --- a/archive-manager-server/src/test/java/de/ozgcloud/archive/file/OzgFileRemoteServiceTest.java +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/file/OzgFileRemoteServiceTest.java @@ -24,17 +24,23 @@ package de.ozgcloud.archive.file; import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.stream.Stream; +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.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedConstruction; import org.mockito.Spy; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextAttachingInterceptor; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextProvider; +import de.ozgcloud.archive.common.callcontext.CallContextProvider; import de.ozgcloud.archive.vorgang.EingangTestFactory; import de.ozgcloud.vorgang.grpc.file.FileServiceGrpc.FileServiceBlockingStub; import de.ozgcloud.vorgang.grpc.file.GrpcGetAttachmentsRequest; @@ -46,11 +52,13 @@ class OzgFileRemoteServiceTest { @InjectMocks @Spy - private OzgFileRemoteService remoteService; + private OzgFileRemoteService service; @Mock private FileServiceBlockingStub grpcService; @Mock private OzgFileMapper mapper; + @Mock + private CallContextProvider contextProvider; @Nested class TestGetAttachmentsByEingang { @@ -58,24 +66,35 @@ class OzgFileRemoteServiceTest { private final GrpcGetAttachmentsResponse response = GrpcGetAttachmentsResponseTestFactory.create(); private final GrpcGetAttachmentsRequest request = GrpcGetAttachmentsRequestTestFactory.create(); + @Mock + private FileServiceBlockingStub grpcServiceWithInterceptor; + @BeforeEach void mock() { - doReturn(request).when(remoteService).buildGrpcGetAttachmentsRequest(EingangTestFactory.ID); - when(grpcService.getAttachments(request)).thenReturn(response); + doReturn(request).when(service).buildGrpcGetAttachmentsRequest(EingangTestFactory.ID); + doReturn(grpcServiceWithInterceptor).when(service).getGrpcServiceWithInterceptor(); + when(grpcServiceWithInterceptor.getAttachments(request)).thenReturn(response); + } + + @Test + void shouldGetGrpcServiceWithInterceptor() { + callService(); + + verify(service).getGrpcServiceWithInterceptor(); } @Test void shouldBuildRequest() { callService(); - verify(remoteService).buildGrpcGetAttachmentsRequest(EingangTestFactory.ID); + verify(service).buildGrpcGetAttachmentsRequest(EingangTestFactory.ID); } @Test void shouldCallGrpcService() { callService(); - verify(grpcService).getAttachments(request); + verify(grpcServiceWithInterceptor).getAttachments(request); } @Test @@ -96,7 +115,7 @@ class OzgFileRemoteServiceTest { } private Stream<OzgFile> callService() { - return remoteService.getAttachmentsByEingang(EingangTestFactory.ID); + return service.getAttachmentsByEingang(EingangTestFactory.ID); } } @@ -105,7 +124,7 @@ class OzgFileRemoteServiceTest { @Test void shouldReturnRequest() { - var request = remoteService.buildGrpcGetAttachmentsRequest(EingangTestFactory.ID); + var request = service.buildGrpcGetAttachmentsRequest(EingangTestFactory.ID); assertThat(request).usingRecursiveComparison().isEqualTo(GrpcGetAttachmentsRequestTestFactory.create()); } @@ -117,24 +136,35 @@ class OzgFileRemoteServiceTest { private final GrpcGetRepresentationsResponse response = GrpcGetRepresentationsResponseTestFactory.create(); private final GrpcGetRepresentationsRequest request = GrpcGetRepresentationsRequestTestFactory.create(); + @Mock + private FileServiceBlockingStub grpcServiceWithInterceptor; + @BeforeEach void mock() { - doReturn(request).when(remoteService).buildGrpcGetRepresentationsRequest(EingangTestFactory.ID); - when(grpcService.getRepresentations(request)).thenReturn(response); + doReturn(grpcServiceWithInterceptor).when(service).getGrpcServiceWithInterceptor(); + doReturn(request).when(service).buildGrpcGetRepresentationsRequest(EingangTestFactory.ID); + when(grpcServiceWithInterceptor.getRepresentations(request)).thenReturn(response); + } + + @Test + void shouldGetGrpcServiceWithInterceptor() { + callService(); + + verify(service).getGrpcServiceWithInterceptor(); } @Test void shouldBuildRequest() { callService(); - verify(remoteService).buildGrpcGetRepresentationsRequest(EingangTestFactory.ID); + verify(service).buildGrpcGetRepresentationsRequest(EingangTestFactory.ID); } @Test void shouldCallGrpcService() { callService(); - verify(grpcService).getRepresentations(request); + verify(grpcServiceWithInterceptor).getRepresentations(request); } @Test @@ -155,7 +185,7 @@ class OzgFileRemoteServiceTest { } private Stream<OzgFile> callService() { - return remoteService.getRepresentationsByEingang(EingangTestFactory.ID); + return service.getRepresentationsByEingang(EingangTestFactory.ID); } } @@ -164,9 +194,66 @@ class OzgFileRemoteServiceTest { @Test void shouldReturnRequest() { - var request = remoteService.buildGrpcGetRepresentationsRequest(EingangTestFactory.ID); + var request = service.buildGrpcGetRepresentationsRequest(EingangTestFactory.ID); assertThat(request).usingRecursiveComparison().isEqualTo(GrpcGetRepresentationsRequestTestFactory.create()); } } + + @Nested + class TestGetGrpcServiceWithInterceptor { + + private MockedConstruction<OzgCloudCallContextAttachingInterceptor> mockedConstructionInterceptor; + private OzgCloudCallContextProvider argumentContextProvider; + @Mock + private OzgCloudCallContextAttachingInterceptor interceptor; + + @BeforeEach + void setUpMocks() { + mockedConstructionInterceptor = mockConstruction(OzgCloudCallContextAttachingInterceptor.class, (mock, context) -> { + argumentContextProvider = (OzgCloudCallContextProvider) context.arguments().get(0); + interceptor = mock; + }); + } + + @AfterEach + void cleanUp() { + mockedConstructionInterceptor.close(); + } + + @Test + void shoudCreateOzgCloudCallContextAttachingInterceptor() { + callService(); + + assertThat(mockedConstructionInterceptor.constructed()).hasSize(1); + } + + @Test + void shoudCreateOzgCloudCallContextAttachingInterceptorWithContextProvider() { + callService(); + + assertThat(argumentContextProvider).isEqualTo(contextProvider); + } + + @Test + void shouldSetInterceptor() { + callService(); + + verify(grpcService).withInterceptors(interceptor); + } + + @Test + void shouldReturnServiceStub() { + when(grpcService.withInterceptors(any(OzgCloudCallContextAttachingInterceptor.class))).thenReturn(grpcService); + + var returnedServiceStub = callService(); + + assertThat(returnedServiceStub).isEqualTo(grpcService); + } + + private FileServiceBlockingStub callService() { + return service.getGrpcServiceWithInterceptor(); + } + } + } \ No newline at end of file diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/kommentar/KommentarRemoteServiceTest.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/kommentar/KommentarRemoteServiceTest.java index 13a2a7fcb5ddebb65dfe826b4bc938ed26ebfd4f..80dd6dfc1b2bb63c25a628fe46fbf2b20a6b0b27 100644 --- a/archive-manager-server/src/test/java/de/ozgcloud/archive/kommentar/KommentarRemoteServiceTest.java +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/kommentar/KommentarRemoteServiceTest.java @@ -24,16 +24,24 @@ package de.ozgcloud.archive.kommentar; import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import java.util.stream.Stream; + +import org.junit.jupiter.api.AfterEach; 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.MockedConstruction; import org.mockito.Spy; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextAttachingInterceptor; +import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextProvider; +import de.ozgcloud.archive.common.callcontext.CallContextProvider; import de.ozgcloud.vorgang.vorgangAttachedItem.GrpcFindVorgangAttachedItemRequest; import de.ozgcloud.vorgang.vorgangAttachedItem.GrpcFindVorgangAttachedItemResponse; import de.ozgcloud.vorgang.vorgangAttachedItem.VorgangAttachedItemServiceGrpc.VorgangAttachedItemServiceBlockingStub; @@ -47,6 +55,8 @@ class KommentarRemoteServiceTest { private VorgangAttachedItemServiceBlockingStub grpcService; @Mock private KommentarMapper mapper; + @Mock + private CallContextProvider contextProvider; @DisplayName("Find by vorgangId") @Nested @@ -56,21 +66,32 @@ class KommentarRemoteServiceTest { private final GrpcFindVorgangAttachedItemResponse response = GrpcFindVorgangAttachedItemResponseTestFactory.create(); private final Kommentar kommentar = KommentarTestFactory.create(); + @Mock + private VorgangAttachedItemServiceBlockingStub grpcServiceWithInterceptor; + @BeforeEach void init() { - when(grpcService.find(request)).thenReturn(response); + doReturn(grpcServiceWithInterceptor).when(service).getGrpcServiceWithInterceptor(); + when(grpcServiceWithInterceptor.find(request)).thenReturn(response); + } + + @Test + void shouldGetGrpcServiceWithInterceptor() { + callService(); + + verify(service).getGrpcServiceWithInterceptor(); } @Test void shouldCallGrpcService() { - service.findByVorgangId(GrpcFindVorgangAttachedItemRequestTestFactory.VORGANG_ID); + callService(); - verify(grpcService).find(request); + verify(grpcServiceWithInterceptor).find(request); } @Test void shouldCallMapper() { - service.findByVorgangId(GrpcFindVorgangAttachedItemRequestTestFactory.VORGANG_ID).toList(); + callService().toList(); verify(mapper).fromItem(GrpcFindVorgangAttachedItemResponseTestFactory.ATTACHED_ITEM); } @@ -79,9 +100,70 @@ class KommentarRemoteServiceTest { void shouldReturnFoundKommentare() { when(mapper.fromItem(GrpcFindVorgangAttachedItemResponseTestFactory.ATTACHED_ITEM)).thenReturn(kommentar); - var foundKommentare = service.findByVorgangId(GrpcFindVorgangAttachedItemRequestTestFactory.VORGANG_ID).toList(); + var foundKommentare = callService().toList(); assertThat(foundKommentare).containsExactly(kommentar); } + + private Stream<Kommentar> callService() { + return service.findByVorgangId(GrpcFindVorgangAttachedItemRequestTestFactory.VORGANG_ID); + } + } + + @Nested + class TestGetGrpcServiceWithInterceptor { + + private MockedConstruction<OzgCloudCallContextAttachingInterceptor> mockedConstructionInterceptor; + private OzgCloudCallContextProvider argumentContextProvider; + @Mock + private OzgCloudCallContextAttachingInterceptor interceptor; + + @BeforeEach + void setUpMocks() { + mockedConstructionInterceptor = mockConstruction(OzgCloudCallContextAttachingInterceptor.class, (mock, context) -> { + argumentContextProvider = (OzgCloudCallContextProvider) context.arguments().get(0); + interceptor = mock; + }); + } + + @AfterEach + void cleanUp() { + mockedConstructionInterceptor.close(); + } + + @Test + void shoudCreateOzgCloudCallContextAttachingInterceptor() { + callService(); + + assertThat(mockedConstructionInterceptor.constructed()).hasSize(1); + } + + @Test + void shoudCreateOzgCloudCallContextAttachingInterceptorWithContextProvider() { + callService(); + + assertThat(argumentContextProvider).isEqualTo(contextProvider); + } + + @Test + void shouldSetInterceptor() { + callService(); + + verify(grpcService).withInterceptors(interceptor); + } + + @Test + void shouldReturnServiceStub() { + when(grpcService.withInterceptors(any(OzgCloudCallContextAttachingInterceptor.class))).thenReturn(grpcService); + + var returnedServiceStub = callService(); + + assertThat(returnedServiceStub).isEqualTo(grpcService); + } + + private VorgangAttachedItemServiceBlockingStub callService() { + return service.getGrpcServiceWithInterceptor(); + } } + } diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/vorgang/VorgangRemoteServiceTest.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/vorgang/VorgangRemoteServiceTest.java index 1ec27ed7484f190b224579504fc158bd517f18a2..5d511e6cb49f4a1e5f6d159c997b81588080d6bd 100644 --- a/archive-manager-server/src/test/java/de/ozgcloud/archive/vorgang/VorgangRemoteServiceTest.java +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/vorgang/VorgangRemoteServiceTest.java @@ -19,7 +19,6 @@ import de.ozgcloud.archive.common.callcontext.CallContextProvider; import de.ozgcloud.vorgang.vorgang.GrpcFindVorgangWithEingangRequest; import de.ozgcloud.vorgang.vorgang.GrpcVorgangWithEingang; import de.ozgcloud.vorgang.vorgang.VorgangServiceGrpc.VorgangServiceBlockingStub; -import io.grpc.Channel; class VorgangRemoteServiceTest { @@ -42,24 +41,22 @@ class VorgangRemoteServiceTest { private final GrpcFindVorgangWithEingangRequest request = GrpcFindVorgangWithEingangRequestTestFactory.create(); @Mock - private Channel channel; - @Mock - private VorgangServiceBlockingStub vorgangServiceBlockingStubWithInterceptor; + private VorgangServiceBlockingStub grpcServiceWithInterceptor; @BeforeEach void mockReturnValue() { doReturn(request).when(service).buildFindVorgangWithEingangRequest(VorgangWithEingangTestFactory.ID); - doReturn(vorgangServiceBlockingStubWithInterceptor).when(service).getGrpcService(); - when(vorgangServiceBlockingStubWithInterceptor.findVorgangWithEingang(request)) + doReturn(grpcServiceWithInterceptor).when(service).getGrpcServiceWithInterceptor(); + when(grpcServiceWithInterceptor.findVorgangWithEingang(request)) .thenReturn(GrpcFindVorgangWithEingangResponseTestFactory.createVorgangWithEingangResponse()); when(vorgangWithEingangMapper.fromGrpc(grpcVorgangWithEingang)).thenReturn(vorgangWithEingang); } @Test - void shouldGetVorgangGrpcService() { + void shouldGetGrpcServiceWithInterceptor() { callService(); - verify(service).getGrpcService(); + verify(service).getGrpcServiceWithInterceptor(); } @Test @@ -73,7 +70,7 @@ class VorgangRemoteServiceTest { void shouldCallGrpcService() { callService(); - verify(vorgangServiceBlockingStubWithInterceptor).findVorgangWithEingang(request); + verify(grpcServiceWithInterceptor).findVorgangWithEingang(request); } @Test @@ -93,6 +90,7 @@ class VorgangRemoteServiceTest { private VorgangWithEingang callService() { return service.findVorgangWithEingang(VorgangWithEingangTestFactory.ID); } + } @Nested @@ -107,7 +105,7 @@ class VorgangRemoteServiceTest { } @Nested - class TestGetVorgangServiceStub { + class TestGetGrpcServiceWithInterceptor { private MockedConstruction<OzgCloudCallContextAttachingInterceptor> mockedConstructionInterceptor; private OzgCloudCallContextProvider argumentContextProvider; @@ -149,7 +147,7 @@ class VorgangRemoteServiceTest { } @Test - void shouldReturnVorgangServiceStub() { + void shouldReturnServiceStub() { when(grpcService.withInterceptors(any(OzgCloudCallContextAttachingInterceptor.class))).thenReturn(grpcService); var returnedServiceStub = callService(); @@ -158,7 +156,8 @@ class VorgangRemoteServiceTest { } private VorgangServiceBlockingStub callService() { - return service.getGrpcService(); + return service.getGrpcServiceWithInterceptor(); } } + } \ No newline at end of file diff --git a/archive-manager-server/src/test/java/de/ozgcloud/archive/vorgang/VorgangWithEingangTestFactory.java b/archive-manager-server/src/test/java/de/ozgcloud/archive/vorgang/VorgangWithEingangTestFactory.java index f0807cafdd827abc045b486a3e4b1424efead9d2..ea7133d6c55a5b8c2f8604965b901e60b684fb45 100644 --- a/archive-manager-server/src/test/java/de/ozgcloud/archive/vorgang/VorgangWithEingangTestFactory.java +++ b/archive-manager-server/src/test/java/de/ozgcloud/archive/vorgang/VorgangWithEingangTestFactory.java @@ -6,6 +6,7 @@ import java.util.UUID; import com.thedeanda.lorem.LoremIpsum; import de.ozgcloud.archive.common.XDomeaTestUtils; +import de.ozgcloud.common.test.TestUtils; public class VorgangWithEingangTestFactory { @@ -28,4 +29,8 @@ public class VorgangWithEingangTestFactory { .createdAt(CREATED_AT) .eingang(EingangTestFactory.create()); } + + public static String buildMinimalVorgangAsXml() { + return TestUtils.loadTextFile("xml-templates/vorgang.xml.template"); + } } diff --git a/archive-manager-server/src/test/resources/application-itcase.yaml b/archive-manager-server/src/test/resources/application-itcase.yaml index c6f85b1e94d61d1ee6c5992b97cbac70b7260e24..9b265da3d97bcf15a69eb41c456810e518dbd49b 100644 --- a/archive-manager-server/src/test/resources/application-itcase.yaml +++ b/archive-manager-server/src/test/resources/application-itcase.yaml @@ -2,4 +2,8 @@ ozgcloud: xdomea: behoerdenschluessel: ABC behoerdenschluesselUri: http://meine.behoer.de - behoerdenschluesselVersion: 1.0.15b \ No newline at end of file + behoerdenschluesselVersion: 1.0.15b + +grpc: + server: + port: -1 \ No newline at end of file diff --git a/archive-manager-server/src/test/resources/xml-templates/vorgang.xml.template b/archive-manager-server/src/test/resources/xml-templates/vorgang.xml.template new file mode 100644 index 0000000000000000000000000000000000000000..f81b06546c662bda68022fc644c1d878d2e67924 --- /dev/null +++ b/archive-manager-server/src/test/resources/xml-templates/vorgang.xml.template @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<xdomea:Abgabe.Abgabe.0401 xmlns:xoev-code="http://xoev.de/schemata/code/1_0" xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:xoev-lc="http://xoev.de/latinchars/1_1/datatypes" xmlns:dinspec91379="urn:xoev-de:kosit:xoev:datentyp:din-spec-91379_2019-03" xmlns:xdomea="urn:xoev-de:xdomea:schema:3.0.0" xmlns:ns3="urn:ozgcloud-de:xdomea:schema:1.0.0"> + <xdomea:Kopf> + <xdomea:Importbestaetigung>false</xdomea:Importbestaetigung> + <xdomea:Empfangsbestaetigung>false</xdomea:Empfangsbestaetigung> + </xdomea:Kopf> + <xdomea:Schriftgutobjekt> + <xdomea:Vorgang/> + </xdomea:Schriftgutobjekt> + <xdomea:Schriftgutobjekt> + <xdomea:Akte/> + </xdomea:Schriftgutobjekt> +</xdomea:Abgabe.Abgabe.0401> \ No newline at end of file diff --git a/pom.xml b/pom.xml index f27fdd85a26e1d4ac88de01e1918984af5cf67a8..763830a4b1d786a6125826b54ba2818e13a4ebdf 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,7 @@ <api-lib.version>0.11.0</api-lib.version> <find-and-replace-maven-plugin.version>1.2.0</find-and-replace-maven-plugin.version> <protoc-jar-plugin.version>3.11.4</protoc-jar-plugin.version> + <ozgcloud-common.version>4.5.0-SNAPSHOT</ozgcloud-common.version> </properties> <dependencyManagement> <dependencies> @@ -49,6 +50,11 @@ <artifactId>api-lib-core</artifactId> <version>${api-lib.version}</version> </dependency> + <dependency> + <groupId>de.ozgcloud.common</groupId> + <artifactId>ozgcloud-common-lib</artifactId> + <version>${ozgcloud-common.version}</version> + </dependency> <dependency> <groupId>de.ozgcloud.api-lib</groupId> <artifactId>api-lib-core</artifactId>