diff --git a/bescheid-manager/pom.xml b/bescheid-manager/pom.xml index c33028eb7bd0a0344ff922ca671c29468d84b1b8..fd462edc30289980adea15766cf8b3aff47c2313 100644 --- a/bescheid-manager/pom.xml +++ b/bescheid-manager/pom.xml @@ -30,7 +30,7 @@ <parent> <groupId>de.ozgcloud.common</groupId> <artifactId>ozgcloud-common-parent</artifactId> - <version>4.7.0</version> + <version>4.9.0-SNAPSHOT</version> <relativePath /> </parent> @@ -41,9 +41,9 @@ <inceptionYear>2020</inceptionYear> <properties> - <vorgang-manager.version>2.21.0-SNAPSHOT</vorgang-manager.version> - <nachrichten-manager.version>2.15.0</nachrichten-manager.version> - <document-manager.version>1.1.0</document-manager.version> + <vorgang-manager.version>2.20.0-SNAPSHOT</vorgang-manager.version> + <nachrichten-manager.version>2.16.0-SNAPSHOT</nachrichten-manager.version> + <document-manager.version>1.2.0-SNAPSHOT</document-manager.version> <api-lib.version>0.14.0</api-lib.version> <spring-cloud-config-client.version>4.1.3</spring-cloud-config-client.version> </properties> diff --git a/vorgang-manager-base/pom.xml b/vorgang-manager-base/pom.xml index 5648a38b31f2d21e021bc10ae6806161694f2924..b2c2b11f6ce7f25b29e1d56c1ca2ea3b74652308 100644 --- a/vorgang-manager-base/pom.xml +++ b/vorgang-manager-base/pom.xml @@ -31,7 +31,7 @@ <parent> <groupId>de.ozgcloud.common</groupId> <artifactId>ozgcloud-common-parent</artifactId> - <version>4.7.0</version> + <version>4.9.0-SNAPSHOT</version> <relativePath /> </parent> diff --git a/vorgang-manager-base/src/test/java/de/ozgcloud/vorgang/callcontext/TestCallContextAttachingInterceptor.java b/vorgang-manager-base/src/test/java/de/ozgcloud/vorgang/callcontext/TestCallContextAttachingInterceptor.java index 034260a3895c4a986e77f2a34d2fc32c78dd8a70..72cc88019d829bb1df5a2487979ff6b497093f6b 100644 --- a/vorgang-manager-base/src/test/java/de/ozgcloud/vorgang/callcontext/TestCallContextAttachingInterceptor.java +++ b/vorgang-manager-base/src/test/java/de/ozgcloud/vorgang/callcontext/TestCallContextAttachingInterceptor.java @@ -55,6 +55,7 @@ public class TestCallContextAttachingInterceptor implements ClientInterceptor { public void start(Listener<RespT> responseListener, Metadata headers) { var metadata = new Metadata(); metadata.put(GrpcUtil.createKeyOf(CallContextHandleInterceptor.KEY_CLIENT_NAME), clientName.getBytes()); + metadata.put(GrpcUtil.HEADER_KEY_USER_ID, UserTestFactory.ID.getBytes()); metadata.put(GrpcUtil.createKeyOf(CallContextHandleInterceptor.KEY_ACCESS_LIMITED_ORGAID), organisationEinheitId.getBytes()); metadata.put(GrpcUtil.createKeyOf(CallContextHandleInterceptor.KEY_ACCESS_LIMITED), organisationEinheitIdCheckNecessary.toString().getBytes()); diff --git a/vorgang-manager-interface/src/main/protobuf/binaryfile.model.proto b/vorgang-manager-interface/src/main/protobuf/binaryfile.model.proto new file mode 100644 index 0000000000000000000000000000000000000000..1a88a3cf2e096d6c1501e92c0a74e6599d5150d2 --- /dev/null +++ b/vorgang-manager-interface/src/main/protobuf/binaryfile.model.proto @@ -0,0 +1,98 @@ +/* + * 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. + */ +syntax = "proto3"; + +package de.ozgcloud.vorgang.grpc.binaryFile; + +import "callcontext.proto"; +import "file.model.proto"; + +option java_multiple_files = true; +option java_package = "de.ozgcloud.vorgang.grpc.binaryFile"; +option java_outer_classname = "BinaryFileModelProto"; + +message GrpcBinaryFilesRequest { + de.ozgcloud.vorgang.grpc.command.GrpcCallContext context = 1; + repeated string fileId = 2; +} + +message GrpcFindFilesResponse { + repeated de.ozgcloud.vorgang.grpc.file.GrpcOzgFile file = 1; +} + +message GrpcBinaryFileDataRequest { + de.ozgcloud.vorgang.grpc.command.GrpcCallContext context = 1; + string fileId = 2; +} + +message GrpcUploadBinaryFileRequest { + oneof request { + GrpcUploadBinaryFileMetaData metadata = 1; + bytes fileContent = 2; + } +} + +message GrpcUploadBinaryFileMetaData { + de.ozgcloud.vorgang.grpc.command.GrpcCallContext context = 1; + string vorgangId = 2; + string field = 3; + string fileName = 4; + string contentType = 5; + int64 size = 6 [deprecated = true]; +} + +message GrpcUploadBinaryFileResponse { + string fileId = 2; +} + +message GrpcGetBinaryFileDataRequest { + de.ozgcloud.vorgang.grpc.command.GrpcCallContext context = 1; + string fileId = 2; +} + +message GrpcGetBinaryFileDataResponse { + bytes fileContent = 1; +} + +message GrpcBinaryFile { + string id = 1; + string name = 2; + int64 size = 3; + string contentType = 4; +} + +message GrpcUpdateBinaryFileRequest { + oneof request { + GrpcBinaryFileMetadata metadata = 1; + bytes content = 2; + } +} + +message GrpcBinaryFileMetadata { + string fileId = 1; + string fileName = 2; + string contentType = 3; +} + +message GrpcUpdateBinaryFileResponse {} \ No newline at end of file diff --git a/vorgang-manager-interface/src/main/protobuf/binaryfile.proto b/vorgang-manager-interface/src/main/protobuf/binaryfile.proto index b9ff8205436dabe835f429448dcfc3a252524b76..1b555f47d87d9bb8f05d79f2f6daf9991d910f80 100644 --- a/vorgang-manager-interface/src/main/protobuf/binaryfile.proto +++ b/vorgang-manager-interface/src/main/protobuf/binaryfile.proto @@ -25,8 +25,7 @@ syntax = "proto3"; package de.ozgcloud.vorgang.grpc.binaryFile; -import "callcontext.proto"; -import "file.model.proto"; +import "binaryfile.model.proto"; option java_multiple_files = true; option java_package = "de.ozgcloud.vorgang.grpc.binaryFile"; @@ -42,54 +41,7 @@ service BinaryFileService { rpc FindBinaryFilesMetaData(GrpcBinaryFilesRequest) returns (GrpcFindFilesResponse) { } -} - -message GrpcBinaryFilesRequest { - de.ozgcloud.vorgang.grpc.command.GrpcCallContext context = 1; - repeated string fileId = 2; -} - -message GrpcFindFilesResponse { - repeated de.ozgcloud.vorgang.grpc.file.GrpcOzgFile file = 1; -} - -message GrpcBinaryFileDataRequest { - de.ozgcloud.vorgang.grpc.command.GrpcCallContext context = 1; - string fileId = 2; -} -message GrpcUploadBinaryFileRequest { - oneof request { - GrpcUploadBinaryFileMetaData metadata = 1; - bytes fileContent = 2; + rpc UpdateBinaryFile(stream GrpcUpdateBinaryFileRequest) returns (GrpcUpdateBinaryFileResponse) { } } - -message GrpcUploadBinaryFileMetaData { - de.ozgcloud.vorgang.grpc.command.GrpcCallContext context = 1; - string vorgangId = 2; - string field = 3; - string fileName = 4; - string contentType = 5; - int64 size = 6 [deprecated=true]; -} - -message GrpcUploadBinaryFileResponse { - string fileId = 2; -} - -message GrpcGetBinaryFileDataRequest { - de.ozgcloud.vorgang.grpc.command.GrpcCallContext context = 1; - string fileId = 2; -} - -message GrpcGetBinaryFileDataResponse { - bytes fileContent = 1; -} - -message GrpcBinaryFile { - string id = 1; - string name = 2; - int64 size = 3; - string contentType = 4; -} \ No newline at end of file diff --git a/vorgang-manager-server/pom.xml b/vorgang-manager-server/pom.xml index 8d62130edc126483cdbe565da4114c4588407841..7ab016718e586f5e415376590eaf3265b6486a7a 100644 --- a/vorgang-manager-server/pom.xml +++ b/vorgang-manager-server/pom.xml @@ -32,7 +32,7 @@ <parent> <groupId>de.ozgcloud.common</groupId> <artifactId>ozgcloud-common-parent</artifactId> - <version>4.7.0</version> + <version>4.9.0-SNAPSHOT</version> <relativePath /> </parent> @@ -55,12 +55,12 @@ <user-manager-interface.version>2.12.0</user-manager-interface.version> <bescheid-manager.version>1.24.0-SNAPSHOT</bescheid-manager.version> <processor-manager.version>0.5.0</processor-manager.version> - <nachrichten-manager.version>2.15.0</nachrichten-manager.version> - <api-lib.version>0.15.0</api-lib.version> - <notification-manager.version>2.14.0</notification-manager.version> + <nachrichten-manager.version>2.17.0-SNAPSHOT</nachrichten-manager.version> + <api-lib.version>0.16.0-SNAPSHOT</api-lib.version> + <notification-manager.version>2.15.0-SNAPSHOT</notification-manager.version> <collaboration-manager.version>0.7.0</collaboration-manager.version> <archive-manager.version>0.2.0-SNAPSHOT</archive-manager.version> - <document-manager.version>1.1.0</document-manager.version> + <document-manager.version>1.2.0-SNAPSHOT</document-manager.version> <zip.version>2.11.5</zip.version> <jsoup.version>1.15.3</jsoup.version> diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/VorgangManagerServerConfiguration.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/VorgangManagerServerConfiguration.java index cc3e98af5730e8a6ba96b32af5d8b716401b13a9..9ae1627de5586094048df4edf11c5ac1be2d79f8 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/VorgangManagerServerConfiguration.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/VorgangManagerServerConfiguration.java @@ -23,6 +23,7 @@ */ package de.ozgcloud.vorgang; +import org.mapstruct.factory.Mappers; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @@ -32,6 +33,8 @@ import org.springframework.data.mongodb.repository.config.EnableMongoRepositorie import de.ozgcloud.apilib.common.command.OzgCloudCommandService; import de.ozgcloud.apilib.common.command.grpc.CommandMapper; import de.ozgcloud.apilib.common.command.grpc.GrpcOzgCloudCommandService; +import de.ozgcloud.apilib.vorgang.OzgCloudFileIdMapper; +import de.ozgcloud.apilib.vorgang.OzgCloudUserIdMapper; import de.ozgcloud.vorgang.grpc.command.CommandServiceGrpc; import io.mongock.runner.springboot.EnableMongock; import net.devh.boot.grpc.client.inject.GrpcClient; @@ -51,4 +54,14 @@ public class VorgangManagerServerConfiguration { return new GrpcOzgCloudCommandService(commandServiceStub, commandMapper, contextProvider, GrpcOzgCloudCommandService.DEFAULT_COMMAND_REQUEST_THRESHOLD_MILLIS); } + + @Bean + OzgCloudFileIdMapper ozgCloudFileIdMapper() { + return Mappers.getMapper(OzgCloudFileIdMapper.class); + } + + @Bean + OzgCloudUserIdMapper ozgCloudUserIdMapper() { + return Mappers.getMapper(OzgCloudUserIdMapper.class); + } } diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/PersistPostfachNachrichtByCommandService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/PersistPostfachNachrichtByCommandService.java index 7e417524f868acc569c55663de2d6bef73c164cd..2e2ad5c358ad8bcb3ce8f7f955236cd6efb1b6f8 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/PersistPostfachNachrichtByCommandService.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/PersistPostfachNachrichtByCommandService.java @@ -23,28 +23,17 @@ */ package de.ozgcloud.vorgang.command; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.net.URLConnection; -import java.util.Base64; import java.util.Map; import java.util.Optional; import java.util.stream.Stream; -import jakarta.activation.MimetypesFileTypeMap; - -import org.apache.http.entity.ContentType; import org.springframework.stereotype.Service; -import de.ozgcloud.common.errorhandling.TechnicalException; -import de.ozgcloud.nachrichten.postfach.AttachmentFile; +import de.ozgcloud.nachrichten.file.AttachmentFile; import de.ozgcloud.nachrichten.postfach.PersistPostfachNachrichtService; import de.ozgcloud.nachrichten.postfach.PostfachNachricht; import de.ozgcloud.vorgang.attached_item.VorgangAttachedItem; import de.ozgcloud.vorgang.attached_item.VorgangAttachedItemService; -import de.ozgcloud.vorgang.files.FileService; -import de.ozgcloud.vorgang.files.OzgFile; -import de.ozgcloud.vorgang.files.UploadedFilesReference; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; @@ -59,10 +48,8 @@ class PersistPostfachNachrichtByCommandService implements PersistPostfachNachric static final String CLIENT = "OzgCloud_NachrichtenManager"; static final String ITEM_NAME = "PostfachMail"; - static final String ATTACHMENT_NAME = "PostfachAttachment"; private final VorgangAttachedItemService attachedItemService; - private final FileService fileService; @Override public void persistNachricht(Optional<String> userId, PostfachNachricht nachricht) { @@ -91,49 +78,7 @@ class PersistPostfachNachrichtByCommandService implements PersistPostfachNachric @Override public String persistAttachment(String vorgangId, AttachmentFile attachment) { - var contentType = getTypeByFile(attachment); - try (var content = attachment.getContent()) { - var decContent = Base64.getDecoder().decode(content.readAllBytes()); - - return fileService.uploadFileStream( - createUploadedFilesReference(vorgangId), - createOzgFile(attachment.getName(), contentType, decContent.length), - Optional.empty(), - new ByteArrayInputStream(decContent)).toString(); - } catch (IOException e) { - throw new TechnicalException("Can not read attached file", e); - } - } - - UploadedFilesReference createUploadedFilesReference(String vorgangId) { - return UploadedFilesReference.builder().vorgangId(vorgangId).client(CLIENT).name(ATTACHMENT_NAME).build(); - } - - OzgFile createOzgFile(String fileName, String contentType, long size) { - return OzgFile.builder().name(fileName).contentType(contentType).size(size).build(); - } - - String getTypeByFile(AttachmentFile attachmentFile) { - var fileNameMap = URLConnection.getFileNameMap(); - - return Optional.ofNullable(fileNameMap.getContentTypeFor(attachmentFile.getName())).orElseGet(() -> getTypeByContent(attachmentFile)); - } - - private String getTypeByContent(AttachmentFile attachmentFile) { - try (var contentStream = attachmentFile.getContent()) { - return Optional.ofNullable(URLConnection.guessContentTypeFromStream(contentStream)) - .orElseGet(() -> getByMimeTypes(attachmentFile.getName())); - } catch (IOException e) { - LOG.warn("IO-Exception while guessing content type", e); - } - return ContentType.APPLICATION_OCTET_STREAM.toString(); - } - - // uses map file: src/main/resources/mime.types - private String getByMimeTypes(String fileName) { - var fileTypeMap = new MimetypesFileTypeMap(); - - return fileTypeMap.getContentType(fileName); + throw new UnsupportedOperationException("Not implemented. Use a gRPC service instead."); } @Override diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/migration/M013_CreateOzgCloudFileCollection.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/migration/M013_CreateOzgCloudFileCollection.java new file mode 100644 index 0000000000000000000000000000000000000000..aadcacb5de8dcfd2163427d738099acd2ac29c8a --- /dev/null +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/migration/M013_CreateOzgCloudFileCollection.java @@ -0,0 +1,57 @@ +package de.ozgcloud.vorgang.common.migration; + +import org.bson.Document; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.MergeOperation; + +import io.mongock.api.annotations.ChangeUnit; +import io.mongock.api.annotations.Execution; +import io.mongock.api.annotations.RollbackExecution; + +@ChangeUnit(id = "2024-11-27 09:00:00 OZG-6810", order = "M013", author = "ebardin", runAlways = true) +public class M013_CreateOzgCloudFileCollection { // NOSONAR + + static final String OLD_COLLECTION_NAME = "fs.files"; + static final String NEW_COLLECTION_NAME = "ozgCloudFile"; + static final String FIELD_ID = "_id"; + static final String FIELD_VERSION = "version"; + static final String FIELD_FILE_NAME = "name"; + static final String FIELD_CONTENT_TYPE = "contentType"; + static final String FIELD_CLIENT = "client"; + static final String FIELD_VORGANG_ID = "vorgangId"; + static final String FIELD_CREATED_BY = "createdBy"; + static final String FIELD_FIELD_NAME = "fieldName"; + static final String FIELD_CONTENT_ID = "contentId"; + static final String FIELD_SIZE = "size"; + static final String FIELD_LENGTH = "length"; + static final String SET_VERSION_EXPRESSION = "{$literal: 0}"; + static final String SET_CONTENT_ID_EXPRESSION = "{$toString: \"$%s\" }".formatted(FIELD_ID); + + @Execution + public void doMigration(MongoTemplate template) { + template.aggregate(buildAggregationPipeline(), OLD_COLLECTION_NAME, Document.class); + } + + private Aggregation buildAggregationPipeline() { + return Aggregation.newAggregation( + Aggregation.project(FIELD_ID) + .andExpression(SET_VERSION_EXPRESSION).as(FIELD_VERSION) + .and(RenameUtil.METADATA_KEY + "." + FIELD_VORGANG_ID).as(FIELD_VORGANG_ID) + .and(RenameUtil.METADATA_KEY + "." + FIELD_CLIENT).as(FIELD_CLIENT) + .and(RenameUtil.METADATA_KEY + "." + FIELD_FIELD_NAME).as(FIELD_FIELD_NAME) + .and(RenameUtil.METADATA_KEY + "." + FIELD_CONTENT_TYPE).as(FIELD_CONTENT_TYPE) + .and(RenameUtil.METADATA_KEY + "." + FIELD_FILE_NAME).as(FIELD_FILE_NAME) + .and(RenameUtil.METADATA_KEY + "." + FIELD_CREATED_BY).as(FIELD_CREATED_BY) + .andExpression(SET_CONTENT_ID_EXPRESSION).as(FIELD_CONTENT_ID) + .and(FIELD_LENGTH).as(FIELD_SIZE), + Aggregation.merge().intoCollection(NEW_COLLECTION_NAME).on(FIELD_ID) + .whenDocumentsMatch(MergeOperation.WhenDocumentsMatch.keepExistingDocument()).build() + ); + } + + @RollbackExecution + public void rollback() { + // kein rollback implementiert + } +} \ No newline at end of file diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/security/PolicyRepository.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/security/PolicyRepository.java index 7d8b554d9de12c57b9ea771f63a440e3612de492..4b3fb8ad150032313db008bb00b114062279198e 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/security/PolicyRepository.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/security/PolicyRepository.java @@ -33,27 +33,27 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationOperation; -import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.stereotype.Repository; import de.ozgcloud.command.Command; import de.ozgcloud.vorgang.attached_item.VorgangAttachedItem; import de.ozgcloud.vorgang.common.db.CriteriaUtil; import de.ozgcloud.vorgang.common.errorhandling.NotFoundException; +import de.ozgcloud.vorgang.files.OzgCloudFile; import de.ozgcloud.vorgang.vorgang.Vorgang; @Repository class PolicyRepository { - private static final String VORANG_ID_AS_OBJECT_ID_FIELD = """ - {"$addFields" : {"vorgangObjecId" : {"$toObjectId": "$vorgangId"}}} + private static final String VORGANG_ID_AS_OBJECT_ID_FIELD = """ + {"$addFields" : {"vorgangObjectId" : {"$toObjectId": "$vorgangId"}}} """; - private static final String VORANG_ID_FROM_FILE_AS_OBJECT_ID_FIELD = """ - {"$addFields" : {"vorgangObjecId" : {"$toObjectId": "$metadata.vorgangId"}}} + private static final String VORGANG_ID_FROM_FILE_AS_OBJECT_ID_FIELD = """ + {"$addFields" : {"vorgangObjectId" : {"$toObjectId": "$vorgangId"}}} """; - private static final AggregationOperation ADD_VORGANG_OBJECT_ID_FIELD = context -> org.bson.Document.parse(VORANG_ID_AS_OBJECT_ID_FIELD); + private static final AggregationOperation ADD_VORGANG_OBJECT_ID_FIELD = context -> org.bson.Document.parse(VORGANG_ID_AS_OBJECT_ID_FIELD); - private static final AggregationOperation ADD_VORGANG_FROM_FILE = context -> org.bson.Document.parse(VORANG_ID_FROM_FILE_AS_OBJECT_ID_FIELD); + private static final AggregationOperation ADD_VORGANG_FROM_FILE = context -> org.bson.Document.parse(VORGANG_ID_FROM_FILE_AS_OBJECT_ID_FIELD); public static final String FIELD_ORGANISATIONSEINHEIT_ID = "organisationseinheitenId"; public static final String FIELD_VORGANG = "vorgang"; @@ -69,12 +69,12 @@ class PolicyRepository { } public boolean existsByFileId(String fileId, Collection<String> organisationEinheitenIds) { - var resultDocument = executeAggregation(buildAggregation(matchId(fileId), ADD_VORGANG_FROM_FILE), "fs.files"); + var resultDocument = executeAggregation(buildAggregation(matchId(fileId), ADD_VORGANG_FROM_FILE), OzgCloudFile.class); return resultDocument.map(document -> evaluateOrganisationseinheitId(document, organisationEinheitenIds)).orElse(false); } private AggregationOperation matchId(String id) { - return Aggregation.match(new Criteria(Vorgang.MONGODB_FIELDNAME_ID).is(id)); + return Aggregation.match(CriteriaUtil.isId(id)); } public boolean existsByVorgangAttachedItem(String vorgangAttachedItemId, Collection<String> organisationEinheitenIds) { @@ -97,7 +97,7 @@ class PolicyRepository { } private AggregationOperation lookupVorgang() { - return Aggregation.lookup(Vorgang.COLLECTION_NAME, "vorgangObjecId", "_id", FIELD_VORGANG); + return Aggregation.lookup(Vorgang.COLLECTION_NAME, "vorgangObjectId", "_id", FIELD_VORGANG); } private Optional<Document> executeAggregation(Aggregation aggregation, Class<?> inputType) { @@ -105,11 +105,6 @@ class PolicyRepository { return getResult(results); } - private Optional<Document> executeAggregation(Aggregation aggregation, String collectionName) { - var results = template.aggregate(aggregation, collectionName, Document.class).getRawResults(); - return getResult(results); - } - Optional<Document> getResult(Document aggregationResult) { return aggregationResult.getList("results", Document.class).stream().findFirst(); } diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/BinaryFileRepository.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/BinaryFileRepository.java deleted file mode 100644 index ba302ba1cd4b52fd6c4194e155e7bdc0bfe85716..0000000000000000000000000000000000000000 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/BinaryFileRepository.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -package de.ozgcloud.vorgang.files; - -import static org.springframework.data.mongodb.core.query.Criteria.*; -import static org.springframework.data.mongodb.core.query.Query.*; - -import java.io.InputStream; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.apache.commons.lang3.StringUtils; -import org.bson.Document; -import org.bson.types.ObjectId; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.gridfs.GridFsTemplate; -import org.springframework.data.mongodb.gridfs.GridFsUpload; -import org.springframework.stereotype.Repository; - -import com.mongodb.client.gridfs.model.GridFSFile; - -@Repository -class BinaryFileRepository { - - static final String FILE_NAME = "name"; - static final String CONTENT_TYPE = "contentType"; - static final String CLIENT = "client"; - static final String VORGANG_ID = "vorgangId"; - static final String CREATED_BY = "createdBy"; - static final String FIELD_NAME = "fieldName"; - - static final String GRID_FS_FIELD_ID = "_id"; - - @Autowired - private GridFsTemplate gridFsTemplate; - - public FileId addContentStream(UploadedFilesReference ref, OzgFile file, Optional<String> userId, InputStream content) { - GridFsUpload<ObjectId> upload = GridFsUpload.fromStream(content) - .metadata(createMetaData(ref, file, userId.orElse(null))) - .filename(createFilePath(ref, file)) - .build(); - ObjectId id = gridFsTemplate.store(upload); - - return FileId.from(id.toString()); - } - - String createFilePath(UploadedFilesReference ref, OzgFile file) { - return String.format("%s/%s/%s/%s", ref.getVorgangId(), ref.getClient(), ref.getName(), file.getName()); - } - - Document createMetaData(UploadedFilesReference ref, OzgFile file, String userId) { - Document metadata = new Document(); - metadata.append(VORGANG_ID, ref.getVorgangId()); - metadata.append(CLIENT, ref.getClient()); - metadata.append(FIELD_NAME, ref.getName()); - metadata.append(CONTENT_TYPE, file.getContentType()); - metadata.append(FILE_NAME, file.getName()); - metadata.append(CREATED_BY, userId); - return metadata; - } - - public InputStream getFileContent(FileId fileId) { - return gridFsTemplate.getResource(getFile(fileId)).getContent(); - } - - private Query queryByObjectId(FileId fileId) { - return Query.query(Criteria.where(GRID_FS_FIELD_ID).is(fileId.toString())); - } - - public Stream<OzgFile> getAll(Iterable<String> fileIds) { - return StreamSupport.stream(fileIds.spliterator(), false) - .map(FileId::from) - .map(this::getFile) - .filter(Objects::nonNull) - .map(this::mapToOzgFile); - } - - public GridFSFile getFile(FileId fileId) { - return gridFsTemplate.findOne(queryByObjectId(fileId)); - } - - Stream<OzgFile> mapGridFsFiles(Iterable<GridFSFile> gridFiles) { - return StreamSupport.stream(gridFiles.spliterator(), false).map(this::mapToOzgFile); - } - - OzgFile mapToOzgFile(GridFSFile gridFile) { - return OzgFile.builder() - .id(FileId.from(gridFile.getObjectId().toString())) - .vorgangId(gridFile.getMetadata().getString(VORGANG_ID)) - .name((String) gridFile.getMetadata().getOrDefault(FILE_NAME, StringUtils.EMPTY)) - .size(gridFile.getLength()) - .contentType((String) gridFile.getMetadata().getOrDefault(CONTENT_TYPE, StringUtils.EMPTY)) - .build(); - } - - public void deleteAllByVorgang(String vorgangId) { - gridFsTemplate.delete(query(where("metadata." + VORGANG_ID).is(vorgangId))); - } -} diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/EingangFilesRepository.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/EingangFilesRepository.java index 3e4c9d91307a8d6fb305bea807974733e5d1b205..172e30e0274ce53d2aaa4c2cd2d72207aeafc9a3 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/EingangFilesRepository.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/EingangFilesRepository.java @@ -35,6 +35,7 @@ import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.stereotype.Repository; import de.ozgcloud.vorgang.common.db.CriteriaUtil; +import de.ozgcloud.vorgang.vorgang.IncomingFile; import de.ozgcloud.vorgang.vorgang.Vorgang; @Repository @@ -49,7 +50,7 @@ class EingangFilesRepository { private static final String FIELD_ATTACHMENT_FILES = DB_FIELDNAME_EINGANGS + ".attachments." + DB_FIELDNAME_FILES; private static final String FIELD_REPRESENTATION_FILES = DB_FIELDNAME_EINGANGS + ".representations"; - public List<OzgFile> findAttachments(String vorgangId, Optional<String> eingangId) { + public List<IncomingFile> findAttachments(String vorgangId, Optional<String> eingangId) { return findFiles(buildSearchCriteria(vorgangId, eingangId), buildExtractAttachmentsAggregation()); } @@ -65,7 +66,7 @@ class EingangFilesRepository { ); } - public List<OzgFile> findRepresentations(String vorgangId, Optional<String> eingangId) { + public List<IncomingFile> findRepresentations(String vorgangId, Optional<String> eingangId) { return findFiles(buildSearchCriteria(vorgangId, eingangId), buildFindRepresentationsAggregation()); } @@ -91,10 +92,10 @@ class EingangFilesRepository { Aggregation.replaceRoot(DB_FIELDNAME_FILES)); } - private List<OzgFile> findFiles(Criteria criteria, List<AggregationOperation> extractOperations) { + private List<IncomingFile> findFiles(Criteria criteria, List<AggregationOperation> extractOperations) { List<AggregationOperation> operations = new ArrayList<>(); operations.add(Aggregation.match(criteria)); operations.addAll(extractOperations); - return mongoTemplate.aggregate(Aggregation.newAggregation(operations), Vorgang.COLLECTION_NAME, OzgFile.class).getMappedResults(); + return mongoTemplate.aggregate(Aggregation.newAggregation(operations), Vorgang.COLLECTION_NAME, IncomingFile.class).getMappedResults(); } } diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/FileService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/FileService.java index c1d7f24fae27e67b8eefd1c8c3c681ad0c61974c..046e90d7556fd5be53960979e3f04ba20d68cc97 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/FileService.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/FileService.java @@ -26,78 +26,70 @@ package de.ozgcloud.vorgang.files; import java.io.InputStream; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Stream; -import java.util.stream.StreamSupport; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; -import com.mongodb.client.gridfs.model.GridFSFile; - -import de.ozgcloud.nachrichten.postfach.BinaryFileService; import de.ozgcloud.vorgang.common.security.PolicyService; +import de.ozgcloud.vorgang.vorgang.IncomingFile; import lombok.RequiredArgsConstructor; //TODO make service package protected, as soon PersistPostfachMailByCommandService and FileStreamService is gone @Service @RequiredArgsConstructor -public class FileService implements BinaryFileService { +public class FileService { public static final int CHUNK_SIZE = 255 * 1024; private final PolicyService policyService; private final EingangFilesRepository repository; - private final BinaryFileRepository binaryFileRepository; + private final OzgCloudFileRepository ozgCloudFileRepository; private final FileIdMapper fileIdMapper; - public List<OzgFile> getAttachments(String vorgangId, Optional<String> eingangId) { + public List<IncomingFile> getAttachments(String vorgangId, Optional<String> eingangId) { policyService.checkPermission(vorgangId); return repository.findAttachments(vorgangId, eingangId); } - public List<OzgFile> getRepresentations(String vorgangId, Optional<String> eingangId) { + public List<IncomingFile> getRepresentations(String vorgangId, Optional<String> eingangId) { policyService.checkPermission(vorgangId); return repository.findRepresentations(vorgangId, eingangId); } - public FileId uploadFileStream(UploadedFilesReference ref, OzgFile file, Optional<String> userId, InputStream content) { - return uploadFile(ref, file, userId, content); + public FileId saveOzgCloudFile(OzgCloudFile ozgCloudFile) { + return fileIdMapper.toFileId(ozgCloudFileRepository.save(ozgCloudFile).getId()); } - @Async - public CompletableFuture<FileId> uploadFileStreamAsync(UploadedFilesReference ref, OzgFile file, Optional<String> userId, InputStream content) { - return CompletableFuture.completedFuture(uploadFile(ref, file, userId, content)); + public void patch(FileId fileId, long version, Map<String, Object> patch) { + ozgCloudFileRepository.patch(fileId, version, patch); } - FileId uploadFile(UploadedFilesReference ref, OzgFile file, Optional<String> userId, InputStream content) { - return binaryFileRepository.addContentStream(ref, file, userId, content); - } - - public InputStream getUploadedFileStream(FileId fileId) { - return binaryFileRepository.getFileContent(fileId); + @Async + public CompletableFuture<String> uploadFileContentStreamAsync(UploadedFilesReference ref, InputStream content) { + return CompletableFuture.completedFuture(uploadFileContent(ref, content)); } - @Override - public InputStream getUploadedFileStream(de.ozgcloud.nachrichten.postfach.FileId fileId) { - return binaryFileRepository.getFileContent(FileId.from(fileId.toString())); + String uploadFileContent(UploadedFilesReference ref, InputStream content) { + return ozgCloudFileRepository.uploadContent(ref, content); } - @Override - public GridFSFile getFile(de.ozgcloud.nachrichten.postfach.FileId fileId) { - return binaryFileRepository.getFile(FileId.from(fileId.toString())); + public InputStream getUploadedFileStream(FileId fileId) { + return ozgCloudFileRepository.getFileContent(fileId); } - public Stream<OzgFile> findFilesMetaData(Collection<FileId> ids) { - return StreamSupport.stream(binaryFileRepository.getAll(mapFileIdToString(ids)).spliterator(), false); + public Stream<OzgCloudFile> findOzgCloudFiles(Collection<FileId> ids) { + return ozgCloudFileRepository.findAll(ids); } - private Iterable<String> mapFileIdToString(Collection<FileId> ids) { - return ids.stream().map(fileIdMapper::toString).toList(); + public void deleteAllByVorgang(String vorgangId) { + ozgCloudFileRepository.deleteAllByVorgang(vorgangId); } - public void deleteAllByVorgang(String vorgangId) { - binaryFileRepository.deleteAllByVorgang(vorgangId); + public void deleteContent(String contentId) { + ozgCloudFileRepository.deleteContent(contentId); } } \ No newline at end of file diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcBinaryFileService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcBinaryFileService.java index 564e6c265acfb593791d50256354a4e01de2f3c9..81d539b400f6bc00bb0795fd0f8351f7b956debb 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcBinaryFileService.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcBinaryFileService.java @@ -24,59 +24,63 @@ package de.ozgcloud.vorgang.files; import java.io.BufferedInputStream; -import java.io.IOException; import java.io.InputStream; import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import org.apache.commons.io.IOUtils; -import org.springframework.beans.factory.annotation.Autowired; +import org.apache.commons.lang3.StringUtils; import com.google.protobuf.ByteString; import de.ozgcloud.common.errorhandling.TechnicalException; +import de.ozgcloud.vorgang.callcontext.CallContextUser; +import de.ozgcloud.vorgang.callcontext.CurrentUserService; +import de.ozgcloud.vorgang.common.errorhandling.NotFoundException; import de.ozgcloud.vorgang.common.security.PolicyService; import de.ozgcloud.vorgang.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceImplBase; import de.ozgcloud.vorgang.grpc.binaryFile.GrpcBinaryFilesRequest; import de.ozgcloud.vorgang.grpc.binaryFile.GrpcFindFilesResponse; import de.ozgcloud.vorgang.grpc.binaryFile.GrpcGetBinaryFileDataRequest; import de.ozgcloud.vorgang.grpc.binaryFile.GrpcGetBinaryFileDataResponse; +import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUpdateBinaryFileRequest; +import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUpdateBinaryFileResponse; import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileRequest; import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileResponse; import io.grpc.stub.CallStreamObserver; import io.grpc.stub.StreamObserver; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import net.devh.boot.grpc.server.service.GrpcService; @Log4j2 @GrpcService +@RequiredArgsConstructor public class GrpcBinaryFileService extends BinaryFileServiceImplBase { - // FIXME use client from security context - static final String CLIENT = "Alfa"; // currently single client is hardcoded - - @Autowired - private FileService service; - @Autowired - private GrpcOzgFileMapper fileMapper; - @Autowired - private PolicyService policyService; + private final FileService service; + private final GrpcOzgFileMapper fileMapper; + private final PolicyService policyService; + private final CurrentUserService currentUserService; @Override public void findBinaryFilesMetaData(GrpcBinaryFilesRequest request, StreamObserver<GrpcFindFilesResponse> responseObserver) { - var found = service.findFilesMetaData(mapFileIds(request.getFileIdList())).toList(); + var found = service.findOzgCloudFiles(mapFileIds(request.getFileIdList())).toList(); - policyService.checkPermission(found.stream().map(OzgFile::getVorgangId).collect(Collectors.toSet())); + policyService.checkPermission(found.stream().map(OzgCloudFile::getVorgangId).collect(Collectors.toSet())); responseObserver.onNext(buildResponseForOzgFiles(found)); responseObserver.onCompleted(); } - public GrpcFindFilesResponse buildResponseForOzgFiles(List<OzgFile> files) { + GrpcFindFilesResponse buildResponseForOzgFiles(List<OzgCloudFile> files) { var builder = GrpcFindFilesResponse.newBuilder(); - files.stream().map(file -> fileMapper.map(file)).forEach(builder::addFile); + files.stream().map(fileMapper::map).forEach(builder::addFile); return builder.build(); } @@ -86,10 +90,47 @@ public class GrpcBinaryFileService extends BinaryFileServiceImplBase { @Override public StreamObserver<GrpcUploadBinaryFileRequest> uploadBinaryFileAsStream(StreamObserver<GrpcUploadBinaryFileResponse> responseObserver) { - return new UploadStreamObserver(responseObserver, service, policyService); + return UploadServerStreamObserver.<OzgCloudFile, GrpcUploadBinaryFileRequest>builder() + .requestVerifier(fileUploadRequest -> policyService.checkPermission(fileUploadRequest.getMetadata().getVorgangId())) + .hasMetadata(GrpcUploadBinaryFileRequest::hasMetadata) + .metadataMapper(this::buildOzgCloudFile) + .metadataUploader(this::uploadOzgCloudFile) + .fileContentUploader(this::uploadFileContent) + .getFileContent(fileUploadRequest -> fileUploadRequest.getFileContent().toByteArray()) + .responseConsumer(fileId -> completeRequest(buildResponse(fileId), responseObserver)) + .cleanupFileContent(service::deleteContent) + .build(); + } + + FileId uploadOzgCloudFile(OzgCloudFile ozgCloudFile, Optional<String> contentId) { + return service.saveOzgCloudFile(updateContentId(ozgCloudFile, contentId)); + } + + OzgCloudFile buildOzgCloudFile(GrpcUploadBinaryFileRequest fileUploadRequest) { + return OzgCloudFile.builder() + .vorgangId(fileUploadRequest.getMetadata().getVorgangId()) + .name(fileUploadRequest.getMetadata().getFileName()) + .contentType(fileUploadRequest.getMetadata().getContentType()) + .fieldName(fileUploadRequest.getMetadata().getField()) + .createdBy(getUserId(fileUploadRequest)) + .client(getClient(fileUploadRequest)) + .build(); + } + + String getUserId(GrpcUploadBinaryFileRequest fileUploadRequest) { + return currentUserService.findUser().flatMap(CallContextUser::getUserId) + .orElseGet(() -> fileUploadRequest.getMetadata().getContext().getUser().getId()); } - static GrpcUploadBinaryFileResponse buildResponse(FileId fileId) { + String getClient(GrpcUploadBinaryFileRequest fileUploadRequest) { + return findContextClient().or(() -> getRequestClient(fileUploadRequest)).orElseThrow(() -> new IllegalStateException("Client is missing")); + } + + Optional<String> getRequestClient(GrpcUploadBinaryFileRequest fileUploadRequest) { + return Optional.of(fileUploadRequest.getMetadata().getContext().getClient()); + } + + GrpcUploadBinaryFileResponse buildResponse(FileId fileId) { return GrpcUploadBinaryFileResponse.newBuilder().setFileId(fileId.toString()).build(); } @@ -102,57 +143,149 @@ public class GrpcBinaryFileService extends BinaryFileServiceImplBase { downloadFile(responseObserver, new BufferedInputStream(fileContent, FileService.CHUNK_SIZE)); } - void downloadFile(final StreamObserver<GrpcGetBinaryFileDataResponse> streamObserver, final InputStream fileStream) { + void downloadFile(StreamObserver<GrpcGetBinaryFileDataResponse> streamObserver, InputStream fileStream) { AtomicBoolean doSendFile = new AtomicBoolean(true); - final CallStreamObserver<GrpcGetBinaryFileDataResponse> callObserver = (CallStreamObserver<GrpcGetBinaryFileDataResponse>) streamObserver; + var callObserver = (CallStreamObserver<GrpcGetBinaryFileDataResponse>) streamObserver; callObserver.setOnReadyHandler(() -> sendChunks(callObserver, fileStream, doSendFile)); + sendChunks(callObserver, fileStream, doSendFile); } - void sendChunks(CallStreamObserver<GrpcGetBinaryFileDataResponse> callObserver, InputStream fileStream, AtomicBoolean doSendFile) { - try { - // TODO remove additional flow control when alfa side has been fixed see - // OZG-2631 - if (doSendFile.get()) { - int size = sendNextChunk(callObserver, fileStream); - - if (size < FileService.CHUNK_SIZE) { - handleFileEndReached(callObserver, fileStream, doSendFile); - } + synchronized void sendChunks(CallStreamObserver<GrpcGetBinaryFileDataResponse> callObserver, InputStream fileStream, AtomicBoolean doSendFile) { + if (!doSendFile.get()) { + return; + } + while (callObserver.isReady()) { + LOG.debug("observer is ready. sending..."); + int size = sendNextChunk(callObserver, fileStream); + if (size < FileService.CHUNK_SIZE) { + handleFileEndReached(callObserver, fileStream, doSendFile); + break; } - } catch (Exception e) { - handleException(fileStream, e); } + LOG.debug("Stop sending"); } - int sendNextChunk(CallStreamObserver<GrpcGetBinaryFileDataResponse> callObserver, InputStream fileStream) - throws IOException { + int sendNextChunk(CallStreamObserver<GrpcGetBinaryFileDataResponse> callObserver, InputStream fileStream) { var bytes = new byte[FileService.CHUNK_SIZE]; - int size = fileStream.read(bytes); + try { + int size = fileStream.read(bytes); + LOG.debug("Read {} bytes from file.", size); + if (size > 0) { + callObserver.onNext(buildChunkResponse(bytes, size)); + } + return size; - if (size > 0) { - callObserver.onNext(buildChunkResponse(bytes, size)); + } catch (Exception exception) { + IOUtils.closeQuietly(fileStream, e -> LOG.error("InputStream cannot be closed.", e)); + throw new TechnicalException("Fehler beim Senden des Datei-Chunks", exception); } - - return size; } - private void handleFileEndReached(CallStreamObserver<GrpcGetBinaryFileDataResponse> callObserver, InputStream fileStream, - AtomicBoolean doSendFile) { - IOUtils.closeQuietly(fileStream); - callObserver.onCompleted(); + void handleFileEndReached(CallStreamObserver<GrpcGetBinaryFileDataResponse> callObserver, InputStream fileStream, AtomicBoolean doSendFile) { + LOG.debug("File end reached"); doSendFile.getAndSet(false); + IOUtils.closeQuietly(fileStream, e -> LOG.error("InputStream cannot be closed.", e)); + callObserver.onCompleted(); + } + + GrpcGetBinaryFileDataResponse buildChunkResponse(byte[] bytes, int size) { + return GrpcGetBinaryFileDataResponse.newBuilder().setFileContent(ByteString.copyFrom(bytes, 0, size)).build(); + } + + @Override + public StreamObserver<GrpcUpdateBinaryFileRequest> updateBinaryFile(StreamObserver<GrpcUpdateBinaryFileResponse> responseObserver) { + return UploadServerStreamObserver.<OzgCloudFile, GrpcUpdateBinaryFileRequest>builder() + .requestVerifier(this::verifyUpdateRequest) + .hasMetadata(GrpcUpdateBinaryFileRequest::hasMetadata) + .metadataMapper(this::buildOzgCloudFile) + .metadataUploader(this::patchOzgCloudFile) + .fileContentUploader(this::uploadFileContent) + .getFileContent(fileUpdateRequest -> fileUpdateRequest.getContent().toByteArray()) + .responseConsumer(fileId -> completeRequest(GrpcUpdateBinaryFileResponse.getDefaultInstance(), responseObserver)) + .cleanupFileContent(service::deleteContent) + .build(); + } + void verifyUpdateRequest(GrpcUpdateBinaryFileRequest fileUpdateRequest) { + validateMetadata(fileUpdateRequest); + policyService.checkPermissionByFile(fileUpdateRequest.getMetadata().getFileId()); } - private void handleException(InputStream fileStream, Exception e) { - LOG.error("Fehler beim Senden des Datei-Chunks", e); + void validateMetadata(GrpcUpdateBinaryFileRequest fileUpdateRequest) { + if (!fileUpdateRequest.hasMetadata()) { + throw new IllegalArgumentException("Metadata is missing"); + } + var metadata = fileUpdateRequest.getMetadata(); + if (StringUtils.isBlank(metadata.getFileId())) { + throw new IllegalArgumentException("File ID is missing"); + } else if (StringUtils.isBlank(metadata.getFileName())) { + throw new IllegalArgumentException("File name is missing"); + } else if (StringUtils.isBlank(metadata.getContentType())) { + throw new IllegalArgumentException("Content type is missing"); + } + } - IOUtils.closeQuietly(fileStream); + OzgCloudFile buildOzgCloudFile(GrpcUpdateBinaryFileRequest fileUpdateRequest) { + var fileId = FileId.from(fileUpdateRequest.getMetadata().getFileId()); + return service.findOzgCloudFiles(List.of(fileId)).map(ozgCloudFile -> updateOzgCloudFile(ozgCloudFile, fileUpdateRequest)).findFirst() + .orElseThrow(() -> new NotFoundException(OzgCloudFile.class, fileId)); + } - throw new TechnicalException("Fehler beim Senden des Datei-Chunks", e); + OzgCloudFile updateOzgCloudFile(OzgCloudFile ozgCloudFile, GrpcUpdateBinaryFileRequest fileUpdateRequest) { + return ozgCloudFile.toBuilder() + .name(fileUpdateRequest.getMetadata().getFileName()) + .contentType(fileUpdateRequest.getMetadata().getContentType()) + .build(); } - GrpcGetBinaryFileDataResponse buildChunkResponse(byte[] bytes, int size) { - return GrpcGetBinaryFileDataResponse.newBuilder().setFileContent(ByteString.copyFrom(bytes, 0, size)).build(); + FileId patchOzgCloudFile(OzgCloudFile ozgCloudFile, Optional<String> newContentId) { + var fileId = FileId.from(ozgCloudFile.getId()); + service.patch(fileId, ozgCloudFile.getVersion(), buildPatch(updateContentId(ozgCloudFile, newContentId))); + newContentId.ifPresent(newId -> service.deleteContent(ozgCloudFile.getContentId())); + return fileId; + } + + OzgCloudFile updateContentId(OzgCloudFile ozgCloudFile, Optional<String> contentId) { + return contentId.map(id -> ozgCloudFile.toBuilder().contentId(id).build()).orElse(ozgCloudFile); + } + + Map<String, Object> buildPatch(OzgCloudFile ozgCloudFile) { + return Map.of( + OzgCloudFile.FIELD_FILE_NAME, ozgCloudFile.getName(), + OzgCloudFile.FIELD_CONTENT_TYPE, ozgCloudFile.getContentType(), + OzgCloudFile.FIELD_CONTENT_ID, ozgCloudFile.getContentId(), + OzgCloudFile.FIELD_CREATED_BY, getUserId(), + OzgCloudFile.FIELD_CLIENT, getClient() + ); + } + + String getUserId() { + return currentUserService.getUser().getUserId().orElseThrow(() -> new IllegalStateException("User ID is missing")); } + + String getClient() { + return findContextClient().orElseThrow(() -> new IllegalStateException("Client is missing")); + } + + Optional<String> findContextClient() { + return currentUserService.findUser().map(CallContextUser::getClientName); + } + + CompletableFuture<String> uploadFileContent(OzgCloudFile ozgCloudFile, InputStream pipedInput) { + return service.uploadFileContentStreamAsync(buildUploadFilesReferences(ozgCloudFile), pipedInput); + } + + UploadedFilesReference buildUploadFilesReferences(OzgCloudFile ozgCloudFile) { + return UploadedFilesReference.builder() + .vorgangId(ozgCloudFile.getVorgangId()) + .client(ozgCloudFile.getClient()) + .fileName(ozgCloudFile.getName()) + .name(ozgCloudFile.getFieldName()).build(); + } + + <T> void completeRequest(T response, StreamObserver<T> responseObserver) { + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + } \ No newline at end of file diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcFileService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcFileService.java index 7e2f7f5b63dbf8bd35d9869325f9445a6ee12370..40b879e78a2f97c049ccc2aa75d96d0a8dc9a652 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcFileService.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcFileService.java @@ -35,6 +35,7 @@ import de.ozgcloud.vorgang.grpc.file.GrpcGetAttachmentsRequest; import de.ozgcloud.vorgang.grpc.file.GrpcGetAttachmentsResponse; import de.ozgcloud.vorgang.grpc.file.GrpcGetRepresentationsRequest; import de.ozgcloud.vorgang.grpc.file.GrpcGetRepresentationsResponse; +import de.ozgcloud.vorgang.vorgang.IncomingFile; import io.grpc.stub.StreamObserver; import net.devh.boot.grpc.server.service.GrpcService; @@ -49,9 +50,8 @@ public class GrpcFileService extends FileServiceImplBase { @Override public void getAttachments(GrpcGetAttachmentsRequest request, StreamObserver<GrpcGetAttachmentsResponse> responseObserver) { - List<OzgFile> response = service.getAttachments(getVorgangId(request), getEingangId(request)); - - responseObserver.onNext(GrpcGetAttachmentsResponse.newBuilder().addAllFile(fileMapper.map(response)).build()); + var response = service.getAttachments(getVorgangId(request), getEingangId(request)); + responseObserver.onNext(buildAttachmentsResponse(response)); responseObserver.onCompleted(); } @@ -63,11 +63,17 @@ public class GrpcFileService extends FileServiceImplBase { return Optional.of(request.getEingangId()).map(StringUtils::trimToNull); } + + GrpcGetAttachmentsResponse buildAttachmentsResponse(List<IncomingFile> files) { + var responseBuilder = GrpcGetAttachmentsResponse.newBuilder(); + files.stream().map(fileMapper::map).forEach(responseBuilder::addFile); + return responseBuilder.build(); + } + @Override public void getRepresentations(GrpcGetRepresentationsRequest request, StreamObserver<GrpcGetRepresentationsResponse> responseObserver) { - List<OzgFile> response = service.getRepresentations(getVorgangId(request), getEingangId(request)); - - responseObserver.onNext(GrpcGetRepresentationsResponse.newBuilder().addAllFile(fileMapper.map(response)).build()); + var response = service.getRepresentations(getVorgangId(request), getEingangId(request)); + responseObserver.onNext(buildRepresentationsResponse(response)); responseObserver.onCompleted(); } @@ -79,6 +85,12 @@ public class GrpcFileService extends FileServiceImplBase { return Optional.of(request.getEingangId()).map(StringUtils::trimToNull); } + GrpcGetRepresentationsResponse buildRepresentationsResponse(List<IncomingFile> files) { + var responseBuilder = GrpcGetRepresentationsResponse.newBuilder(); + files.stream().map(fileMapper::map).forEach(responseBuilder::addFile); + return responseBuilder.build(); + } + TechnicalException createMissingVorgangIdException() { return new TechnicalException("VorgangId is required"); } diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcOzgFileMapper.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcOzgFileMapper.java index e7eb4da2b1ebabff3f5b632bc224f6d8737888c2..343a545b2d671524e51e0856c822b221d7ba647d 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcOzgFileMapper.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/GrpcOzgFileMapper.java @@ -30,11 +30,13 @@ import org.mapstruct.Mapping; import org.mapstruct.NullValueCheckStrategy; import de.ozgcloud.vorgang.grpc.file.GrpcOzgFile; +import de.ozgcloud.vorgang.vorgang.IncomingFile; // TODO rename to GrpcBinaryFileMapper @Mapper(uses = FileIdMapper.class, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) interface GrpcOzgFileMapper { + @Mapping(target = "defaultInstanceForType", ignore = true) @Mapping(target = "mergeFrom", ignore = true) @Mapping(target = "clearField", ignore = true) @Mapping(target = "clearOneof", ignore = true) @@ -44,7 +46,19 @@ interface GrpcOzgFileMapper { @Mapping(target = "nameBytes", ignore = true) @Mapping(target = "unknownFields", ignore = true) @Mapping(target = "allFields", ignore = true) - GrpcOzgFile map(OzgFile file); + GrpcOzgFile map(OzgCloudFile file); - List<GrpcOzgFile> map(List<OzgFile> files); + @Mapping(target = "unknownFields", ignore = true) + @Mapping(target = "nameBytes", ignore = true) + @Mapping(target = "mergeUnknownFields", ignore = true) + @Mapping(target = "mergeFrom", ignore = true) + @Mapping(target = "idBytes", ignore = true) + @Mapping(target = "defaultInstanceForType", ignore = true) + @Mapping(target = "contentTypeBytes", ignore = true) + @Mapping(target = "clearOneof", ignore = true) + @Mapping(target = "clearField", ignore = true) + @Mapping(target = "allFields", ignore = true) + GrpcOzgFile map(IncomingFile file); + + List<GrpcOzgFile> map(List<OzgCloudFile> files); } diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgCloudFile.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgCloudFile.java new file mode 100644 index 0000000000000000000000000000000000000000..991d42fb434dd442a19e7eaa68cdda8062117227 --- /dev/null +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgCloudFile.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.files; + +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.TypeAlias; +import org.springframework.data.annotation.Version; +import org.springframework.data.mongodb.core.mapping.Document; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Builder(toBuilder = true) +@ToString +@Getter +@AllArgsConstructor(access = AccessLevel.PUBLIC) +@Document(collection = OzgCloudFile.COLLECTION_NAME) +@TypeAlias("OzgCloudFile") +public class OzgCloudFile { + + public static final String COLLECTION_NAME = "ozgCloudFile"; + + static final String FIELD_ID = "_id"; + static final String FIELD_VERSION = "version"; + static final String FIELD_FILE_NAME = "name"; + static final String FIELD_CONTENT_TYPE = "contentType"; + static final String FIELD_SIZE = "size"; + static final String FIELD_CONTENT_ID = "contentId"; + static final String FIELD_CLIENT = "client"; + static final String FIELD_VORGANG_ID = "vorgangId"; + static final String FIELD_CREATED_BY = "createdBy"; + static final String FIELD_FIELD_NAME = "fieldName"; + + @Id + private String id; + @Version + private long version; + private String vorgangId; + private String name; + private Long size; + private String contentType; + private String fieldName; + private String client; + private String contentId; + private String createdBy; +} diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgCloudFileRepository.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgCloudFileRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..d9ec33225bde9b06c4ed58a412a517d291d29660 --- /dev/null +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgCloudFileRepository.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.files; + +import static org.springframework.data.mongodb.core.query.Criteria.*; +import static org.springframework.data.mongodb.core.query.Query.*; + +import java.io.InputStream; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; + +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.data.mongodb.core.query.UpdateDefinition; +import org.springframework.data.mongodb.gridfs.GridFsResource; +import org.springframework.data.mongodb.gridfs.GridFsTemplate; +import org.springframework.data.mongodb.gridfs.GridFsUpload; +import org.springframework.stereotype.Repository; + +import com.mongodb.client.gridfs.model.GridFSFile; + +import de.ozgcloud.vorgang.common.db.CollisionVerifier; +import de.ozgcloud.vorgang.common.db.CriteriaUtil; +import de.ozgcloud.vorgang.common.errorhandling.NotFoundException; +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +class OzgCloudFileRepository { + + static final String GRID_FS_FIELD_ID = "_id"; + + private final CollisionVerifier collisionVerifier = new CollisionVerifier(this::existOzgCloudFile); + + private final GridFsTemplate gridFsTemplate; + private final MongoOperations mongoOperations; + + public OzgCloudFile save(OzgCloudFile file) { + return mongoOperations.save(setContentSize(file)); + } + + private OzgCloudFile setContentSize(OzgCloudFile file) { + return file.toBuilder().size(getContentSize(file.getContentId())).build(); + } + + public void patch(FileId fileId, long version, Map<String, Object> patch) { + var updateResult = mongoOperations.updateFirst(queryByIdAndVersion(fileId, version), buildUpdate(patch), OzgCloudFile.COLLECTION_NAME); + collisionVerifier.verify(updateResult, fileId.toString()); + } + + private Query queryByIdAndVersion(FileId fileId, long version) { + return query(CriteriaUtil.whereIdAndVersion(fileId.toString(), version)); + } + + private UpdateDefinition buildUpdate(Map<String, Object> patch) { + var update = new Update(); + update.inc(OzgCloudFile.FIELD_VERSION); + patch.forEach(update::set); + Optional.ofNullable(patch.get(OzgCloudFile.FIELD_CONTENT_ID)).map(String.class::cast) + .ifPresent(contentId -> update.set(OzgCloudFile.FIELD_SIZE, getContentSize(contentId))); + return update; + } + + private long getContentSize(String contentId) { + var contentGridFsFile = gridFsTemplate.find(queryByObjectId(contentId)).first(); + if (Objects.isNull(contentGridFsFile)) { + throw new NotFoundException(GridFSFile.class, contentId); + } + return contentGridFsFile.getLength(); + } + + public String uploadContent(UploadedFilesReference ref, InputStream content) { + return gridFsTemplate.store(buildGridFsUpload(ref, content)).toHexString(); + } + + GridFsUpload<ObjectId> buildGridFsUpload(UploadedFilesReference ref, InputStream content) { + return GridFsUpload.fromStream(content).filename(createFilePath(ref)).build(); + } + + String createFilePath(UploadedFilesReference ref) { + return String.format("%s/%s/%s/%s", ref.getVorgangId(), ref.getClient(), ref.getName(), ref.getFileName()); + } + + public InputStream getFileContent(FileId fileId) { + return Optional.ofNullable(mongoOperations.findById(fileId.toString(), OzgCloudFile.class)) + .map(OzgCloudFile::getContentId) + .map(this::getFile) + .map(gridFsTemplate::getResource) + .map(GridFsResource::getContent) + .orElseThrow(() -> new NotFoundException(OzgCloudFile.class, fileId)); + } + + GridFSFile getFile(String contentId) { + return gridFsTemplate.findOne(queryByObjectId(contentId)); + } + + public Stream<OzgCloudFile> findAll(Collection<FileId> fileIds) { + var ids = fileIds.stream().map(FileId::toString).toList(); + return mongoOperations.stream(query(where(OzgCloudFile.FIELD_ID).in(ids)), OzgCloudFile.class); + } + + public void deleteAllByVorgang(String vorgangId) { + var deletedOzgCloudFile = mongoOperations.findAllAndRemove(query(where(OzgCloudFile.FIELD_VORGANG_ID).is(vorgangId)), OzgCloudFile.class); + deletedOzgCloudFile.stream().map(OzgCloudFile::getContentId).filter(Objects::nonNull).forEach(this::deleteContent); + } + + public void deleteContent(String contentId) { + gridFsTemplate.delete(queryByObjectId(contentId)); + } + + private Query queryByObjectId(String contentId) { + return Query.query(Criteria.where(GRID_FS_FIELD_ID).is(contentId)); + } + + private boolean existOzgCloudFile(String fileId) { + return mongoOperations.exists(query(where(OzgCloudFile.FIELD_ID).is(fileId)), OzgCloudFile.class); + } +} diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgFile.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgCloudFileUploadException.java similarity index 71% rename from vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgFile.java rename to vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgCloudFileUploadException.java index 23772dd6777e2262ac9c3d48c481ed9b873ac7a9..1f26bf4fee38aff2f57fa74f356d66277e217824 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgFile.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/OzgCloudFileUploadException.java @@ -23,23 +23,11 @@ */ package de.ozgcloud.vorgang.files; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.ToString; +import de.ozgcloud.vorgang.common.errorhandling.FunctionalException; -// TODO: Rename to BinaryFile +public class OzgCloudFileUploadException extends FunctionalException { -@Builder(toBuilder = true) -@ToString -@Getter -@AllArgsConstructor(access = AccessLevel.PUBLIC) -public class OzgFile { - - private FileId id; - private String vorgangId; - private String name; - private Long size; - private String contentType; + public OzgCloudFileUploadException(String message) { + super(message, () -> "OZG_CLOUD_FILE.UPLOAD_FAILED"); + } } diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/UploadServerStreamObserver.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/UploadServerStreamObserver.java new file mode 100644 index 0000000000000000000000000000000000000000..9821a129bf1383698da976c85fc3d787d92c13d0 --- /dev/null +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/UploadServerStreamObserver.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.files; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.apache.commons.io.IOUtils; + +import de.ozgcloud.common.errorhandling.TechnicalException; +import io.grpc.stub.StreamObserver; +import lombok.Builder; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +class UploadServerStreamObserver<M, T> implements StreamObserver<T> { + + static final int TIMEOUT_MINUTES = 10; + + private final Consumer<T> requestVerifier; + private final Predicate<T> hasMetadata; + private final Function<T, M> metadataMapper; + private final BiFunction<M, Optional<String>, FileId> metadataUploader; + private final BiFunction<M, InputStream, CompletableFuture<String>> fileContentUploader; + private final Function<T, byte[]> getFileContent; + private final Consumer<FileId> responseConsumer; + private final Consumer<String> cleanupFileContent; + + private final AtomicBoolean metadataUploaded = new AtomicBoolean(false); + private final AtomicReference<M> fileMetadata = new AtomicReference<>(); + + private PipedOutputStream pipedOutput; + private PipedInputStream pipedInput; + private CompletableFuture<String> contentIdFuture; + + @Builder + UploadServerStreamObserver(Consumer<T> requestVerifier, Predicate<T> hasMetadata, Function<T, M> metadataMapper, //NOSONAR + BiFunction<M, Optional<String>, FileId> metadataUploader, BiFunction<M, InputStream, CompletableFuture<String>> fileContentUploader, + Function<T, byte[]> getFileContent, Consumer<FileId> responseConsumer, Consumer<String> cleanupFileContent) { + this.requestVerifier = requestVerifier; + this.hasMetadata = hasMetadata; + this.metadataMapper = metadataMapper; + this.metadataUploader = metadataUploader; + this.fileContentUploader = fileContentUploader; + this.getFileContent = getFileContent; + this.responseConsumer = responseConsumer; + this.cleanupFileContent = cleanupFileContent; + } + + @Override + public synchronized void onNext(T fileUploadRequest) { + if (hasMetadata.test(fileUploadRequest)) { + setMetadata(fileUploadRequest); + } else { + if (Objects.isNull(contentIdFuture)) { + contentIdFuture = initContentUpload(); + } + storeFileContent(fileUploadRequest); + } + } + + CompletableFuture<String> initContentUpload() { + return fileContentUploader.apply(fileMetadata.get(), initPipedStreams()); + } + + void setMetadata(T fileUploadRequest) { + if (metadataUploaded.getAndSet(true)) { + throw new OzgCloudFileUploadException("Attempt to upload multiple files. Multiple uploads are not allowed."); + } + requestVerifier.accept(fileUploadRequest); + fileMetadata.set(metadataMapper.apply(fileUploadRequest)); + } + + void storeFileContent(T fileUploadRequest) { + if (Objects.isNull(fileMetadata.get())) { + throw new OzgCloudFileUploadException("File content received before metadata."); + } + try { + pipedOutput.write(getFileContent.apply(fileUploadRequest)); + } catch (IOException e) { + throw new TechnicalException("Error when writing file content.", e); + } + } + + InputStream initPipedStreams() { + try { + pipedInput = new PipedInputStream(FileService.CHUNK_SIZE); + pipedOutput = new PipedOutputStream(pipedInput); + return pipedInput; + } catch (IOException e) { + throw new TechnicalException("Upload initialization failed", e); + } + } + + @Override + public void onError(Throwable t) { + LOG.error("Error happened. Upload stream closed", t); + closeOutputPipe(); + closeInputPipe(); + } + + @Override + public void onCompleted() { + LOG.debug("Complete upload request."); + closeOutputPipe(); + var newContentId = getContentId(); + try { + var fileId = metadataUploader.apply(fileMetadata.get(), newContentId); + responseConsumer.accept(fileId); + } catch (RuntimeException e) { + newContentId.ifPresent(cleanupFileContent); + throw new TechnicalException("Cannot complete request.", e); + } + } + + Optional<String> getContentId() { + try { + return Objects.isNull(contentIdFuture) ? Optional.empty() : Optional.of(contentIdFuture.get(TIMEOUT_MINUTES, TimeUnit.MINUTES)); + } catch (ExecutionException | TimeoutException e) { + throw new TechnicalException("Upload failed", e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new TechnicalException("Upload was interrupted", e); + } finally { + closeInputPipe(); + } + } + + void closeOutputPipe() { + IOUtils.closeQuietly(pipedOutput, e -> LOG.error("Cannot close output stream", e)); + } + + void closeInputPipe() { + IOUtils.closeQuietly(pipedInput, e -> LOG.error("Cannot close input stream", e)); + } +} \ No newline at end of file diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/UploadStreamObserver.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/UploadStreamObserver.java deleted file mode 100644 index 92749b32b947afe2a60c63f8fcbebd56db45ea5a..0000000000000000000000000000000000000000 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/UploadStreamObserver.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -package de.ozgcloud.vorgang.files; - -import java.io.IOException; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import de.ozgcloud.common.errorhandling.TechnicalException; -import de.ozgcloud.vorgang.common.security.PolicyService; -import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileRequest; -import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileResponse; -import io.grpc.stub.StreamObserver; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.extern.log4j.Log4j2; - -@Log4j2 -class UploadStreamObserver implements StreamObserver<GrpcUploadBinaryFileRequest> { - private final FileService service; - private final PolicyService policyService; - private final StreamObserver<GrpcUploadBinaryFileResponse> responseObserver; - @Getter(value = AccessLevel.PACKAGE) - private PipedOutputStream pipedOutput; - @Getter(value = AccessLevel.PACKAGE) - private PipedInputStream pipedInput; - - private CompletableFuture<FileId> fileIdFuture; - - public UploadStreamObserver(StreamObserver<GrpcUploadBinaryFileResponse> responseObserver, FileService service, PolicyService policyService) { - this.responseObserver = responseObserver; - this.service = service; - this.policyService = policyService; - } - - @Override - public void onNext(GrpcUploadBinaryFileRequest fileUploadRequest) { - if (fileUploadRequest.hasMetadata()) { - policyService.checkPermission(Set.of(fileUploadRequest.getMetadata().getVorgangId())); - initPipedStreams(); - fileIdFuture = storeFileMetaData(fileUploadRequest, pipedInput, pipedOutput); - } else { - storeFileContent(fileUploadRequest, pipedOutput); - } - } - - void initPipedStreams() { - pipedOutput = new PipedOutputStream(); - pipedInput = new PipedInputStream(FileService.CHUNK_SIZE); - } - - void storeFileContent(GrpcUploadBinaryFileRequest fileUploadRequest, PipedOutputStream pipedOutput) { - try { - pipedOutput.write(fileUploadRequest.getFileContent().toByteArray()); - } catch (IOException e) { - LOG.error("Fehler beim Speichern des Dateiinhalts.", e); - } - } - - CompletableFuture<FileId> storeFileMetaData(GrpcUploadBinaryFileRequest fileUploadRequest, PipedInputStream pipedInput, - PipedOutputStream pipedOutput) { - connectInputStreamToOutputStream(pipedInput, pipedOutput); - - return storeMetaData(fileUploadRequest); - } - - void connectInputStreamToOutputStream(PipedInputStream is, PipedOutputStream os) { - try { - is.connect(os); - } catch (IOException e) { - LOG.warn("Fehler beim Verbinden der Input/Outputs", e); - } - } - - CompletableFuture<FileId> storeMetaData(GrpcUploadBinaryFileRequest fileUploadRequest) { - var userId = fileUploadRequest.getMetadata().getContext().getUser().getId(); - - return service.uploadFileStreamAsync( - buildUploadFilesReferences(fileUploadRequest), - buildOzgFile(fileUploadRequest), - Optional.of(userId), pipedInput); - } - - UploadedFilesReference buildUploadFilesReferences(GrpcUploadBinaryFileRequest fileUploadRequest) { - return UploadedFilesReference.builder() - .vorgangId(fileUploadRequest.getMetadata().getVorgangId()) - .client(GrpcBinaryFileService.CLIENT) - .name(fileUploadRequest.getMetadata().getField()).build(); - } - - OzgFile buildOzgFile(GrpcUploadBinaryFileRequest fileUploadRequest) { - return OzgFile.builder() - .name(fileUploadRequest.getMetadata().getFileName()) - .size(fileUploadRequest.getMetadata().getSize()) - .contentType(fileUploadRequest.getMetadata().getContentType()) - .build(); - } - - @Override - public void onError(Throwable t) { - LOG.error("GrpcLargeFileRequestObserver onError", t); - throw new TechnicalException("GrpcLargeFileRequestObserver onError has been called", t); - } - - @Override - public void onCompleted() { - try { - pipedOutput.close(); - - responseObserver.onNext(GrpcBinaryFileService.buildResponse(fileIdFuture.get(10, TimeUnit.MINUTES))); - - pipedInput.close(); - } catch (ExecutionException | IOException | TimeoutException e) { - handleException(e); - } catch (InterruptedException ie) { - handleException(ie); - Thread.currentThread().interrupt(); - } - responseObserver.onCompleted(); - - } - - void handleException(Exception e) { - responseObserver.onNext(GrpcUploadBinaryFileResponse.newBuilder().build()); - throw new TechnicalException(e.getMessage(), e); - } -} \ No newline at end of file diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/UploadedFilesReference.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/UploadedFilesReference.java index e05596fc8b73ce347a4bb73b58908fc9589bdfe8..614b83882f926724d86da5ddef113dc470e104ef 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/UploadedFilesReference.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/files/UploadedFilesReference.java @@ -33,4 +33,5 @@ public class UploadedFilesReference { private String vorgangId; private String client; private String name; + private String fileName; } diff --git a/vorgang-manager-server/src/main/resources/application-dev.yml b/vorgang-manager-server/src/main/resources/application-dev.yml index 1deba0a36997fd105df02532f5b84151169c7efb..f0354b98680d11d5aeb2605a24bb903960b8e7bc 100644 --- a/vorgang-manager-server/src/main/resources/application-dev.yml +++ b/vorgang-manager-server/src/main/resources/application-dev.yml @@ -1,4 +1,3 @@ - ozgcloud: redirect: mail-from: dev-environment@ozg-cloud.de diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/document/bescheid/BescheidITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/document/bescheid/BescheidITCase.java index 90bf36e78e775f9bdd28cc97b125602749363529..2ab3074d28285281022fc60a97c55056c8a2fb5b 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/document/bescheid/BescheidITCase.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/document/bescheid/BescheidITCase.java @@ -47,14 +47,9 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.gridfs.GridFsTemplate; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.test.annotation.DirtiesContext; -import com.mongodb.client.gridfs.model.GridFSFile; - import de.ozgcloud.apilib.user.OzgCloudUserId; import de.ozgcloud.apilib.user.OzgCloudUserProfile; import de.ozgcloud.apilib.user.OzgCloudUserProfileService; @@ -77,6 +72,7 @@ import de.ozgcloud.vorgang.callcontext.CallContextAuthenticationTokenTestFactory import de.ozgcloud.vorgang.callcontext.WithMockCustomUser; import de.ozgcloud.vorgang.command.CommandService; import de.ozgcloud.vorgang.command.CreateCommandRequest; +import de.ozgcloud.vorgang.files.OzgCloudFile; import de.ozgcloud.vorgang.servicekonto.ServiceKontoTestFactory; import de.ozgcloud.vorgang.status.StatusChangedEvent; import de.ozgcloud.vorgang.vorgang.Vorgang; @@ -106,8 +102,6 @@ class BescheidITCase { @Autowired private MongoOperations mongoOperations; - @Autowired - private GridFsTemplate gridFsTemplate; @MockBean private PostfachRemoteService postfachRemoteService; @@ -175,9 +169,9 @@ class BescheidITCase { .build(); } - private GridFSFile getBescheidDocumentFile() { + private OzgCloudFile getBescheidDocumentFile() { var fileId = getBescheidDocument().getItem().get(Document.FIELD_DOCUMENT_FILE).toString(); - return gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(fileId))); + return mongoOperations.findById(fileId, OzgCloudFile.class); } private VorgangAttachedItem getBescheidDocument() { diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumITCase.java index b4370662e31e5988269df10a44045aac25258faa..deeab0b4bb03d66fde39686fd94b065e25bd8e64 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumITCase.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumITCase.java @@ -44,7 +44,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.gridfs.GridFsTemplate; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.annotation.DirtiesContext; @@ -62,8 +61,8 @@ import de.ozgcloud.vorgang.VorgangManagerServerApplication; import de.ozgcloud.vorgang.attached_item.VorgangAttachedItem; import de.ozgcloud.vorgang.attached_item.VorgangAttachedItem.VorgangAttachedItemBuilder; import de.ozgcloud.vorgang.attached_item.VorgangAttachedItemTestFactory; -import de.ozgcloud.vorgang.files.GridFsTestFactory; -import de.ozgcloud.vorgang.files.OzgFileTestFactory; +import de.ozgcloud.vorgang.files.OzgCloudFileTestFactory; +import de.ozgcloud.vorgang.files.OzgCloudFileTestUtil; import de.ozgcloud.vorgang.servicekonto.PostfachAddressTestFactory; import de.ozgcloud.vorgang.servicekonto.ServiceKonto; import de.ozgcloud.vorgang.servicekonto.ServiceKontoTestFactory; @@ -96,7 +95,7 @@ class AntragraumITCase { @Autowired private MongoOperations mongoOperations; @Autowired - private GridFsTemplate gridFsTemplate; + private OzgCloudFileTestUtil ozgCloudFileTestUtil; @MockBean private Saml2Decrypter decrypter; @@ -299,7 +298,7 @@ class AntragraumITCase { @BeforeEach void prepareDatabase() { - fileId = gridFsTemplate.store(GridFsTestFactory.createUpload()).toString(); + fileId = ozgCloudFileTestUtil.saveOzgCloudFile().getId(); var savedVorgang = mongoOperations.save(createVorgang(TrustLevel.LEVEL_3), Vorgang.COLLECTION_NAME); vorgangAttachedItem = mongoOperations.save( createPostfachNachrichtVorgangAttachedItem(savedVorgang.getId(), fileId), @@ -321,9 +320,9 @@ class AntragraumITCase { .setFileId(fileId) .build(); var expectedMetadata = GrpcFileMetadata.newBuilder() - .setContentType(OzgFileTestFactory.CONTENT_TYPE) - .setName(OzgFileTestFactory.NAME) - .setSize(OzgFileTestFactory.CONTENT.length) + .setContentType(OzgCloudFileTestFactory.CONTENT_TYPE) + .setName(OzgCloudFileTestFactory.NAME) + .setSize(OzgCloudFileTestFactory.CONTENT.length) .build(); grpcService.getAttachmentMetadata(request, responseObserver); @@ -423,7 +422,7 @@ class AntragraumITCase { @BeforeEach void prepareDatabase() { - fileId = gridFsTemplate.store(GridFsTestFactory.createUpload()).toString(); + fileId = ozgCloudFileTestUtil.saveOzgCloudFile().getId(); var savedVorgang = mongoOperations.save(createVorgang(TrustLevel.LEVEL_3), Vorgang.COLLECTION_NAME); vorgangAttachedItem = mongoOperations.save( createPostfachNachrichtVorgangAttachedItem(savedVorgang.getId(), fileId), @@ -453,7 +452,7 @@ class AntragraumITCase { .setNachrichtId(vorgangAttachedItem.getId()) .setFileId(fileId) .build(); - var expectedContent = ByteString.copyFrom(OzgFileTestFactory.CONTENT); + var expectedContent = ByteString.copyFrom(OzgCloudFileTestFactory.CONTENT); grpcService.getAttachmentContent(request, responseObserver); diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/nachrichten/postfach/PostfachMailITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/nachrichten/postfach/PostfachMailITCase.java index a5df8366cf4bfb43af9718f5103288244db9f704..eb0983c1cd56dc31bb4d9f4918502dff4407dcab 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/nachrichten/postfach/PostfachMailITCase.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/nachrichten/postfach/PostfachMailITCase.java @@ -55,6 +55,8 @@ import org.springframework.test.web.client.ExpectedCount; import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.web.client.RestTemplate; +import com.thedeanda.lorem.LoremIpsum; + import de.ozgcloud.common.test.DataITCase; import de.ozgcloud.nachrichten.common.grpc.NachrichtenCallContextAttachingInterceptor; import de.ozgcloud.nachrichten.postfach.osi.MessageAttachmentTestFactory; @@ -68,7 +70,8 @@ import de.ozgcloud.vorgang.command.CommandService; import de.ozgcloud.vorgang.common.security.PolicyService; import de.ozgcloud.vorgang.files.FileId; import de.ozgcloud.vorgang.files.FileService; -import de.ozgcloud.vorgang.files.OzgFile; +import de.ozgcloud.vorgang.files.OzgCloudFile; +import de.ozgcloud.vorgang.files.OzgCloudFileTestUtil; import de.ozgcloud.vorgang.vorgang.Vorgang; import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; import io.grpc.stub.StreamObserver; @@ -77,8 +80,9 @@ import io.grpc.stub.StreamObserver; "grpc.server.in-process-name=postfachitcase", "grpc.client.ozgcloud-command-manager.address=in-process:postfachitcase", "grpc.client.command-manager.address=in-process:postfachitcase", - "grpc.client.-manager.address=in-process:postfachitcase", + "grpc.client.vorgang-manager.address=in-process:postfachitcase", "grpc.client.pluto.address=in-process:postfachitcase", + "grpc.client.file-manager.address=in-process:postfachitcase", "ozgcloud.osi.postfach.proxyapi.url=http://localhost/ApiProxy/V1/Message", "ozgcloud.osi.postfach.proxyapi.key=1234", "ozgcloud.osi.postfach.proxyapi.realm=test-realm", @@ -100,6 +104,8 @@ class PostfachMailITCase { private RestTemplate restTemplate; @Autowired private FileService fileService; + @Autowired + private OzgCloudFileTestUtil ozgCloudFileTestUtil; @MockBean private PolicyService policyService; @@ -142,11 +148,20 @@ class PostfachMailITCase { @Nested class TestSendingMail { + private static final String ATTACHMENT_CONTENT = LoremIpsum.getInstance().getWords(4); + @Mock private StreamObserver<GrpcSendPostfachMailResponse> responseObserver; @Mock private StreamObserver<GrpcResendPostfachMailResponse> resendResponseObserver; + private OzgCloudFile ozgCloudFile; + + @BeforeEach + void init() { + ozgCloudFile = ozgCloudFileTestUtil.saveOzgCloudFile(); + } + @Test void shouldCallOsiPostfachServer() { mockServerSendSuccess(); @@ -218,7 +233,9 @@ class PostfachMailITCase { private GrpcSendPostfachMailRequest buildSendPostfachMailRequestWithoutAttachments(String postfachType) { var grpcPostfachMail = GrpcPostfachMailTestFactory.createBuilder().clearId().clearAttachment().clearCreatedAt().clearDirection() .setPostfachAddress(GrpcPostfachAddressTestFactory.createBuilder().setServiceKontoType(postfachType).build()) - .setVorgangId(vorgang.getId()).build(); + .setVorgangId(vorgang.getId()) + .addAttachment(ozgCloudFile.getId()) + .build(); return GrpcSendPostfachMailRequestTestFactory.createBuilder().setMail(grpcPostfachMail).build(); } @@ -334,8 +351,8 @@ class PostfachMailITCase { var mails = callGrpcListEndpoint(); assertThat(mails).hasSize(1); - var binaryFiles = loadAttachments(mails.getFirst()); - assertThat(binaryFiles).hasSize(1).first().extracting(OzgFile::getContentType) + var ozgCloudFiles = loadAttachments(mails.getFirst()); + assertThat(ozgCloudFiles).hasSize(1).first().extracting(OzgCloudFile::getContentType) .isEqualTo("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); } @@ -366,9 +383,9 @@ class PostfachMailITCase { verify(commandService, timeout(60000)).setCommandFinished(any(), any()); } - private List<OzgFile> loadAttachments(GrpcPostfachMail mail) { + private List<OzgCloudFile> loadAttachments(GrpcPostfachMail mail) { assertThat(mail.getAttachmentList()).hasSize(1); - return fileService.findFilesMetaData((mail.getAttachmentList().stream().map(FileId::from).toList())).toList(); + return fileService.findOzgCloudFiles((mail.getAttachmentList().stream().map(FileId::from).toList())).toList(); } private ClientAttribute getHasNewNachrichtAttribute() { diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/processor/ProcessorITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/processor/ProcessorITCase.java index d9b857b4e886acfbe1454f426cb3e4eeac0be919..d1744dcdccc0ac0f177caed45104ac28594f358c 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/processor/ProcessorITCase.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/processor/ProcessorITCase.java @@ -41,11 +41,13 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.annotation.DirtiesContext; import de.ozgcloud.command.CommandStatus; import de.ozgcloud.command.VorgangCreatedEvent; +import de.ozgcloud.common.test.ITCase; import de.ozgcloud.notification.postfach.PostfachService; +import de.ozgcloud.notification.user.UserNotificationEventListener; import de.ozgcloud.processor.processor.ProcessorService; import de.ozgcloud.processor.vorgang.Vorgang; import de.ozgcloud.processor.vorgang.VorgangId; @@ -62,11 +64,12 @@ import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; "ozgcloud.processors.0.address=http://localhost:8090/testprocessor", "ozgcloud.processors.0.forms.0.formEngineName=testFormEngine", "ozgcloud.processors.0.forms.0.formId=testForm", - "grpc.server.port=9091", - "ozgcloud.command-manager.address=static://127.0.0.1:9091" + "grpc.server.in-process-name=test", + "ozgcloud.command-manager.address=in-process:test", }, classes = { VorgangManagerServerApplication.class }) -@ActiveProfiles({ "local", "itcase" }) @WithMockCustomUser +@ITCase +@DirtiesContext class ProcessorITCase { @Autowired @@ -79,6 +82,8 @@ class ProcessorITCase { private PostfachService postfachService; @MockBean private de.ozgcloud.notification.vorgang.VorgangService notificationService; + @MockBean + private UserNotificationEventListener userNotificationEventListener; @MockBean private CommandService commandService; diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/TestConfiguration.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/TestConfiguration.java index 5ef251fa5dbd21027b7005cc0bbeedcbf2822385..fa5eada55a5f2ce8c574277bfd632e6e4e85c3ce 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/TestConfiguration.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/TestConfiguration.java @@ -27,8 +27,12 @@ import static org.mockito.Mockito.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.gridfs.GridFsTemplate; import org.springframework.mail.javamail.JavaMailSender; +import de.ozgcloud.vorgang.files.OzgCloudFileTestUtil; + @Configuration public class TestConfiguration { @@ -37,4 +41,8 @@ public class TestConfiguration { return mock(JavaMailSender.class); } + @Bean + OzgCloudFileTestUtil ozgCloudFileTestUtil(MongoOperations mongoOperations, GridFsTemplate gridFsTemplate) { + return new OzgCloudFileTestUtil(mongoOperations, gridFsTemplate); + } } \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/VorgangManagerServerApplicationTests.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/VorgangManagerServerApplicationITCase.java similarity index 96% rename from vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/VorgangManagerServerApplicationTests.java rename to vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/VorgangManagerServerApplicationITCase.java index 33e9b67b565d331b23680c5a953b3eb44c00bd05..dc59d07de19615063257e93dee8b8e9fd9adc04f 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/VorgangManagerServerApplicationTests.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/VorgangManagerServerApplicationITCase.java @@ -28,7 +28,7 @@ import org.junit.jupiter.api.Test; import de.ozgcloud.common.test.ITCase; @ITCase -class VorgangManagerServerApplicationTests { +class VorgangManagerServerApplicationITCase { @Test void contextLoads() { // NOSONAR empty test just starting spring-boot diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/collaboration/CollaborationITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/collaboration/CollaborationITCase.java index 7285590660bcc4f959b39c081ee093131be46848..e080b53a758d72b31dc6b4c688a85da1ade091d3 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/collaboration/CollaborationITCase.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/collaboration/CollaborationITCase.java @@ -38,7 +38,6 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.TimeUnit; -import org.bson.types.ObjectId; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -91,9 +90,9 @@ import de.ozgcloud.vorgang.callcontext.WithMockCustomUser; import de.ozgcloud.vorgang.command.CommandService; import de.ozgcloud.vorgang.command.CommandTestFactory; import de.ozgcloud.vorgang.command.CreateCommandRequest; +import de.ozgcloud.vorgang.files.OzgCloudFileTestFactory; +import de.ozgcloud.vorgang.files.OzgCloudFileTestUtil; import de.ozgcloud.vorgang.files.FileService; -import de.ozgcloud.vorgang.files.GridFsTestFactory; -import de.ozgcloud.vorgang.files.OzgFileTestFactory; import de.ozgcloud.vorgang.vorgang.EingangTestFactory; import de.ozgcloud.vorgang.vorgang.Vorgang; import de.ozgcloud.vorgang.vorgang.VorgangHead; @@ -127,6 +126,8 @@ class CollaborationITCase { @Autowired private MongoOperations mongoOperations; + @Autowired + private OzgCloudFileTestUtil ozgCloudFileTestUtil; @MockBean @Qualifier(CollaborationManagerConfiguration.OZGCLOUD_USER_PROFILE_SERVICE_NAME) @@ -149,6 +150,7 @@ class CollaborationITCase { mongoOperations.dropCollection(Command.class); mongoOperations.dropCollection(Vorgang.class); mongoOperations.dropCollection(VorgangAttachedItem.class); + ozgCloudFileTestUtil.dropFileCollections(); vorgangId = mongoOperations.save(VorgangTestFactory.createBuilder().id(null).version(0L).build()).getId(); } @@ -163,44 +165,37 @@ class CollaborationITCase { @Autowired private GridFsTemplate gridFsTemplate; - @BeforeEach - void initDatabase() { - gridFsTemplate.delete(new Query()); - } - @Test void shouldReturnStoredSmallFile() { - var fileId = gridFsTemplate.store(GridFsTestFactory.createUpload()); + var fileId = ozgCloudFileTestUtil.saveOzgCloudFile().getId(); - var file = downloadBinaryFile(fileId); + var file = downloadOzgCloudFile(fileId); - assertThat(file).hasSameSizeAs(OzgFileTestFactory.CONTENT).isEqualTo(OzgFileTestFactory.CONTENT); + assertThat(file).hasSameSizeAs(OzgCloudFileTestFactory.CONTENT).isEqualTo(OzgCloudFileTestFactory.CONTENT); } @Test void shouldReturnStoredFileWithSizeEqualChunkSize() { - var content = OzgFileTestFactory.createContentInByte(FileService.CHUNK_SIZE); - var fileId = gridFsTemplate.store(GridFsTestFactory.createUpload(content)); + var content = OzgCloudFileTestFactory.createContentInByte(FileService.CHUNK_SIZE); + var fileId = ozgCloudFileTestUtil.saveOzgCloudFile(content).getId(); - var file = downloadBinaryFile(fileId); + var file = downloadOzgCloudFile(fileId); assertThat(file).hasSameSizeAs(content).isEqualTo(content); } @Test void shouldReturnStoredLargeFile() { - var input = OzgFileTestFactory.createContentInMiB(5); - var fileId = gridFsTemplate - .store(GridFsTestFactory.createUpload(input)); + var input = OzgCloudFileTestFactory.createContentInMiB(5); + var fileId = ozgCloudFileTestUtil.saveOzgCloudFile(input).getId(); - var downloadedFile = downloadBinaryFile(fileId); + var downloadedFile = downloadOzgCloudFile(fileId); assertThat(downloadedFile).hasSameSizeAs(input).isEqualTo(input); } - private byte[] downloadBinaryFile(ObjectId fileId) { - var request = GrpcGetFileContentRequest.newBuilder() - .setId(fileId.toHexString()).build(); + private byte[] downloadOzgCloudFile(String fileId) { + var request = GrpcGetFileContentRequest.newBuilder().setId(fileId).build(); var it = getServiceStub().getFileContent(request); var content = new ByteArrayOutputStream(); while (it.hasNext()) { diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/AttachmentFileTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/AttachmentFileTestFactory.java deleted file mode 100644 index b8eeba2de71da1036e75f50f40a4bdb5f82765ff..0000000000000000000000000000000000000000 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/AttachmentFileTestFactory.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -package de.ozgcloud.vorgang.command; - -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.function.Supplier; - -import org.apache.commons.io.IOUtils; - -import de.ozgcloud.nachrichten.postfach.AttachmentFile; -import de.ozgcloud.nachrichten.postfach.AttachmentFile.AttachmentFileBuilder; - -public class AttachmentFileTestFactory { - - public static final String ATTACHMENT_NAME = "test.txt"; - public static final String CONTENT_TYPE = "text/plain"; - - public static final String BASE64_CONTENT = "dGVzdA=="; - public static final String CONTENT = "test"; - public static final long SIZE = 4L; - public static final Supplier<InputStream> CONTENT_SUPPLIER = () -> IOUtils.toInputStream(CONTENT, Charset.defaultCharset()); - public static final Supplier<InputStream> BASE64_CONTENT_SUPPLIER = () -> IOUtils.toInputStream(BASE64_CONTENT, Charset.defaultCharset()); - - public static AttachmentFile create() { - return createBuilder().build(); - } - - public static AttachmentFileBuilder createBuilder() { - return AttachmentFile.builder() - .name(ATTACHMENT_NAME) - .contentType(CONTENT_TYPE) - .content(CONTENT_SUPPLIER); - } -} diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/PersistPostfachNachrichtByCommandServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/PersistPostfachNachrichtByCommandServiceTest.java index 41f9eacf7ac75658088d2a661d9165a706db7e8f..d4ab5e0f24965e7a9a78b49529f278c980a423eb 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/PersistPostfachNachrichtByCommandServiceTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/PersistPostfachNachrichtByCommandServiceTest.java @@ -27,32 +27,23 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -import java.io.InputStream; import java.util.Map; import java.util.Optional; import java.util.stream.Stream; -import org.apache.http.entity.ContentType; 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.ArgumentCaptor; -import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; +import de.ozgcloud.nachrichten.file.AttachmentFileTestFactory; import de.ozgcloud.nachrichten.postfach.GrpcPostfachMailTestFactory; import de.ozgcloud.nachrichten.postfach.PostfachAddressTestFactory; import de.ozgcloud.nachrichten.postfach.PostfachNachrichtTestFactory; -import de.ozgcloud.nachrichten.postfach.osi.MessageAttachmentTestFactory; -import de.ozgcloud.nachrichten.postfach.osi.MessageTestFactory; import de.ozgcloud.vorgang.attached_item.VorgangAttachedItemService; import de.ozgcloud.vorgang.attached_item.VorgangAttachedItemTestFactory; -import de.ozgcloud.vorgang.files.FileId; -import de.ozgcloud.vorgang.files.FileService; -import de.ozgcloud.vorgang.files.OzgFile; -import de.ozgcloud.vorgang.files.UploadedFilesReference; import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; class PersistPostfachNachrichtByCommandServiceTest { @@ -61,8 +52,6 @@ class PersistPostfachNachrichtByCommandServiceTest { private PersistPostfachNachrichtByCommandService service; @Mock private VorgangAttachedItemService attachedItemService; - @Mock - private FileService fileService; @DisplayName("Persist nachricht") @Nested @@ -165,160 +154,16 @@ class PersistPostfachNachrichtByCommandServiceTest { } } - @Nested - class TestUploadedFilesReferenceCreation { - @Test - void shouldCreateUploadedFilesReference() { - UploadedFilesReference ref = service.createUploadedFilesReference(MessageTestFactory.VORGANG_ID); - - assertThat(ref.getVorgangId()).isEqualTo(MessageTestFactory.VORGANG_ID); - assertThat(ref.getClient()).isEqualTo(PersistPostfachNachrichtByCommandService.CLIENT); - assertThat(ref.getName()).isEqualTo(PersistPostfachNachrichtByCommandService.ATTACHMENT_NAME); - } - } - - @Nested - class TestOzgFileCreation { - @Test - void shouldCreateOzgFile() { - OzgFile file = service.createOzgFile(MessageAttachmentTestFactory.FILENAME, ContentType.APPLICATION_OCTET_STREAM.toString(), - MessageAttachmentTestFactory.SIZE); - - assertThat(file.getContentType()).isEqualTo(ContentType.APPLICATION_OCTET_STREAM.toString()); - assertThat(file.getSize()).isEqualTo(MessageAttachmentTestFactory.SIZE); - assertThat(file.getName()).isEqualTo(MessageAttachmentTestFactory.FILENAME); - } - } - - @DisplayName("On Persisting Attachement") + @DisplayName("On Persisting Attachment") @Nested class TestPersistAttachment { - @BeforeEach - void init() { - when(fileService.uploadFileStream(any(), any(), any(), any())).thenReturn(FileId.from("42")); - } - - @DisplayName("Upload files Reference") - @Nested - class TestUploadFilesReference { - - @Captor - private ArgumentCaptor<UploadedFilesReference> refCaptor; - - @Test - void shouldHaveClientName() { - var ref = callService(); - - assertThat(ref.getClient()).isEqualTo(PersistPostfachNachrichtByCommandService.CLIENT); - } - - @Test - void shouldHaveName() { - var ref = callService(); - - assertThat(ref.getName()).isEqualTo(PersistPostfachNachrichtByCommandService.ATTACHMENT_NAME); - } - - @Test - void shouldHaveVorgangId() { - var ref = callService(); - - assertThat(ref.getVorgangId()).isEqualTo(VorgangTestFactory.ID); - } - - private UploadedFilesReference callService() { - service.persistAttachment(VorgangTestFactory.ID, AttachmentFileTestFactory.create()); - - verify(fileService).uploadFileStream(refCaptor.capture(), any(), any(), any()); - - return refCaptor.getValue(); - } - } - - @DisplayName("OZG-File") - @Nested - class TestOzgFile { - @Captor - private ArgumentCaptor<OzgFile> ozgFileCaptor; - - @Test - void shouldHaveName() { - var file = callService(); - - assertThat(file.getName()).isEqualTo(MessageAttachmentTestFactory.FILENAME); - } - - @Test - void shouldHaveSize() { - var file = callService(); - - assertThat(file.getSize()).isEqualTo(MessageAttachmentTestFactory.SIZE); - } - - @Test - void shouldHaveContentType() { - var file = callService(); - - assertThat(file.getContentType()).isEqualTo("text/plain"); - } - - private OzgFile callService() { - var attachmentFile = AttachmentFileTestFactory.createBuilder().content(AttachmentFileTestFactory.BASE64_CONTENT_SUPPLIER).build(); - - service.persistAttachment(VorgangTestFactory.ID, attachmentFile); - - verify(fileService).uploadFileStream(any(), ozgFileCaptor.capture(), any(), any()); - - return ozgFileCaptor.getValue(); - } - } - - @Test - void shouldHaveContent() { - ArgumentCaptor<InputStream> contentCaptor = ArgumentCaptor.forClass(InputStream.class); - var attachment = AttachmentFileTestFactory.createBuilder().content(AttachmentFileTestFactory.BASE64_CONTENT_SUPPLIER).build(); - - service.persistAttachment(VorgangTestFactory.ID, attachment); - - verify(fileService).uploadFileStream(any(), any(), any(), contentCaptor.capture()); - assertThat(contentCaptor.getValue()).hasContent(AttachmentFileTestFactory.CONTENT); - } - - @Test - void shouldReturnFileId() { - var id = service.persistAttachment(VorgangTestFactory.ID, AttachmentFileTestFactory.create()); - - assertThat(id).isEqualTo("42"); - } - } - - @Nested - class TestGetTypeByFileName { - @Test - void shouldReturnDocxType() { - var attachmentFile = AttachmentFileTestFactory.createBuilder().name("file.docx").contentType("word-file").build(); - - var type = service.getTypeByFile(attachmentFile); - - assertThat(type).isEqualTo("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); - } @Test - void shouldReturnOctedStreamAsUnkown() { - var attachmentFile = AttachmentFileTestFactory.createBuilder().name("file.qtsch").contentType("lalal").build(); - - var type = service.getTypeByFile(attachmentFile); - - assertThat(type).isEqualTo(ContentType.APPLICATION_OCTET_STREAM.toString()); - } - - @Test - void shouldReturnOdtType() { - var attachmentFile = AttachmentFileTestFactory.createBuilder().name("file.odt").contentType("opendoc file").build(); - - var type = service.getTypeByFile(attachmentFile); + void shouldThrowException() { + var attachmentFile = AttachmentFileTestFactory.create(); - assertThat(type).isEqualTo("application/vnd.oasis.opendocument.text"); + assertThatThrownBy(() -> service.persistAttachment(VorgangTestFactory.ID, attachmentFile)).isInstanceOf( + UnsupportedOperationException.class); } } @@ -345,7 +190,7 @@ class PersistPostfachNachrichtByCommandServiceTest { void shouldThrowException() { assertThatThrownBy(() -> service.findAnswers(serviceKontoType, PostfachAddressTestFactory.STRING_BASED_IDENTIFIER_POSTFACH_ID_VALUE, GrpcPostfachMailTestFactory.REFERENCED_NACHRICHT_ID)) - .isInstanceOf(UnsupportedOperationException.class); + .isInstanceOf(UnsupportedOperationException.class); } } } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/migration/M013_CreateOzgCloudFileCollectionTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/migration/M013_CreateOzgCloudFileCollectionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..36b17c3d6891bb628c0f6141a38b444bd27af62c --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/migration/M013_CreateOzgCloudFileCollectionTest.java @@ -0,0 +1,149 @@ +/* + * 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.vorgang.common.migration; + +import static org.assertj.core.api.Assertions.*; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Random; +import java.util.UUID; + +import org.bson.Document; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.gridfs.GridFsTemplate; +import org.springframework.data.mongodb.gridfs.GridFsUpload; + +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.common.test.DataITCase; +import de.ozgcloud.vorgang.files.OzgCloudFileTestUtil; + +@DataITCase +class M013_CreateOzgCloudFileCollectionTest { // NOSONAR + + private static final String VORGANG_ID = UUID.randomUUID().toString(); + private static final String CLIENT = LoremIpsum.getInstance().getWords(1); + private static final String FIELD_NAME = LoremIpsum.getInstance().getWords(1); + private static final String FILE_NAME = LoremIpsum.getInstance().getWords(1); + private static final String CONTENT_TYPE = LoremIpsum.getInstance().getWords(1); + private static final String CREATED_BY = UUID.randomUUID().toString(); + private static final byte[] CONTENT = LoremIpsum.getInstance().getWords(11).getBytes(); + private static final long SIZE = CONTENT.length; + + @InjectMocks + private M013_CreateOzgCloudFileCollection migration; + + @Autowired + private MongoTemplate template; + @Autowired + private OzgCloudFileTestUtil ozgCloudFileTestUtil; + @Autowired + private GridFsTemplate gridFsTemplate; + + private String fileId; + @BeforeEach + void init() { + ozgCloudFileTestUtil.dropFileCollections(); + fileId = uploadFile(); + } + + @Test + void shouldMigrateMetadata() { + migration.doMigration(template); + + assertThat(loadOzgCloudFile()).containsAllEntriesOf(createOzgCloudFileFromMetadata(fileId)); + } + + @Test + void shouldLoadContentByContentId() { + migration.doMigration(template); + + var ozgCloudFileDocument = loadOzgCloudFile(); + assertThat(getFileContentByContentId(ozgCloudFileDocument)).hasBinaryContent(CONTENT); + } + + @Test + void shouldSkipExistingFiles() { + var ozgCloudFile = createNewOzgCloudFileFrom(fileId); + template.save(ozgCloudFile, M013_CreateOzgCloudFileCollection.NEW_COLLECTION_NAME); + + migration.doMigration(template); + + var ozgCloudFiles = template.findAll(Document.class, M013_CreateOzgCloudFileCollection.NEW_COLLECTION_NAME); + assertThat(ozgCloudFiles).hasSize(1).first().usingRecursiveComparison().isEqualTo(ozgCloudFile); + } + + private String uploadFile() { + var upload = GridFsUpload.fromStream(new ByteArrayInputStream(CONTENT)).metadata(createMetadata()).filename("test").build(); + return gridFsTemplate.store(upload).toHexString(); + } + + private Document createNewOzgCloudFileFrom(String fileId) { + var ozgCloudFile = createMetadata(); + ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_ID, new ObjectId(fileId)); + ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_CONTENT_ID, fileId); + ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_VORGANG_ID, UUID.randomUUID().toString()); + ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_CLIENT, LoremIpsum.getInstance().getWords(1)); + ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_FIELD_NAME, LoremIpsum.getInstance().getWords(1)); + ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_CONTENT_TYPE, LoremIpsum.getInstance().getWords(1)); + ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_FILE_NAME, LoremIpsum.getInstance().getWords(1)); + ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_CREATED_BY, UUID.randomUUID().toString()); + ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_SIZE, new Random().nextLong()); + return ozgCloudFile; + } + + private Document createOzgCloudFileFromMetadata(String fileId) { + var ozgCloudFile = createMetadata(); + ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_ID, new ObjectId(fileId)); + ozgCloudFile.append(M013_CreateOzgCloudFileCollection.FIELD_CONTENT_ID, fileId); + return ozgCloudFile; + } + + private Document createMetadata() { + var metadata = new Document(); + metadata.append(M013_CreateOzgCloudFileCollection.FIELD_VORGANG_ID, VORGANG_ID); + metadata.append(M013_CreateOzgCloudFileCollection.FIELD_CLIENT, CLIENT); + metadata.append(M013_CreateOzgCloudFileCollection.FIELD_FIELD_NAME, FIELD_NAME); + metadata.append(M013_CreateOzgCloudFileCollection.FIELD_CONTENT_TYPE, CONTENT_TYPE); + metadata.append(M013_CreateOzgCloudFileCollection.FIELD_FILE_NAME, FILE_NAME); + metadata.append(M013_CreateOzgCloudFileCollection.FIELD_CREATED_BY, CREATED_BY); + metadata.append(M013_CreateOzgCloudFileCollection.FIELD_SIZE, SIZE); + return metadata; + } + + private Document loadOzgCloudFile() { + return template.findById(fileId, Document.class, M013_CreateOzgCloudFileCollection.NEW_COLLECTION_NAME); + } + + private InputStream getFileContentByContentId(Document ozgCloudFile) { + return ozgCloudFileTestUtil.getFileContent(ozgCloudFile.getString(M013_CreateOzgCloudFileCollection.FIELD_CONTENT_ID)); + } + +} \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/security/PolicyRepositoryITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/security/PolicyRepositoryITCase.java index 76dc79013329c8b7ea0005cecaf19505ccf3a436..b0e25dfd3e1e32bf82d25c412792e08522ad407b 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/security/PolicyRepositoryITCase.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/security/PolicyRepositoryITCase.java @@ -41,6 +41,8 @@ import de.ozgcloud.vorgang.attached_item.VorgangAttachedItem; import de.ozgcloud.vorgang.attached_item.VorgangAttachedItemTestFactory; import de.ozgcloud.vorgang.command.CommandTestFactory; import de.ozgcloud.vorgang.common.errorhandling.NotFoundException; +import de.ozgcloud.vorgang.files.OzgCloudFile; +import de.ozgcloud.vorgang.files.OzgCloudFileTestUtil; import de.ozgcloud.vorgang.files.GridFsTestFactory; import de.ozgcloud.vorgang.vorgang.Vorgang; import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; @@ -53,10 +55,14 @@ class PolicyRepositoryITCase { private PolicyRepository repository; @Autowired private MongoOperations mongoOperations; + @Autowired + private OzgCloudFileTestUtil ozgCloudFileTestUtil; @BeforeEach void prepareDatabase() { mongoOperations.dropCollection(Vorgang.COLLECTION_NAME); + mongoOperations.dropCollection(OzgCloudFile.class); + } @DisplayName("Match organisationEinheitenId") @@ -145,18 +151,18 @@ class PolicyRepositoryITCase { @Nested class TestByFileId { + private OzgCloudFile ozgCloudFile; + @BeforeEach void initDatabase() { - mongoOperations.dropCollection("fs.files"); - - mongoOperations.save(GridFsTestFactory.create(), "fs.files"); + ozgCloudFile = ozgCloudFileTestUtil.saveOzgCloudFile(); } @Test void shouldReturnTrueIfMatch() { mongoOperations.save(VorgangTestFactory.create()); - var result = repository.existsByFileId(GridFsTestFactory.ID.toHexString(), + var result = repository.existsByFileId(ozgCloudFile.getId(), List.of(ZustaendigeStelleTestFactory.ORGANISATIONSEINHEIT_ID)); assertThat(result).isTrue(); @@ -172,5 +178,6 @@ class PolicyRepositoryITCase { assertThat(result).isFalse(); } } + } } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/security/PolicyServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/security/PolicyServiceTest.java index 791ccd2e2b620953e8bb460fb525e97285c5fa7c..aa71570d74282dae1fd253712f2b90c9ad4a81b6 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/security/PolicyServiceTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/security/PolicyServiceTest.java @@ -51,7 +51,7 @@ import de.ozgcloud.vorgang.callcontext.CurrentUserService; import de.ozgcloud.vorgang.callcontext.UserTestFactory; import de.ozgcloud.vorgang.command.CommandTestFactory; import de.ozgcloud.vorgang.files.GridFsTestFactory; -import de.ozgcloud.vorgang.files.OzgFileTestFactory; +import de.ozgcloud.vorgang.files.OzgCloudFileTestFactory; import de.ozgcloud.vorgang.vorgang.Vorgang; import de.ozgcloud.vorgang.vorgang.VorgangService; import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; @@ -175,14 +175,14 @@ class PolicyServiceTest { void shouldDoNothingOnAllMatch() { when(repository.existsByFileId(any(), any())).thenReturn(true); - assertDoesNotThrow(() -> service.checkPermissionByFile(OzgFileTestFactory.ID.toString())); + assertDoesNotThrow(() -> service.checkPermissionByFile(OzgCloudFileTestFactory.ID.toString())); } @Test void shouldThrowAccessDeniedExceptionOnAllNonMatch() { when(repository.existsByFileId(any(), any())).thenReturn(false); - var vorgangId = OzgFileTestFactory.ID.toString(); + var vorgangId = OzgCloudFileTestFactory.ID.toString(); assertThrows(AccessDeniedException.class, () -> service.checkPermissionByFile(vorgangId)); } } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/BinaryFileITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/BinaryFileITCase.java deleted file mode 100644 index e6dc5d8bc2b331972f81955daaddeedd4fbe802f..0000000000000000000000000000000000000000 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/BinaryFileITCase.java +++ /dev/null @@ -1,377 +0,0 @@ -/* - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -package de.ozgcloud.vorgang.files; - -import static org.assertj.core.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.fail; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Iterator; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.bson.types.ObjectId; -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.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.gridfs.GridFsResource; -import org.springframework.data.mongodb.gridfs.GridFsTemplate; -import org.springframework.test.annotation.DirtiesContext; - -import com.google.protobuf.ByteString; -import com.mongodb.client.gridfs.model.GridFSFile; - -import de.ozgcloud.common.test.DataITCase; -import de.ozgcloud.vorgang.callcontext.TestCallContextAttachingInterceptor; -import de.ozgcloud.vorgang.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceBlockingStub; -import de.ozgcloud.vorgang.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceStub; -import de.ozgcloud.vorgang.grpc.binaryFile.GrpcFindFilesResponse; -import de.ozgcloud.vorgang.grpc.binaryFile.GrpcGetBinaryFileDataRequest; -import de.ozgcloud.vorgang.grpc.binaryFile.GrpcGetBinaryFileDataResponse; -import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileMetaData; -import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileRequest; -import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileResponse; -import de.ozgcloud.vorgang.vorgang.Vorgang; -import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; -import io.grpc.StatusRuntimeException; -import io.grpc.stub.StreamObserver; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import net.devh.boot.grpc.client.inject.GrpcClient; - -@SpringBootTest(properties = { - "grpc.server.inProcessName=binary-file-test", - "grpc.server.port=-1", - "grpc.client.inProcess.address=in-process:binary-file-test" -}) -@DirtiesContext -@DataITCase -public class BinaryFileITCase { - - @GrpcClient("inProcess") - private BinaryFileServiceBlockingStub binaryFileServiceBlockingStub; - @GrpcClient("inProcess") - private BinaryFileServiceStub binaryFileServiceStub; - - @Autowired - private GridFsTemplate gridFsTemplate; - @Autowired - private MongoOperations mongoOperations; - - @DisplayName("Find binary files meta data") - @Nested - class TestFindBinaryFilesMetaData { - private ObjectId fileId; - - @BeforeEach - void initDatabase() { - gridFsTemplate.delete(new Query()); - fileId = gridFsTemplate.store(GridFsTestFactory.createUpload()); - } - - @Nested - class OnMatchingOrganisationEinheitIdAtVorgang { - - @BeforeEach - void initDatabase() { - mongoOperations.dropCollection(Vorgang.COLLECTION_NAME); - mongoOperations.save(VorgangTestFactory.create()); - } - - @Test - void shouldReturnStoredFile() { - var response = callFindBinaryFilesMetaData(); - - assertThat(response.getFileList()).hasSize(1); - - var file = response.getFileList().get(0); - - assertThat(file.getId()).isEqualTo(fileId.toHexString()); - assertThat(file.getName()).isEqualTo(OzgFileTestFactory.NAME); - assertThat(file.getContentType()).isEqualTo(OzgFileTestFactory.CONTENT_TYPE); - } - } - - @Nested - class OnNonMatchingOrganisationEinheitIdOnVorgang { - - @BeforeEach - void initDatabase() { - mongoOperations.dropCollection(Vorgang.COLLECTION_NAME); - mongoOperations.save(VorgangTestFactory.createWithOrganisationEinheitId("73")); - } - - @Test - void shouldThrowStatusRuntimeException() { - assertThrows(StatusRuntimeException.class, () -> callFindBinaryFilesMetaData()); - } - } - - private GrpcFindFilesResponse callFindBinaryFilesMetaData() { - return prepareBinaryFileServiceBlockingStub().findBinaryFilesMetaData( - GrpcBinaryFilesRequestTestFactory.createBuilder().clearFileId().addFileId(fileId.toHexString()).build()); - } - - } - - @Nested - @DisplayName("Download files of different sizes") - class TestDownloadFile { - - @BeforeEach - void initDatabase() { - gridFsTemplate.delete(new Query()); - } - - @Nested - class OnMatchingOrganisationEinheitIdAtVorgang { - - @BeforeEach - void initDatabase() { - mongoOperations.dropCollection(Vorgang.COLLECTION_NAME); - mongoOperations.save(VorgangTestFactory.create()); - } - - @Test - void shouldReturnStoredSmallFile() { - var fileId = gridFsTemplate.store(GridFsTestFactory.createUpload()); - - var file = downloadBinaryFile(fileId); - - assertThat(file).hasSameSizeAs(OzgFileTestFactory.CONTENT).isEqualTo(OzgFileTestFactory.CONTENT); - } - - @Test - void shouldReturnStoredFileWithSizeEqualChunkSize() throws IOException { - var content = OzgFileTestFactory.createContentInByte(FileService.CHUNK_SIZE); - var fileId = gridFsTemplate.store(GridFsTestFactory.createUpload(content)); - - var file = downloadBinaryFile(fileId); - - assertThat(file).hasSameSizeAs(content).isEqualTo(content); - } - - @Test - void shouldReturnStoredLargeFile() { - var input = OzgFileTestFactory.createContentInMiB(5); - var fileId = gridFsTemplate - .store(GridFsTestFactory.createUpload(input)); - - var downloadedFile = downloadBinaryFile(fileId); - - assertThat(downloadedFile).hasSameSizeAs(input).isEqualTo(input); - } - } - - @Nested - class OnNonMatchingOrganisationEinheitIdAtVorgang { - - @BeforeEach - void initDatabase() { - mongoOperations.dropCollection(Vorgang.COLLECTION_NAME); - mongoOperations.save(VorgangTestFactory.createWithOrganisationEinheitId("73")); - } - - @Test - void shouldThrowStatusRuntimeException() { - var fileId = gridFsTemplate.store(GridFsTestFactory.createUpload()); - - assertThrows(StatusRuntimeException.class, () -> downloadBinaryFile(fileId)); - } - } - - private byte[] downloadBinaryFile(ObjectId fileId) { - GrpcGetBinaryFileDataRequest request = GrpcGetBinaryFileDataRequest.newBuilder() - .setFileId(fileId.toHexString()).build(); - Iterator<GrpcGetBinaryFileDataResponse> it = prepareBinaryFileServiceBlockingStub().getBinaryFileContent(request); - var content = new ByteArrayOutputStream(); - while (it.hasNext()) { - ByteString chunkContent = it.next().getFileContent(); - try { - content.write(chunkContent.toByteArray()); - } catch (IOException e) { - fail(e); - } - } - - return content.toByteArray(); - } - } - - private BinaryFileServiceBlockingStub prepareBinaryFileServiceBlockingStub() { - return binaryFileServiceBlockingStub.withInterceptors(new TestCallContextAttachingInterceptor()); - } - - @Nested - @DisplayName("Upload file") - class TestFileUpload { - private int onNextCount = 0; - private AtomicBoolean hasError = new AtomicBoolean(false); - private AtomicBoolean completed = new AtomicBoolean(false); - private GrpcUploadBinaryFileMetaData metaData = GrpcUploadBinaryFileMetaDataTestFactory.create(); - private ByteString fileContent = ByteString.copyFrom(OzgFileTestFactory.CONTENT); - private StubStreamObserver responseObserver; - - @RequiredArgsConstructor(access = AccessLevel.PROTECTED) - class StubStreamObserver implements StreamObserver<GrpcUploadBinaryFileResponse> { - private final CompletableFuture<FileId> fileIdFuture; - @Getter - private String fileId; - - @Override - public void onNext(GrpcUploadBinaryFileResponse value) { - fileId = value.getFileId(); - onNextCount += 1; - } - - @Override - public void onError(Throwable t) { - hasError.set(true); - fileIdFuture.completeExceptionally(t); - } - - @Override - public void onCompleted() { - completed.set(true); - fileIdFuture.complete(FileId.from(fileId)); - } - }; - - @BeforeEach - void initDatabase() { - gridFsTemplate.delete(new Query()); - } - - @Nested - class OnMatchingOrganisationEinheitIdAtVorgang { - @BeforeEach - void init() { - mongoOperations.dropCollection(Vorgang.COLLECTION_NAME); - mongoOperations.save(VorgangTestFactory.create()); - } - - @Test - void shouldCallOnNext() { - var fileIdFuture = saveFileWithContent(); - - assertThat(fileIdFuture).isCompleted(); - assertThat(onNextCount).isEqualTo(1); - } - - @Test - void shouldCallOnComplete() { - var fileIdFuture = saveFileWithContent(); - - assertThat(fileIdFuture).isCompleted(); - assertThat(completed).isTrue(); - } - - @Test - void shouldHaveFileId() { - var fileIdFuture = saveFileWithContent(); - - assertThat(fileIdFuture).isCompleted().isDone().isNotNull(); - } - - @Test - void shouldSaveVorgangId() { - var fileIdFuture = saveFileWithContent(); - - assertThat(fileIdFuture).isCompleted(); - GridFSFile file = gridFsTemplate.findOne(Query.query(Criteria.where("metadata.vorgangId").is(VorgangTestFactory.ID))); - assertThat(file.getMetadata()).containsEntry("vorgangId", VorgangTestFactory.ID); - } - - @Test - void shouldSaveFileContent() throws Exception { - var fileIdFuture = saveFileWithContent(); - - assertThat(fileIdFuture).isCompleted(); - GridFSFile file = gridFsTemplate.findOne(Query.query(Criteria.where("metadata.vorgangId").is(VorgangTestFactory.ID))); - GridFsResource resource = gridFsTemplate.getResource(file); - try (InputStream fileStream = resource.getContent()) { - byte[] fileContentFromDb = fileStream.readAllBytes(); - assertThat(fileContentFromDb).hasSameSizeAs(fileContent.toByteArray()).isEqualTo(fileContent.toByteArray()); - } - - } - } - - @Nested - class OnMatchingNonOrganisationEinheitIdAtVorgang { - @BeforeEach - void init() { - mongoOperations.dropCollection(Vorgang.COLLECTION_NAME); - mongoOperations.save(VorgangTestFactory.createWithOrganisationEinheitId("73")); - } - - @Test - void shouldThowStatusRuntimeException() { - var fileIdFuture = saveFileWithContent(); - assertThat(fileIdFuture).isCompletedExceptionally().isNotCancelled().failsWithin(1, TimeUnit.SECONDS) - .withThrowableOfType(ExecutionException.class).withCauseInstanceOf(StatusRuntimeException.class); - } - } - - private CompletableFuture<FileId> saveFileWithContent() { - var fileIdFuture = new CompletableFuture<FileId>(); - GrpcUploadBinaryFileRequest request1 = GrpcUploadBinaryFileRequest.newBuilder().setMetadata(metaData).build(); - GrpcUploadBinaryFileRequest request2 = GrpcUploadBinaryFileRequest.newBuilder().setFileContent(fileContent).build(); - - responseObserver = new StubStreamObserver(fileIdFuture); - - StreamObserver<GrpcUploadBinaryFileRequest> observer = prepareBinaryFileServiceStub().uploadBinaryFileAsStream(responseObserver); - - observer.onNext(request1); - observer.onNext(request2); - observer.onCompleted(); - - try { - fileIdFuture.get(10, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - // ignored, the right exception will be checked in the test case - } - - return fileIdFuture; - - } - - private BinaryFileServiceStub prepareBinaryFileServiceStub() { - return binaryFileServiceStub.withInterceptors(new TestCallContextAttachingInterceptor()); - } - } -} \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/BinaryFileRepositoryITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/BinaryFileRepositoryITCase.java deleted file mode 100644 index 13253b711bda2839832da3e90ecc1e9ca2c82de7..0000000000000000000000000000000000000000 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/BinaryFileRepositoryITCase.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -package de.ozgcloud.vorgang.files; - -import static org.assertj.core.api.Assertions.*; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import java.util.stream.Stream; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.mongodb.core.MongoTemplate; - -import de.ozgcloud.common.test.DataITCase; -import de.ozgcloud.vorgang.callcontext.UserTestFactory; -import de.ozgcloud.vorgang.vorgang.IncomingFileTestFactory; -import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; - -@DataITCase -class BinaryFileRepositoryITCase { - - @Autowired - private BinaryFileRepository repository; - - @Autowired - private MongoTemplate template; - - @BeforeEach - void clearDB() { - template.dropCollection("fs.files"); - template.dropCollection("fs.chunks"); - } - - @Nested - class TestUploadFile { - private UploadedFilesReference ref; - private OzgFile file; - - @BeforeEach - void init() { - ref = UploadedFilesReference.builder() - .client("client") - .name("test.file") - .vorgangId(UUID.randomUUID().toString()) - .build(); - file = OzgFileTestFactory.create(); - } - - @Test - void shouldStoreFiles() throws IOException { - FileId id = storeFile(45); - - assertThat(repository.getFileContent(id)).isNotEmpty(); - } - - @Test - void shouldLoadFile() throws IOException { - int savedFileMB = 25; - int fileSizeInBytes = savedFileMB * 1_000_000; - - FileId id = storeFile(savedFileMB); - - InputStream in = repository.getFileContent(id); - byte[] buffer = in.readAllBytes(); - - assertThat(buffer).hasSize(fileSizeInBytes); - } - - private FileId storeFile(int sizeInMB) throws IOException { - InputStream stream = IncomingFileTestFactory.createContentStreamInMB(sizeInMB); - - FileId id = repository.addContentStream(ref, file, Optional.empty(), stream); - stream.close(); - return id; - } - } - - @Nested - class TestFindAllMetaData { - - private FileId fileId; - - @Nested - class TestSingleMetaData { - - @BeforeEach - void initData() { - fileId = initContentStream(OzgFileTestFactory.create()); - } - - @Test - void shouldReturnResult() { - var result = callRepository(List.of(fileId.toString())); - - assertThat(result.count()).isEqualTo(1); - } - - @Test - void shouldReturnOzgFileData() { - var result = callRepository(List.of(fileId.toString())); - - var ozgFile = result.toList().get(0); - assertThat(ozgFile.getId()).isEqualTo(fileId); - assertThat(ozgFile.getName()).isEqualTo(OzgFileTestFactory.NAME); - assertThat(ozgFile.getContentType()).isEqualTo(OzgFileTestFactory.CONTENT_TYPE); - assertThat(ozgFile.getSize()).isEqualTo(OzgFileTestFactory.SIZE); - } - } - - @Nested - class TestMultipleMetaData { - - private FileId otherFileId; - - @BeforeEach - void initData() { - fileId = initContentStream(OzgFileTestFactory.create()); - otherFileId = initContentStream(OzgFileTestFactory.create()); - } - - @Test - void shouldReturnResult() { - var result = callRepository(List.of(fileId.toString(), otherFileId.toString())); - - assertThat(result.count()).isEqualTo(2); - } - - @Test - void shouldReturnOzgFileData() { - var result = callRepository(List.of(fileId.toString(), otherFileId.toString())); - - var ozgFile = result.toList().get(1); - assertThat(ozgFile.getId()).isEqualTo(otherFileId); - assertThat(ozgFile.getName()).isEqualTo(OzgFileTestFactory.NAME); - assertThat(ozgFile.getContentType()).isEqualTo(OzgFileTestFactory.CONTENT_TYPE); - assertThat(ozgFile.getSize()).isEqualTo(OzgFileTestFactory.SIZE); - } - - } - - private FileId initContentStream(OzgFile ozgFile) { - return repository.addContentStream(UploadedFilesReferenceTestFactory.create(), ozgFile, Optional.of(UserTestFactory.ID), - new ByteArrayInputStream(OzgFileTestFactory.CONTENT)); - } - - private Stream<OzgFile> callRepository(Collection<String> fileIds) { - return repository.getAll(fileIds); - } - } - - @Nested - class TestDeleteByVorgang { - @Test - void shouldDeleteFiles() { - var fileId = persistFile(); - - repository.deleteAllByVorgang(VorgangTestFactory.ID); - - var loaded = repository.getFile(fileId); - assertThat(loaded).isNull(); - } - - @Test - void shouldNotDeleteOtherFiles() { - var fileId = persistFile(); - - repository.deleteAllByVorgang("other"); - - var loaded = repository.getFile(fileId); - assertThat(loaded).isNotNull(); - } - - private FileId persistFile() { - return repository.addContentStream(UploadedFilesReferenceTestFactory.create(), - OzgFileTestFactory.create(), - Optional.of(UserTestFactory.ID), - new ByteArrayInputStream(OzgFileTestFactory.CONTENT)); - } - } -} \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/BinaryFileRepositoryTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/BinaryFileRepositoryTest.java deleted file mode 100644 index e7d6ebd8262a7cc639c68b995c233b3899e18cee..0000000000000000000000000000000000000000 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/BinaryFileRepositoryTest.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -package de.ozgcloud.vorgang.files; - -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -import java.util.Collections; -import java.util.List; - -import org.apache.commons.lang3.StringUtils; -import org.bson.Document; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Spy; -import org.springframework.data.mongodb.gridfs.GridFsTemplate; - -import static org.assertj.core.api.Assertions.*; - -import de.ozgcloud.vorgang.callcontext.CallContextTestFactory; -import de.ozgcloud.vorgang.callcontext.UserTestFactory; -import de.ozgcloud.vorgang.vorgang.IncomingFileTestFactory; -import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; - -class BinaryFileRepositoryTest { - - @Spy - @InjectMocks - private BinaryFileRepository grisFsService = new BinaryFileRepository(); - @Mock - private GridFsTemplate gridFsTemplate; - - @Nested - class TestMetaDataCreation { - - @Test - void shouldCreateMetaData() { - var doc = callCreateMetadata(); - - assertThat(doc).isNotNull(); - } - - @Test - void shouldHaveVorgangId() { - var doc = callCreateMetadata(); - - assertThat(doc).containsEntry(BinaryFileRepository.VORGANG_ID, UploadedFilesReferenceTestFactory.VORGANG_ID); - } - - @Test - void shouldHaveClient() { - var doc = callCreateMetadata(); - - assertThat(doc).containsEntry(BinaryFileRepository.CLIENT, CallContextTestFactory.CLIENT); - } - - @Test - void shouldHaveContentType() { - var doc = callCreateMetadata(); - - assertThat(doc).containsEntry(BinaryFileRepository.CONTENT_TYPE, OzgFileTestFactory.CONTENT_TYPE); - } - - @Test - void shouldHaveCreatedBY() { - var doc = callCreateMetadata(); - - assertThat(doc).containsEntry(BinaryFileRepository.CREATED_BY, UserTestFactory.ID); - } - - @Test - void shouldHaveFileName() { - var doc = callCreateMetadata(); - - assertThat(doc).containsEntry(BinaryFileRepository.FILE_NAME, OzgFileTestFactory.NAME); - } - - @Test - void shouldHaveFieldName() { - var doc = callCreateMetadata(); - - assertThat(doc).containsEntry(BinaryFileRepository.FIELD_NAME, UploadedFilesReferenceTestFactory.NAME); - } - - private Document callCreateMetadata() { - return grisFsService.createMetaData(UploadedFilesReferenceTestFactory.create(), OzgFileTestFactory.create(), UserTestFactory.ID); - } - } - - @Nested - class TestFilePath { - @Test - void shouldCreatePath() { - String path = grisFsService.createFilePath( - UploadedFilesReferenceTestFactory.create(), - OzgFileTestFactory.create()); - - assertThat(path).isEqualTo( - UploadedFilesReferenceTestFactory.VORGANG_ID + "/" + - CallContextTestFactory.CLIENT + "/" + - UploadedFilesReferenceTestFactory.NAME + "/" + - OzgFileTestFactory.NAME); - } - } - - @Nested - class TestFindAllMetaData { - - @Test - void shouldCallGridFSTemplate() { - when(gridFsTemplate.findOne(any())).thenReturn(GridFsTestFactory.create()); - - grisFsService.getAll(Collections.singleton(IncomingFileTestFactory.ID.toString())).toList(); - - verify(gridFsTemplate).findOne(any()); - } - - @Test - void shouldCallGridFsTemplateFind() { - when(gridFsTemplate.findOne(any())).thenReturn(GridFsTestFactory.create()); - - var files = grisFsService.getAll(Collections.singleton(IncomingFileTestFactory.ID.toString())).toList(); - - assertThat(files).hasSize(1); - } - - @Test - @DisplayName("should not map to OZG File when FileId is empty") - void shouldNotMapWhenFileIdEmpty() { - grisFsService.getAll(List.of(StringUtils.EMPTY)).toList(); - - verify(grisFsService, never()).mapToOzgFile(any()); - } - - @Test - @DisplayName("should not map to OZG File when FileId is null") - void shouldNotMapWhenFileIdNull() { - grisFsService.getAll(Collections.singletonList(null)).toList(); - - verify(grisFsService, never()).mapToOzgFile(any()); - } - - @Nested - class TestMapToOzgFile { - - @Test - void shouldContainFileId() { - var ozgFile = callRepositoryMap(); - - assertThat(ozgFile.getId()).hasToString(GridFsTestFactory.ID.toHexString()); - } - - @Test - void shouldContainVorgangId() { - var ozgFile = callRepositoryMap(); - - assertThat(ozgFile.getVorgangId()).isEqualTo(VorgangTestFactory.ID); - } - - @Test - void shouldContainName() { - var ozgFile = callRepositoryMap(); - - assertThat(ozgFile.getName()).hasToString(OzgFileTestFactory.NAME); - } - - @Test - void shouldContainSize() { - var ozgFile = callRepositoryMap(); - - assertThat(ozgFile.getSize()).isEqualTo(OzgFileTestFactory.SIZE); - } - - @Test - void shouldContainContentType() { - var ozgFile = callRepositoryMap(); - - assertThat(ozgFile.getContentType()).hasToString(OzgFileTestFactory.CONTENT_TYPE); - } - - private OzgFile callRepositoryMap() { - return grisFsService.mapToOzgFile(GridFsTestFactory.create()); - } - } - } -} diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/EingangFilesRepositoryITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/EingangFilesRepositoryITCase.java index dc5fa3c197e1f58b41fd2274e561ad92bf0d5b20..513408b4ca51675df07163df98b8bd3093235872 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/EingangFilesRepositoryITCase.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/EingangFilesRepositoryITCase.java @@ -103,7 +103,7 @@ class EingangFilesRepositoryITCase { assertThat(result).isEmpty(); } - private List<OzgFile> findAttachments(String vorgangId) { + private List<IncomingFile> findAttachments(String vorgangId) { return repository.findAttachments(vorgangId, Optional.empty()); } } @@ -187,7 +187,7 @@ class EingangFilesRepositoryITCase { assertThat(result).isEmpty(); } - private List<OzgFile> findRepresentations(String vorgangId) { + private List<IncomingFile> findRepresentations(String vorgangId) { return repository.findRepresentations(vorgangId, Optional.empty()); } } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/FileServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/FileServiceTest.java index 598d57dd26460240fce6949aef4ec9e59efb9a94..04015d85e9c98f4cbd89ca0b4de4d57af95c73bd 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/FileServiceTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/FileServiceTest.java @@ -33,6 +33,8 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.UUID; +import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -44,8 +46,10 @@ import org.mockito.Spy; import de.ozgcloud.vorgang.common.security.PolicyService; import de.ozgcloud.vorgang.vorgang.EingangTestFactory; +import de.ozgcloud.vorgang.vorgang.IncomingFile; import de.ozgcloud.vorgang.vorgang.IncomingFileTestFactory; import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; +import lombok.SneakyThrows; class FileServiceTest { @@ -58,103 +62,120 @@ class FileServiceTest { @Mock private EingangFilesRepository repository; @Mock - private BinaryFileRepository binaryFileRepository; + private OzgCloudFileRepository ozgCloudFileRepository; @Mock private FileIdMapper fileIdMapper; private final UploadedFilesReference ref = UploadedFilesReferenceTestFactory.create(); - private final Optional<String> user = Optional.empty(); - private final OzgFile file = OzgFileTestFactory.create(); + private final OzgCloudFile file = OzgCloudFileTestFactory.create(); private final InputStream contentStream = new ByteArrayInputStream(IncomingFileTestFactory.CONTENT); - @DisplayName("Upload filestream") + @DisplayName("Upload file") @Nested - class TestUploadFileStream { + class TestUploadFile { + + private static final String SAVED_FILE_ID = UUID.randomUUID().toString(); + private final OzgCloudFile savedFile = OzgCloudFileTestFactory.createBuilder().id(SAVED_FILE_ID).build(); + + @BeforeEach + void init() { + when(ozgCloudFileRepository.save(any())).thenReturn(savedFile); + when(fileIdMapper.toFileId(any())).thenReturn(FileId.from(SAVED_FILE_ID)); + } + + @Test + void shouldCallSave() { + service.saveOzgCloudFile(file); + + verify(ozgCloudFileRepository).save(file); + } @Test - void shouldCallUploadFile() { - service.uploadFileStream(ref, file, user, contentStream); + void shouldCallFileIdMapper() { + service.saveOzgCloudFile(file); + + verify(fileIdMapper).toFileId(SAVED_FILE_ID); + } - verify(service).uploadFile(ref, file, user, contentStream); + @Test + void shouldReturnFileId() { + var result = service.saveOzgCloudFile(file); + + assertThat(result).hasToString(SAVED_FILE_ID); } + } @DisplayName("upload filestream async") @Nested - class TestUploadFileStreamAsync { + class TestUploadFileContentStreamAsync { @Test - void shouldCallUploadFile() { - service.uploadFileStream(ref, file, user, contentStream); + void shouldCallUploadFileContent() { + service.uploadFileContentStreamAsync(ref, contentStream); + + verify(service).uploadFileContent(ref, contentStream); + } + + @SneakyThrows + @Test + void shouldReturnFileId() { + doReturn(OzgCloudFileTestFactory.CONTENT_ID).when(service).uploadFileContent(any(), any()); + + var id = service.uploadFileContentStreamAsync(ref, contentStream); - verify(service).uploadFile(ref, file, user, contentStream); + assertThat(id.get()).isEqualTo(OzgCloudFileTestFactory.CONTENT_ID); } } @DisplayName("Upload file") @Nested - class TestUploadFile { + class TestUploadFileContent { @BeforeEach void init() { - when(binaryFileRepository.addContentStream(any(), any(), any(), any())).thenReturn(IncomingFileTestFactory.ID); + when(ozgCloudFileRepository.uploadContent(any(), any())).thenReturn(OzgCloudFileTestFactory.CONTENT_ID); } @Test void shouldCallAddContentSream() { - service.uploadFileStream(ref, file, user, contentStream); - - verify(binaryFileRepository).addContentStream(ref, file, user, contentStream); - } + service.uploadFileContent(ref, contentStream); - @Test - void shouldAddContent() { - service.uploadFileStream(ref, file, user, contentStream); - - verify(binaryFileRepository).addContentStream(ref, file, user, contentStream); + verify(ozgCloudFileRepository).uploadContent(ref, contentStream); } @Test void shouldReturnId() { - var id = service.uploadFileStream(ref, file, user, contentStream); + var id = service.uploadFileContent(ref, contentStream); - assertThat(id).isEqualTo(IncomingFileTestFactory.ID); - } - } - - @Nested - class TestGetUploadedFileContent { - - @Test - void shouldBinaryFileRepository() { - service.getUploadedFileStream(OzgFileTestFactory.ID); - - verify(binaryFileRepository).getFileContent(OzgFileTestFactory.ID); + assertThat(id).isEqualTo(OzgCloudFileTestFactory.CONTENT_ID); } } @Nested class TestFindFilesMetaData { - private final Collection<FileId> ids = Collections.singleton(OzgFileTestFactory.ID); - - @BeforeEach - void mockFileMapper() { - when(fileIdMapper.toString(any())).thenReturn(OzgFileTestFactory.ID.toString()); - } + private final Collection<FileId> ids = Collections.singleton(OzgCloudFileTestFactory.ID); @Test - void shouldCallFileIdMapper() { - service.findFilesMetaData(ids); + void shouldCallRepository() { + findFilesMetaData(); - verify(fileIdMapper).toString(OzgFileTestFactory.ID); + verify(ozgCloudFileRepository).findAll(ids); } @Test - void shouldCallRepository() { - service.findFilesMetaData(ids); + void shouldReturnFilesMetaData() { + var file = OzgCloudFileTestFactory.create(); + when(ozgCloudFileRepository.findAll(any())).thenReturn(Stream.of(file)); + + var result = findFilesMetaData(); + + assertThat(result).containsExactly(file); + } - verify(binaryFileRepository).getAll(Collections.singletonList(OzgFileTestFactory.ID.toString())); + private List<OzgCloudFile> findFilesMetaData() { + return service.findOzgCloudFiles(ids).toList(); } } @@ -164,7 +185,7 @@ class FileServiceTest { void shouldCallRepository() { service.deleteAllByVorgang(VorgangTestFactory.ID); - verify(binaryFileRepository).deleteAllByVorgang(VorgangTestFactory.ID); + verify(ozgCloudFileRepository).deleteAllByVorgang(VorgangTestFactory.ID); } } @@ -187,7 +208,7 @@ class FileServiceTest { @Test void shouldReturnAttachmentsByVorgangIdAndEingangId() { - var attachment = OzgFileTestFactory.create(); + var attachment = IncomingFileTestFactory.create(); when(repository.findAttachments(any(), any())).thenReturn(List.of(attachment)); var result = getAttachments(); @@ -195,7 +216,7 @@ class FileServiceTest { assertThat(result).containsExactly(attachment); } - private List<OzgFile> getAttachments() { + private List<IncomingFile> getAttachments() { return service.getAttachments(VorgangTestFactory.ID, Optional.of(EingangTestFactory.ID)); } } @@ -219,7 +240,7 @@ class FileServiceTest { @Test void shouldReturnRepresentationsByVorgangIdAndEingangId() { - var representation = OzgFileTestFactory.create(); + var representation = IncomingFileTestFactory.create(); when(repository.findRepresentations(any(), any())).thenReturn(List.of(representation)); var result = getRepresentations(); @@ -227,7 +248,7 @@ class FileServiceTest { assertThat(result).containsExactly(representation); } - private List<OzgFile> getRepresentations() { + private List<IncomingFile> getRepresentations() { return service.getRepresentations(VorgangTestFactory.ID, Optional.of(EingangTestFactory.ID)); } } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GridFsTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GridFsTestFactory.java index f8109c717128e6b45f04690efeba8c4477189f84..55ef756ee61e3c1d8041810867ddfaeb10dedd02 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GridFsTestFactory.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GridFsTestFactory.java @@ -34,7 +34,6 @@ import org.springframework.data.mongodb.util.BsonUtils; import com.mongodb.client.gridfs.model.GridFSFile; import de.ozgcloud.vorgang.callcontext.CallContextTestFactory; -import de.ozgcloud.vorgang.callcontext.UserTestFactory; import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; public class GridFsTestFactory { @@ -47,12 +46,12 @@ public class GridFsTestFactory { public static final Integer CHUNK_SIZE = 1; public static GridFSFile create() { - return new GridFSFile(BsonUtils.simpleToBsonValue(ID), OzgFileTestFactory.NAME, OzgFileTestFactory.SIZE, CHUNK_SIZE, UPLOAD_DATE, - createMetadata()); + return new GridFSFile(BsonUtils.simpleToBsonValue(ID), OzgCloudFileTestFactory.NAME, OzgCloudFileTestFactory.SIZE, CHUNK_SIZE, UPLOAD_DATE, + new Document()); } public static GridFsUpload<ObjectId> createUpload() { - return createUploadBuilder(OzgFileTestFactory.CONTENT).build(); + return createUploadBuilder(OzgCloudFileTestFactory.CONTENT).build(); } public static GridFsUpload<ObjectId> createUpload(byte[] content) { @@ -63,24 +62,11 @@ public class GridFsTestFactory { public static GridFsUpload.GridFsUploadBuilder<ObjectId> createUploadBuilder(byte[] content) { return GridFsUpload.fromStream(new ByteArrayInputStream(content)) - .metadata(createMetadata()) .filename(createFilePath()); } - private static Document createMetadata() { - Document metadata = new Document(); - metadata.append(BinaryFileRepository.VORGANG_ID, VorgangTestFactory.ID); - metadata.append(BinaryFileRepository.CLIENT, CallContextTestFactory.CLIENT); - metadata.append(BinaryFileRepository.FIELD_NAME, UploadedFilesReferenceTestFactory.NAME); - metadata.append(BinaryFileRepository.CONTENT_TYPE, OzgFileTestFactory.CONTENT_TYPE); - metadata.append(BinaryFileRepository.FILE_NAME, OzgFileTestFactory.NAME); - metadata.append(BinaryFileRepository.CREATED_BY, UserTestFactory.ID); - - return metadata; - } - private static String createFilePath() { return String.format("%s/%s/%s/%s", VorgangTestFactory.ID, CallContextTestFactory.CLIENT, - UploadedFilesReferenceTestFactory.NAME, OzgFileTestFactory.NAME); + UploadedFilesReferenceTestFactory.NAME, OzgCloudFileTestFactory.NAME); } } \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcOzgFileMapperTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFileMapperTest.java similarity index 92% rename from vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcOzgFileMapperTest.java rename to vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFileMapperTest.java index b75e9d032d61eef6ea75938d7fe4669f43d90f7d..ef222837306da6744869e5d2e626b34c79270f3d 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcOzgFileMapperTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFileMapperTest.java @@ -34,7 +34,7 @@ import org.mockito.Spy; import de.ozgcloud.vorgang.grpc.file.GrpcOzgFile; -class GrpcOzgFileMapperTest { +class GrpcBinaryFileMapperTest { @InjectMocks private GrpcOzgFileMapper mapper = Mappers.getMapper(GrpcOzgFileMapper.class); @@ -44,7 +44,7 @@ class GrpcOzgFileMapperTest { @Test void validateMapping() { - List<GrpcOzgFile> response = mapper.map(List.of(OzgFileTestFactory.create())); + List<GrpcOzgFile> response = mapper.map(List.of(OzgCloudFileTestFactory.create())); assertThat(response).isNotNull().hasSize(1); } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFileMetadataTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFileMetadataTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..daa09b8a312f2689bba0df1b730fae296af180b3 --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFileMetadataTestFactory.java @@ -0,0 +1,46 @@ +/* + * 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.vorgang.files; + +import de.ozgcloud.vorgang.grpc.binaryFile.GrpcBinaryFileMetadata; +import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; + +public class GrpcBinaryFileMetadataTestFactory { + + public static final String FILE_ID = OzgCloudFileTestFactory.ID.toString(); + public static final String VORGANG_ID = VorgangTestFactory.ID; + public static final String CONTENT_TYPE = OzgCloudFileTestFactory.CONTENT_TYPE; + public static final String FILE_NAME = OzgCloudFileTestFactory.NAME; + + public static GrpcBinaryFileMetadata create() { + return createBuilder().build(); + } + + public static GrpcBinaryFileMetadata.Builder createBuilder() { + return GrpcBinaryFileMetadata.newBuilder() + .setFileId(FILE_ID) + .setContentType(CONTENT_TYPE) + .setFileName(FILE_NAME); + } +} \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFileServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFileServiceTest.java index 4935ca6391a0afb95de3fa00f23cb8f08080358f..9e7a4208cea818c56cd7c1b0c3423ccb20a9eb20 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFileServiceTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFileServiceTest.java @@ -24,17 +24,31 @@ package de.ozgcloud.vorgang.files; import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; import java.util.Set; +import java.util.UUID; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; - +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -45,20 +59,37 @@ import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; - +import org.springframework.test.util.ReflectionTestUtils; + +import com.google.protobuf.ByteString; +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.common.errorhandling.TechnicalException; +import de.ozgcloud.vorgang.callcontext.CallContextTestFactory; +import de.ozgcloud.vorgang.callcontext.CallContextUserTestFactory; +import de.ozgcloud.vorgang.callcontext.CurrentUserService; +import de.ozgcloud.vorgang.callcontext.UserTestFactory; +import de.ozgcloud.vorgang.command.GrpcCallContextTestFactory; +import de.ozgcloud.vorgang.command.GrpcUserTestFactory; +import de.ozgcloud.vorgang.common.errorhandling.NotFoundException; import de.ozgcloud.vorgang.common.security.PolicyService; import de.ozgcloud.vorgang.grpc.binaryFile.GrpcFindFilesResponse; import de.ozgcloud.vorgang.grpc.binaryFile.GrpcGetBinaryFileDataRequest; import de.ozgcloud.vorgang.grpc.binaryFile.GrpcGetBinaryFileDataResponse; -import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileMetaData; +import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUpdateBinaryFileRequest; +import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUpdateBinaryFileResponse; import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileRequest; import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileResponse; +import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; import io.grpc.stub.CallStreamObserver; -import io.grpc.stub.ServerCallStreamObserver; import io.grpc.stub.StreamObserver; +import lombok.SneakyThrows; class GrpcBinaryFileServiceTest { + private static final OzgCloudFile BINARY_FILE = OzgCloudFileTestFactory.create(); + private static final String NEW_CONTENT_FILE_ID = UUID.randomUUID().toString(); + @InjectMocks @Spy private GrpcBinaryFileService grpcService; @@ -68,9 +99,11 @@ class GrpcBinaryFileServiceTest { private GrpcOzgFileMapper fileMapper; @Mock private PolicyService policyService; + @Mock + private CurrentUserService currentUserService; @Nested - class TestFindBinaryFilesMetaData { + class TestFindOzgCloudFilesMetaData { @Mock private StreamObserver<GrpcFindFilesResponse> responseObserver; @@ -90,8 +123,8 @@ class GrpcBinaryFileServiceTest { void shouldCallService() { findBinaryFilesMetaData(); - verify(service).findFilesMetaData(fileIdsCaptor.capture()); - assertThat(fileIdsCaptor.getValue()).contains(OzgFileTestFactory.ID); + verify(service).findOzgCloudFiles(fileIdsCaptor.capture()); + assertThat(fileIdsCaptor.getValue()).contains(OzgCloudFileTestFactory.ID); } @Test @@ -115,71 +148,270 @@ class GrpcBinaryFileServiceTest { } } - @DisplayName("Upload filestream async") @Nested - class TestUploadFileStreamAsync { - - private static final int FILE_SIZE = 5 * 1_000_000; + class TestUploadOzgCloudFile { @Mock - private StreamObserver<GrpcFindFilesResponse> responseObserver; + private OzgCloudFile updatedOzgCloudFile; + + @BeforeEach + void init() { + when(service.saveOzgCloudFile(any())).thenReturn(OzgCloudFileTestFactory.ID); + doReturn(updatedOzgCloudFile).when(grpcService).updateContentId(any(), any()); + } + + @Test + void shouldCallUpdateContentId() { + uploadBinaryFile(); + + verify(grpcService).updateContentId(BINARY_FILE, Optional.of(OzgCloudFileTestFactory.CONTENT_ID)); + } + + @Test + void shouldCallUploadFile() { + uploadBinaryFile(); + + verify(service).saveOzgCloudFile(updatedOzgCloudFile); + } + + @Test + void shouldReturnFileId() { + var result = uploadBinaryFile(); + + assertThat(result).isEqualTo(OzgCloudFileTestFactory.ID); + } + + private FileId uploadBinaryFile() { + return grpcService.uploadOzgCloudFile(BINARY_FILE, Optional.of(OzgCloudFileTestFactory.CONTENT_ID)); + } + + } + + @Nested + class TestUploadOzgCloudFileAsStream { - @Spy - private ServerCallStreamObserver<GrpcUploadBinaryFileResponse> uploadFileResponseObserver; + private static final GrpcUploadBinaryFileRequest REQUEST = GrpcUploadBinaryFileRequestTestFactory.create(); @Mock - private StreamObserver<GrpcGetBinaryFileDataResponse> dataResponseObserver; + private StreamObserver<GrpcUploadBinaryFileResponse> responseObserver; + @Mock + private GrpcUploadBinaryFileResponse response; + @Mock + private GrpcUploadBinaryFileRequest requestMock; + @Mock + private ByteString content; + @Captor + private ArgumentCaptor<InputStream> inputStreamCaptor; - @BeforeEach - void init() { - when(service.uploadFileStreamAsync(any(), any(), any(), any())).thenReturn(CompletableFuture.supplyAsync(() -> FileId.createNew())); + @Test + void shouldSetMetadataVerifier() { + var uploadObserver = uploadBinaryFileAsStream(); + + callMetadataVerifier(uploadObserver, REQUEST); + verify(policyService).checkPermission(VorgangTestFactory.ID); + } + + @Test + void shouldSetHasMetadata() { + var uploadObserver = uploadBinaryFileAsStream(); + + callHasMetadata(uploadObserver, requestMock); + verify(requestMock).hasMetadata(); + } + + @Test + void shouldSetMetadataMapper() { + doReturn(UserTestFactory.ID).when(grpcService).getUserId(any()); + doReturn(CallContextTestFactory.CLIENT).when(grpcService).getClient(any()); + + var uploadObserver = uploadBinaryFileAsStream(); + + callMetadataMapper(uploadObserver, REQUEST); + verify(grpcService).buildOzgCloudFile(REQUEST); + } + + @Test + void shouldCallMetadataUploader() { + var uploadObserver = uploadBinaryFileAsStream(); + + callMetadataUploader(uploadObserver); + verify(grpcService).uploadOzgCloudFile(BINARY_FILE, Optional.of(NEW_CONTENT_FILE_ID)); + } + + @Test + void shouldCallFileContentUploader() { + var uploadObserver = uploadBinaryFileAsStream(); + + callFileContentUploader(uploadObserver); + verify(grpcService).uploadFileContent(eq(BINARY_FILE), inputStreamCaptor.capture()); + assertThat(inputStreamCaptor.getValue()).hasBinaryContent(OzgCloudFileTestFactory.CONTENT); + } + + @Test + void shouldCallGetFileContent() { + when(requestMock.getFileContent()).thenReturn(content); + + var uploadObserver = uploadBinaryFileAsStream(); + + callGetFileContent(uploadObserver, requestMock); + verify(requestMock).getFileContent(); + verify(content).toByteArray(); + } + + @Test + void shouldCallBuildResponse() { + var uploadObserver = uploadBinaryFileAsStream(); + + callResponseConsumer(uploadObserver); + verify(grpcService).buildResponse(OzgCloudFileTestFactory.ID); } @Test - void shouldCallService() throws IOException { - uploadFile(FILE_SIZE); + void shouldCallCompleteRequest() { + doReturn(response).when(grpcService).buildResponse(any()); + + var uploadObserver = uploadBinaryFileAsStream(); - verify(service, timeout(500)).uploadFileStreamAsync(any(), any(), any(), any()); + callResponseConsumer(uploadObserver); + verify(grpcService).completeRequest(response, responseObserver); } @Test - void shouldCallOnComplete() throws IOException { - uploadFile(FILE_SIZE); + void shouldCallDeleteContent() { + var uploadObserver = uploadBinaryFileAsStream(); - verify(uploadFileResponseObserver, timeout(500)).onCompleted(); + callCleanupFileContent(uploadObserver); + verify(service).deleteContent(OzgCloudFileTestFactory.CONTENT_ID); } + private StreamObserver<GrpcUploadBinaryFileRequest> uploadBinaryFileAsStream() { + return grpcService.uploadBinaryFileAsStream(responseObserver); + } + + } + + @Nested + class TestBuildOzgCloudFile { + @Test - void shouldCallOnNext() throws IOException { - uploadFile(FILE_SIZE); + void shouldBuildBinaryFile() { + doReturn(OzgCloudFileTestFactory.CREATED_BY).when(grpcService).getUserId(any()); + doReturn(OzgCloudFileTestFactory.CLIENT).when(grpcService).getClient(any()); + + var result = buildBinaryFile(); + + assertThat(result).usingRecursiveComparison().ignoringFields("id", "version", "contentId", "size") + .isEqualTo(OzgCloudFileTestFactory.create()); + } - verify(uploadFileResponseObserver, timeout(500)).onNext(any()); + private OzgCloudFile buildBinaryFile() { + return grpcService.buildOzgCloudFile(GrpcUploadBinaryFileRequestTestFactory.create()); } + } + + @Nested + class TestGetUserId { + + @Nested + class TestWithUploadRequest { + + private final GrpcUploadBinaryFileRequest fileUploadRequest = GrpcUploadBinaryFileRequestTestFactory.create(); + + @Nested + class TestContextUserId { + + @BeforeEach + void init() { + when(currentUserService.findUser()).thenReturn(Optional.of(CallContextUserTestFactory.create())); + } - void uploadFile(int fileSize) { - var observer = grpcService.uploadBinaryFileAsStream(uploadFileResponseObserver); + @Test + void shouldCallFindUser() { + getUserId(); - try { - uploadChunks(fileSize, observer).get(); - } catch (InterruptedException | ExecutionException e) { - Thread.currentThread().interrupt(); + verify(currentUserService).findUser(); + } + + @Test + void shouldReturnContextUserId() { + var result = getUserId(); + + assertThat(result).contains(CallContextUserTestFactory.ID); + } + } + + @Nested + class TestRequestUserId { + + @BeforeEach + void init() { + when(currentUserService.findUser()).thenReturn( + Optional.of(CallContextUserTestFactory.createBuilder().userId(Optional.empty()).build())); + } + + @Test + void shouldCallFindUser() { + getUserId(); + + verify(currentUserService).findUser(); + } + + @Test + void shouldReturnRequestUserId() { + var result = getUserId(); + + assertThat(result).contains(GrpcUserTestFactory.ID); + } + } + + private String getUserId() { + return grpcService.getUserId(fileUploadRequest); } } - private CompletableFuture<Boolean> uploadChunks(final int fileSize, final StreamObserver<GrpcUploadBinaryFileRequest> observer) { - return CompletableFuture.supplyAsync(() -> { - GrpcUploadBinaryFileRequest firstRequest = GrpcUploadBinaryFileRequest.newBuilder() - .setMetadata(GrpcUploadBinaryFileMetaData.newBuilder() - .setFileName("test") - .build()) - .build(); + @Nested + class TestWithoutRequest { + + @Nested + class TestUserExists { + + @BeforeEach + void init() { + when(currentUserService.getUser()).thenReturn(CallContextUserTestFactory.create()); + } + + @Test + void shouldCallGetUser() { + grpcService.getUserId(); + + verify(currentUserService).getUser(); + } + + @Test + void shouldReturnUserId() { + var result = grpcService.getUserId(); - observer.onNext(firstRequest); + assertThat(result).isEqualTo(CallContextUserTestFactory.ID); + } + } + + @Nested + class TestMissingUser { + + @Test + void shouldThrowExceptionIfMissingUser() { + when(currentUserService.getUser()).thenThrow(new IllegalStateException()); + + Assertions.assertThrows(IllegalStateException.class, grpcService::getUserId); + } - observer.onCompleted(); + @Test + void shouldThrowExceptionIfMissingUserId() { + when(currentUserService.getUser()).thenReturn(CallContextUserTestFactory.createBuilder().userId(Optional.empty()).build()); - return Boolean.TRUE; - }); + assertThrows(IllegalStateException.class, grpcService::getUserId); + } + } } } @@ -197,13 +429,13 @@ class GrpcBinaryFileServiceTest { @BeforeEach void init() { - request = GrpcGetBinaryFileDataRequest.newBuilder().setFileId(OzgFileTestFactory.ID.toString()).build(); + request = GrpcGetBinaryFileDataRequest.newBuilder().setFileId(OzgCloudFileTestFactory.ID.toString()).build(); } @Test void shouldCallService() { when(service.getUploadedFileStream(any(FileId.class))) - .thenReturn(new ByteArrayInputStream(OzgFileTestFactory.createContentInMiB(FILE_SIZE_MB))); + .thenReturn(new ByteArrayInputStream(OzgCloudFileTestFactory.createContentInMiB(FILE_SIZE_MB))); grpcService.getBinaryFileContent(request, dataResponseObserver); @@ -219,61 +451,47 @@ class GrpcBinaryFileServiceTest { } @Nested - class TestGetBinaryFileData { + class TestGetBinaryFileContent { + + private static final InputStream INPUT_STREAM = new BufferedInputStream(new ByteArrayInputStream(OzgCloudFileTestFactory.CONTENT), + FileService.CHUNK_SIZE); @Mock private CallStreamObserver<GrpcGetBinaryFileDataResponse> responseObserver; + @Captor - private ArgumentCaptor<Collection<FileId>> fileIdsCaptor; - @Captor - private ArgumentCaptor<GrpcGetBinaryFileDataResponse> responseCaptor; + private ArgumentCaptor<InputStream> inputStreamCaptor; @BeforeEach void mock() { - when(service.getUploadedFileStream(any(FileId.class))).thenReturn(new ByteArrayInputStream(OzgFileTestFactory.CONTENT)); + when(service.getUploadedFileStream(any(FileId.class))).thenReturn(INPUT_STREAM); } @Test void shouldCallPolicyService() { - getBinaryFileData(); + getBinaryFileContent(); verify(policyService).checkPermissionByFile(anyString()); } @Test void shouldCallService() { - getBinaryFileData(); + getBinaryFileContent(); verify(service).getUploadedFileStream(FileId.from(GrpcGetBinaryFileDataRequestTestFactory.FILE_ID)); } @Test - void shouldCallSetOnReadyHandler() { - getBinaryFileData(); - - verify(responseObserver).setOnReadyHandler(any()); - } - - @Test - void shouldCallOnNext() { - getBinaryFileData(); - - verify(responseObserver, times(1)).onNext(any()); - } - - @Test - void shouldCallOnComplete() { - getBinaryFileData(); + void shouldCallCallDownloadFile() { + getBinaryFileContent(); - verify(responseObserver).onCompleted(); + verify(grpcService).downloadFile(eq(responseObserver), inputStreamCaptor.capture()); + assertThat(inputStreamCaptor.getValue()).hasBinaryContent(OzgCloudFileTestFactory.CONTENT); } - private void getBinaryFileData() { - AtomicBoolean doSendFile = new AtomicBoolean(true); + private void getBinaryFileContent() { grpcService.getBinaryFileContent(GrpcGetBinaryFileDataRequestTestFactory.create(), responseObserver); - grpcService.sendChunks(responseObserver, new ByteArrayInputStream(OzgFileTestFactory.CONTENT), doSendFile); - } } @@ -281,8 +499,8 @@ class GrpcBinaryFileServiceTest { class TestBuildChunkResponse { @Test void shouldHaveFileContent() { - GrpcGetBinaryFileDataResponse response = grpcService.buildChunkResponse(OzgFileTestFactory.CONTENT, - OzgFileTestFactory.CONTENT.length); + GrpcGetBinaryFileDataResponse response = grpcService.buildChunkResponse(OzgCloudFileTestFactory.CONTENT, + OzgCloudFileTestFactory.CONTENT.length); assertThat(response.getFileContent()).isNotEmpty(); } @@ -290,116 +508,933 @@ class GrpcBinaryFileServiceTest { @Nested class TestBuildUploadResponse { + @Test void shouldHaveFileId() { - GrpcUploadBinaryFileResponse response = GrpcBinaryFileService.buildResponse(FileId.createNew()); + var fileId = "file-id"; - assertThat(response.getFileId()).isNotNull(); + var response = grpcService.buildResponse(FileId.from(fileId)); + + assertThat(response.getFileId()).isEqualTo(fileId); } } @Nested class TestDownloadFile { + @Mock private CallStreamObserver<GrpcGetBinaryFileDataResponse> responseObserver; + @Mock + private InputStream contentStream; + + @Captor + private ArgumentCaptor<Runnable> onReadyHandlerCaptor; + @Captor + private ArgumentCaptor<AtomicBoolean> doSendFileCaptor; + + @BeforeEach + void init() { + doNothing().when(grpcService).sendChunks(any(), any(), any()); + } @Test void shouldSetOnReadyHandler() { - grpcService.downloadFile(responseObserver, new ByteArrayInputStream(OzgFileTestFactory.createContentInMiB(3))); + grpcService.downloadFile(responseObserver, contentStream); - verify(responseObserver).setOnReadyHandler(any()); + verify(responseObserver).setOnReadyHandler(onReadyHandlerCaptor.capture()); + onReadyHandlerCaptor.getValue().run(); + verify(grpcService, times(2)).sendChunks(eq(responseObserver), eq(contentStream), doSendFileCaptor.capture()); + assertThat(doSendFileCaptor.getValue()).isTrue(); + } + + @Test + void shouldCallSendChunks() { + grpcService.downloadFile(responseObserver, contentStream); + + verify(grpcService).sendChunks(eq(responseObserver), eq(contentStream), doSendFileCaptor.capture()); + assertThat(doSendFileCaptor.getValue()).isTrue(); } } @Nested - class TestOnReadyHandler { + class TestSendChunk { + + private static final AtomicBoolean DO_SEND_FILE = new AtomicBoolean(true); + @Mock private CallStreamObserver<GrpcGetBinaryFileDataResponse> responseObserver; + @Mock + private InputStream contentStream; @Test - void shouldSetOnReadyHandler() { - grpcService.downloadFile(responseObserver, new ByteArrayInputStream(OzgFileTestFactory.createContentInMiB(3))); + void shouldCallIsReady() { + sendChunks(); + + verify(responseObserver).isReady(); + } + + @Nested + class TestSending { + + private static final int SENT_LAST_BYTES = new Random().nextInt(FileService.CHUNK_SIZE); + + @SneakyThrows + @BeforeEach + void init() { + when(responseObserver.isReady()).thenReturn(true, false); + } + + @Test + void shouldCallSendNextChunk() { + doReturn(FileService.CHUNK_SIZE).when(grpcService).sendNextChunk(any(), any()); + + sendChunks(); + + verify(grpcService).sendNextChunk(responseObserver, contentStream); + verify(grpcService, never()).handleFileEndReached(any(), any(), any()); + } + + @Test + void shouldCallHandleFileEndReached() { + doReturn(SENT_LAST_BYTES).when(grpcService).sendNextChunk(any(), any()); + + sendChunks(); + + verify(grpcService).handleFileEndReached(responseObserver, contentStream, DO_SEND_FILE); + verify(responseObserver, times(1)).isReady(); + } + + } + + @Nested + class TestNotSending { + + @SneakyThrows + @Test + void shouldNotSendIfDownloadCompleted() { + grpcService.sendChunks(responseObserver, contentStream, new AtomicBoolean(false)); + + verify(grpcService, never()).sendNextChunk(any(), any()); + } + + @Test + void shouldNotSendIfNotReady() { + grpcService.sendChunks(responseObserver, contentStream, new AtomicBoolean(true)); + + verify(grpcService, never()).sendNextChunk(any(), any()); + } + } - verify(responseObserver).setOnReadyHandler(any()); + private void sendChunks() { + grpcService.sendChunks(responseObserver, contentStream, DO_SEND_FILE); } } @Nested - class TestSendChunk { + class TestSendNextChunk { + + private static final int READ_BYTES = new Random().nextInt(1, FileService.CHUNK_SIZE); + private static final byte[] BUFFER = new byte[READ_BYTES]; + + @Mock + private CallStreamObserver<GrpcGetBinaryFileDataResponse> responseObserver; + @Mock + private GrpcGetBinaryFileDataResponse binaryFileDataGrpcResponse; + @Mock + private InputStream contentStream; + + @Nested + class TestSendingSuccessfully { + @Captor + private ArgumentCaptor<byte[]> bufferCaptor; + + @SneakyThrows + @BeforeEach + void init() { + doReturn(binaryFileDataGrpcResponse).when(grpcService).buildChunkResponse(any(), anyInt()); + new Random().nextBytes(BUFFER); + when(contentStream.read(any())).thenAnswer(invocation -> { + byte[] buffer = invocation.getArgument(0); + System.arraycopy(BUFFER, 0, buffer, 0, READ_BYTES); + return READ_BYTES; + }); + } + + @Test + void shouldCallBuildChuckResponse() { + grpcService.sendNextChunk(responseObserver, contentStream); + + verify(grpcService).buildChunkResponse(bufferCaptor.capture(), eq(READ_BYTES)); + assertThat(bufferCaptor.getValue()).contains(BUFFER); + } + + @Test + void shouldCallOnNext() { + grpcService.sendNextChunk(responseObserver, contentStream); + + verify(responseObserver).onNext(binaryFileDataGrpcResponse); + } + } + + @Nested + class TestOnException { + + @SneakyThrows + @Test + void shouldThrowException() { + doThrow(IOException.class).when(contentStream).read(any()); + + assertThrows(TechnicalException.class, () -> grpcService.sendNextChunk(responseObserver, contentStream)); + } + + @SneakyThrows + @Test + void shouldCloseStreamOnException() { + try (var ioUtilsMock = mockStatic(IOUtils.class)) { + doThrow(IOException.class).when(contentStream).read(any()); + + try { + grpcService.sendNextChunk(responseObserver, contentStream); + } catch (TechnicalException e) { + // expected + } + + ioUtilsMock.verify(() -> IOUtils.closeQuietly(eq(contentStream), any(Consumer.class))); + } + } + } + } + + @Nested + class TestHandleFileEndReached { + + private static final AtomicBoolean DO_SEND_FILE = new AtomicBoolean(true); @Mock private CallStreamObserver<GrpcGetBinaryFileDataResponse> responseObserver; + @Mock + private InputStream contentStream; + + @Test + void shouldSetDoSendFile() { + handleFileEndReached(); + + assertThat(DO_SEND_FILE).isFalse(); + } @Test - void shouldComplete() throws IOException { - var res = OzgFileTestFactory.createContentInMiB(1); - var inputStream = new ByteArrayInputStream(res); + void shouldCallCloseQuietly() { + try (var ioUtilsMock = mockStatic(IOUtils.class)) { + handleFileEndReached(); + + ioUtilsMock.verify(() -> IOUtils.closeQuietly(eq(contentStream), any(Consumer.class))); + } + } - grpcService.downloadFile(responseObserver, inputStream); + @Test + void shouldCallOnCompleted() { + handleFileEndReached(); - triggerSendingChunks(res, inputStream); + verify(responseObserver).onCompleted(); + } - verify(responseObserver, timeout(500)).onCompleted(); + private void handleFileEndReached() { + grpcService.handleFileEndReached(responseObserver, contentStream, DO_SEND_FILE); } + } + + @DisplayName("Upload filestream async") + @Nested + class TestUpdateOzgCloudFile { + + private static final GrpcUpdateBinaryFileRequest REQUEST = GrpcUpdateBinaryFileRequestTestFactory.create(); + + @Mock + private StreamObserver<GrpcUpdateBinaryFileResponse> responseObserver; + @Mock + private GrpcUpdateBinaryFileRequest requestMock; + @Mock + private ByteString content; + @Captor + private ArgumentCaptor<InputStream> inputStreamCaptor; @Test - void shoudSendChunks() { - grpcService.sendChunks(responseObserver, new ByteArrayInputStream(OzgFileTestFactory.createContentInMiB(3)), new AtomicBoolean(true)); + void shouldSetMetadataVerifier() { + var uploadObserver = updateBinaryFile(); - verify(grpcService).sendChunks(any(), any(), any()); + callMetadataVerifier(uploadObserver, REQUEST); + verify(grpcService).verifyUpdateRequest(REQUEST); } @Test - void shouldSendOneChunk() { - grpcService.sendChunks(responseObserver, new ByteArrayInputStream(OzgFileTestFactory.createContentInMiB(3)), new AtomicBoolean(true)); + void shouldSetHasMetadata() { + var uploadObserver = updateBinaryFile(); - verify(responseObserver).onNext(any()); + callHasMetadata(uploadObserver, requestMock); + verify(requestMock).hasMetadata(); } @Test - void shouldSendOneChunkWhenFileSizeEqualsChunkSize() throws IOException { - byte[] res = OzgFileTestFactory.createContentInByte(FileService.CHUNK_SIZE); - var inputStream = new ByteArrayInputStream(res); + void shouldSetMetadataMapper() { + doReturn(BINARY_FILE).when(grpcService).buildOzgCloudFile(any(GrpcUpdateBinaryFileRequest.class)); + + var uploadObserver = updateBinaryFile(); + + callMetadataMapper(uploadObserver, REQUEST); + verify(grpcService).buildOzgCloudFile(REQUEST); + } - grpcService.downloadFile(responseObserver, inputStream); + @Test + void shouldCallMetadataUploader() { + doReturn(OzgCloudFileTestFactory.CREATED_BY).when(grpcService).getUserId(); + doReturn(OzgCloudFileTestFactory.CLIENT).when(grpcService).getClient(); - triggerSendingChunks(res, inputStream); + var uploadObserver = updateBinaryFile(); - verify(grpcService).buildChunkResponse(any(), anyInt()); - verify(responseObserver).onNext(any()); - verify(responseObserver, timeout(500)).onCompleted(); + callMetadataUploader(uploadObserver); + verify(grpcService).patchOzgCloudFile(BINARY_FILE, Optional.of(NEW_CONTENT_FILE_ID)); } @Test - void shouldSendManyChunks() throws IOException { - byte[] res = OzgFileTestFactory.createContentInMiB(1); - var inputStream = new ByteArrayInputStream(res); + void shouldCallFileContentUploader() { + var uploadObserver = updateBinaryFile(); + + callFileContentUploader(uploadObserver); + verify(grpcService).uploadFileContent(eq(BINARY_FILE), inputStreamCaptor.capture()); + assertThat(inputStreamCaptor.getValue()).hasBinaryContent(OzgCloudFileTestFactory.CONTENT); + } - grpcService.downloadFile(responseObserver, inputStream); + @Test + void shouldCallGetFileContent() { + when(requestMock.getContent()).thenReturn(content); - int expected = triggerSendingChunks(res, inputStream); + var uploadObserver = updateBinaryFile(); - verify(grpcService, times(expected)).buildChunkResponse(any(), anyInt()); - verify(responseObserver, times(expected)).onNext(any()); + callGetFileContent(uploadObserver, requestMock); + verify(requestMock).getContent(); + verify(content).toByteArray(); } - private int triggerSendingChunks(byte[] res, ByteArrayInputStream inputStream) { - int expected = computeExpectedCunkCount(res); - AtomicBoolean doSendFile = new AtomicBoolean(true); - for (int i = 0; i < expected; i++) { - grpcService.sendChunks(responseObserver, inputStream, doSendFile); - } - if (res.length % FileService.CHUNK_SIZE == 0) { - grpcService.sendChunks(responseObserver, inputStream, doSendFile); - } - return expected; + @Test + void shouldCallCompleteRequest() { + var uploadObserver = updateBinaryFile(); + + callResponseConsumer(uploadObserver); + verify(grpcService).completeRequest(GrpcUpdateBinaryFileResponse.getDefaultInstance(), responseObserver); } - int computeExpectedCunkCount(byte[] res) { - int expected = res.length / FileService.CHUNK_SIZE; - if (res.length % FileService.CHUNK_SIZE != 0) { - expected++; - } - return expected; + @Test + void shouldCallDeleteContent() { + var uploadObserver = updateBinaryFile(); + + callCleanupFileContent(uploadObserver); + verify(service).deleteContent(OzgCloudFileTestFactory.CONTENT_ID); } + + private StreamObserver<GrpcUpdateBinaryFileRequest> updateBinaryFile() { + return grpcService.updateBinaryFile(responseObserver); + } + + } + + @DisplayName("Verify update request") + @Nested + class TestVerifyUpdateRequest { + + @BeforeEach + void init() { + doNothing().when(grpcService).validateMetadata(any()); + doNothing().when(policyService).checkPermissionByFile(any()); + } + + @Test + void shouldCallValidateMetadata() { + var request = GrpcUpdateBinaryFileRequestTestFactory.create(); + + grpcService.verifyUpdateRequest(request); + + verify(grpcService).validateMetadata(request); + } + + @Test + void shouldCallVerifyPermissions() { + var request = GrpcUpdateBinaryFileRequestTestFactory.create(); + + grpcService.verifyUpdateRequest(request); + + verify(policyService).checkPermissionByFile(OzgCloudFileTestFactory.ID_STR); + } + } + + @DisplayName("Validate metadata of update binary file request") + @Nested + class TestValidateMetadata { + + @DisplayName("should validate metadata") + @Test + void shouldValidateRequest() { + var request = GrpcUpdateBinaryFileRequestTestFactory.create(); + + assertDoesNotThrow(() -> grpcService.validateMetadata(request)); + } + + @DisplayName("should throw exception when") + @Nested + class TestThrowException { + + @DisplayName("metadata missing") + @Test + void shouldThrowWhenMetadataMissing() { + var request = GrpcUpdateBinaryFileRequestTestFactory.createBuilder().clearMetadata().build(); + + assertThrows(IllegalArgumentException.class, () -> grpcService.validateMetadata(request)); + } + + @DisplayName("fileID missing") + @Test + void shouldThrowWhenFileIdMissing() { + var request = GrpcUpdateBinaryFileRequestTestFactory.createBuilder() + .setMetadata(GrpcBinaryFileMetadataTestFactory.createBuilder().clearFileId()).build(); + + assertThrows(IllegalArgumentException.class, () -> grpcService.validateMetadata(request)); + } + + @DisplayName("file name missing") + @Test + void shouldThrowWhenFileNameMissing() { + var request = GrpcUpdateBinaryFileRequestTestFactory.createBuilder() + .setMetadata(GrpcBinaryFileMetadataTestFactory.createBuilder().clearFileName()).build(); + + assertThrows(IllegalArgumentException.class, () -> grpcService.validateMetadata(request)); + } + + @DisplayName("content type missing") + @Test + void shouldThrowWhenContentTypeMissing() { + var request = GrpcUpdateBinaryFileRequestTestFactory.createBuilder() + .setMetadata(GrpcBinaryFileMetadataTestFactory.createBuilder().clearContentType()).build(); + + assertThrows(IllegalArgumentException.class, () -> grpcService.validateMetadata(request)); + } + + } + } + + @DisplayName("Build binary file from update request") + @Nested + class TestBuildOzgCloudFileUpdate { + + private static final GrpcUpdateBinaryFileRequest REQUEST = GrpcUpdateBinaryFileRequestTestFactory.create(); + private static final OzgCloudFile FOUND_BINARY_FILE = OzgCloudFileTestFactory.create(); + private static final OzgCloudFile UPDATED_BINARY_FILE = OzgCloudFileTestFactory.create(); + + @Test + void shouldThrowExceptionIfFileNotFound() { + assertThrows(NotFoundException.class, this::buildBinaryFile); + } + + @Nested + class TestFileExists { + + @BeforeEach + void init() { + when(service.findOzgCloudFiles(anyList())).thenReturn(Stream.of(FOUND_BINARY_FILE)); + doReturn(UPDATED_BINARY_FILE).when(grpcService).updateOzgCloudFile(any(), any()); + } + + @Test + void shouldCallFindFilesMetaData() { + var fileid = FileId.from(GrpcBinaryFileMetadataTestFactory.FILE_ID); + + buildBinaryFile(); + + verify(service).findOzgCloudFiles(List.of(fileid)); + } + + @Test + void shouldCallUpdateBinaryFile() { + buildBinaryFile(); + + verify(grpcService).updateOzgCloudFile(FOUND_BINARY_FILE, REQUEST); + } + + @Test + void shouldReturnUpdatedBinaryFile() { + var result = buildBinaryFile(); + + assertThat(result).isSameAs(UPDATED_BINARY_FILE); + } + } + + private OzgCloudFile buildBinaryFile() { + return grpcService.buildOzgCloudFile(REQUEST); + } + } + + @Nested + class TestUpdateOzgCloudFileFields { + + private static final String NEW_FILE_NAME = LoremIpsum.getInstance().getWords(1); + private static final String NEW_CONTENT_TYPE = LoremIpsum.getInstance().getWords(1); + + @Test + void shouldUpdateFileName() { + var result = updateBinaryFile(); + + assertThat(result.getName()).isEqualTo(NEW_FILE_NAME); + } + + @Test + void shouldUpdateContentType() { + var result = updateBinaryFile(); + + assertThat(result.getContentType()).isEqualTo(NEW_CONTENT_TYPE); + } + + @Test + void shouldNotChangeOtherFields() { + var result = updateBinaryFile(); + + assertThat(result).usingRecursiveComparison().ignoringFields("name", "contentType").isEqualTo(OzgCloudFileTestFactory.create()); + } + + private OzgCloudFile updateBinaryFile() { + return grpcService.updateOzgCloudFile(OzgCloudFileTestFactory.create(), GrpcUpdateBinaryFileRequestTestFactory.createBuilder() + .setMetadata(GrpcBinaryFileMetadataTestFactory.createBuilder().setFileName(NEW_FILE_NAME).setContentType(NEW_CONTENT_TYPE)) + .build()); + } + } + + @Nested + class TestPatchFileMetadata { + + private static final OzgCloudFile BINARY_FILE = OzgCloudFileTestFactory.create(); + private static final OzgCloudFile UPDATED_BINARY_FILE = OzgCloudFileTestFactory.createBuilder().contentId(NEW_CONTENT_FILE_ID) + .build(); + private static final Map<String, Object> PATCH_MAP = Map.of("key", "value"); + + @BeforeEach + void init() { + doReturn(PATCH_MAP).when(grpcService).buildPatch(any()); + } + + @Test + void shouldCallUpdateContentId() { + patchBinaryFile(); + + verify(grpcService).updateContentId(BINARY_FILE, Optional.of(NEW_CONTENT_FILE_ID)); + } + + @Test + void shouldCallBuildPatch() { + doReturn(UPDATED_BINARY_FILE).when(grpcService).updateContentId(any(), any()); + + patchBinaryFile(); + + verify(grpcService).buildPatch(UPDATED_BINARY_FILE); + } + + @Test + void shouldCallPatch() { + patchBinaryFile(); + + verify(service).patch(OzgCloudFileTestFactory.ID, OzgCloudFileTestFactory.VERSION, PATCH_MAP); + } + + @Test + void shouldCallDeleteContent() { + patchBinaryFile(); + + verify(service).deleteContent(OzgCloudFileTestFactory.CONTENT_ID); + } + + @Test + void shouldNotCallDeleteContent() { + grpcService.patchOzgCloudFile(BINARY_FILE, Optional.empty()); + + verify(service, never()).deleteContent(any()); + } + + @Test + void shouldReturnFileId() { + var result = patchBinaryFile(); + + assertThat(result).isEqualTo(OzgCloudFileTestFactory.ID); + } + + private FileId patchBinaryFile() { + return grpcService.patchOzgCloudFile(BINARY_FILE, Optional.of(NEW_CONTENT_FILE_ID)); + } + } + + @Nested + class TestUpdateContentId { + + private static final OzgCloudFile BINARY_FILE = OzgCloudFileTestFactory.create(); + + @Test + void shouldUpdateContentId() { + var result = updateContentId(); + + assertThat(result.getContentId()).isEqualTo(NEW_CONTENT_FILE_ID); + } + + @Test + void shouldNotChangeOtherFields() { + var result = updateContentId(); + + assertThat(result).usingRecursiveComparison().ignoringFields("contentId").isEqualTo(BINARY_FILE); + } + + @Test + void shouldReturnUnchangedBinaryFile() { + var result = grpcService.updateContentId(BINARY_FILE, Optional.empty()); + + assertThat(result).isSameAs(BINARY_FILE); + } + + private OzgCloudFile updateContentId() { + return grpcService.updateContentId(BINARY_FILE, Optional.of(NEW_CONTENT_FILE_ID)); + } + } + + @Nested + class TestBuildPatch { + + private static final Map<String, Object> PATCH_MAP = Map.of( + OzgCloudFile.FIELD_FILE_NAME, OzgCloudFileTestFactory.NAME, + OzgCloudFile.FIELD_CONTENT_TYPE, OzgCloudFileTestFactory.CONTENT_TYPE, + OzgCloudFile.FIELD_CONTENT_ID, OzgCloudFileTestFactory.CONTENT_ID, + OzgCloudFile.FIELD_CREATED_BY, OzgCloudFileTestFactory.CREATED_BY, + OzgCloudFile.FIELD_CLIENT, OzgCloudFileTestFactory.CLIENT); + + @BeforeEach + void init() { + doReturn(OzgCloudFileTestFactory.CREATED_BY).when(grpcService).getUserId(); + doReturn(CallContextTestFactory.CLIENT).when(grpcService).getClient(); + } + + @Test + void shouldBuildPatch() { + var result = grpcService.buildPatch(OzgCloudFileTestFactory.create()); + + assertThat(result).containsExactlyEntriesOf(PATCH_MAP); + } + } + + @Nested + class TestUploadFileContent { + + private static final OzgCloudFile BINARY_FILE = OzgCloudFileTestFactory.create(); + private static final UploadedFilesReference UPLOADED_FILES_REFERENCE = UploadedFilesReferenceTestFactory.create(); + private static final CompletableFuture<String> FILE_ID_FUTURE = CompletableFuture.completedFuture(OzgCloudFileTestFactory.CONTENT_ID); + + @Mock + private InputStream inputStream; + + @BeforeEach + void init() { + doReturn(UPLOADED_FILES_REFERENCE).when(grpcService).buildUploadFilesReferences(any()); + when(service.uploadFileContentStreamAsync(any(), any())).thenReturn(FILE_ID_FUTURE); + } + + @Test + void shouldCallBuildUploadFilesReferences() { + uploadFileContent(); + + verify(grpcService).buildUploadFilesReferences(BINARY_FILE); + } + + @Test + void shouldCallUploadFileContent() { + uploadFileContent(); + + verify(service).uploadFileContentStreamAsync(UPLOADED_FILES_REFERENCE, inputStream); + } + + @Test + void shouldReturnFutureFileId() { + var result = uploadFileContent(); + + assertThat(result).isSameAs(FILE_ID_FUTURE); + } + + private CompletableFuture<String> uploadFileContent() { + return grpcService.uploadFileContent(BINARY_FILE, inputStream); + } + } + + @Nested + class TestBuildUploadFilesReferences { + + @Test + void shouldSetVorgangId() { + var uploadFilesReference = buildUploadFilesReferences(); + + assertThat(uploadFilesReference.getVorgangId()).isEqualTo(VorgangTestFactory.ID); + } + + @Test + void shouldSetName() { + var uploadFilesReference = buildUploadFilesReferences(); + + assertThat(uploadFilesReference.getName()).isEqualTo(UploadedFilesReferenceTestFactory.NAME); + } + + @Test + void shouldSetClient() { + var uploadFilesReference = buildUploadFilesReferences(); + + assertThat(uploadFilesReference.getClient()).isEqualTo(CallContextTestFactory.CLIENT); + } + + @Test + void shouldSetFileName() { + var uploadFilesReference = buildUploadFilesReferences(); + + assertThat(uploadFilesReference.getFileName()).isEqualTo(UploadedFilesReferenceTestFactory.FILE_NAME); + } + + private UploadedFilesReference buildUploadFilesReferences() { + return grpcService.buildUploadFilesReferences(OzgCloudFileTestFactory.create()); + } + } + + @Nested + class TestGetClient { + + @Nested + class TestWithRequest { + + @Nested + class TestGetFromContext { + + @BeforeEach + void init() { + doReturn(Optional.of(CallContextTestFactory.CLIENT)).when(grpcService).findContextClient(); + } + + @Test + void shouldCallFindContextClient() { + getClient(); + + verify(grpcService).findContextClient(); + } + + @Test + void shouldReturnResult() { + var result = getClient(); + + assertThat(result).contains(CallContextTestFactory.CLIENT); + } + } + + @Nested + class TestFromRequest { + + @BeforeEach + void init() { + doReturn(Optional.empty()).when(grpcService).findContextClient(); + doReturn(Optional.of(GrpcCallContextTestFactory.CLIENT)).when(grpcService).getRequestClient(any()); + } + + @Test + void shouldCallGetRequestClient() { + var grpcRequest = GrpcUploadBinaryFileRequestTestFactory.create(); + + grpcService.getClient(grpcRequest); + + verify(grpcService).getRequestClient(grpcRequest); + } + + @Test + void shouldReturnClientName() { + var result = getClient(); + + assertThat(result).isEqualTo(GrpcCallContextTestFactory.CLIENT); + } + } + + @Nested + class TestNoClient { + + @Test + void shouldThrowException() { + doReturn(Optional.empty()).when(grpcService).findContextClient(); + doReturn(Optional.empty()).when(grpcService).getRequestClient(any()); + + assertThrows(IllegalStateException.class, TestWithRequest.this::getClient); + } + } + + private String getClient() { + return grpcService.getClient(GrpcUploadBinaryFileRequestTestFactory.create()); + } + } + + @Nested + class TestFromContext { + + @Test + void shouldCallFindUser() { + doReturn(Optional.of(CallContextTestFactory.CLIENT)).when(grpcService).findContextClient(); + + grpcService.getClient(); + + verify(grpcService).findContextClient(); + } + + @Test + void shouldReturnClientName() { + doReturn(Optional.of(CallContextTestFactory.CLIENT)).when(grpcService).findContextClient(); + + var result = grpcService.getClient(); + + assertThat(result).isEqualTo(CallContextTestFactory.CLIENT); + } + + @Test + void shouldThrowException() { + doReturn(Optional.empty()).when(grpcService).findContextClient(); + + assertThrows(IllegalStateException.class, grpcService::getClient); + } + } + } + + @Nested + class TestFindContextClient { + + @Nested + class TestWithUser { + + @BeforeEach + void init() { + when(currentUserService.findUser()).thenReturn(Optional.of(CallContextUserTestFactory.create())); + } + + @Test + void shouldCallGetUser() { + findContextClient(); + + verify(currentUserService).findUser(); + } + + @Test + void shouldReturnClientName() { + var result = findContextClient(); + + assertThat(result).contains(CallContextTestFactory.CLIENT); + } + } + + @Nested + class TestWithoutUser { + + @BeforeEach + void init() { + when(currentUserService.findUser()).thenReturn(Optional.empty()); + } + + @Test + void shouldReturnEmpty() { + var result = findContextClient(); + + assertThat(result).isEmpty(); + } + } + + private Optional<String> findContextClient() { + return grpcService.findContextClient(); + } + } + + @Nested + class TestGetRequestClient { + + @Test + void shouldReturnClientName() { + var result = grpcService.getRequestClient(GrpcUploadBinaryFileRequestTestFactory.create()); + + assertThat(result).contains(GrpcCallContextTestFactory.CLIENT); + } + } + + @Nested + class TestCompleteRequest { + + @Mock + private StreamObserver<TestResponse> responseObserver; + @Mock + private TestResponse response; + + @Test + void shouldCallOnNext() { + completeRequest(); + + verify(responseObserver).onNext(response); + } + + @Test + void shouldCallOnComplete() { + completeRequest(); + + verify(responseObserver).onCompleted(); + } + + private void completeRequest() { + grpcService.completeRequest(response, responseObserver); + } + + private interface TestResponse { + } + } + + @SuppressWarnings("unchecked") + private <T> void callMetadataVerifier(StreamObserver<T> uploadObserver, T request) { + var metadataVerifier = (Consumer<T>) ReflectionTestUtils.getField(uploadObserver, "requestVerifier"); + metadataVerifier.accept(request); + } + + @SuppressWarnings("unchecked") + private <T> void callHasMetadata(StreamObserver<T> uploadObserver, T request) { + var hasMetadata = (Predicate<T>) ReflectionTestUtils.getField(uploadObserver, "hasMetadata"); + hasMetadata.test(request); + } + + @SuppressWarnings("unchecked") + private <T> void callMetadataMapper(StreamObserver<T> uploadObserver, T request) { + var metadataMapper = (Function<T, OzgCloudFile>) ReflectionTestUtils.getField(uploadObserver, "metadataMapper"); + metadataMapper.apply(request); + } + + @SuppressWarnings("unchecked") + private <T> void callMetadataUploader(StreamObserver<T> uploadObserver) { + var metadataUploader = (BiFunction<OzgCloudFile, Optional<String>, FileId>) ReflectionTestUtils.getField(uploadObserver, "metadataUploader"); + metadataUploader.apply(BINARY_FILE, Optional.of(NEW_CONTENT_FILE_ID)); + } + + @SuppressWarnings("unchecked") + private <T> void callFileContentUploader(StreamObserver<T> uploadObserver) { + var fileContentUploader = (BiFunction<OzgCloudFile, InputStream, CompletableFuture<FileId>>) + ReflectionTestUtils.getField(uploadObserver, "fileContentUploader"); + fileContentUploader.apply(BINARY_FILE, new ByteArrayInputStream(OzgCloudFileTestFactory.CONTENT)); + } + + @SuppressWarnings("unchecked") + private <T> void callGetFileContent(StreamObserver<T> uploadObserver, T request) { + var getFileContent = (Function<T, byte[]>) ReflectionTestUtils.getField(uploadObserver, "getFileContent"); + getFileContent.apply(request); + } + + @SuppressWarnings("unchecked") + private <T> void callResponseConsumer(StreamObserver<T> uploadObserver) { + var responseConsumer = (Consumer<FileId>) ReflectionTestUtils.getField(uploadObserver, "responseConsumer"); + responseConsumer.accept(OzgCloudFileTestFactory.ID); + } + + @SuppressWarnings("unchecked") + private <T> void callCleanupFileContent(StreamObserver<T> uploadObserver) { + var cleanupFileContent = (Consumer<String>) ReflectionTestUtils.getField(uploadObserver, "cleanupFileContent"); + cleanupFileContent.accept(OzgCloudFileTestFactory.CONTENT_ID); } } \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFilesRequestTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFilesRequestTestFactory.java index 18265b4546b412c899a86346e5730a7cfad8fc45..e4c50fc234b7248c32f23602d9074e758282ab6b 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFilesRequestTestFactory.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcBinaryFilesRequestTestFactory.java @@ -33,6 +33,6 @@ public class GrpcBinaryFilesRequestTestFactory { public static GrpcBinaryFilesRequest.Builder createBuilder() { return GrpcBinaryFilesRequest.newBuilder() - .addFileId(OzgFileTestFactory.ID.toString()); + .addFileId(OzgCloudFileTestFactory.ID.toString()); } } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcFileServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcFileServiceTest.java index abedba973a572b2661a4b6025ad1144821600f56..10a6235256d67363f7d884600cf8ca013525aa90 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcFileServiceTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcFileServiceTest.java @@ -32,20 +32,23 @@ import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Spy; import de.ozgcloud.vorgang.grpc.file.GrpcGetAttachmentsResponse; import de.ozgcloud.vorgang.grpc.file.GrpcGetRepresentationsResponse; import de.ozgcloud.vorgang.grpc.file.GrpcOzgFile; +import de.ozgcloud.vorgang.vorgang.IncomingFile; +import de.ozgcloud.vorgang.vorgang.IncomingFileTestFactory; import io.grpc.stub.StreamObserver; class GrpcFileServiceTest { private static final GrpcOzgFile GRPC_OZG_FILE = GrpcOzgFileTestFactory.create(); + private static final IncomingFile INCOMING_FILE = IncomingFileTestFactory.create(); + @Spy @InjectMocks private GrpcFileService grpcFileService; @@ -55,19 +58,18 @@ class GrpcFileServiceTest { @Mock private GrpcOzgFileMapper fileMapper; - @BeforeEach - void init() { - when(fileMapper.map(anyList())).thenReturn(List.of(GRPC_OZG_FILE)); - } - @Nested class TestGetAttachments { + @Mock + private GrpcGetAttachmentsResponse getAttachmentsResponse; @Mock private StreamObserver<GrpcGetAttachmentsResponse> responseObserver; - @Captor - private ArgumentCaptor<GrpcGetAttachmentsResponse> responseCaptor; + @BeforeEach + void init() { + doReturn(getAttachmentsResponse).when(grpcFileService).buildAttachmentsResponse(anyList()); + } @Test void shouldCallGetAttachments() { @@ -78,21 +80,20 @@ class GrpcFileServiceTest { } @Test - void shouldCallFileMapper() { - var ozgFiles = List.of(OzgFileTestFactory.create()); - when(fileService.getAttachments(any(), any())).thenReturn(ozgFiles); + void shouldCallBuildAttachmentsResponse() { + var files = List.of(INCOMING_FILE); + when(fileService.getAttachments(any(), any())).thenReturn(files); getAttachments(); - verify(fileMapper).map(ozgFiles); + verify(grpcFileService).buildAttachmentsResponse(files); } @Test void shouldAddFilesToResponse() { getAttachments(); - verify(responseObserver).onNext(responseCaptor.capture()); - assertThat(responseCaptor.getValue().getFileList()).containsExactly(GRPC_OZG_FILE); + verify(responseObserver).onNext(getAttachmentsResponse); } @Test @@ -107,14 +108,47 @@ class GrpcFileServiceTest { } } + @Nested + class TestBuildAttachmentsResponse { + + @BeforeEach + void init() { + when(fileMapper.map(any(IncomingFile.class))).thenReturn(GRPC_OZG_FILE); + } + + @Test + void shouldCallFileMapper() { + when(fileMapper.map(INCOMING_FILE)).thenReturn(GRPC_OZG_FILE); + + buildAttachmentsResponse(); + + verify(fileMapper).map(INCOMING_FILE); + } + + @Test + void shouldSetGrpcFile() { + var result = buildAttachmentsResponse(); + + assertThat(result.getFileList()).containsExactly(GRPC_OZG_FILE); + } + + private GrpcGetAttachmentsResponse buildAttachmentsResponse() { + return grpcFileService.buildAttachmentsResponse(List.of(INCOMING_FILE)); + } + } + @Nested class TestGetRepresentations { + @Mock + private GrpcGetRepresentationsResponse getRepresentationsResponse; @Mock private StreamObserver<GrpcGetRepresentationsResponse> responseObserver; - @Captor - private ArgumentCaptor<GrpcGetRepresentationsResponse> responseCaptor; + @BeforeEach + void init() { + doReturn(getRepresentationsResponse).when(grpcFileService).buildRepresentationsResponse(any()); + } @Test void shouldCallGetRepresentations() { @@ -125,21 +159,20 @@ class GrpcFileServiceTest { } @Test - void shouldCallFileMapper() { - var ozgFiles = List.of(OzgFileTestFactory.create()); - when(fileService.getRepresentations(any(), any())).thenReturn(ozgFiles); + void shouldCallBuildRepresentationsResponse() { + var file = IncomingFileTestFactory.create(); + when(fileService.getRepresentations(any(), any())).thenReturn(List.of(file)); getRepresentations(); - verify(fileMapper).map(ozgFiles); + verify(grpcFileService).buildRepresentationsResponse(List.of(file)); } @Test void shouldAddFilesToResponse() { getRepresentations(); - verify(responseObserver).onNext(responseCaptor.capture()); - assertThat(responseCaptor.getValue().getFileList()).containsExactly(GRPC_OZG_FILE); + verify(responseObserver).onNext(getRepresentationsResponse); } @Test @@ -153,4 +186,33 @@ class GrpcFileServiceTest { grpcFileService.getRepresentations(GrpcGetRepresentationsRequestTestFactory.create(), responseObserver); } } + + @Nested + class TestBuildRepresentationsResponse { + + @BeforeEach + void init() { + when(fileMapper.map(any(IncomingFile.class))).thenReturn(GRPC_OZG_FILE); + } + + @Test + void shouldCallFileMapper() { + when(fileMapper.map(INCOMING_FILE)).thenReturn(GRPC_OZG_FILE); + + buildRepresentationsResponse(); + + verify(fileMapper).map(INCOMING_FILE); + } + + @Test + void shouldSetGrpcFile() { + var result = buildRepresentationsResponse(); + + assertThat(result.getFileList()).containsExactly(GRPC_OZG_FILE); + } + + private GrpcGetRepresentationsResponse buildRepresentationsResponse() { + return grpcFileService.buildRepresentationsResponse(List.of(INCOMING_FILE)); + } + } } \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcOzgFileTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcOzgFileTestFactory.java index e2dec587074c6fa5d6a13ef1be50e9bbddaa1bbd..df2fd4feaf07b916f397c6561c66acd4a9f25ce1 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcOzgFileTestFactory.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcOzgFileTestFactory.java @@ -33,9 +33,9 @@ public class GrpcOzgFileTestFactory { public static GrpcOzgFile.Builder createBuilder() { return GrpcOzgFile.newBuilder() - .setId(OzgFileTestFactory.ID.toString()) - .setName(OzgFileTestFactory.NAME) - .setSize(OzgFileTestFactory.SIZE) - .setContentType(OzgFileTestFactory.CONTENT_TYPE); + .setId(OzgCloudFileTestFactory.ID.toString()) + .setName(OzgCloudFileTestFactory.NAME) + .setSize(OzgCloudFileTestFactory.SIZE) + .setContentType(OzgCloudFileTestFactory.CONTENT_TYPE); } } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcUpdateBinaryFileRequestTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcUpdateBinaryFileRequestTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..0c2212f9f71977b56f2b40dfa93af0db1e6fc7db --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcUpdateBinaryFileRequestTestFactory.java @@ -0,0 +1,46 @@ +/* + * 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.vorgang.files; + +import com.google.protobuf.ByteString; + +import de.ozgcloud.vorgang.grpc.binaryFile.GrpcBinaryFileMetadata; +import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUpdateBinaryFileRequest; +import de.ozgcloud.vorgang.vorgang.IncomingFileTestFactory; + +public class GrpcUpdateBinaryFileRequestTestFactory { + + static final ByteString FILE_CONTENT = ByteString.copyFrom(IncomingFileTestFactory.CONTENT); + static final GrpcBinaryFileMetadata METADATA = GrpcBinaryFileMetadataTestFactory.create(); + + public static GrpcUpdateBinaryFileRequest create() { + return createBuilder().build(); + } + + public static GrpcUpdateBinaryFileRequest.Builder createBuilder() { + return GrpcUpdateBinaryFileRequest.newBuilder() + .setContent(FILE_CONTENT) + .setMetadata(METADATA); + } +} \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcUploadBinaryFileMetaDataTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcUploadBinaryFileMetaDataTestFactory.java index 5b7086c3e395ad184031efe707ff86f96acec637..f779d55bc0eb7a836efb0b7bc7fc6449a8be8b4d 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcUploadBinaryFileMetaDataTestFactory.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/GrpcUploadBinaryFileMetaDataTestFactory.java @@ -36,10 +36,10 @@ public class GrpcUploadBinaryFileMetaDataTestFactory { public static GrpcUploadBinaryFileMetaData.Builder createBuilder() { return GrpcUploadBinaryFileMetaData.newBuilder() .setVorgangId(VorgangTestFactory.ID) - .setContentType(OzgFileTestFactory.CONTENT_TYPE) - .setFileName(OzgFileTestFactory.NAME) + .setContentType(OzgCloudFileTestFactory.CONTENT_TYPE) + .setFileName(OzgCloudFileTestFactory.NAME) .setContext(GrpcCallContextTestFactory.create()) - .setSize(OzgFileTestFactory.SIZE) + .setSize(OzgCloudFileTestFactory.SIZE) .setField(UploadedFilesReferenceTestFactory.NAME); } } \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..1f6333117bc70f54d72c955d009f83012b2bd6d3 --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileITCase.java @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.files; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.*; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +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.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.test.annotation.DirtiesContext; + +import com.google.protobuf.ByteString; +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.common.test.DataITCase; +import de.ozgcloud.vorgang.callcontext.TestCallContextAttachingInterceptor; +import de.ozgcloud.vorgang.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceBlockingStub; +import de.ozgcloud.vorgang.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceStub; +import de.ozgcloud.vorgang.grpc.binaryFile.GrpcBinaryFileMetadata; +import de.ozgcloud.vorgang.grpc.binaryFile.GrpcFindFilesResponse; +import de.ozgcloud.vorgang.grpc.binaryFile.GrpcGetBinaryFileDataRequest; +import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUpdateBinaryFileRequest; +import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUpdateBinaryFileResponse; +import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileMetaData; +import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileRequest; +import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileResponse; +import de.ozgcloud.vorgang.vorgang.Vorgang; +import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; +import io.grpc.StatusRuntimeException; +import io.grpc.stub.StreamObserver; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import net.devh.boot.grpc.client.inject.GrpcClient; + +@SpringBootTest(properties = { + "grpc.server.inProcessName=binary-file-test", + "grpc.server.port=-1", + "grpc.client.inProcess.address=in-process:binary-file-test" +}) +@DirtiesContext +@DataITCase +public class OzgCloudFileITCase { + + @GrpcClient("inProcess") + private BinaryFileServiceBlockingStub binaryFileServiceBlockingStub; + @GrpcClient("inProcess") + private BinaryFileServiceStub binaryFileServiceStub; + + @Autowired + private MongoOperations mongoOperations; + @Autowired + private OzgCloudFileTestUtil ozgCloudFileTestUtil; + + @BeforeEach + void init() { + mongoOperations.dropCollection(Vorgang.COLLECTION_NAME); + ozgCloudFileTestUtil.dropFileCollections(); + } + + @DisplayName("Find binary files meta data") + @Nested + class TestFindOzgCloudFilesMetaData { + + private String fileId; + + @BeforeEach + void initDatabase() { + fileId = ozgCloudFileTestUtil.saveOzgCloudFile(OzgCloudFileTestFactory.CONTENT).getId(); + } + + @Nested + class OnMatchingOrganisationEinheitIdAtVorgang { + + @BeforeEach + void initDatabase() { + mongoOperations.save(VorgangTestFactory.create()); + } + + @Test + void shouldReturnStoredFile() { + var response = callFindBinaryFilesMetaData(); + + assertThat(response.getFileList()).hasSize(1).first().satisfies(file -> { + assertThat(file.getId()).isEqualTo(fileId); + assertThat(file.getName()).isEqualTo(OzgCloudFileTestFactory.NAME); + assertThat(file.getContentType()).isEqualTo(OzgCloudFileTestFactory.CONTENT_TYPE); + }); + } + } + + @Nested + class OnNonMatchingOrganisationEinheitIdOnVorgang { + + @BeforeEach + void initDatabase() { + mongoOperations.save(VorgangTestFactory.createWithOrganisationEinheitId("73")); + } + + @Test + void shouldThrowStatusRuntimeException() { + assertThrows(StatusRuntimeException.class, () -> callFindBinaryFilesMetaData()); + } + } + + private GrpcFindFilesResponse callFindBinaryFilesMetaData() { + return prepareBinaryFileServiceBlockingStub().findBinaryFilesMetaData( + GrpcBinaryFilesRequestTestFactory.createBuilder().clearFileId().addFileId(fileId).build()); + } + + } + + @Nested + @DisplayName("Download files of different sizes") + class TestDownloadFile { + + @Nested + class OnMatchingOrganisationEinheitIdAtVorgang { + + @BeforeEach + void initDatabase() { + mongoOperations.save(VorgangTestFactory.create()); + } + + @Test + void shouldReturnStoredSmallFile() { + var fileId = ozgCloudFileTestUtil.saveOzgCloudFile().getId(); + + var file = downloadBinaryFile(fileId); + + assertThat(file).hasSameSizeAs(OzgCloudFileTestFactory.CONTENT).isEqualTo(OzgCloudFileTestFactory.CONTENT); + } + + @Test + void shouldReturnStoredFileWithSizeEqualChunkSize() { + var content = OzgCloudFileTestFactory.createContentInByte(FileService.CHUNK_SIZE); + var fileId = ozgCloudFileTestUtil.saveOzgCloudFile(content).getId(); + + var file = downloadBinaryFile(fileId); + + assertThat(file).hasSameSizeAs(content).isEqualTo(content); + } + + @Test + void shouldReturnStoredLargeFile() { + var input = OzgCloudFileTestFactory.createContentInMiB(5); + var fileId = ozgCloudFileTestUtil.saveOzgCloudFile(input).getId(); + + var downloadedFile = downloadBinaryFile(fileId); + + assertThat(downloadedFile).hasSameSizeAs(input).isEqualTo(input); + } + } + + @Nested + class OnNonMatchingOrganisationEinheitIdAtVorgang { + + @BeforeEach + void initDatabase() { + mongoOperations.save(VorgangTestFactory.createWithOrganisationEinheitId("73")); + } + + @Test + void shouldThrowStatusRuntimeException() { + var fileId = ozgCloudFileTestUtil.saveOzgCloudFile().getId(); + + assertThrows(StatusRuntimeException.class, () -> downloadBinaryFile(fileId)); + } + } + + private byte[] downloadBinaryFile(String fileId) { + var request = GrpcGetBinaryFileDataRequest.newBuilder().setFileId(fileId).build(); + var responseIterator = prepareBinaryFileServiceBlockingStub().getBinaryFileContent(request); + var content = new ByteArrayOutputStream(); + while (responseIterator.hasNext()) { + ByteString chunkContent = responseIterator.next().getFileContent(); + try { + content.write(chunkContent.toByteArray()); + } catch (IOException e) { + fail(e); + } + } + + return content.toByteArray(); + } + } + + private BinaryFileServiceBlockingStub prepareBinaryFileServiceBlockingStub() { + return binaryFileServiceBlockingStub.withInterceptors(new TestCallContextAttachingInterceptor()); + } + + @Nested + @DisplayName("Upload file") + class TestFileUpload { + + private static final GrpcUploadBinaryFileMetaData REQUEST_METADATA = GrpcUploadBinaryFileMetaDataTestFactory.create(); + + private UploadTestStreamObserver responseObserver = new UploadTestStreamObserver(); + + @Nested + class OnMatchingOrganisationEinheitIdAtVorgang { + + @BeforeEach + void init() { + mongoOperations.save(VorgangTestFactory.create()); + } + + @SneakyThrows + @Test + void shouldSaveVorgangId() { + var fileId = uploadFileWithContent(); + + var file = mongoOperations.findById(fileId, OzgCloudFile.class); + assertThat(file.getVorgangId()).isEqualTo(VorgangTestFactory.ID); + } + + @SneakyThrows + @Test + void shouldSaveFileContent() { + var fileId = uploadFileWithContent(); + + var file = mongoOperations.findById(fileId, OzgCloudFile.class); + try (var fileStream = ozgCloudFileTestUtil.getFileContent(file.getContentId())) { + byte[] fileContentFromDb = fileStream.readAllBytes(); + assertThat(fileContentFromDb).isEqualTo(OzgCloudFileTestFactory.CONTENT); + } + + } + } + + @Nested + class OnMatchingNonOrganisationEinheitIdAtVorgang { + + @BeforeEach + void init() { + mongoOperations.save(VorgangTestFactory.createWithOrganisationEinheitId("73")); + } + + @Test + void shouldThrowStatusRuntimeException() { + assertThatThrownBy(TestFileUpload.this::uploadFileWithContent).cause().isInstanceOf(StatusRuntimeException.class); + } + } + + @SneakyThrows + private String uploadFileWithContent() { + GrpcUploadBinaryFileRequest metadataRequest = GrpcUploadBinaryFileRequest.newBuilder().setMetadata(REQUEST_METADATA).build(); + GrpcUploadBinaryFileRequest contentRequest = GrpcUploadBinaryFileRequest.newBuilder() + .setFileContent(ByteString.copyFrom(OzgCloudFileTestFactory.CONTENT)).build(); + + StreamObserver<GrpcUploadBinaryFileRequest> observer = prepareBinaryFileServiceStub().uploadBinaryFileAsStream(responseObserver); + + observer.onNext(metadataRequest); + observer.onNext(contentRequest); + observer.onCompleted(); + + return responseObserver.getFileIdFuture().get(10, TimeUnit.SECONDS); + } + + } + + @Nested + class TestUpdateOzgCloudFile { + + private static final String NEW_NAME = "new_name_" + LoremIpsum.getInstance().getWords(1); + private static final String NEW_CONTENT_TYPE = "new_content_type_" + LoremIpsum.getInstance().getWords(1); + private static final byte[] NEW_CONTENT = LoremIpsum.getInstance().getWords(10).getBytes(); + + private UpdateTestStreamObserver responseStreamObserver = new UpdateTestStreamObserver(); + + private OzgCloudFile ozgCloudFile; + + @BeforeEach + void init() { + mongoOperations.save(VorgangTestFactory.create()); + ozgCloudFile = ozgCloudFileTestUtil.saveOzgCloudFile(OzgCloudFileTestFactory.CONTENT); + } + + @Test + void shouldUpdateMetadata() { + updateFileMetadata(ozgCloudFile.getId()); + + var updatedFile = mongoOperations.findById(ozgCloudFile.getId(), OzgCloudFile.class); + assertThat(updatedFile.getName()).isEqualTo(NEW_NAME); + assertThat(updatedFile.getContentType()).isEqualTo(NEW_CONTENT_TYPE); + assertThat(ozgCloudFileTestUtil.getFileContent(ozgCloudFile.getContentId())).hasBinaryContent(OzgCloudFileTestFactory.CONTENT); + } + + @Test + void shouldUpdateFileWithContent() { + updateFileWithContent(ozgCloudFile.getId()); + + var updatedFile = mongoOperations.findById(ozgCloudFile.getId(), OzgCloudFile.class); + assertThat(updatedFile.getName()).isEqualTo(NEW_NAME); + assertThat(updatedFile.getContentType()).isEqualTo(NEW_CONTENT_TYPE); + assertThat(ozgCloudFileTestUtil.getFileContent(updatedFile.getContentId())).hasBinaryContent(NEW_CONTENT); + } + + @Test + void shouldThrowExceptionWhenFileNotFound() { + assertThatThrownBy(() -> updateFileMetadata("not_existing_file_id")).cause().isInstanceOf(StatusRuntimeException.class); + } + + @SneakyThrows + private void updateFileMetadata(String fileId) { + var observer = prepareBinaryFileServiceStub().updateBinaryFile(responseStreamObserver); + + observer.onNext(buildMetadataRequest(fileId)); + observer.onCompleted(); + + responseStreamObserver.getFileIdFuture().get(10, TimeUnit.SECONDS); + } + + @SneakyThrows + private void updateFileWithContent(String fileId) { + var contentRequest = GrpcUpdateBinaryFileRequest.newBuilder().setContent(ByteString.copyFrom(NEW_CONTENT)).build(); + + var observer = prepareBinaryFileServiceStub().updateBinaryFile(responseStreamObserver); + + observer.onNext(buildMetadataRequest(fileId)); + observer.onNext(contentRequest); + observer.onCompleted(); + + responseStreamObserver.getFileIdFuture().get(10, TimeUnit.SECONDS); + } + + private GrpcUpdateBinaryFileRequest buildMetadataRequest(String fileId) { + var metadata = GrpcBinaryFileMetadata.newBuilder().setFileId(fileId).setFileName(NEW_NAME).setContentType(NEW_CONTENT_TYPE).build(); + return GrpcUpdateBinaryFileRequest.newBuilder().setMetadata(metadata).build(); + } + } + + private BinaryFileServiceStub prepareBinaryFileServiceStub() { + return binaryFileServiceStub.withInterceptors(new TestCallContextAttachingInterceptor()); + } + + @Getter + @RequiredArgsConstructor(access = AccessLevel.PROTECTED) + static class UploadTestStreamObserver implements StreamObserver<GrpcUploadBinaryFileResponse> { + + private final CompletableFuture<String> fileIdFuture = new CompletableFuture<>(); + private String fileId; + + @Override + public void onNext(GrpcUploadBinaryFileResponse value) { + fileId = value.getFileId(); + } + + @Override + public void onError(Throwable t) { + fileIdFuture.completeExceptionally(t); + } + + @Override + public void onCompleted() { + fileIdFuture.complete(fileId); + } + } + + @Getter + @RequiredArgsConstructor(access = AccessLevel.PROTECTED) + static class UpdateTestStreamObserver implements StreamObserver<GrpcUpdateBinaryFileResponse> { + + private final CompletableFuture<Void> fileIdFuture = new CompletableFuture<>(); + + @Override + public void onNext(final GrpcUpdateBinaryFileResponse value) { + } + + @Override + public void onError(Throwable t) { + fileIdFuture.completeExceptionally(t); + } + + @Override + public void onCompleted() { + fileIdFuture.complete(null); + } + } + +} \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileRepositoryITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileRepositoryITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..6efa60a53808485f8b1ed5c8252887c1a10f6905 --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileRepositoryITCase.java @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.files; + +import static org.assertj.core.api.Assertions.*; + +import java.io.ByteArrayInputStream; +import java.io.FileNotFoundException; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoOperations; + +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.common.test.DataITCase; +import de.ozgcloud.vorgang.common.errorhandling.NotFoundException; +import de.ozgcloud.vorgang.vorgang.IncomingFileTestFactory; +import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; + +@DataITCase +class OzgCloudFileRepositoryITCase { + + @Autowired + private OzgCloudFileRepository repository; + + @Autowired + private MongoOperations mongoOperations; + @Autowired + private OzgCloudFileTestUtil ozgCloudFileTestUtil; + + @BeforeEach + void clearDB() { + mongoOperations.dropCollection(OzgCloudFile.COLLECTION_NAME); + mongoOperations.dropCollection("fs.files"); + mongoOperations.dropCollection("fs.chunks"); + } + + @Nested + class TestSave { + + private OzgCloudFile ozgCloudFile; + + @BeforeEach + void init() { + var contentId = ozgCloudFileTestUtil.storeFileContent(OzgCloudFileTestFactory.CONTENT); + ozgCloudFile = OzgCloudFileTestFactory.createBuilder().id(null).version(0).contentId(contentId).build(); + } + + @Test + void shouldSaveFile() { + var result = repository.save(ozgCloudFile); + + assertThat(result).usingRecursiveComparison().ignoringFields("id", OzgCloudFile.FIELD_VERSION).isEqualTo(ozgCloudFile); + var savedOzgCloudFile = mongoOperations.findById(result.getId(), OzgCloudFile.class); + assertThat(result).usingRecursiveComparison().isEqualTo(savedOzgCloudFile); + } + + @Test + void shouldIncreaseVersion() { + var result = repository.save(ozgCloudFile); + + assertThat(result.getVersion()).isEqualTo(1); + } + + @Test + void shouldSetContentSize() { + var ozgCloudFile = this.ozgCloudFile.toBuilder().size(new Random().nextLong()).build(); + + var result = repository.save(ozgCloudFile); + + assertThat(result.getSize()).isEqualTo(OzgCloudFileTestFactory.SIZE); + } + + @Test + void shouldFailIfContentMissing() { + var ozgCloudFile = OzgCloudFileTestFactory.createBuilder().id(null).version(0).contentId("missing").build(); + + assertThatThrownBy(() -> repository.save(ozgCloudFile)).isInstanceOf(NotFoundException.class); + } + } + + @Nested + class TestUploadContent { + + @Test + void shouldStoreFiles() { + var content = IncomingFileTestFactory.getFilledChunk(45); + + uploadContent(content); + + assertThat(ozgCloudFileTestUtil.getFileContentByFileName()).hasBinaryContent(content); + } + + private void uploadContent(byte[] content) { + repository.uploadContent(UploadedFilesReferenceTestFactory.create(), new ByteArrayInputStream(content)); + } + } + + @Nested + class TestGetFileContent { + + @Test + void shouldLoadFile() { + var fileContent = IncomingFileTestFactory.getFilledChunk(45); + var ozgCloudFileId = FileId.from(ozgCloudFileTestUtil.saveOzgCloudFile(fileContent).getId()); + + var result = repository.getFileContent(ozgCloudFileId); + + assertThat(result).hasBinaryContent(fileContent); + } + + @Test + void shouldThrowExceptionWhenFileNotFound() { + var fileId = FileId.from("not-existing"); + + assertThatThrownBy(() -> repository.getFileContent(fileId)) + .isInstanceOf(NotFoundException.class) + .hasMessageContaining(fileId.toString()); + } + } + + @Nested + class TestFindAll { + + private OzgCloudFile ozgCloudFile; + + @BeforeEach + void initData() { + ozgCloudFile = mongoOperations.save(OzgCloudFileTestFactory.createBuilder().id(null).version(0).build()); + } + + @Test + void shouldReturnFile() { + var result = findAll(List.of(ozgCloudFile.getId())); + + assertThat(result).hasSize(1).first().usingRecursiveComparison().isEqualTo(ozgCloudFile); + } + + @Test + void shouldReturnAllFiles() { + var otherFileId = mongoOperations.save(OzgCloudFileTestFactory.createBuilder().id(null).version(0).build()); + + var result = findAll(List.of(ozgCloudFile.getId(), otherFileId.getId())); + + assertThat(result).hasSize(2).usingRecursiveFieldByFieldElementComparator().containsExactly(ozgCloudFile, otherFileId); + } + + private List<OzgCloudFile> findAll(Collection<String> fileIds) { + return repository.findAll(fileIds.stream().map(FileId::from).toList()).toList(); + } + } + + @Nested + class TestDeleteAllByVorgang { + + @Test + void shouldDeleteFiles() { + ozgCloudFileTestUtil.saveOzgCloudFile(); + + repository.deleteAllByVorgang(VorgangTestFactory.ID); + + var loaded = mongoOperations.findAll(OzgCloudFile.class); + assertThat(loaded).isEmpty(); + assertThatThrownBy(ozgCloudFileTestUtil::getFileContentByFileName).cause().isInstanceOf(FileNotFoundException.class); + } + + @Test + void shouldNotDeleteOtherFiles() { + var fileContent = IncomingFileTestFactory.getFilledChunk(1); + var ozgCloudFile = ozgCloudFileTestUtil.saveOzgCloudFile(fileContent); + + repository.deleteAllByVorgang("other"); + + var loaded = mongoOperations.findById(ozgCloudFile.getId(), OzgCloudFile.class); + assertThat(loaded).usingRecursiveComparison().isEqualTo(ozgCloudFile); + assertThat(ozgCloudFileTestUtil.getFileContentByFileName()).hasBinaryContent(fileContent); + } + } + + @Nested + class TestPatch { + + private static final String NEW_NAME = "newName"; + private static final Map<String, Object> PATCH_MAP = Map.of(OzgCloudFile.FIELD_FILE_NAME, NEW_NAME); + + private OzgCloudFile ozgCloudFile; + + @BeforeEach + void init() { + ozgCloudFile = ozgCloudFileTestUtil.saveOzgCloudFile(); + } + + @Test + void shouldPatchOzgCloudFile() { + repository.patch(FileId.from(ozgCloudFile.getId()), ozgCloudFile.getVersion(), PATCH_MAP); + + var loaded = mongoOperations.findById(ozgCloudFile.getId(), OzgCloudFile.class); + assertThat(loaded.getName()).isEqualTo(NEW_NAME); + } + + @Test + void shouldIncreaseVersion() { + repository.patch(FileId.from(ozgCloudFile.getId()), ozgCloudFile.getVersion(), PATCH_MAP); + + var loaded = mongoOperations.findById(ozgCloudFile.getId(), OzgCloudFile.class); + assertThat(loaded.getVersion()).isEqualTo(2); + } + + @Test + void shouldPatchContentSize() { + var content = LoremIpsum.getInstance().getWords(23).getBytes(); + var newContentId = ozgCloudFileTestUtil.storeFileContent(content); + var patchMap = Map.<String, Object>of(OzgCloudFile.FIELD_CONTENT_ID, newContentId); + + repository.patch(FileId.from(ozgCloudFile.getId()), ozgCloudFile.getVersion(), patchMap); + + var loaded = mongoOperations.findById(ozgCloudFile.getId(), OzgCloudFile.class); + assertThat(loaded.getSize()).isEqualTo(content.length); + } + + @Test + void shouldThrowConcurrentModificationException() { + var ozgCloudFile = ozgCloudFileTestUtil.saveOzgCloudFile(); + + var id = FileId.from(ozgCloudFile.getId()); + var version = ozgCloudFile.getVersion() - 1; + assertThatThrownBy(() -> repository.patch(id, version, PATCH_MAP)).isInstanceOf(ConcurrentModificationException.class); + } + } + + @Nested + class TestDeleteContent { + + @Test + void shouldDeleteContent() { + var contentId = ozgCloudFileTestUtil.storeFileContent(OzgCloudFileTestFactory.CONTENT); + + repository.deleteContent(contentId); + + assertThatThrownBy(() -> ozgCloudFileTestUtil.getFileContentByFileName()).cause().isInstanceOf(FileNotFoundException.class); + } + } +} \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileRepositoryTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileRepositoryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3bf5641b33a42b912b273db06a79d648f74181e9 --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileRepositoryTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.files; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.io.InputStream; + +import org.bson.types.ObjectId; +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.Spy; +import org.springframework.data.mongodb.gridfs.GridFsTemplate; +import org.springframework.data.mongodb.gridfs.GridFsUpload; + +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.vorgang.callcontext.CallContextTestFactory; + +class OzgCloudFileRepositoryTest { + + private static final UploadedFilesReference UPLOAD_REFERENCE = UploadedFilesReferenceTestFactory.create(); + + @Spy + @InjectMocks + private OzgCloudFileRepository repository; + + @Mock + private GridFsTemplate gridFsTemplate; + @Mock + private InputStream contentStream; + + @Nested + class TestAddContentStream { + + private static final ObjectId OBJECT_ID = new ObjectId(); + + @Mock + private GridFsUpload<ObjectId> gridFsUpload; + + @BeforeEach + void init() { + doReturn(gridFsUpload).when(repository).buildGridFsUpload(any(), any()); + when(gridFsTemplate.store(any())).thenReturn(OBJECT_ID); + } + + @Test + void shouldCallBuildGridFsUpload() { + addContentStream(); + + verify(repository).buildGridFsUpload(UPLOAD_REFERENCE, contentStream); + } + + @Test + void shouldCallGridFsTemplateStore() { + addContentStream(); + + verify(gridFsTemplate).store(gridFsUpload); + } + + @Test + void shouldReturnFileId() { + + var result = addContentStream(); + + assertThat(result).hasToString(OBJECT_ID.toString()); + } + + private String addContentStream() { + return repository.uploadContent(UPLOAD_REFERENCE, contentStream); + } + } + + @Nested + class TestBuildGridFsUpload { + + @Test + void shouldSetContent() { + + var result = buildGridFsUpload(); + + assertThat(result.getContent()).isSameAs(contentStream); + } + + @Test + void shouldCallCreateFilePath() { + buildGridFsUpload(); + + verify(repository).createFilePath(UPLOAD_REFERENCE); + } + + @Test + void shouldSetFilename() { + var fileName = LoremIpsum.getInstance().getName(); + doReturn(fileName).when(repository).createFilePath(UPLOAD_REFERENCE); + + var result = buildGridFsUpload(); + + assertThat(result.getFilename()).isEqualTo(fileName); + } + + private GridFsUpload<ObjectId> buildGridFsUpload() { + return repository.buildGridFsUpload(UPLOAD_REFERENCE, contentStream); + } + } + + @Nested + class TestCreateFilePath { + @Test + void shouldCreatePath() { + String path = repository.createFilePath(UploadedFilesReferenceTestFactory.create()); + + assertThat(path).isEqualTo( + UploadedFilesReferenceTestFactory.VORGANG_ID + "/" + + CallContextTestFactory.CLIENT + "/" + + UploadedFilesReferenceTestFactory.NAME + "/" + + OzgCloudFileTestFactory.NAME); + } + } + +} diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgFileTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileTestFactory.java similarity index 63% rename from vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgFileTestFactory.java rename to vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileTestFactory.java index bfa02e7e244eba5a1465c4cf0b9193c097e5c9e3..124cfe2a44122a9d576477520e727226993ea5cb 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgFileTestFactory.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileTestFactory.java @@ -23,34 +23,47 @@ */ package de.ozgcloud.vorgang.files; -import java.io.ByteArrayInputStream; -import java.io.InputStream; import java.util.Random; +import java.util.UUID; +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.vorgang.callcontext.CallContextTestFactory; +import de.ozgcloud.vorgang.callcontext.UserTestFactory; import de.ozgcloud.vorgang.vorgang.IncomingFileTestFactory; import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; -public class OzgFileTestFactory { +public class OzgCloudFileTestFactory { - public static final FileId ID = FileId.createNew(); - public static final String NAME = "Filename1.pdf"; + public static final String ID_STR = UUID.randomUUID().toString(); + public static final FileId ID = FileId.from(ID_STR); + public static final long VERSION = new Random().nextLong(); + public static final String NAME = LoremIpsum.getInstance().getWords(1); public static final String CONTENT_TYPE = IncomingFileTestFactory.CONTENT_TYPE_STR; + public static final String CONTENT_ID = UUID.randomUUID().toString(); + public static final String CLIENT = CallContextTestFactory.CLIENT; + public static final String FIELD_NAME = LoremIpsum.getInstance().getWords(1); + public static final String CREATED_BY = UserTestFactory.ID; public static final byte[] CONTENT = "juhu, ein bild".getBytes(); public static final long SIZE = CONTENT.length; - public static final InputStream CONTENT_STREAM = new ByteArrayInputStream(CONTENT); - public static OzgFile create() { + public static OzgCloudFile create() { return createBuilder().build(); } - public static OzgFile.OzgFileBuilder createBuilder() { - return OzgFile.builder() - .id(ID) + public static OzgCloudFile.OzgCloudFileBuilder createBuilder() { + return OzgCloudFile.builder() + .id(ID_STR) + .version(VERSION) .vorgangId(VorgangTestFactory.ID) .name(NAME) .contentType(CONTENT_TYPE) - .size(SIZE); + .size(SIZE) + .contentId(CONTENT_ID) + .client(CLIENT) + .createdBy(CREATED_BY) + .fieldName(FIELD_NAME); } public static byte[] createContentInMiB(int sizeInMiB) { diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileTestUtil.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileTestUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..e09d825686f62ad82ce881f3f2ea44b512cf1d70 --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/OzgCloudFileTestUtil.java @@ -0,0 +1,69 @@ +/* + * 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.vorgang.files; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.gridfs.GridFsTemplate; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class OzgCloudFileTestUtil { + + private final MongoOperations mongoOperations; + private final GridFsTemplate gridFsTemplate; + + public OzgCloudFile saveOzgCloudFile() { + return saveOzgCloudFile(OzgCloudFileTestFactory.CONTENT); + } + + public OzgCloudFile saveOzgCloudFile(byte[] content) { + var contentId = storeFileContent(content); + return mongoOperations.save(OzgCloudFileTestFactory.createBuilder().id(null).version(0).contentId(contentId).build()); + } + + public String storeFileContent(byte[] content) { + return gridFsTemplate.store(new ByteArrayInputStream(content), UploadedFilesReferenceTestFactory.getGridFsFileName()).toString(); + } + + public InputStream getFileContentByFileName() { + return gridFsTemplate.getResource(UploadedFilesReferenceTestFactory.getGridFsFileName()).getContent(); + } + + public InputStream getFileContent(String contentId) { + var gridFSFile = gridFsTemplate.findOne(Query.query(Criteria.where(OzgCloudFileRepository.GRID_FS_FIELD_ID).is(contentId))); + return gridFsTemplate.getResource(gridFSFile).getContent(); + } + + public void dropFileCollections() { + mongoOperations.dropCollection(OzgCloudFile.class); + mongoOperations.dropCollection("fs.files"); + mongoOperations.dropCollection("fs.chunks"); + } +} diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/UploadServerStreamObserverTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/UploadServerStreamObserverTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a39c5a120d0f07b363ae2e99489ae326aae8d5ce --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/UploadServerStreamObserverTest.java @@ -0,0 +1,468 @@ +/* + * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.files; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.io.InputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +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.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mock; +import org.mockito.Spy; +import org.springframework.test.util.ReflectionTestUtils; + +import de.ozgcloud.common.errorhandling.TechnicalException; +import lombok.SneakyThrows; + +class UploadServerStreamObserverTest { + + private static final String FIELD_CONTENT_ID_FUTURE = "contentIdFuture"; + private static final String FIELD_FILE_METADATA = "fileMetadata"; + private static final String FIELD_PIPED_OUTPUT = "pipedOutput"; + + @Mock + private Consumer<UploadTestRequest> requestVerifier; + @Mock + private Predicate<UploadTestRequest> hasMetadata; + @Mock + private BiFunction<MetadataTest, Optional<String>, FileId> metadataUploader; + @Mock + private BiFunction<MetadataTest, InputStream, CompletableFuture<String>> fileContentUploader; + @Mock + private Function<UploadTestRequest, byte[]> getFileContent; + @Mock + private Function<UploadTestRequest, MetadataTest> metadataMapper; + @Mock + private Consumer<FileId> responseConsumer; + @Mock + private Consumer<String> cleanupFileContent; + @Mock + private UploadTestRequest fileUploadRequest; + + private UploadServerStreamObserver<MetadataTest, UploadTestRequest> uploadStreamObserver; + + @BeforeEach + void init() { + // Ich habe es anders nicht hinbekommen den Observer zu initialisieren. + // Wenn @InjectMocks verwendet wird, schafft mockito nicht die Mocks richtig zu initialisieren und \ + // wenn TestOnCompleted.shouldCallResponseConsumer gestartet wird, wird metadataVerifier als responseConsumer gesetzt. + // Und in TestStoreMetadata#shouldCallMetadataVerifier passiert es umgekehrt. + // In Debug-Modus funktioniert alles richtig. + uploadStreamObserver = spy(UploadServerStreamObserver.<MetadataTest, UploadTestRequest>builder() + .requestVerifier(requestVerifier) + .hasMetadata(hasMetadata) + .metadataUploader(metadataUploader) + .fileContentUploader(fileContentUploader) + .getFileContent(getFileContent) + .metadataMapper(metadataMapper) + .responseConsumer(responseConsumer) + .cleanupFileContent(cleanupFileContent) + .build()); + } + + private interface UploadTestRequest { + } + + private interface MetadataTest { + } + + @Nested + class TestOnNext { + + @Nested + class TestWithMetadata { + + @BeforeEach + void init() { + when(hasMetadata.test(any())).thenReturn(true); + doNothing().when(uploadStreamObserver).setMetadata(any()); + } + + @Test + void shouldCallSetMetadata() { + onNext(); + + verify(uploadStreamObserver).setMetadata(fileUploadRequest); + } + + @Test + void shouldNotCallStoreFileContent() { + onNext(); + + verify(uploadStreamObserver, never()).storeFileContent(any()); + } + } + + @Nested + class TestWithFileContent { + + private final CompletableFuture<String> contentIdFuture = CompletableFuture.completedFuture(OzgCloudFileTestFactory.CONTENT_ID); + + @BeforeEach + void init() { + when(hasMetadata.test(any())).thenReturn(false); + doNothing().when(uploadStreamObserver).storeFileContent(any()); + doReturn(contentIdFuture).when(uploadStreamObserver).initContentUpload(); + } + + @Test + void shouldCallStoreFileContent() { + onNext(); + + verify(uploadStreamObserver).storeFileContent(fileUploadRequest); + } + + @Test + void shouldNotCallStoreMetadata() { + onNext(); + + verify(uploadStreamObserver, never()).setMetadata(any()); + } + + @Test + void shouldCallInitContentUploadOnce() { + onNext(); + onNext(); + + verify(uploadStreamObserver).initContentUpload(); + } + + @Test + void shouldSetContentIdFuture() { + onNext(); + + assertThat(uploadStreamObserver).extracting(FIELD_CONTENT_ID_FUTURE, as(COMPLETABLE_FUTURE)).isCompletedWithValue(OzgCloudFileTestFactory.CONTENT_ID); + } + } + + private void onNext() { + uploadStreamObserver.onNext(fileUploadRequest); + } + } + + @Nested + class TestInitContentUpload { + + private final CompletableFuture<String> contentIdFuture = CompletableFuture.completedFuture(OzgCloudFileTestFactory.CONTENT_ID); + + @Mock + private PipedInputStream pipedInputStream; + @Mock + private MetadataTest metadata; + + @BeforeEach + void init() { + when(fileContentUploader.apply(any(), any())).thenReturn(contentIdFuture); + doReturn(pipedInputStream).when(uploadStreamObserver).initPipedStreams(); + ReflectionTestUtils.setField(uploadStreamObserver, FIELD_FILE_METADATA, new AtomicReference<>(metadata)); + } + + @Test + void shouldCallInitPipedStreams() { + initContentUpload(); + + verify(uploadStreamObserver).initPipedStreams(); + } + + @Test + void shouldCallFileContentUploader() { + initContentUpload(); + + verify(fileContentUploader).apply(metadata, pipedInputStream); + } + + private CompletableFuture<String> initContentUpload() { + return uploadStreamObserver.initContentUpload(); + } + } + + @Nested + class TestSetMetadata { + + @Mock + private PipedInputStream pipedInputStream; + @Mock + private CompletableFuture<FileId> fileIdFuture; + @Mock + private MetadataTest metadata; + + @BeforeEach + void init() { + when(metadataMapper.apply(any())).thenReturn(metadata); + } + + @Test + void shouldCallMetadataVerifier() { + setMetadata(); + + verify(requestVerifier).accept(fileUploadRequest); + } + + @Test + void shouldCallMetadataMapper() { + setMetadata(); + + verify(metadataMapper).apply(fileUploadRequest); + } + + @Test + void shouldCallSetFileMetadata() { + setMetadata(); + + assertThat(uploadStreamObserver).extracting(FIELD_FILE_METADATA, as(ATOMIC_REFERENCE)).hasValue(metadata); + } + + @Test + void shouldThrowExceptionIfMetadataAlreadyStored() { + setMetadata(); + + assertThrows(OzgCloudFileUploadException.class, this::setMetadata); + assertThrows(OzgCloudFileUploadException.class, this::setMetadata); + } + + private void setMetadata() { + uploadStreamObserver.setMetadata(fileUploadRequest); + } + } + + @Nested + class TestStoreFileContent { + + @Nested + class TestStore { + + @Mock + private PipedOutputStream pipedOutputStream; + @Mock + private MetadataTest metadata; + + @BeforeEach + void init() { + ReflectionTestUtils.setField(uploadStreamObserver, FIELD_PIPED_OUTPUT, pipedOutputStream); + ReflectionTestUtils.setField(uploadStreamObserver, FIELD_FILE_METADATA, new AtomicReference<>(metadata)); + } + + @Test + void shouldCallGetFileContent() { + storeFileContent(); + + verify(getFileContent).apply(fileUploadRequest); + } + + @SneakyThrows + @Test + void shouldWriteFileContent() { + byte[] fileContent = new byte[] { 1, 2, 3 }; + doReturn(fileContent).when(getFileContent).apply(any()); + + storeFileContent(); + + verify(pipedOutputStream).write(fileContent); + } + } + + @Nested + class TestWithException { + + @Test + void shouldThrowExceptionIfNoMetadata() { + assertThrows(OzgCloudFileUploadException.class, TestStoreFileContent.this::storeFileContent); + } + } + + private void storeFileContent() { + uploadStreamObserver.storeFileContent(fileUploadRequest); + } + } + + @Nested + class TestOnError { + + @Mock + private Throwable throwable; + @Mock + private MetadataTest metadata; + + @BeforeEach + void init() { + ReflectionTestUtils.setField(uploadStreamObserver, FIELD_FILE_METADATA, new AtomicReference<>(metadata)); + } + + @Test + void shouldCallCloseOutputPipe() { + uploadStreamObserver.onError(throwable); + + verify(uploadStreamObserver).closeOutputPipe(); + } + + @Test + void shouldCloseInputPipe() { + uploadStreamObserver.onError(throwable); + + verify(uploadStreamObserver).closeInputPipe(); + } + } + + @Nested + class TestOnCompleted { + + @Mock + private MetadataTest metadata; + + @BeforeEach + void init() { + ReflectionTestUtils.setField(uploadStreamObserver, FIELD_FILE_METADATA, new AtomicReference<>(metadata)); + doReturn(Optional.of(OzgCloudFileTestFactory.CONTENT_ID)).when(uploadStreamObserver).getContentId(); + } + + @Test + void shouldCallGetContentId() { + uploadStreamObserver.onCompleted(); + + verify(uploadStreamObserver).getContentId(); + } + + @Test + void shouldCallMetadataUploader() { + uploadStreamObserver.onCompleted(); + + verify(metadataUploader).apply(metadata, Optional.of(OzgCloudFileTestFactory.CONTENT_ID)); + } + + @Test + void shouldCallResponseConsumer() { + when(metadataUploader.apply(any(), any())).thenReturn(OzgCloudFileTestFactory.ID); + + uploadStreamObserver.onCompleted(); + + verify(responseConsumer).accept(OzgCloudFileTestFactory.ID); + } + + @Test + void shouldThrowException() { + when(metadataUploader.apply(any(), any())).thenThrow(new RuntimeException()); + + assertThrows(TechnicalException.class, uploadStreamObserver::onCompleted); + } + + @Test + void shouldCallCleanupFileContent() { + when(metadataUploader.apply(any(), any())).thenThrow(new RuntimeException()); + + try { + uploadStreamObserver.onCompleted(); + } catch (TechnicalException e) { + // expected + } + + verify(cleanupFileContent).accept(OzgCloudFileTestFactory.CONTENT_ID); + } + + @Test + void shouldCallCloseOutputPipe() { + uploadStreamObserver.onCompleted(); + + verify(uploadStreamObserver).closeOutputPipe(); + } + + } + + @Nested + class TestGetContentId { + + @Nested + class TestWithContentId { + + @Spy + private CompletableFuture<String> contentIdFuture = CompletableFuture.completedFuture(OzgCloudFileTestFactory.CONTENT_ID); + + @BeforeEach + @SneakyThrows + void init() { + ReflectionTestUtils.setField(uploadStreamObserver, FIELD_CONTENT_ID_FUTURE, contentIdFuture); + } + + @SneakyThrows + @Test + void shouldCallGetContentIdFuture() { + uploadStreamObserver.getContentId(); + + verify(contentIdFuture).get(UploadServerStreamObserver.TIMEOUT_MINUTES, TimeUnit.MINUTES); + } + + @Test + void shouldCallCloseInputPipe() { + uploadStreamObserver.getContentId(); + + verify(uploadStreamObserver).closeInputPipe(); + } + + @Test + void shouldReturnContentId() { + var result = uploadStreamObserver.getContentId(); + + assertThat(result).hasValue(OzgCloudFileTestFactory.CONTENT_ID); + } + + @SneakyThrows + @DisplayName("should throw exception and close input pipe when") + @ParameterizedTest(name = "{0} thrown") + @ValueSource(classes = { ExecutionException.class, TimeoutException.class, InterruptedException.class }) + void shouldThrowException(Class<? extends Exception> exceptionClass) { + doThrow(exceptionClass).when(contentIdFuture).get(anyLong(), any()); + + assertThrows(TechnicalException.class, uploadStreamObserver::getContentId); + verify(uploadStreamObserver).closeInputPipe(); + } + } + + @Test + void shouldReturnWhenNoContentId() { + var result = uploadStreamObserver.getContentId(); + + assertThat(result).isEmpty(); + } + + } +} diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/UploadStreamObserverTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/UploadStreamObserverTest.java deleted file mode 100644 index 3ea245760090dff1c7af20282f083503f6114748..0000000000000000000000000000000000000000 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/UploadStreamObserverTest.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -package de.ozgcloud.vorgang.files; - -import static org.assertj.core.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -import java.io.IOException; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; - -import com.google.protobuf.ByteString; - -import de.ozgcloud.common.errorhandling.TechnicalException; -import de.ozgcloud.vorgang.common.security.PolicyService; -import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileRequest; -import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileResponse; -import de.ozgcloud.vorgang.vorgang.IncomingFileTestFactory; -import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; -import io.grpc.stub.StreamObserver; - -class UploadStreamObserverTest { - - @Mock - private StreamObserver<GrpcUploadBinaryFileResponse> responseObserver; - @Mock - private FileService service; - @Mock - private PolicyService policyService; - - private GrpcUploadBinaryFileRequest fileUploadRequest; - private UploadStreamObserver uploadStreamObserver; - - @Nested - class TestSteamObserver { - - private PipedInputStream pipedInputStream = new PipedInputStream(); - private PipedOutputStream pipedOutputStream = new PipedOutputStream(); - - @BeforeEach - void init() { - uploadStreamObserver = new UploadStreamObserver(responseObserver, service, policyService); - fileUploadRequest = GrpcUploadBinaryFileRequestTestFactory.createBuilder() - .setFileContent(ByteString.copyFrom(IncomingFileTestFactory.CONTENT)).build(); - } - - @Nested - class TestInitializing { - - @Test - void shouldConnectPipedStreams() { - assertDoesNotThrow(() -> uploadStreamObserver.connectInputStreamToOutputStream(pipedInputStream, pipedOutputStream)); - } - - @Test - void shouldWriteToPipedOutputStream() { - uploadStreamObserver.connectInputStreamToOutputStream(pipedInputStream, pipedOutputStream); - - assertDoesNotThrow(() -> pipedOutputStream.write(7574557)); - } - - @Test - void shouldInitPipedStreams() throws Exception { - uploadStreamObserver.initPipedStreams(); - - PipedOutputStream pipedOutputStream = uploadStreamObserver.getPipedOutput(); - uploadStreamObserver.connectInputStreamToOutputStream(uploadStreamObserver.getPipedInput(), pipedOutputStream); - - assertDoesNotThrow(() -> pipedOutputStream.write(7574557)); - } - - @Test - void shouldWriteFileContentToPipedOutputError() { - uploadStreamObserver.initPipedStreams(); - - uploadStreamObserver.storeFileContent(fileUploadRequest, uploadStreamObserver.getPipedOutput()); - - assertThatThrownBy(() -> uploadStreamObserver.getPipedInput().read()).isInstanceOf(IOException.class); - } - - @Test - void shouldWriteFileContentToPipedOutput() throws IOException, InterruptedException, ExecutionException, TimeoutException { - initPipedStreams(); - - CompletableFuture<Integer> resFuture = writeFileContent(); - - assertThat(resFuture.get(30, TimeUnit.SECONDS) > 0).isTrue(); - - } - - private void initPipedStreams() { - uploadStreamObserver.initPipedStreams(); - uploadStreamObserver.connectInputStreamToOutputStream(uploadStreamObserver.getPipedInput(), uploadStreamObserver.getPipedOutput()); - } - - private CompletableFuture<Integer> writeFileContent() { - uploadStreamObserver.storeFileContent(fileUploadRequest, uploadStreamObserver.getPipedOutput()); - - CompletableFuture<Integer> resFuture = CompletableFuture.supplyAsync(() -> { - try { - byte[] buffer = new byte[IncomingFileTestFactory.CONTENT.length]; - return uploadStreamObserver.getPipedInput().read(buffer); - } catch (IOException e) { - return -1; - } - }); - return resFuture; - } - - @Test - void shouldHandleException() { - final IOException iOException = new IOException("test"); - assertThrows(TechnicalException.class, () -> uploadStreamObserver.handleException(iOException)); - - verify(responseObserver).onNext(any()); - } - } - - @Nested - class TestStoreMetaData { - - @BeforeEach - void init() { - when(service.uploadFileStreamAsync(any(), any(), any(), any())).thenReturn(CompletableFuture.completedFuture(FileId.createNew())); - } - - @Test - void shouldInitPipedStreamsAndStoreMetadata() throws TimeoutException, InterruptedException, ExecutionException { - var fileIdFuture = uploadStreamObserver.storeMetaData(fileUploadRequest); - - assertThat(fileIdFuture.get(30, TimeUnit.SECONDS)).isNotNull(); - } - - @Test - void shouldStoreMetadata() throws InterruptedException, ExecutionException, TimeoutException { - var fileIdFuture = uploadStreamObserver.storeMetaData(fileUploadRequest); - - assertThat(fileIdFuture.get(30, TimeUnit.SECONDS)).isNotNull(); - } - - @Test - void shouldStoreFileMetadata() throws InterruptedException, ExecutionException, TimeoutException { - uploadStreamObserver.initPipedStreams(); - uploadStreamObserver.storeFileMetaData(fileUploadRequest, uploadStreamObserver.getPipedInput(), - uploadStreamObserver.getPipedOutput()); - - verify(service).uploadFileStreamAsync(any(), any(), any(), any()); - } - } - - @Nested - class TestBuildUploadFilesReferences { - - @Test - void shouldContainsVorgangId() { - var uploadFilesReference = buildUploadFilesReferences(); - - assertThat(uploadFilesReference.getVorgangId()).isEqualTo(VorgangTestFactory.ID); - } - - @Test - void shouldContainsName() { - var uploadFilesReference = buildUploadFilesReferences(); - - assertThat(uploadFilesReference.getName()).isEqualTo(UploadedFilesReferenceTestFactory.NAME); - } - - @Test - void shouldContainsClient() { - var uploadFilesReference = buildUploadFilesReferences(); - - assertThat(uploadFilesReference.getClient()).isEqualTo(GrpcBinaryFileService.CLIENT); - } - - private UploadedFilesReference buildUploadFilesReferences() { - return uploadStreamObserver.buildUploadFilesReferences(GrpcUploadBinaryFileRequestTestFactory.create()); - } - } - - @Nested - class TestBuildOzgFile { - - @Test - void shouldContainsName() { - var ozgFile = buildOzgFile(); - - assertThat(ozgFile.getName()).isEqualTo(OzgFileTestFactory.NAME); - } - - @Test - void shouldContainsSize() { - var ozgFile = buildOzgFile(); - - assertThat(ozgFile.getSize()).isEqualTo(OzgFileTestFactory.SIZE); - } - - @Test - void shouldContainsContentType() { - var ozgFile = buildOzgFile(); - - assertThat(ozgFile.getContentType()).isEqualTo(OzgFileTestFactory.CONTENT_TYPE); - } - - private OzgFile buildOzgFile() { - return uploadStreamObserver.buildOzgFile(GrpcUploadBinaryFileRequestTestFactory.create()); - } - } - } -} diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/UploadedFilesReferenceTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/UploadedFilesReferenceTestFactory.java index efbc02f46e98b0c5a4c5135fca1636c2326232de..0cb77e1b61fef08713d95aace0e0fd86fce88a6d 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/UploadedFilesReferenceTestFactory.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/files/UploadedFilesReferenceTestFactory.java @@ -29,7 +29,9 @@ import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; public class UploadedFilesReferenceTestFactory { public static final String VORGANG_ID = VorgangTestFactory.ID; - public static final String NAME = "files"; + public static final String NAME = OzgCloudFileTestFactory.FIELD_NAME; + public static final String FILE_NAME = OzgCloudFileTestFactory.NAME; + public static final String CLIENT = CallContextTestFactory.CLIENT; public static UploadedFilesReference create() { return createBuilder().build(); @@ -38,7 +40,12 @@ public class UploadedFilesReferenceTestFactory { public static UploadedFilesReference.UploadedFilesReferenceBuilder createBuilder() { return UploadedFilesReference.builder() .vorgangId(VORGANG_ID) - .client(CallContextTestFactory.CLIENT) + .client(CLIENT) + .fileName(FILE_NAME) .name(NAME); } + + public static String getGridFsFileName() { + return "%s/%s/%s/%s".formatted(VORGANG_ID, CallContextTestFactory.CLIENT, NAME, OzgCloudFileTestFactory.NAME); + } } \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/IncomingFileTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/IncomingFileTestFactory.java index 9067f0456d2e2a647fb8073fde4a21504e687bd6..58e4b93245d3bfacb920d8e060c7ecef87ef53f3 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/IncomingFileTestFactory.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/IncomingFileTestFactory.java @@ -72,7 +72,7 @@ public class IncomingFileTestFactory { return new ByteArrayInputStream(getFilledChunk(fileSize)); } - static byte[] getFilledChunk(int size) { + public static byte[] getFilledChunk(int size) { Random r = new Random(); byte[] chunk = new byte[size]; for (int i = 0; i < chunk.length; i++) { diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardVorgangITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardVorgangITCase.java index 6277d91558b5bd74aa921eebd83394f06802152b..568fcce86d175b22887017c8253bcb6b07f9ae43 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardVorgangITCase.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardVorgangITCase.java @@ -28,11 +28,8 @@ import static org.awaitility.Awaitility.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import org.bson.types.ObjectId; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -43,7 +40,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.gridfs.GridFsTemplate; import org.springframework.security.test.context.support.WithMockUser; import de.ozgcloud.command.Command; @@ -56,9 +52,10 @@ import de.ozgcloud.nachrichten.email.MailService; import de.ozgcloud.vorgang.command.GrpcCommandService; import de.ozgcloud.vorgang.command.GrpcCreateCommandRequestTestFactory; import de.ozgcloud.vorgang.command.PersistedCommand; +import de.ozgcloud.vorgang.files.OzgCloudFileTestFactory; +import de.ozgcloud.vorgang.files.OzgCloudFileTestUtil; import de.ozgcloud.vorgang.files.FileId; import de.ozgcloud.vorgang.files.GridFsTestFactory; -import de.ozgcloud.vorgang.files.OzgFileTestFactory; import de.ozgcloud.vorgang.grpc.command.GrpcCommandResponse; import de.ozgcloud.vorgang.grpc.command.GrpcCreateCommandRequest; import de.ozgcloud.vorgang.grpc.command.GrpcRedirectRequest; @@ -85,7 +82,7 @@ class ForwardVorgangITCase { @Autowired private MongoOperations mongoOperations; @Autowired - private GridFsTemplate gridFsTemplate; + private OzgCloudFileTestUtil ozgCloudFileTestUtil; @Mock private StreamObserver<GrpcCommandResponse> responseObserver; @@ -93,7 +90,7 @@ class ForwardVorgangITCase { private ArgumentCaptor<GrpcCommandResponse> responseCapture; @BeforeEach - void init() throws InterruptedException, ExecutionException, TimeoutException { + void init() { cleanDatabase(); prepareVorgang(); } @@ -104,7 +101,7 @@ class ForwardVorgangITCase { mongoOperations.dropCollection(GridFsTestFactory.COLLECTION_NAME); } - private void prepareVorgang() throws InterruptedException, ExecutionException, TimeoutException { + private void prepareVorgang() { var attachment = IncomingFileGroupTestFactory.createBuilder().clearFiles().file( IncomingFileTestFactory.createBuilder().id(storeNewFile()).build()).build(); var representation = IncomingFileTestFactory.createBuilder().id(storeNewFile()).build(); @@ -120,9 +117,8 @@ class ForwardVorgangITCase { } private FileId storeNewFile() { - var fileIdAttachmentId = new ObjectId(); - gridFsTemplate.store(GridFsTestFactory.createUploadBuilder(OzgFileTestFactory.CONTENT).id(fileIdAttachmentId).build()); - return FileId.from(fileIdAttachmentId.toHexString()); + var ozgCloudFile = ozgCloudFileTestUtil.saveOzgCloudFile(OzgCloudFileTestFactory.CONTENT); + return FileId.from(ozgCloudFile.getId()); } @Nested