diff --git a/Jenkinsfile b/Jenkinsfile index 81a74c97531aa991385a034402a13fbbb38786b6..c8239e400dc3ecbd0d35fe9332489a4d4a04d914 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -44,6 +44,27 @@ pipeline { } } } + + stage('Set Version') { + when { + not { + anyOf { + branch 'master' + branch 'release' + } + } + } + steps { + script { + FAILED_STAGE=env.STAGE_NAME + JAR_TAG = getPomVersion('pom.xml').replace("SNAPSHOT", "${env.BRANCH_NAME}-SNAPSHOT") + } + configFileProvider([configFile(fileId: 'maven-settings', variable: 'MAVEN_SETTINGS')]) { + sh "mvn -s $MAVEN_SETTINGS versions:set -DnewVersion=${JAR_TAG} -DprocessAllModules=true" + + } + } + } stage('Build VorgangManager') { steps { @@ -58,18 +79,13 @@ pipeline { } stage('Deploy to Nexus'){ - when { - anyOf { - branch 'master' - branch 'release' - } - } steps { script { FAILED_STAGE = env.STAGE_NAME } configFileProvider([configFile(fileId: 'maven-settings', variable: 'MAVEN_SETTINGS')]) { sh 'mvn --no-transfer-progress -s $MAVEN_SETTINGS -DskipTests deploy -Dmaven.wagon.http.retryHandler.count=3' + sh "mvn -s $MAVEN_SETTINGS versions:revert" } } } diff --git a/bescheid-manager/pom.xml b/bescheid-manager/pom.xml index 29c0c07efd75e9fb1f25d046b4ffdf824032bf9a..27acb0c4ffd60cdf9acd3a905de168d91ac7417e 100644 --- a/bescheid-manager/pom.xml +++ b/bescheid-manager/pom.xml @@ -5,19 +5,19 @@ <parent> <groupId>de.ozgcloud.common</groupId> <artifactId>ozgcloud-common-parent</artifactId> - <version>4.2.0</version> + <version>4.3.0</version> <relativePath /> </parent> <groupId>de.ozgcloud.bescheid</groupId> <artifactId>bescheid-manager</artifactId> <name>OZG-Cloud Bescheid Manager</name> - <version>1.13.0-SNAPSHOT</version> + <version>1.15.0-SNAPSHOT</version> <properties> - <vorgang-manager.version>2.9.0-SNAPSHOT</vorgang-manager.version> - <nachrichten-manager.version>2.9.0-SNAPSHOT</nachrichten-manager.version> - <api-lib.version>0.10.0-SNAPSHOT</api-lib.version> + <vorgang-manager.version>2.9.0</vorgang-manager.version> + <nachrichten-manager.version>2.9.0</nachrichten-manager.version> + <api-lib.version>0.10.0</api-lib.version> </properties> <dependencies> diff --git a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/BescheidEventListener.java b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/BescheidEventListener.java index 2016ea933f0c61b98a22b8ef0e04492e0df6af6e..49fbce9beef117e017788ae682323792bdb7478e 100644 --- a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/BescheidEventListener.java +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/BescheidEventListener.java @@ -30,6 +30,7 @@ import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; +import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.event.EventListener; @@ -48,6 +49,7 @@ import de.ozgcloud.command.CommandCreatedEvent; import de.ozgcloud.command.CommandFailedEvent; import de.ozgcloud.common.errorhandling.TechnicalException; import de.ozgcloud.document.BescheidDocumentCreatedEvent; +import de.ozgcloud.document.Document; import de.ozgcloud.document.DocumentService; import lombok.NonNull; import lombok.RequiredArgsConstructor; @@ -79,10 +81,9 @@ class BescheidEventListener { public static final Predicate<Command> IS_SEND_BESCHEID_COMMAND = command -> SEND_BESCHEID_ORDER.equals(command.getOrder()); private static final String IS_SEND_BESCHEID = "{T(de.ozgcloud.bescheid.BescheidEventListener).IS_SEND_BESCHEID_COMMAND.test(event.getSource())}"; - private static final String TEMPLATE_GROUP_KIEL = "Kiel"; - static final String VORGANG_ID_BODYKEY = "vorgangId"; - static final String BESCHEID_VOM_BODYKEY = "bescheidVom"; - static final String GENEHMIGT_BODYKEY = "genehmigt"; + static final String VORGANG_ID_BODY_KEY = "vorgangId"; + static final String BESCHEID_VOM_BODY_KEY = "bescheidVom"; + static final String GENEHMIGT_BODY_KEY = "genehmigt"; private static final String LOG_MESSAGE_TEMPLATE = "{}. Command failed."; private static final String ERROR_MESSAGE_TEMPLATE = "Error on executing %s Command."; @@ -130,10 +131,16 @@ class BescheidEventListener { } void doUpdateBescheid(Command command) { - attachedItemService.updateBescheidDraft(command); + attachedItemService.updateBescheidDraft(command, getDocument(command)); eventPublisher.publishEvent(new BescheidUpdatedEvent(command)); } + Optional<Document> getDocument(Command command) { + return Optional.ofNullable(MapUtils.getString(command.getBodyObject(), Bescheid.FIELD_BESCHEID_DOCUMENT)) + .map(StringUtils::trimToNull) + .map(documentService::getDocument); + } + @EventListener(condition = IS_CREATE_BESCHEID_DOCUMENT) public void onCreatedBescheidDocument(CommandCreatedEvent event) { runWithSecurityContext(event.getSource(), this::doCreateBescheidDocument); @@ -174,10 +181,9 @@ class BescheidEventListener { var builder = BescheidRequest.builder(); builder.vorgangId(VorgangId.from(command.getVorgangId())); - Optional.ofNullable(eventBody.get(BESCHEID_VOM_BODYKEY)) - .map(String.class::cast).map(LocalDate::parse).ifPresent(builder::bescheidVom); - Optional.ofNullable(eventBody.get(GENEHMIGT_BODYKEY)) - .map(Object::toString).map(Boolean::valueOf) + Optional.ofNullable(MapUtils.getString(eventBody, BESCHEID_VOM_BODY_KEY)) + .map(LocalDate::parse).ifPresent(builder::bescheidVom); + Optional.ofNullable(MapUtils.getBoolean(eventBody, GENEHMIGT_BODY_KEY)) .ifPresentOrElse(builder::genehmigt, () -> builder.genehmigt(true)); builder.createFor(userProfileService.getUserProfile()); diff --git a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/BescheidManagerConfiguration.java b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/BescheidManagerConfiguration.java index 4cf07e25e4cca9e218a57be1ae82d837639c58d9..824f5ad0608057bec16dd5ef1d701fe481d9ff71 100644 --- a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/BescheidManagerConfiguration.java +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/BescheidManagerConfiguration.java @@ -36,10 +36,12 @@ import net.devh.boot.grpc.client.inject.GrpcClient; @Configuration public class BescheidManagerConfiguration { + public static final String COMMAND_SERVICE_NAME = "bescheid_OzgCloudCommandService"; + @GrpcClient("command-manager") private CommandServiceGrpc.CommandServiceBlockingStub commandServiceStub; - @Bean("bescheid_OzgCloudCommandService") + @Bean(COMMAND_SERVICE_NAME) OzgCloudCommandService grpcOzgCloudCommandService(CommandMapper commandMapper, BescheidManagerCallContextProvider contextProvider) { return new GrpcOzgCloudCommandService(commandServiceStub, commandMapper, contextProvider, GrpcOzgCloudCommandService.DEFAULT_COMMAND_REQUEST_THRESHOLD_MILLIS); diff --git a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/AttachedItemService.java b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/AttachedItemService.java index 4c25578f1064dd54ad74ddada10e25b612d8a503..180d95031923d482c4f25f3f42f42ba0620b111d 100644 --- a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/AttachedItemService.java +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/AttachedItemService.java @@ -32,11 +32,10 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Optional; -import java.util.function.Predicate; +import java.util.function.Function; import java.util.stream.Collectors; import org.apache.commons.collections.MapUtils; -import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -47,15 +46,18 @@ import de.ozgcloud.apilib.common.command.OzgCloudCommandService; import de.ozgcloud.apilib.common.command.grpc.CommandMapper; import de.ozgcloud.bescheid.Bescheid; import de.ozgcloud.bescheid.BescheidCallContextAttachingInterceptor; +import de.ozgcloud.bescheid.BescheidManagerConfiguration; import de.ozgcloud.bescheid.common.callcontext.CallContextUser; import de.ozgcloud.bescheid.common.callcontext.CurrentUserService; +import de.ozgcloud.bescheid.common.freemarker.TemplateHandler; import de.ozgcloud.bescheid.vorgang.VorgangId; import de.ozgcloud.command.Command; import de.ozgcloud.common.errorhandling.TechnicalException; +import de.ozgcloud.document.Document; import lombok.extern.log4j.Log4j2; -@Service @Log4j2 +@Service public class AttachedItemService { public static final String BESCHEID_ITEM_NAME = "Bescheid"; @@ -64,10 +66,11 @@ public class AttachedItemService { static final String DELETE_ATTACHED_ITEM = "DELETE_ATTACHED_ITEM"; static final String PATCH_ATTACHED_ITEM = "PATCH_ATTACHED_ITEM"; - private static final Predicate<String> notExpectedSendByValue = sendBy -> !ArrayUtils.contains(Bescheid.SendBy.values(), sendBy); + static final String DEFAULT_SUBJECT = "Ihr Bescheid zum Antrag"; + static final String DEFAULT_TEMPLATE_FILE = "bescheid.nachrichtTemplate.txt.ftlh"; + @Qualifier(BescheidManagerConfiguration.COMMAND_SERVICE_NAME) @Autowired - @Qualifier("bescheid_OzgCloudCommandService") private OzgCloudCommandService commandService; @Autowired private VorgangAttachedItemRemoteService remoteService; @@ -75,13 +78,15 @@ public class AttachedItemService { private CommandMapper commandMapper; @Autowired private CurrentUserService currentUserService; + @Autowired + private TemplateHandler templateHandler; public Optional<AttachedItem> findBescheidItem(String vorgangId) { return remoteService.findBescheidDraft(vorgangId); } - public AttachedItem getBescheidItem(String id) { - return remoteService.getBescheid(id); + public AttachedItem getItem(String id) { + return remoteService.getItem(id); } public String createBescheidDraft(Command command) { @@ -90,45 +95,96 @@ public class AttachedItemService { .map(bescheid -> overrideAttachedItem(bescheid, command)).orElseGet(() -> createAttachedItem(command)); } + void validateBescheidData(Map<String, Object> bodyObject) { + validateBescheidField(bodyObject, Bescheid.FIELD_BESCHIEDEN_AM); + validateBescheidField(bodyObject, Bescheid.FIELD_BEWILLIGT); + validateSendBy(getSendBy(bodyObject)); + } + + void validateBescheidField(Map<String, Object> bescheid, String field) { + if (isNull(bescheid.get(field))) { + throw new TechnicalException("Fields '%s' is required for bescheid creation".formatted(field)); + } + } + + void validateSendBy(String sendBy) { + if (isUnknownSendByValue(sendBy)) { + var possibleSendByValues = Arrays.stream(Bescheid.SendBy.values()).map(Bescheid.SendBy::name).collect(Collectors.joining(",")); + var message = String.format("Unexpected value for field '%s': %s. Allowed are: %s", Bescheid.FIELD_SEND_BY, sendBy, possibleSendByValues); + throw new TechnicalException(message); + } + } + + private boolean isUnknownSendByValue(String sendByValue) { + return Arrays.stream(Bescheid.SendBy.values()).noneMatch(sendBy -> sendBy.hasValue(sendByValue)); + } + String overrideAttachedItem(AttachedItem bescheidItem, Command command) { - var bodyObject = buildAttachedItemAsMap(command, buildItemMapWithAllBescheidFields(command)); - var finishedOzgCloudCommand = commandService.createAndWaitUntilDone(buildUpdateAttachedItemCommand(bescheidItem, bodyObject)); + var attachedItem = buildAttachedItemAsMap(command, buildItemMapWithAllBescheidFields(command)); + var finishedOzgCloudCommand = commandService.createAndWaitUntilDone(buildPatchAttachedItemCommand(bescheidItem, attachedItem)); return finishedOzgCloudCommand.getCreatedResource(); } Map<String, Object> buildItemMapWithAllBescheidFields(Command command) { var result = new HashMap<String, Object>(); - result.put(Bescheid.FIELD_BESCHIEDEN_AM, command.getBodyObject().get(Bescheid.FIELD_BESCHIEDEN_AM)); - result.put(Bescheid.FIELD_BEWILLIGT, command.getBodyObject().get(Bescheid.FIELD_BEWILLIGT)); - result.put(Bescheid.FIELD_BESCHEID_DOCUMENT, command.getBodyObject().get(Bescheid.FIELD_BESCHEID_DOCUMENT)); - result.put(Bescheid.FIELD_SEND_BY, command.getBodyObject().get(Bescheid.FIELD_SEND_BY)); - result.put(Bescheid.FIELD_NACHRICHT_TEXT, command.getBodyObject().get(Bescheid.FIELD_NACHRICHT_TEXT)); - result.put(Bescheid.FIELD_NACHRICHT_SUBJECT, command.getBodyObject().get(Bescheid.FIELD_NACHRICHT_SUBJECT)); - result.put(Bescheid.FIELD_ATTACHMENTS, command.getBodyObject().get(Bescheid.FIELD_ATTACHMENTS)); + result.put(Bescheid.FIELD_BESCHIEDEN_AM, MapUtils.getObject(command.getBodyObject(), Bescheid.FIELD_BESCHIEDEN_AM)); + result.put(Bescheid.FIELD_BEWILLIGT, MapUtils.getObject(command.getBodyObject(), Bescheid.FIELD_BEWILLIGT)); + result.put(Bescheid.FIELD_BESCHEID_DOCUMENT, MapUtils.getString(command.getBodyObject(), Bescheid.FIELD_BESCHEID_DOCUMENT)); + result.put(Bescheid.FIELD_SEND_BY, getSendBy(command.getBodyObject())); + result.put(Bescheid.FIELD_NACHRICHT_TEXT, MapUtils.getString(command.getBodyObject(), Bescheid.FIELD_NACHRICHT_TEXT)); + result.put(Bescheid.FIELD_NACHRICHT_SUBJECT, MapUtils.getString(command.getBodyObject(), Bescheid.FIELD_NACHRICHT_SUBJECT)); + result.put(Bescheid.FIELD_ATTACHMENTS, MapUtils.getObject(command.getBodyObject(), Bescheid.FIELD_ATTACHMENTS)); return result; } - public void updateBescheidDraft(Command command) { - var bescheidItem = remoteService.getBescheid(command.getRelationId()); - validateBescheidStatus(bescheidItem); + private String getSendBy(Map<String, Object> body) { + return MapUtils.getString(body, Bescheid.FIELD_SEND_BY); + } + + public void updateBescheidDraft(Command command, Optional<Document> document) { + createCommandAfterValidation(command, bescheid -> buildUpdateBescheidDraftCommand(command, document, bescheid)); + } + + private OzgCloudCommand buildUpdateBescheidDraftCommand(Command command, Optional<Document> document, AttachedItem bescheidItem) { + return buildPatchAttachedItemCommand(bescheidItem, buildAttachedItemAsMap(command, buildBescheidItemAsMap(command, document))); + } + + Map<String, Object> buildBescheidItemAsMap(Command command, Optional<Document> newDocument) { var itemMap = buildItemMapWithExistingBescheidFields(command); - var bodyObject = buildAttachedItemAsMap(command, itemMap); - commandService.createAndWaitUntilDone(buildUpdateAttachedItemCommand(bescheidItem, bodyObject)); + var savedBescheid = getItem(command.getRelationId()); + + getNewNachrichtText(savedBescheid, newDocument).ifPresent(nachrichtText -> itemMap.put(Bescheid.FIELD_NACHRICHT_TEXT, nachrichtText)); + + return itemMap; + } + + Optional<String> getNewNachrichtText(AttachedItem bescheidItem, Optional<Document> newDocument) { + return newDocument + .filter(document -> isDifferentDocument(document, bescheidItem)) + .map(Document::getNachrichtText) + .filter(StringUtils::isNotBlank); } - OzgCloudCommand buildUpdateAttachedItemCommand(AttachedItem bescheidItem, Map<String, Object> bodyObject) { + boolean isDifferentDocument(Document document, AttachedItem bescheidItem) { + return !StringUtils.equals(document.getId(), getBescheidDocumentId(bescheidItem)); + } + + private String getBescheidDocumentId(AttachedItem item) { + return MapUtils.getString(item.getItem(), Bescheid.FIELD_BESCHEID_DOCUMENT); + } + + OzgCloudCommand buildPatchAttachedItemCommand(AttachedItem bescheidItem, Map<String, Object> bodyObject) { return OzgCloudCommand.builder() .vorgangId(commandMapper.toOzgCloudVorgangId(bescheidItem.getVorgangId())) .relationId(commandMapper.mapRelationId(bescheidItem.getId())) .relationVersion(bescheidItem.getVersion()) - .order(UPDATE_ATTACHED_ITEM_ORDER) + .order(PATCH_ATTACHED_ITEM) .bodyObject(bodyObject) .build(); } String createAttachedItem(Command command) { - var finishedOzgCloudCommand = commandService.createAndWaitUntilDone(buildCreateAttachedItemCommand(command)); - return finishedOzgCloudCommand.getCreatedResource(); + return commandService.createAndWaitUntilDone(buildCreateAttachedItemCommand(command)).getCreatedResource(); } OzgCloudCommand buildCreateAttachedItemCommand(Command command) { @@ -136,21 +192,31 @@ public class AttachedItemService { .vorgangId(commandMapper.toOzgCloudVorgangId(command.getVorgangId())) .relationId(commandMapper.mapRelationId(command.getRelationId())) .order(CREATE_ATTACHED_ITEM_ORDER) - .bodyObject(buildAttachedItemAsMap(command, buildItemMapWithExistingBescheidFields(command))).build(); + .bodyObject(buildAttachedItemAsMap(command, buildCreateBescheidItemMap(command))).build(); } - Map<String, Object> buildAttachedItemAsMap(Command command, Map<String, Object> itemmap) { + Map<String, Object> buildAttachedItemAsMap(Command command, Map<String, Object> itemMap) { var result = new HashMap<String, Object>(); result.put(AttachedItem.PROPERTY_VORGANG_ID, command.getVorgangId()); result.put(AttachedItem.PROPERTY_CLIENT, BescheidCallContextAttachingInterceptor.BESCHEID_MANAGER_CLIENT_NAME); result.put(AttachedItem.PROPERTY_ITEM_NAME, BESCHEID_ITEM_NAME); - result.put(AttachedItem.PROPERTY_ITEM, itemmap); + result.put(AttachedItem.PROPERTY_ITEM, itemMap); + return result; + } + + Map<String, Object> buildCreateBescheidItemMap(Command command) { + var result = buildItemMapWithExistingBescheidFields(command); + if (StringUtils.isEmpty(getNachrichtSubject(command))) { + result.put(Bescheid.FIELD_NACHRICHT_SUBJECT, DEFAULT_SUBJECT); + } + if (StringUtils.isEmpty(getNachrichtText(command))) { + result.put(Bescheid.FIELD_NACHRICHT_TEXT, templateHandler.getRawTemplate(DEFAULT_TEMPLATE_FILE)); + } return result; } Map<String, Object> buildItemMapWithExistingBescheidFields(Command command) { - var result = new HashMap<String, Object>(); - result.put(Bescheid.FIELD_STATUS, Bescheid.Status.DRAFT.name()); + var result = buildItemMap(Bescheid.Status.DRAFT); addValueFromMapIfExists(command.getBodyObject(), Bescheid.FIELD_BESCHIEDEN_AM, result); addValueFromMapIfExists(command.getBodyObject(), Bescheid.FIELD_BEWILLIGT, result); addValueFromMapIfExists(command.getBodyObject(), Bescheid.FIELD_BESCHEID_DOCUMENT, result); @@ -161,30 +227,28 @@ public class AttachedItemService { return result; } + private String getNachrichtSubject(Command command) { + return MapUtils.getString(command.getBodyObject(), Bescheid.FIELD_NACHRICHT_SUBJECT); + } + + private String getNachrichtText(Command command) { + return MapUtils.getString(command.getBodyObject(), Bescheid.FIELD_NACHRICHT_TEXT); + } + void addValueFromMapIfExists(Map<String, Object> sourceMap, String key, Map<String, Object> targetMap) { if (sourceMap.containsKey(key)) { targetMap.put(key, sourceMap.get(key)); } } - void validateBescheidData(Map<String, Object> bodyObject) { - if (isNull(bodyObject.get(Bescheid.FIELD_BESCHIEDEN_AM))) { - throw new TechnicalException("Fields '%s' is required for bescheid creation".formatted(Bescheid.FIELD_BESCHIEDEN_AM)); - } - if (isNull(bodyObject.get(Bescheid.FIELD_BEWILLIGT))) { - throw new TechnicalException("Fields '%s' is required for bescheid creation".formatted(Bescheid.FIELD_BEWILLIGT)); - } - Optional.ofNullable(MapUtils.getString(bodyObject, Bescheid.FIELD_SEND_BY)).filter(notExpectedSendByValue) - .ifPresent(sendBy -> - LOG.warn("Unexpected value for field '%s': %s. Allowed are: %s".formatted(Bescheid.FIELD_SEND_BY, sendBy, - Arrays.stream(Bescheid.SendBy.values()).map(Bescheid.SendBy::name).collect(Collectors.joining(",")))) - ); + public void deleteBescheidDraft(Command command) { + createCommandAfterValidation(command, bescheid -> buildDeleteItemCommand(command, bescheid)); } - public void deleteBescheidDraft(Command command) { - var bescheid = remoteService.getBescheid(command.getRelationId()); + private void createCommandAfterValidation(Command command, Function<AttachedItem, OzgCloudCommand> buildCommand) { + var bescheid = remoteService.getItem(command.getRelationId()); validateBescheidStatus(bescheid); - commandService.createAndWaitUntilDone(buildDeleteItemCommand(command, bescheid)); + commandService.createAndWaitUntilDone(buildCommand.apply(bescheid)); } void validateBescheidStatus(AttachedItem bescheid) { @@ -203,13 +267,8 @@ public class AttachedItemService { .build(); } - public AttachedItem getItem(String id) { - return remoteService.getItem(id); - } - public void setBescheidSentStatus(String id, long version) { - var bodyObject = buildBescheidSentStatusBodyObject(id); - commandService.createAndWaitUntilDone(buildPatchBescheidCommand(id, version, bodyObject)); + createPatchCommand(id, version, buildBescheidSentStatusBodyObject(id)); } Map<String, Object> buildBescheidSentStatusBodyObject(String bescheidId) { @@ -226,8 +285,7 @@ public class AttachedItemService { Map<String, Object> buildSentInfoMap() { return Map.of( Bescheid.FIELD_SENT_AT, ZonedDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME), - Bescheid.FIELD_SENT_BY, getUserId() - ); + Bescheid.FIELD_SENT_BY, getUserId()); } String getUserId() { @@ -235,8 +293,11 @@ public class AttachedItemService { } public void revertBescheidSendStatus(String id, long version) { - var bodyObject = buildRevertBescheidSentStatusBodyObject(id); - commandService.createAndWaitUntilDone(buildPatchBescheidCommand(id, version, bodyObject)); + createPatchCommand(id, version, buildRevertBescheidSentStatusBodyObject(id)); + } + + private void createPatchCommand(String id, long version, Map<String, Object> itemMap) { + commandService.createAndWaitUntilDone(buildPatchBescheidCommand(id, version, itemMap)); } Map<String, Object> buildRevertBescheidSentStatusBodyObject(String bescheidId) { diff --git a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/VorgangAttachedItemRemoteService.java b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/VorgangAttachedItemRemoteService.java index ff07e6d16434e02343d1a07605c8351330d640ba..418361772a1446dfe7923822906111ae4b5c46a3 100644 --- a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/VorgangAttachedItemRemoteService.java +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/VorgangAttachedItemRemoteService.java @@ -97,11 +97,6 @@ class VorgangAttachedItemRemoteService { return Bescheid.Status.DRAFT.hasValue(bescheidItem.getItem().get(Bescheid.FIELD_STATUS)); } - public AttachedItem getBescheid(String bescheidId) { - var grpcVorgangAttachedItemResponse = getServiceStub().getById(buildGetByIdRequest(bescheidId)); - return attachedItemMapper.mapFromVorgangAttachedItem(grpcVorgangAttachedItemResponse.getVorgangAttachedItem()); - } - public AttachedItem getItem(String id) { var grpcVorgangAttachedItemResponse = getServiceStub().getById(buildGetByIdRequest(id)); return attachedItemMapper.mapFromVorgangAttachedItem(grpcVorgangAttachedItemResponse.getVorgangAttachedItem()); diff --git a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/common/freemarker/TemplateHandler.java b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/common/freemarker/TemplateHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..8355a9ffca2cf8b9ad91449133240e11745131c5 --- /dev/null +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/common/freemarker/TemplateHandler.java @@ -0,0 +1,43 @@ +package de.ozgcloud.bescheid.common.freemarker; + +import java.io.IOException; +import java.io.StringWriter; + +import org.springframework.stereotype.Component; + +import de.ozgcloud.common.errorhandling.TechnicalException; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class TemplateHandler { + + private final Configuration freemarkerConfig; + + public String getRawTemplate(String templateName) { + return getTemplate(templateName).toString(); + } + + public String fillTemplate(String templateName, Object dataModel) { + try { + var template = getTemplate(templateName); + var stringWriter = new StringWriter(); + template.process(dataModel, stringWriter); + return stringWriter.toString(); + + } catch (IOException | TemplateException e) { + throw new TechnicalException("Error filling template", e); + } + } + + private Template getTemplate(String templateName) { + try { + return freemarkerConfig.getTemplate(templateName); + } catch (IOException e) { + throw new TechnicalException("Error loading mail template", e); + } + } +} diff --git a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/nachricht/NachrichtService.java b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/nachricht/NachrichtService.java index 0f20aad8d7458fbe5a324168462b52cafc224e94..ce261151ddaa6d682521e59cfbb806c21c033656 100644 --- a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/nachricht/NachrichtService.java +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/nachricht/NachrichtService.java @@ -1,7 +1,5 @@ package de.ozgcloud.bescheid.nachricht; -import java.io.IOException; -import java.io.StringWriter; import java.util.List; import java.util.Map; import java.util.Objects; @@ -17,13 +15,11 @@ import de.ozgcloud.apilib.common.command.OzgCloudCommand; import de.ozgcloud.apilib.common.command.OzgCloudCommandService; import de.ozgcloud.apilib.common.command.grpc.CommandMapper; import de.ozgcloud.bescheid.Bescheid; +import de.ozgcloud.bescheid.common.freemarker.TemplateHandler; import de.ozgcloud.bescheid.vorgang.Vorgang; import de.ozgcloud.bescheid.vorgang.Vorgang.PostfachAddress; import de.ozgcloud.common.binaryfile.FileId; import de.ozgcloud.common.errorhandling.TechnicalException; -import freemarker.template.Configuration; -import freemarker.template.Template; -import freemarker.template.TemplateException; import lombok.extern.log4j.Log4j2; @Service @@ -41,14 +37,14 @@ public class NachrichtService { @Autowired private NachrichtRemoteService remoteService; @Autowired - private Configuration freemarkerCfg; - @Autowired @Qualifier("bescheid_OzgCloudCommandService") private OzgCloudCommandService commandService; @Autowired private CommandMapper commandMapper; + @Autowired + private TemplateHandler templateHandler; - static final String SUBJECT = "Ihr Antrag"; + static final String SUBJECT = "Ihr Bescheid zum Antrag"; private static final String TEMPLATE_FILE = "bescheid.nachrichtTemplate.txt.ftlh"; @@ -69,27 +65,7 @@ public class NachrichtService { String buildMessage(Bescheid bescheid) { return bescheid.getNachrichtText() - .orElseGet(() -> fillTemplate(TEMPLATE_FILE, bescheid)); - } - - String fillTemplate(String templateName, Object dataModel) { - try { - Template template = getTemplate(templateName); - StringWriter stringWriter = new StringWriter(); - template.process(dataModel, stringWriter); - return stringWriter.toString(); - - } catch (IOException | TemplateException e) { - throw new TechnicalException("Error filling template", e); - } - } - - Template getTemplate(String templateName) { - try { - return freemarkerCfg.getTemplate(templateName); - } catch (IOException e) { - throw new TechnicalException("Error loading mail template", e); - } + .orElseGet(() -> templateHandler.fillTemplate(TEMPLATE_FILE, bescheid)); } public void sendNachricht(Bescheid bescheid) { @@ -112,8 +88,7 @@ public class NachrichtService { FIELD_SUBJECT, bescheid.getNachrichtSubject().orElse(SUBJECT), FIELD_MAIL_BODY, bescheid.getNachrichtText().orElse(StringUtils.EMPTY), FIELD_ATTACHMENTS, buildAttachments(bescheid), - Vorgang.ServiceKonto.FIELD_POSTFACH_ADDRESS, buildPostfachAddress(bescheid) - ); + Vorgang.ServiceKonto.FIELD_POSTFACH_ADDRESS, buildPostfachAddress(bescheid)); } diff --git a/bescheid-manager/src/main/java/de/ozgcloud/document/DocumentService.java b/bescheid-manager/src/main/java/de/ozgcloud/document/DocumentService.java index 54d398b8c5d55ce78f57da999f09df3326d23d99..a49c8581074b511f05bc5c13b0666933753a457e 100644 --- a/bescheid-manager/src/main/java/de/ozgcloud/document/DocumentService.java +++ b/bescheid-manager/src/main/java/de/ozgcloud/document/DocumentService.java @@ -82,7 +82,7 @@ public class DocumentService { } void validateBescheidItem(String bescheidId) { - var bescheidData = attachedItemService.getBescheidItem(bescheidId).getItem(); + var bescheidData = attachedItemService.getItem(bescheidId).getItem(); var status = MapUtils.getString(bescheidData, Bescheid.FIELD_STATUS); if (Bescheid.Status.DRAFT.not(status)) { throw new TechnicalException("Bescheid is not in draft status"); diff --git a/bescheid-manager/src/main/resources/templates/bescheid.nachrichtTemplate.txt.ftlh b/bescheid-manager/src/main/resources/templates/bescheid.nachrichtTemplate.txt.ftlh index 861bcc67d7f7cffdd9fc2608a6378b8baf23454b..368804ed6ea51f622c32a2e90a21d5f1dd00763f 100644 --- a/bescheid-manager/src/main/resources/templates/bescheid.nachrichtTemplate.txt.ftlh +++ b/bescheid-manager/src/main/resources/templates/bescheid.nachrichtTemplate.txt.ftlh @@ -1,8 +1,7 @@ -Sehr geehrte/r Antragstellende/r, +Sehr geehrte/r Antragsteller/in, -ihr Antrag wurde <#if genehmigt>genehmigt<#else>abgelehnt</#if>. +im Folgenden erhalten Sie Ihren Bescheid. -Sie können innerhalb von vier Wochen Einspruch einlegen. +Mit freundlichen Grüßen -Mit freundlichen Grüßen, Ihre Verwaltung \ No newline at end of file diff --git a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/BescheidEventListenerITCase.java b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/BescheidEventListenerITCase.java index 4f6cf30d7290c65804d6a0e36ac0615b0c338a94..96c5ef5dafcba70a2f79b18ae9d7aa8b24bf8670 100644 --- a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/BescheidEventListenerITCase.java +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/BescheidEventListenerITCase.java @@ -105,7 +105,7 @@ class BescheidEventListenerITCase { void shouldCallService() { publisher.publishEvent(CommandCreatedEventTestFactory.withCommand(command)); - verify(attachedItemService).updateBescheidDraft(any()); + verify(attachedItemService).updateBescheidDraft(any(), any()); } @Test diff --git a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/BescheidEventListenerTest.java b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/BescheidEventListenerTest.java index 1288fbea070e6808012351618bf72a299175d5c1..823d76cd45304b38173621fa521a6e3cbec6820e 100644 --- a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/BescheidEventListenerTest.java +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/BescheidEventListenerTest.java @@ -7,6 +7,7 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; import org.junit.jupiter.api.Assertions; @@ -27,6 +28,7 @@ import de.ozgcloud.bescheid.attacheditem.AttachedItemService; import de.ozgcloud.bescheid.attacheditem.AttachedItemTestFactory; import de.ozgcloud.bescheid.binaryfile.BinaryFileService; import de.ozgcloud.bescheid.common.callcontext.CurrentUserService; +import de.ozgcloud.bescheid.common.freemarker.TemplateHandler; import de.ozgcloud.bescheid.common.user.UserProfile; import de.ozgcloud.bescheid.common.user.UserProfileService; import de.ozgcloud.bescheid.common.user.UserProfileTestFactory; @@ -37,7 +39,9 @@ import de.ozgcloud.command.CommandFailedEvent; import de.ozgcloud.command.CommandTestFactory; import de.ozgcloud.common.errorhandling.TechnicalException; import de.ozgcloud.document.BescheidDocumentCreatedEvent; +import de.ozgcloud.document.Document; import de.ozgcloud.document.DocumentService; +import de.ozgcloud.document.DocumentTestFactory; class BescheidEventListenerTest { @@ -64,6 +68,8 @@ class BescheidEventListenerTest { private CurrentUserService userService; @Mock private UserProfileService userProfileService; + @Mock + private TemplateHandler templateHandler; @Nested class TestOnCreateBescheidCommand { @@ -90,9 +96,9 @@ class BescheidEventListenerTest { private static final Command COMMAND = CommandTestFactory.createBuilder() .bodyObject( - Map.of(VORGANG_ID_BODYKEY, VORGANG_ID.toString(), - BESCHEID_VOM_BODYKEY, BESCHEID_VOM_STRING, - GENEHMIGT_BODYKEY, GENEHMIGT)) + Map.of(VORGANG_ID_BODY_KEY, VORGANG_ID.toString(), + BESCHEID_VOM_BODY_KEY, BESCHEID_VOM_STRING, + GENEHMIGT_BODY_KEY, GENEHMIGT)) .build(); @Captor @@ -177,8 +183,8 @@ class BescheidEventListenerTest { private static final Command COMMAND = CommandTestFactory.createBuilder() .vorgangId(VORGANG_ID.toString()) .bodyObject( - Map.of(BESCHEID_VOM_BODYKEY, BESCHEID_VOM_STRING, - GENEHMIGT_BODYKEY, false)) + Map.of(BESCHEID_VOM_BODY_KEY, BESCHEID_VOM_STRING, + GENEHMIGT_BODY_KEY, false)) .build(); @Test @@ -207,7 +213,7 @@ class BescheidEventListenerTest { Command command = CommandTestFactory.createBuilder() .vorgangId(VORGANG_ID.toString()) .bodyObject( - Map.of(BESCHEID_VOM_BODYKEY, BESCHEID_VOM_STRING)) + Map.of(BESCHEID_VOM_BODY_KEY, BESCHEID_VOM_STRING)) .build(); var request = listener.createRequest(command); @@ -303,12 +309,25 @@ class BescheidEventListenerTest { private ArgumentCaptor<BescheidUpdatedEvent> bescheidUpdatedEventCaptor; private final Command command = CommandTestFactory.createBuilder().build(); + private final Document document = DocumentTestFactory.create(); + + @BeforeEach + void mock() { + doReturn(Optional.of(document)).when(listener).getDocument(command); + } + + @Test + void shouldGetDocument() { + listener.doUpdateBescheid(command); + + verify(listener).getDocument(command); + } @Test void shouldCallUpdateBescheidDraft() { listener.doUpdateBescheid(command); - verify(attachedItemService).updateBescheidDraft(command); + verify(attachedItemService).updateBescheidDraft(command, Optional.of(document)); } @Test @@ -318,7 +337,50 @@ class BescheidEventListenerTest { verify(eventPublisher).publishEvent(bescheidUpdatedEventCaptor.capture()); assertThat(bescheidUpdatedEventCaptor.getValue().getCommand()).isSameAs(command); } + } + + @DisplayName("Get document") + @Nested + class TestGetDocument { + + @DisplayName("if exists") + @Nested + class TestIfExists { + + private final Command command = CommandTestFactory.createBuilder().bodyObject(AttachedItemTestFactory.createBescheidItem()).build(); + + @BeforeEach + void mock() { + when(documentService.getDocument(any())).thenReturn(DocumentTestFactory.create()); + } + + @Test + void shouldCallService() { + listener.getDocument(command); + + verify(documentService).getDocument(AttachedItemTestFactory.BESCHEID_DOCUMENT); + } + + @Test + void shouldReturnValue() { + var document = listener.getDocument(command); + + assertThat(document.get()).usingRecursiveComparison().isEqualTo(DocumentTestFactory.create()); + } + } + + @Test + void shouldReturnEmptyIfNoDocumentExists() { + var document = listener.getDocument(createCommandWithoutDocument()); + + assertThat(document).isEmpty(); + } + private Command createCommandWithoutDocument() { + var bescheidItemMap = AttachedItemTestFactory.createBescheidItem(); + bescheidItemMap.remove(AttachedItemTestFactory.BESCHEID_DOCUMENT); + return CommandTestFactory.createBuilder().bodyObject(bescheidItemMap).build(); + } } @Nested diff --git a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attacheditem/AttachedItemServiceTest.java b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attacheditem/AttachedItemServiceTest.java index ce3d09c04038035e8f966a162f7d6ca99bdd7fac..726edcf3e90a969633d5f3caf885298c65334776 100644 --- a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attacheditem/AttachedItemServiceTest.java +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attacheditem/AttachedItemServiceTest.java @@ -25,6 +25,7 @@ package de.ozgcloud.bescheid.attacheditem; 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.time.ZonedDateTime; @@ -36,30 +37,40 @@ import java.util.Map; import java.util.Optional; import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.StringUtils; 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.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; +import com.thedeanda.lorem.LoremIpsum; + import de.ozgcloud.apilib.common.command.OzgCloudCommand; import de.ozgcloud.apilib.common.command.OzgCloudCommandService; import de.ozgcloud.apilib.common.command.grpc.CommandMapper; import de.ozgcloud.apilib.common.datatypes.GenericId; import de.ozgcloud.apilib.vorgang.OzgCloudVorgangId; import de.ozgcloud.bescheid.Bescheid; +import de.ozgcloud.bescheid.Bescheid.SendBy; import de.ozgcloud.bescheid.BescheidCallContextAttachingInterceptor; import de.ozgcloud.bescheid.common.callcontext.CallContextUser; import de.ozgcloud.bescheid.common.callcontext.CurrentUserService; +import de.ozgcloud.bescheid.common.freemarker.TemplateHandler; import de.ozgcloud.bescheid.vorgang.VorgangId; import de.ozgcloud.bescheid.vorgang.VorgangTestFactory; import de.ozgcloud.command.Command; import de.ozgcloud.command.CommandTestFactory; import de.ozgcloud.common.errorhandling.TechnicalException; +import de.ozgcloud.document.Document; +import de.ozgcloud.document.DocumentTestFactory; import io.grpc.StatusRuntimeException; class AttachedItemServiceTest { @@ -76,6 +87,8 @@ class AttachedItemServiceTest { private VorgangAttachedItemRemoteService remoteService; @Mock private CurrentUserService currentUserService; + @Mock + private TemplateHandler templateHandler; @Nested class TestFindBescheidItem { @@ -98,27 +111,6 @@ class AttachedItemServiceTest { } } - @Nested - class TestGetBescheidItem { - - @Test - void shouldCallGetBescheid() { - service.getBescheidItem(AttachedItemTestFactory.ID); - - verify(remoteService).getBescheid(AttachedItemTestFactory.ID); - } - - @Test - void shouldReturnBescheidItem() { - var expected = AttachedItemTestFactory.createBescheid(); - when(remoteService.getBescheid(anyString())).thenReturn(expected); - - var actual = service.getBescheidItem(AttachedItemTestFactory.ID); - - assertThat(actual).isEqualTo(expected); - } - } - @Nested class TestCreateBescheidDraft { @@ -247,7 +239,7 @@ class AttachedItemServiceTest { @Test void shouldCallBuildAttachedItemAsMap() { var expectedItemMap = Map.<String, Object>of("key", "value"); - doReturn(expectedItemMap).when(service).buildItemMapWithExistingBescheidFields(any()); + doReturn(expectedItemMap).when(service).buildCreateBescheidItemMap(any()); service.buildCreateAttachedItemCommand(command); @@ -272,6 +264,87 @@ class AttachedItemServiceTest { } } + @DisplayName("Build create bescheid itemMap") + @Nested + class TestBuildCreateBescheidItemMap { + + private static final String TEMPLATE_CONTENT_AS_STRING = LoremIpsum.getInstance().getParagraphs(5, 20); + + @DisplayName("on empty nachricht") + @Nested + class TestOnEmptyNachricht { + + @Test + void shouldHaveDefaultNachrichtSubjectInBodyIfMissing() { + var bodyObject = service.buildCreateBescheidItemMap(createCommandWithoutNachricht()); + + assertThat(bodyObject).containsEntry(Bescheid.FIELD_NACHRICHT_SUBJECT, AttachedItemService.DEFAULT_SUBJECT); + } + + @Test + void shouldCallTemplateHandler() { + when(templateHandler.getRawTemplate(any())).thenReturn(TEMPLATE_CONTENT_AS_STRING); + + service.buildCreateBescheidItemMap(createCommandWithoutNachricht()); + + verify(templateHandler).getRawTemplate(AttachedItemService.DEFAULT_TEMPLATE_FILE); + } + + @Test + void shouldHaveDefaultNachrichtTextInBodyIfMissing() { + when(templateHandler.getRawTemplate(any())).thenReturn(TEMPLATE_CONTENT_AS_STRING); + + var bodyObject = service.buildCreateBescheidItemMap(createCommandWithoutNachricht()); + + assertThat(bodyObject).containsEntry(Bescheid.FIELD_NACHRICHT_TEXT, TEMPLATE_CONTENT_AS_STRING); + } + + private Command createCommandWithoutNachricht() { + var bescheidItemMap = AttachedItemTestFactory.createBescheidItem(); + bescheidItemMap.put(Bescheid.FIELD_NACHRICHT_SUBJECT, StringUtils.EMPTY); + bescheidItemMap.put(Bescheid.FIELD_NACHRICHT_TEXT, StringUtils.EMPTY); + return CommandTestFactory.createBuilder().bodyObject(bescheidItemMap).build(); + } + } + + @DisplayName("on non existing nachricht") + @Nested + class TestNonExistingNachricht { + + @Test + void shouldHaveDefaultNachrichtSubjectInBodyIfMissing() { + var bodyObject = service.buildCreateBescheidItemMap(createCommandWithoutNachricht()); + + assertThat(bodyObject).containsEntry(Bescheid.FIELD_NACHRICHT_SUBJECT, AttachedItemService.DEFAULT_SUBJECT); + } + + @Test + void shouldCallTemplateHandler() { + when(templateHandler.getRawTemplate(any())).thenReturn(TEMPLATE_CONTENT_AS_STRING); + + service.buildCreateBescheidItemMap(createCommandWithoutNachricht()); + + verify(templateHandler).getRawTemplate(AttachedItemService.DEFAULT_TEMPLATE_FILE); + } + + @Test + void shouldHaveDefaultNachrichtTextInBodyIfMissing() { + when(templateHandler.getRawTemplate(any())).thenReturn(TEMPLATE_CONTENT_AS_STRING); + + var bodyObject = service.buildCreateBescheidItemMap(createCommandWithoutNachricht()); + + assertThat(bodyObject).containsEntry(Bescheid.FIELD_NACHRICHT_TEXT, TEMPLATE_CONTENT_AS_STRING); + } + + private Command createCommandWithoutNachricht() { + var bescheidItemMap = AttachedItemTestFactory.createBescheidItem(); + bescheidItemMap.remove(Bescheid.FIELD_NACHRICHT_SUBJECT); + bescheidItemMap.remove(Bescheid.FIELD_NACHRICHT_TEXT); + return CommandTestFactory.createBuilder().bodyObject(bescheidItemMap).build(); + } + } + } + @Nested class TestValidateBescheidData { @@ -283,7 +356,7 @@ class AttachedItemServiceTest { @DisplayName(value = "should fail if bescheidAm is missing") @Test void shouldThrowExceptionByBescheidAm() { - Map<String, Object> map = Map.of(Bescheid.FIELD_BEWILLIGT, true); + var map = Map.<String, Object>of(Bescheid.FIELD_BEWILLIGT, true); assertThrows(TechnicalException.class, () -> service.validateBescheidData(map)); } @@ -291,10 +364,37 @@ class AttachedItemServiceTest { @DisplayName(value = "should fail if bewilligt is missing") @Test void shouldThrowExceptionByBewilligt() { - Map<String, Object> map = Map.of(Bescheid.FIELD_BESCHIEDEN_AM, "2021-01-01"); + var map = Map.<String, Object>of(Bescheid.FIELD_BESCHIEDEN_AM, "2021-01-01"); assertThrows(TechnicalException.class, () -> service.validateBescheidData(map)); } + + @Test + void shouldValidateSendBy() { + var bodyObject = Map.<String, Object>of(Bescheid.FIELD_SEND_BY, SendBy.MANUAL.name()); + doNothing().when(service).validateBescheidField(any(), any()); + + service.validateBescheidData(bodyObject); + + verify(service).validateSendBy(SendBy.MANUAL.name()); + } + + @DisplayName("validate sendBy") + @Nested + class TestValidateSendBy { + + @ValueSource(strings = { "not-exist", "" }) + @ParameterizedTest + void shouldThrowExceptionOnNonExistingSendByValue(String sendBy) { + assertThrows(TechnicalException.class, () -> service.validateSendBy(sendBy)); + } + + @EnumSource(names = { "NACHRICHT", "MANUAL" }) + @ParameterizedTest + void shouldNotThrowAnyExceptionOnExistingSendByValue(SendBy sendBy) { + assertDoesNotThrow(() -> service.validateSendBy(sendBy.name())); + } + } } } @@ -306,39 +406,40 @@ class AttachedItemServiceTest { private final Command command = CommandTestFactory.createBuilder().relationId(AttachedItemTestFactory.ID).build(); private final AttachedItem bescheidItem = AttachedItemTestFactory.createBescheid(); + private final Optional<Document> document = Optional.of(DocumentTestFactory.create()); @BeforeEach void init() { - when(remoteService.getBescheid(any())).thenReturn(bescheidItem); + when(remoteService.getItem(any())).thenReturn(bescheidItem); } @Test void shouldCallGetBescheid() { - service.updateBescheidDraft(command); + doUpdateBescheidDraft(); - verify(remoteService).getBescheid(AttachedItemTestFactory.ID); + verify(remoteService, times(2)).getItem(AttachedItemTestFactory.ID); } @Test void shouldCallValidateBescheidStatus() { - service.updateBescheidDraft(command); + doUpdateBescheidDraft(); verify(service).validateBescheidStatus(bescheidItem); } @Test - void shouldCallBuildItemMapWithExistingBescheidFields() { - service.updateBescheidDraft(command); + void shouldCallBuildUpdateBescheidItemMap() { + doUpdateBescheidDraft(); - verify(service).buildItemMapWithExistingBescheidFields(command); + verify(service).buildBescheidItemAsMap(command, document); } @Test void shouldCallBuildAttachedItemAsMap() { var itemMap = Map.<String, Object>of("key", "value"); - doReturn(itemMap).when(service).buildItemMapWithExistingBescheidFields(any()); + doReturn(itemMap).when(service).buildBescheidItemAsMap(any(), any()); - service.updateBescheidDraft(command); + doUpdateBescheidDraft(); verify(service).buildAttachedItemAsMap(command, itemMap); } @@ -348,27 +449,151 @@ class AttachedItemServiceTest { var expectedBodyObject = Map.<String, Object>of("key", "value"); doReturn(expectedBodyObject).when(service).buildAttachedItemAsMap(any(), any()); - service.updateBescheidDraft(command); + doUpdateBescheidDraft(); - verify(service).buildUpdateAttachedItemCommand(bescheidItem, expectedBodyObject); + verify(service).buildPatchAttachedItemCommand(bescheidItem, expectedBodyObject); } @Test void shouldCallCommandService() { - doReturn(updateAttachedItemCommand).when(service).buildUpdateAttachedItemCommand(any(), any()); + doReturn(updateAttachedItemCommand).when(service).buildPatchAttachedItemCommand(any(), any()); - service.updateBescheidDraft(command); + doUpdateBescheidDraft(); verify(commandService).createAndWaitUntilDone(updateAttachedItemCommand); } @Test void shouldThrowExceptionWhenNotFound() { - when(remoteService.getBescheid(any())).thenThrow(StatusRuntimeException.class); + when(remoteService.getItem(any())).thenThrow(StatusRuntimeException.class); - assertThrows(StatusRuntimeException.class, () -> service.updateBescheidDraft(command)); + assertThrows(StatusRuntimeException.class, this::doUpdateBescheidDraft); } + private void doUpdateBescheidDraft() { + service.updateBescheidDraft(command, document); + } + } + + @DisplayName("Build bescheid item") + @Nested + class TestBuildBescheidItemAsMap { + + private final Optional<Document> document = Optional.of(DocumentTestFactory.create()); + private final Command command = CommandTestFactory.createBuilder().bodyObject(AttachedItemTestFactory.createBescheidItem()).build(); + private final AttachedItem bescheidAttachedItem = AttachedItemTestFactory.createBescheid(); + + @BeforeEach + void mock() { + doReturn(bescheidAttachedItem).when(service).getItem(any()); + } + + @Test + void shouldBuildItemMapWithExistingBescheidFields() { + service.buildBescheidItemAsMap(command, document); + + verify(service).buildItemMapWithExistingBescheidFields(command); + } + + @Test + void shouldGetItem() { + service.buildBescheidItemAsMap(command, document); + + verify(service).getItem(CommandTestFactory.RELATION_ID); + } + + @Test + void shouldCallGetNewNachrichtText() { + service.buildBescheidItemAsMap(command, document); + + verify(service).getNewNachrichtText(bescheidAttachedItem, document); + } + + @Test + void shouldOverrideIfNewNachrichtText() { + doReturn(Optional.of(DocumentTestFactory.NACHRICHT_TEXT)).when(service).getNewNachrichtText(any(), any()); + + var bescheidItem = service.buildBescheidItemAsMap(command, document); + + assertThat(bescheidItem).containsEntry(Bescheid.FIELD_NACHRICHT_TEXT, DocumentTestFactory.NACHRICHT_TEXT); + } + + @Test + void shouldKeepNachrichtTextIfNotNew() { + doReturn(Optional.empty()).when(service).getNewNachrichtText(any(), any()); + + var bescheidItem = service.buildBescheidItemAsMap(command, document); + + assertThat(bescheidItem).containsEntry(Bescheid.FIELD_NACHRICHT_TEXT, AttachedItemTestFactory.NACHRICHT_TEXT); + } + } + + @DisplayName("Get new nachrichtText") + @Nested + class TestGetNewNachrichtText { + + private final AttachedItem bescheidItem = AttachedItemTestFactory.createBescheid(); + + @DisplayName("with filled new document") + @Nested + class TestFilledDocument { + + private final Document document = DocumentTestFactory.create(); + private final Optional<Document> documentOpt = Optional.of(document); + + @Test + void shouldCheckIfIsDifferentDocument() { + service.getNewNachrichtText(bescheidItem, documentOpt); + + verify(service).isDifferentDocument(document, bescheidItem); + } + + @DisplayName("and different documentId") + @Nested + class TestAndDifferentDocumentId { + + @BeforeEach + void mock() { + doReturn(true).when(service).isDifferentDocument(any(), any()); + } + + @Test + void shouldReturnNachrichtTextFromDocument() { + var nachrichtText = service.getNewNachrichtText(bescheidItem, documentOpt); + + assertThat(nachrichtText).hasValue(DocumentTestFactory.NACHRICHT_TEXT); + } + } + + @DisplayName("and same documentId") + @Nested + class TestAndSameDocumentId { + + @BeforeEach + void mock() { + doReturn(false).when(service).isDifferentDocument(any(), any()); + } + + @Test + void shouldReturnNachrichtTextFromDocument() { + var nachrichtText = service.getNewNachrichtText(bescheidItem, documentOpt); + + assertThat(nachrichtText).isEmpty(); + } + } + } + + @DisplayName("with empty document") + @Nested + class TestWithEmptyDocument { + + @Test + void shouldReturnEmpty() { + var nachrichtText = service.getNewNachrichtText(bescheidItem, Optional.empty()); + + assertThat(nachrichtText).isEmpty(); + } + } } @Nested @@ -447,12 +672,12 @@ class AttachedItemServiceTest { service.overrideAttachedItem(bescheidItem, command); - verify(service).buildUpdateAttachedItemCommand(bescheidItem, bodyObject); + verify(service).buildPatchAttachedItemCommand(bescheidItem, bodyObject); } @Test void shouldCallCommandService() { - doReturn(updateItemCommand).when(service).buildUpdateAttachedItemCommand(any(), any()); + doReturn(updateItemCommand).when(service).buildPatchAttachedItemCommand(any(), any()); service.overrideAttachedItem(AttachedItemTestFactory.createBescheid(), command); @@ -467,6 +692,62 @@ class AttachedItemServiceTest { } } + @DisplayName("Build map with all bescheid fields") + @Nested + class TestBuildMapWithAllBescheidFields { + + private final Command command = CommandTestFactory.createBuilder().bodyObject(AttachedItemTestFactory.createBescheidItem()).build(); + + @Test + void shouldHaveSetBescheidenAm() { + var bescheidItem = service.buildItemMapWithAllBescheidFields(command); + + assertThat(bescheidItem).containsEntry(Bescheid.FIELD_BESCHIEDEN_AM, AttachedItemTestFactory.BESCHEIDEN_AM); + } + + @Test + void shouldHaveSetBewilligt() { + var bescheidItem = service.buildItemMapWithAllBescheidFields(command); + + assertThat(bescheidItem).containsEntry(Bescheid.FIELD_BEWILLIGT, AttachedItemTestFactory.BEWILLIGT); + } + + @Test + void shouldHaveSetBescheidDocument() { + var bescheidItem = service.buildItemMapWithAllBescheidFields(command); + + assertThat(bescheidItem).containsEntry(Bescheid.FIELD_BESCHEID_DOCUMENT, AttachedItemTestFactory.BESCHEID_DOCUMENT); + } + + @Test + void shouldHaveSetSendBy() { + var bescheidItem = service.buildItemMapWithAllBescheidFields(command); + + assertThat(bescheidItem).containsEntry(Bescheid.FIELD_SEND_BY, AttachedItemTestFactory.SEND_BY.name()); + } + + @Test + void shouldHaveSetNachrichtText() { + var bescheidItem = service.buildItemMapWithAllBescheidFields(command); + + assertThat(bescheidItem).containsEntry(Bescheid.FIELD_NACHRICHT_TEXT, AttachedItemTestFactory.NACHRICHT_TEXT); + } + + @Test + void shouldHaveSetNachrichtSubject() { + var bescheidItem = service.buildItemMapWithAllBescheidFields(command); + + assertThat(bescheidItem).containsEntry(Bescheid.FIELD_NACHRICHT_SUBJECT, AttachedItemTestFactory.NACHRICHT_SUBJECT); + } + + @Test + void shouldHaveSetAttachments() { + var bescheidItem = service.buildItemMapWithAllBescheidFields(command); + + assertThat(bescheidItem).containsEntry(Bescheid.FIELD_ATTACHMENTS, List.of(AttachedItemTestFactory.ATTACHMENT)); + } + } + @Nested class TestAddValueFromMapIfExists { @@ -572,7 +853,7 @@ class AttachedItemServiceTest { @Test void shouldCallVorgangIdMapper() { - service.buildUpdateAttachedItemCommand(AttachedItemTestFactory.createBescheid(), Map.of()); + service.buildPatchAttachedItemCommand(AttachedItemTestFactory.createBescheid(), Map.of()); verify(commandMapper).toOzgCloudVorgangId(CommandTestFactory.VORGANG_ID); } @@ -582,14 +863,14 @@ class AttachedItemServiceTest { var expectedVorgangId = OzgCloudVorgangId.from(CommandTestFactory.VORGANG_ID); when(commandMapper.toOzgCloudVorgangId(any())).thenReturn(expectedVorgangId); - var result = service.buildUpdateAttachedItemCommand(AttachedItemTestFactory.createBescheid(), Map.of()); + var result = service.buildPatchAttachedItemCommand(AttachedItemTestFactory.createBescheid(), Map.of()); assertThat(result.getVorgangId()).isEqualTo(expectedVorgangId); } @Test void shouldCallRelationIdMapper() { - service.buildUpdateAttachedItemCommand(AttachedItemTestFactory.createBescheid(), Map.of()); + service.buildPatchAttachedItemCommand(AttachedItemTestFactory.createBescheid(), Map.of()); verify(commandMapper).mapRelationId(AttachedItemTestFactory.ID); } @@ -599,30 +880,30 @@ class AttachedItemServiceTest { var expectedId = GenericId.from("relationId"); when(commandMapper.mapRelationId(any())).thenReturn(expectedId); - var result = service.buildUpdateAttachedItemCommand(AttachedItemTestFactory.createBescheid(), Map.of()); + var result = service.buildPatchAttachedItemCommand(AttachedItemTestFactory.createBescheid(), Map.of()); assertThat(result.getRelationId()).isEqualTo(expectedId); } @Test void shouldSetRelationVersion() { - var result = service.buildUpdateAttachedItemCommand(AttachedItemTestFactory.createBescheid(), Map.of()); + var result = service.buildPatchAttachedItemCommand(AttachedItemTestFactory.createBescheid(), Map.of()); assertThat(result.getRelationVersion()).isEqualTo(AttachedItemTestFactory.VERSION); } @Test void shouldSetOrder() { - var result = service.buildUpdateAttachedItemCommand(AttachedItemTestFactory.createBescheid(), Map.of()); + var result = service.buildPatchAttachedItemCommand(AttachedItemTestFactory.createBescheid(), Map.of()); - assertThat(result.getOrder()).isEqualTo(AttachedItemService.UPDATE_ATTACHED_ITEM_ORDER); + assertThat(result.getOrder()).isEqualTo(AttachedItemService.PATCH_ATTACHED_ITEM); } @Test void shouldSetBodyObject() { var bodyObject = Map.<String, Object>of("key", "value"); - var result = service.buildUpdateAttachedItemCommand(AttachedItemTestFactory.createBescheid(), bodyObject); + var result = service.buildPatchAttachedItemCommand(AttachedItemTestFactory.createBescheid(), bodyObject); assertThat(result.getBodyObject()).containsAllEntriesOf(bodyObject); } @@ -639,17 +920,17 @@ class AttachedItemServiceTest { @Test void shouldCallGetBescheid() { - when(remoteService.getBescheid(any())).thenReturn(AttachedItemTestFactory.createBescheid()); + when(remoteService.getItem(any())).thenReturn(AttachedItemTestFactory.createBescheid()); deleteBescheidDraft(); - verify(remoteService).getBescheid(AttachedItemTestFactory.ID); + verify(remoteService).getItem(AttachedItemTestFactory.ID); } @Test void shouldCallCommandService() { doReturn(deleteItemOzgCloudCommand).when(service).buildDeleteItemCommand(any(), any()); - when(remoteService.getBescheid(any())).thenReturn(AttachedItemTestFactory.createBescheid()); + when(remoteService.getItem(any())).thenReturn(AttachedItemTestFactory.createBescheid()); deleteBescheidDraft(); @@ -658,7 +939,7 @@ class AttachedItemServiceTest { @Test void shouldThrowExceptionIfBescheidNotFound() { - when(remoteService.getBescheid(any())).thenThrow(StatusRuntimeException.class); + when(remoteService.getItem(any())).thenThrow(StatusRuntimeException.class); assertThrows(StatusRuntimeException.class, this::deleteBescheidDraft); } @@ -730,8 +1011,7 @@ class AttachedItemServiceTest { @Test void shouldThrowExceptionIfBescheidIsNotDraft() { - var bescheidItem = - AttachedItemTestFactory.createBescheidBuilder().clearItem().itemEntry(Bescheid.FIELD_STATUS, "otherStatus").build(); + var bescheidItem = AttachedItemTestFactory.createBescheidBuilder().clearItem().itemEntry(Bescheid.FIELD_STATUS, "otherStatus").build(); assertThrows(TechnicalException.class, () -> service.validateBescheidStatus(bescheidItem)); } diff --git a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attacheditem/AttachedItemTestFactory.java b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attacheditem/AttachedItemTestFactory.java index 8f95f2f61208814a994c88d72ce767b4ff11879b..201fd3f6444931b06f9c1ffec5baab3a90771a6c 100644 --- a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attacheditem/AttachedItemTestFactory.java +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attacheditem/AttachedItemTestFactory.java @@ -44,6 +44,7 @@ public class AttachedItemTestFactory { public static final String CLIENT = "client"; public static final String BESCHEIDEN_AM = "2024-01-01"; + public static final boolean BEWILLIGT = true; public static final String BESCHEID_DOCUMENT = "bescheid-document"; public static final String ATTACHMENT = "attachment-id"; public static final Bescheid.SendBy SEND_BY = Bescheid.SendBy.MANUAL; @@ -64,7 +65,7 @@ public class AttachedItemTestFactory { var item = new HashMap<String, Object>(); item.put(Bescheid.FIELD_STATUS, Status.DRAFT); item.put(Bescheid.FIELD_BESCHIEDEN_AM, BESCHEIDEN_AM); - item.put(Bescheid.FIELD_BEWILLIGT, true); + item.put(Bescheid.FIELD_BEWILLIGT, BEWILLIGT); item.put(Bescheid.FIELD_BESCHEID_DOCUMENT, BESCHEID_DOCUMENT); item.put(Bescheid.FIELD_ATTACHMENTS, List.of(ATTACHMENT)); item.put(Bescheid.FIELD_SEND_BY, SEND_BY.name()); @@ -72,8 +73,7 @@ public class AttachedItemTestFactory { item.put(Bescheid.FIELD_NACHRICHT_SUBJECT, NACHRICHT_SUBJECT); item.put(Bescheid.FIELD_SENT_INFO, Map.of( Bescheid.FIELD_SENT_BY, SENT_BY, - Bescheid.FIELD_SENT_AT, SENT_AT_STR) - ); + Bescheid.FIELD_SENT_AT, SENT_AT_STR)); return item; } diff --git a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attacheditem/VorgangAttachedItemRemoteServiceTest.java b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attacheditem/VorgangAttachedItemRemoteServiceTest.java index 11779af655705946b71d382902ce71b3c97118e6..0fc0a06502b2896cdfb67125649c1ede7c047926 100644 --- a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attacheditem/VorgangAttachedItemRemoteServiceTest.java +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attacheditem/VorgangAttachedItemRemoteServiceTest.java @@ -26,6 +26,7 @@ package de.ozgcloud.bescheid.attacheditem; import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.Collections; @@ -347,7 +348,7 @@ class VorgangAttachedItemRemoteServiceTest { void shouldCallGetServiceStab() { when(grpcVorgangAttachedItemResponse.getVorgangAttachedItem()).thenReturn(grpcVorgangAttachedItem); - getBescheid(); + getItem(); verify(service).getServiceStub(); } @@ -356,7 +357,7 @@ class VorgangAttachedItemRemoteServiceTest { void shouldCallBuildRequest() { when(grpcVorgangAttachedItemResponse.getVorgangAttachedItem()).thenReturn(grpcVorgangAttachedItem); - getBescheid(); + getItem(); verify(service).buildGetByIdRequest(AttachedItemTestFactory.ID); } @@ -366,7 +367,7 @@ class VorgangAttachedItemRemoteServiceTest { when(grpcVorgangAttachedItemResponse.getVorgangAttachedItem()).thenReturn(grpcVorgangAttachedItem); doReturn(grpcVorgangAttachedItemRequest).when(service).buildGetByIdRequest(any()); - getBescheid(); + getItem(); verify(serviceStub).getById(grpcVorgangAttachedItemRequest); } @@ -375,7 +376,7 @@ class VorgangAttachedItemRemoteServiceTest { void shouldCallMapper() { when(grpcVorgangAttachedItemResponse.getVorgangAttachedItem()).thenReturn(grpcVorgangAttachedItem); - getBescheid(); + getItem(); verify(attachedItemMapper).mapFromVorgangAttachedItem(grpcVorgangAttachedItem); } @@ -386,13 +387,13 @@ class VorgangAttachedItemRemoteServiceTest { when(attachedItemMapper.mapFromVorgangAttachedItem(any())).thenReturn(expectedBescheid); when(grpcVorgangAttachedItemResponse.getVorgangAttachedItem()).thenReturn(grpcVorgangAttachedItem); - var result = getBescheid(); + var result = getItem(); assertThat(result).isEqualTo(expectedBescheid); } - private AttachedItem getBescheid() { - return service.getBescheid(AttachedItemTestFactory.ID); + private AttachedItem getItem() { + return service.getItem(AttachedItemTestFactory.ID); } } diff --git a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/common/freemarker/TemplateHandlerITCase.java b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/common/freemarker/TemplateHandlerITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..61873f64571613f97a8a55b58cb23c7d4751a2f4 --- /dev/null +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/common/freemarker/TemplateHandlerITCase.java @@ -0,0 +1,45 @@ +package de.ozgcloud.bescheid.common.freemarker; + +import static org.assertj.core.api.Assertions.*; + +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.info.BuildProperties; +import org.springframework.boot.test.mock.mockito.MockBean; + +import de.ozgcloud.apilib.common.command.OzgCloudCommandService; +import de.ozgcloud.apilib.common.command.grpc.CommandMapper; +import de.ozgcloud.common.test.ITCase; + +@ITCase +class TemplateHandlerITCase { + + private static final String BESCHEID_TEMPLATE = "dummy.template.txt.ftlh"; + + @Autowired + private TemplateHandler handler; + @MockBean + private BuildProperties properties; + @MockBean + private OzgCloudCommandService commandService; + @MockBean + private CommandMapper commandMapper; + + @DisplayName("Get raw template") + @Nested + class TestGetRawTemplate { + + @Test + void shouldReturnTemplateAsString() { + var content = handler.getRawTemplate(BESCHEID_TEMPLATE); + + assertThat(content) + .contains("Sehr geehrte/r Antragsteller/in") + .contains("im folgenden wird evtl was ersetzt") + .contains("valueToReplace") + .contains("Mit freundlichen Grüßen"); + } + } +} diff --git a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/nachricht/NachrichtServiceITCase.java b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/nachricht/NachrichtServiceITCase.java index 5bb7b63531909383162dd11ddcfdaf02949f7d73..e0280020426fe92f6dce27b0f223738f57e108b5 100644 --- a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/nachricht/NachrichtServiceITCase.java +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/nachricht/NachrichtServiceITCase.java @@ -2,8 +2,7 @@ package de.ozgcloud.bescheid.nachricht; import static org.assertj.core.api.Assertions.*; -import java.util.Optional; - +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; @@ -27,6 +26,7 @@ class NachrichtServiceITCase { @MockBean private BuildProperties buildProperties; + @DisplayName("Build message") @Nested class TestBuildMessage { @Test @@ -35,20 +35,5 @@ class NachrichtServiceITCase { assertThat(message).isNotBlank(); } - - @Test - void shouldBeGenehmigt() { - var message = service.buildMessage(BescheidTestFactory.createBuilder().genehmigt(true).nachrichtText(Optional.empty()).build()); - - assertThat(message).contains("genehmigt").doesNotContain("abgelehnt"); - } - - @Test - void shouldBeAbgelehnt() { - var message = service.buildMessage(BescheidTestFactory.createBuilder().genehmigt(false).nachrichtText(Optional.empty()).build()); - - assertThat(message).contains("abgelehnt").doesNotContain("genehmigt"); - } } - -} +} \ No newline at end of file diff --git a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/nachricht/NachrichtServiceTest.java b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/nachricht/NachrichtServiceTest.java index 777babc893515dec6ddf8f32fcfd030a83e9aa5c..f18ffbc9d6851d39b9dfe8676d98f7676013dbe5 100644 --- a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/nachricht/NachrichtServiceTest.java +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/nachricht/NachrichtServiceTest.java @@ -25,6 +25,7 @@ import de.ozgcloud.apilib.vorgang.OzgCloudVorgangId; import de.ozgcloud.bescheid.Bescheid; import de.ozgcloud.bescheid.BescheidTestFactory; import de.ozgcloud.bescheid.attacheditem.AttachedItemTestFactory; +import de.ozgcloud.bescheid.common.freemarker.TemplateHandler; import de.ozgcloud.bescheid.vorgang.PostfachAddressTestFactory; import de.ozgcloud.bescheid.vorgang.ServiceKontoTestFactory; import de.ozgcloud.bescheid.vorgang.Vorgang; @@ -43,6 +44,8 @@ class NachrichtServiceTest { private OzgCloudCommandService commandService; @Mock private CommandMapper commandMapper; + @Mock + private TemplateHandler templateHandler; @Nested class TestCreateNachrichtDraft { @@ -116,7 +119,7 @@ class NachrichtServiceTest { @Test void shouldUseDefaultTemplate() { - doReturn("FROM_TEMPLATE").when(service).fillTemplate(any(), any()); + doReturn("FROM_TEMPLATE").when(templateHandler).fillTemplate(any(), any()); var message = service.buildMessage(BescheidTestFactory.createBuilder().nachrichtText(Optional.empty()).build()); diff --git a/bescheid-manager/src/test/java/de/ozgcloud/document/DocumentServiceTest.java b/bescheid-manager/src/test/java/de/ozgcloud/document/DocumentServiceTest.java index 9589ada3b530def97b9876347110100bf7f788a7..d9666c6f3edcccc48c5ce830547086ab2f9f8d30 100644 --- a/bescheid-manager/src/test/java/de/ozgcloud/document/DocumentServiceTest.java +++ b/bescheid-manager/src/test/java/de/ozgcloud/document/DocumentServiceTest.java @@ -25,6 +25,7 @@ package de.ozgcloud.document; 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.util.Map; @@ -197,23 +198,23 @@ class DocumentServiceTest { @Test void shouldCallGetBescheidItem() { - when(attachedItemService.getBescheidItem(any())).thenReturn(AttachedItemTestFactory.createBescheid()); + when(attachedItemService.getItem(any())).thenReturn(AttachedItemTestFactory.createBescheid()); validateBescheidItem(); - verify(attachedItemService).getBescheidItem(AttachedItemTestFactory.ID); + verify(attachedItemService).getItem(AttachedItemTestFactory.ID); } @Test void shouldThrowExceptionWhenNoBescheidDraft() { - when(attachedItemService.getBescheidItem(any())).thenThrow(ResponseStatusException.class); + when(attachedItemService.getItem(any())).thenThrow(ResponseStatusException.class); assertThrows(ResponseStatusException.class, this::validateBescheidItem); } @Test void shouldNotThrowExceptionIfNotDraft() { - when(attachedItemService.getBescheidItem(any())).thenReturn( + when(attachedItemService.getItem(any())).thenReturn( AttachedItemTestFactory.createBescheidBuilder().clearItem().itemEntry(Bescheid.FIELD_STATUS, "not-draft").build()); assertThrows(TechnicalException.class, this::validateBescheidItem); @@ -221,7 +222,7 @@ class DocumentServiceTest { @Test void shouldValidateBescheidItem() { - when(attachedItemService.getBescheidItem(any())).thenReturn(AttachedItemTestFactory.createBescheid()); + when(attachedItemService.getItem(any())).thenReturn(AttachedItemTestFactory.createBescheid()); assertDoesNotThrow(this::validateBescheidItem); } @@ -425,7 +426,7 @@ class DocumentServiceTest { @Test void shouldCallDocumentMapper() { - var expectedItem = AttachedItemTestFactory.createDocument(); + var expectedItem = AttachedItemTestFactory.createDocument(); when(attachedItemService.getItem(any())).thenReturn(expectedItem); service.getDocument(AttachedItemTestFactory.ID); diff --git a/bescheid-manager/src/test/resources/templates/dummy.template.txt.ftlh b/bescheid-manager/src/test/resources/templates/dummy.template.txt.ftlh new file mode 100644 index 0000000000000000000000000000000000000000..9182e3f004e22d03b94e458faac04d2612efee18 --- /dev/null +++ b/bescheid-manager/src/test/resources/templates/dummy.template.txt.ftlh @@ -0,0 +1,6 @@ +Sehr geehrte/r Antragsteller/in, +im folgenden wird evtl was ersetzt + +<#if domatch>replacedValue<#else>valueToReplace</#if> + +Mit freundlichen Grüßen \ No newline at end of file diff --git a/pom.xml b/pom.xml index 6a91d9c38e7febade4b701411cd8dba92c88b113..1b9b61485236e8343cc4790763c73dde40c7512f 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>de.ozgcloud.vorgang</groupId> <artifactId>vorgang-manager</artifactId> - <version>2.9.0-SNAPSHOT</version> + <version>2.11.0-SNAPSHOT</version> <name>OZG-Cloud Vorgang Manager</name> <packaging>pom</packaging> diff --git a/src/main/helm/templates/_helpers.tpl b/src/main/helm/templates/_helpers.tpl index 7c94e6cfd9851532d35296171f68cff8ba0e46b2..0b1337c8ab4554f6f66827c60f7113b8d6863d86 100644 --- a/src/main/helm/templates/_helpers.tpl +++ b/src/main/helm/templates/_helpers.tpl @@ -101,4 +101,26 @@ app.kubernetes.io/namespace: {{ include "app.namespace" . }} {{- $customList = append $customList (dict "name" $key "value" $value) }} {{- end -}} {{- $customList | toYaml -}} -{{- end -}} \ No newline at end of file +{{- end -}} + + +{{- define "app.bayernidAbsenderName" -}} +{{- quote (required "ozgcloud.bayernid.absender.name must be set if ozgcloud.bayernid is enabled" (((.Values.ozgcloud).bayernid).absender).name) -}} +{{- end -}} + +{{- define "app.bayernidAbsenderMandant" -}} +{{- if (((.Values.ozgcloud).bayernid).absender).mandant -}} +{{ quote .Values.ozgcloud.bayernid.absender.mandant }} +{{- else -}} +{{ include "app.bayernidAbsenderName" . }} +{{- end -}} +{{- end -}} + +{{- define "app.bayernidAbsenderDienst" -}} +{{- if (((.Values.ozgcloud).bayernid).absender).dienst -}} +{{ quote .Values.ozgcloud.bayernid.absender.dienst }} +{{- else -}} +{{ include "app.bayernidAbsenderName" . }} +{{- end -}} +{{- end -}} + diff --git a/src/main/helm/templates/deployment.yaml b/src/main/helm/templates/deployment.yaml index 938946431b8c929bd0bd636f933072eedbe44fca..cbb3ae56d5105372f34de651e87da7b08d689402 100644 --- a/src/main/helm/templates/deployment.yaml +++ b/src/main/helm/templates/deployment.yaml @@ -137,6 +137,10 @@ spec: - name: spring_ssl_bundle_pem_es-root-ca_truststore_certificate value: "/bindings/ca-certificates/es-root-ca.pem" {{- end }} + {{- if ((.Values.ozgcloud).mongodbsearch).enabled }} + - name: ozgcloud_mongodbsearch_enabled + value: {{ quote .Values.ozgcloud.mongodbsearch.enabled }} + {{- end }} {{- with include "app.getCustomList" . }} {{ . | indent 10 }} {{- end }} @@ -170,17 +174,27 @@ spec: - name: grpc_client_bayern-id_negotiationType value: {{ (((.Values.ozgcloud).bayernid).proxy).negotiationType | default "PLAINTEXT" }} - name: ozgcloud_bayernid_absender_name - value: {{ quote (required "ozgcloud.bayernid.absender.name must be set if ozgcloud.bayernid is enabled" (((.Values.ozgcloud).bayernid).absender).name) }} + value: {{ include "app.bayernidAbsenderName" . }} - name: ozgcloud_bayernid_absender_anschrift value: {{ quote (required "ozgcloud.bayernid.absender.anschrift must be set if ozgcloud.bayernid is enabled" (((.Values.ozgcloud).bayernid).absender).anschrift) }} - name: ozgcloud_bayernid_absender_dienst - value: {{ quote (required "ozgcloud.bayernid.absender.dienst must be set if ozgcloud.bayernid is enabled" (((.Values.ozgcloud).bayernid).absender).dienst) }} + value: {{ include "app.bayernidAbsenderDienst" . }} - name: ozgcloud_bayernid_absender_mandant - value: {{ quote (required "ozgcloud.bayernid.absender.mandant must be set if ozgcloud.bayernid is enabled" (((.Values.ozgcloud).bayernid).absender).mandant) }} + value: {{ include "app.bayernidAbsenderMandant" . }} - name: ozgcloud_bayernid_absender_gemeindeSchluessel value: {{ quote (required "ozgcloud.bayernid.absender.gemeindeSchluessel must be set if ozgcloud.bayernid is enabled" (((.Values.ozgcloud).bayernid).absender).gemeindeSchluessel) }} {{- end }} + + {{- if ((.Values.ozgcloud).muk).enabled }} + - name: ozgcloud_muk_sender + value: {{ quote (required "ozgcloud.muk.sender must be set if ozgcloud.muk is enabled" ((.Values.ozgcloud).muk).sender) }} + - name: ozgcloud_muk_server + value: {{ quote (required "ozgcloud.muk.server must be set if ozgcloud.muk is enabled" ((.Values.ozgcloud).muk).server) }} + {{- end }} + + + {{- if ((.Values.ozgcloud).antragraum).enabled }} - name: ozgcloud_antragraum_enabled value: {{ quote .Values.ozgcloud.antragraum.enabled }} diff --git a/src/test/helm/deployment_bayernid_test.yaml b/src/test/helm/deployment_bayernid_test.yaml index 2a73c2761d442304840198966a1641a74081124a..5b4837870b547f1c80384011bbf8dce8305eacc4 100644 --- a/src/test/helm/deployment_bayernid_test.yaml +++ b/src/test/helm/deployment_bayernid_test.yaml @@ -42,10 +42,8 @@ tests: address: https://proxy.address.local absender: postkorbId: "postkorbId" - name: "name" anschrift: "anschrift" - dienst: "dienst" - mandant: "mandant" + name: "name" gemeindeSchluessel: "gemeindeSchluessel" asserts: - contains: @@ -66,18 +64,18 @@ tests: - contains: path: spec.template.spec.containers[0].env content: - name: ozgcloud_bayernid_absender_dienst - value: "dienst" + name: ozgcloud_bayernid_absender_gemeindeSchluessel + value: "gemeindeSchluessel" - contains: path: spec.template.spec.containers[0].env content: - name: ozgcloud_bayernid_absender_mandant - value: "mandant" + name: ozgcloud_bayernid_absender_dienst + value: "name" - contains: path: spec.template.spec.containers[0].env content: - name: ozgcloud_bayernid_absender_gemeindeSchluessel - value: "gemeindeSchluessel" + name: ozgcloud_bayernid_absender_mandant + value: "name" - contains: path: spec.template.spec.containers[0].env content: @@ -99,8 +97,6 @@ tests: absender: postkorbId: "postkorbId" anschrift: "anschrift" - dienst: "dienst" - mandant: "mandant" gemeindeSchluessel: "gemeindeSchluessel" asserts: - failedTemplate: @@ -115,15 +111,14 @@ tests: address: https://proxy.address.local absender: postkorbId: "postkorbId" - name: "name" - dienst: "dienst" - mandant: "mandant" + name: "name_dienst_mandant" gemeindeSchluessel: "gemeindeSchluessel" asserts: - failedTemplate: errorMessage: "ozgcloud.bayernid.absender.anschrift must be set if ozgcloud.bayernid is enabled" - - it: should fail if absender dienst is not set + + - it: should fail if absender gemeindeSchluessel is not set set: ozgcloud: bayernid: @@ -132,82 +127,86 @@ tests: address: https://proxy.address.local absender: postkorbId: "postkorbId" - name: "name" anschrift: "anschrift" - mandant: "mandant" - gemeindeSchluessel: "gemeindeSchluessel" + name: "name_dienst_mandant" asserts: - failedTemplate: - errorMessage: "ozgcloud.bayernid.absender.dienst must be set if ozgcloud.bayernid is enabled" + errorMessage: "ozgcloud.bayernid.absender.gemeindeSchluessel must be set if ozgcloud.bayernid is enabled" - - it: should fail if absender mandant is not set + - it: should fail if bayernid proxy is enabled but proxy address is not configured set: ozgcloud: bayernid: enabled: true - proxy: - address: https://proxy.address.local absender: postkorbId: "postkorbId" - name: "name" anschrift: "anschrift" - dienst: "dienst" + name: "name_dienst_mandant" gemeindeSchluessel: "gemeindeSchluessel" asserts: - failedTemplate: - errorMessage: "ozgcloud.bayernid.absender.mandant must be set if ozgcloud.bayernid is enabled" + errorMessage: "ozgcloud.bayernid.proxy.address must be set if ozgcloud.bayernid is enabled" - - it: should fail if absender gemeindeSchluessel is not set + - it: should set the bayernid proxy grpc negotiationType set: ozgcloud: bayernid: enabled: true proxy: address: https://proxy.address.local + negotiationType: NOT_DEFAULT absender: postkorbId: "postkorbId" - name: "name" anschrift: "anschrift" - dienst: "dienst" - mandant: "mandant" + name: "name_dienst_mandant" + gemeindeSchluessel: "gemeindeSchluessel" asserts: - - failedTemplate: - errorMessage: "ozgcloud.bayernid.absender.gemeindeSchluessel must be set if ozgcloud.bayernid is enabled" + - contains: + path: spec.template.spec.containers[0].env + content: + name: grpc_client_bayern-id_negotiationType + value: NOT_DEFAULT - - it: should fail if bayernid proxy is enabled but proxy address is not configured + - it: should overwrite with absender name if mandant is set set: ozgcloud: bayernid: enabled: true + proxy: + address: https://proxy.address.local absender: postkorbId: "postkorbId" - name: "name" anschrift: "anschrift" - dienst: "dienst" + name: "name" mandant: "mandant" gemeindeSchluessel: "gemeindeSchluessel" asserts: - - failedTemplate: - errorMessage: "ozgcloud.bayernid.proxy.address must be set if ozgcloud.bayernid is enabled" - - - it: should set the bayernid proxy grpc negotiationType + - contains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_bayernid_enabled + value: "true" + - contains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_bayernid_absender_mandant + value: "mandant" + - it: should overwrite with absender name if dienst is set set: ozgcloud: bayernid: enabled: true proxy: address: https://proxy.address.local - negotiationType: NOT_DEFAULT absender: postkorbId: "postkorbId" - name: "name" anschrift: "anschrift" + name: "name" dienst: "dienst" - mandant: "mandant" gemeindeSchluessel: "gemeindeSchluessel" asserts: - contains: path: spec.template.spec.containers[0].env content: - name: grpc_client_bayern-id_negotiationType - value: NOT_DEFAULT + name: ozgcloud_bayernid_absender_dienst + value: "dienst" diff --git a/src/test/helm/deployment_mongodb_test.yaml b/src/test/helm/deployment_mongodb_test.yaml index 7daf5d3196bd7c34413c8aff2741a9159c3a1fdd..ad8a4cb17c47cdca0b5cc89daf2055ec9a24109e 100644 --- a/src/test/helm/deployment_mongodb_test.yaml +++ b/src/test/helm/deployment_mongodb_test.yaml @@ -93,3 +93,21 @@ tests: content: name: spring_data_mongodb_database value: vorgang-manager-database + + - it: check mongodb full text search disabled + asserts: + - notContains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_mongodbsearch_enabled + value: "true" + + - it: check mongodb full text search enabled + set: + ozgcloud.mongodbsearch.enabled: true + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_mongodbsearch_enabled + value: "true" diff --git a/src/test/helm/deployment_muk_test.yaml b/src/test/helm/deployment_muk_test.yaml new file mode 100644 index 0000000000000000000000000000000000000000..33c02db5fd14acf174884dc8f09517d0403bf1b1 --- /dev/null +++ b/src/test/helm/deployment_muk_test.yaml @@ -0,0 +1,89 @@ +# +# 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. +# + +suite: deployment muk +release: + name: vorgang-manager + namespace: sh-helm-test +templates: + - templates/deployment.yaml +set: + ozgcloud: + environment: dev + imagePullSecret: test-image-pull-secret +tests: + - it: should set muk values + set: + ozgcloud: + muk: + enabled: true + server: muk.test.ozg.de + sender: "name" + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_muk_sender + value: "name" + - contains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_muk_server + value: muk.test.ozg.de + - it: should not by default set muk values + asserts: + - notContains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_muk_sender + any: true + - notContains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_muk_server + any: true + + - it: should fail if sender name is not set + set: + ozgcloud: + muk: + enabled: true + server: muk.test.ozg.de + asserts: + - failedTemplate: + errorMessage: "ozgcloud.muk.sender must be set if ozgcloud.muk is enabled" + + + - it: should fail if muk server is not set + set: + ozgcloud: + muk: + enabled: true + sender: "name" + asserts: + - failedTemplate: + errorMessage: "ozgcloud.muk.server must be set if ozgcloud.muk is enabled" + + + diff --git a/vorgang-manager-base/pom.xml b/vorgang-manager-base/pom.xml index 222297d76dd354d3d4e3eb0c88d00b694d316225..01a891cafa5c98d4a5bf87091817403b5c6a9f88 100644 --- a/vorgang-manager-base/pom.xml +++ b/vorgang-manager-base/pom.xml @@ -6,13 +6,13 @@ <parent> <groupId>de.ozgcloud.common</groupId> <artifactId>ozgcloud-common-parent</artifactId> - <version>4.2.0</version> + <version>4.3.0</version> <relativePath /> </parent> <groupId>de.ozgcloud.vorgang</groupId> <artifactId>vorgang-manager-base</artifactId> - <version>2.9.0-SNAPSHOT</version> + <version>2.11.0-SNAPSHOT</version> <name>OZG-Cloud Vorgang Manager Base</name> diff --git a/vorgang-manager-command/pom.xml b/vorgang-manager-command/pom.xml index 0fbbe6fad1054675ef511d164b91eada693b3b07..1427d6e703f39962af7b6136e3319e01c87022e2 100644 --- a/vorgang-manager-command/pom.xml +++ b/vorgang-manager-command/pom.xml @@ -4,13 +4,13 @@ <parent> <groupId>de.ozgcloud.common</groupId> <artifactId>ozgcloud-common-dependencies</artifactId> - <version>4.2.0</version> + <version>4.3.0</version> <relativePath/> </parent> <groupId>de.ozgcloud.command</groupId> <artifactId>command-manager</artifactId> - <version>2.9.0-SNAPSHOT</version> + <version>2.11.0-SNAPSHOT</version> <name>OZG-Cloud Command Manager</name> <properties> diff --git a/vorgang-manager-command/src/main/java/de/ozgcloud/command/CommandStatus.java b/vorgang-manager-command/src/main/java/de/ozgcloud/command/CommandStatus.java index 926ca62da79c8ac64c56bc9fd4d9f0fc59e84aea..31bbfd577e335865cdebef9b37de0c5613c52f18 100644 --- a/vorgang-manager-command/src/main/java/de/ozgcloud/command/CommandStatus.java +++ b/vorgang-manager-command/src/main/java/de/ozgcloud/command/CommandStatus.java @@ -24,5 +24,5 @@ package de.ozgcloud.command; public enum CommandStatus { - PENDING, FINISHED, ERROR, REVOKE_PENDING, REVOKED; + NEW, PENDING, CANCELED, FINISHED, ERROR, REVOKE_PENDING, REVOKED } \ No newline at end of file diff --git a/vorgang-manager-command/src/test/java/de/ozgcloud/command/CommandTestFactory.java b/vorgang-manager-command/src/test/java/de/ozgcloud/command/CommandTestFactory.java index 67e86232e49882c6fc469d07fbb509b52943ca6d..886d82f62b9d791d35214ff718c45cdac71bb2ee 100644 --- a/vorgang-manager-command/src/test/java/de/ozgcloud/command/CommandTestFactory.java +++ b/vorgang-manager-command/src/test/java/de/ozgcloud/command/CommandTestFactory.java @@ -1,12 +1,13 @@ package de.ozgcloud.command; -import java.util.Map; +import java.util.Collections; import java.util.UUID; public class CommandTestFactory { public static final String ID = UUID.randomUUID().toString(); public static final String VORGANG_ID = UUID.randomUUID().toString(); + public static final String RELATION_ID = UUID.randomUUID().toString(); public static final String ORDER = "DO_TEST"; public static final String CREATED_BY = UUID.randomUUID().toString(); @@ -18,9 +19,10 @@ public class CommandTestFactory { public static TestCommand.TestCommandBuilder createBuilder() { return TestCommand.builder() .id(ID) + .relationId(RELATION_ID) .vorgangId(VORGANG_ID) - .body(Map.of()) - .bodyObject(Map.of()) + .body(Collections.emptyMap()) + .bodyObject(Collections.emptyMap()) .createdBy(CREATED_BY); } } diff --git a/vorgang-manager-interface/pom.xml b/vorgang-manager-interface/pom.xml index f3c97b987a7ae006827201ebb600d4c6f7228b05..c64435edbd2a453b7b75be55b0647d46ac6d644b 100644 --- a/vorgang-manager-interface/pom.xml +++ b/vorgang-manager-interface/pom.xml @@ -30,13 +30,13 @@ <parent> <groupId>de.ozgcloud.common</groupId> <artifactId>ozgcloud-common-dependencies</artifactId> - <version>4.2.0</version> + <version>4.3.0</version> <relativePath/> </parent> <groupId>de.ozgcloud.vorgang</groupId> <artifactId>vorgang-manager-interface</artifactId> - <version>2.9.0-SNAPSHOT</version> + <version>2.11.0-SNAPSHOT</version> <name>OZG-Cloud Vorgang Manager gRPC Interface</name> <description>Interface (gRPC) for Vorgang Manager Server</description> diff --git a/vorgang-manager-server/pom.xml b/vorgang-manager-server/pom.xml index f70af29d7f58dd9a5083f38c622ea640bb55da76..ec7bfbbe029cab1c6d2c88820de252b3cec29905 100644 --- a/vorgang-manager-server/pom.xml +++ b/vorgang-manager-server/pom.xml @@ -32,13 +32,13 @@ <parent> <groupId>de.ozgcloud.common</groupId> <artifactId>ozgcloud-common-parent</artifactId> - <version>4.3.0-SNAPSHOT</version> + <version>4.3.0</version> <relativePath /> </parent> <groupId>de.ozgcloud.vorgang</groupId> <artifactId>vorgang-manager-server</artifactId> - <version>2.9.0-SNAPSHOT</version> + <version>2.11.0-SNAPSHOT</version> <name>OZG-Cloud Vorgang Manager Server</name> <description>Server Implementierung des VorgangManagers</description> @@ -53,11 +53,11 @@ <zufi-manager-interface.version>1.0.0-SNAPSHOT</zufi-manager-interface.version> <user-manager-interface.version>2.1.0</user-manager-interface.version> - <bescheid-manager.version>1.13.0-SNAPSHOT</bescheid-manager.version> - <processor-manager.version>0.4.0</processor-manager.version> - <nachrichten-manager.version>2.9.0-SNAPSHOT</nachrichten-manager.version> - <ozgcloud-starter.version>0.10.0-SNAPSHOT</ozgcloud-starter.version> - <notification-manager.version>2.8.0-SNAPSHOT</notification-manager.version> + <bescheid-manager.version>1.14.0</bescheid-manager.version> + <processor-manager.version>0.4.1</processor-manager.version> + <nachrichten-manager.version>2.9.0</nachrichten-manager.version> + <ozgcloud-starter.version>0.10.0</ozgcloud-starter.version> + <notification-manager.version>2.8.0</notification-manager.version> <zip.version>2.11.1</zip.version> <jsoup.version>1.15.3</jsoup.version> diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/VorgangManagerCallContextProvider.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/VorgangManagerCallContextProvider.java index 62811097900c5e6daee5d06831962184579d63b8..3fe994644fae00480fdda6814cae0dcbec5abb5a 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/VorgangManagerCallContextProvider.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/VorgangManagerCallContextProvider.java @@ -31,6 +31,7 @@ import org.springframework.stereotype.Component; import de.ozgcloud.apilib.common.callcontext.CallContext; import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextProvider; import de.ozgcloud.apilib.user.OzgCloudUserId; +import de.ozgcloud.vorgang.callcontext.CallContextUser; import de.ozgcloud.vorgang.callcontext.CurrentUserService; import de.ozgcloud.vorgang.callcontext.VorgangManagerClientCallContextAttachingInterceptor; import lombok.RequiredArgsConstructor; @@ -45,11 +46,11 @@ public class VorgangManagerCallContextProvider implements OzgCloudCallContextPro @Override public CallContext provideContext() { var callContextBuilder = CallContext.builder().clientName(VorgangManagerClientCallContextAttachingInterceptor.VORGANG_MANAGER_CLIENT_NAME); - getUserId().ifPresent(callContextBuilder::userId); + findUserId().ifPresent(callContextBuilder::userId); return callContextBuilder.build(); } - Optional<OzgCloudUserId> getUserId() { - return currentUserService.getUser().getUserId().map(OzgCloudUserId::from); + Optional<OzgCloudUserId> findUserId() { + return currentUserService.findUser().flatMap(CallContextUser::getUserId).map(OzgCloudUserId::from); } } diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/VorgangProcessorConfiguration.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/VorgangProcessorConfiguration.java index 56d602a31ebc0a372e80cb958e604f85c5e7fef1..9fdab580da21c2a57572b28612b428f8a198fbd7 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/VorgangProcessorConfiguration.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/VorgangProcessorConfiguration.java @@ -7,29 +7,15 @@ import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextProvider; import de.ozgcloud.apilib.user.GrpcOzgCloudUserProfileService; import de.ozgcloud.apilib.user.OzgCloudUserProfileService; import de.ozgcloud.apilib.user.UserProfileMapper; -import de.ozgcloud.apilib.vorgang.OzgCloudVorgangService; -import de.ozgcloud.apilib.vorgang.grpc.GrpcOzgCloudVorgangService; -import de.ozgcloud.apilib.vorgang.grpc.OzgCloudVorgangMapper; -import de.ozgcloud.apilib.vorgang.grpc.OzgCloudVorgangStubMapper; import de.ozgcloud.user.grpc.userprofile.UserProfileServiceGrpc.UserProfileServiceBlockingStub; -import de.ozgcloud.vorgang.vorgang.VorgangServiceGrpc.VorgangServiceBlockingStub; import net.devh.boot.grpc.client.inject.GrpcClient; @Configuration class VorgangProcessorConfiguration { - @GrpcClient("vorgang-manager") - private VorgangServiceBlockingStub vorgangServiceStub; - @GrpcClient("user-manager") private UserProfileServiceBlockingStub userProfileServiceGrpc; - @Bean - OzgCloudVorgangService ozgCloudVorgangService(OzgCloudVorgangMapper mapper, OzgCloudVorgangStubMapper stubMapper, - OzgCloudCallContextProvider contextProvider) { - return new GrpcOzgCloudVorgangService(vorgangServiceStub, mapper, stubMapper, contextProvider); - } - @Bean OzgCloudUserProfileService ozgCloudUserProfileService(UserProfileMapper mapper, OzgCloudCallContextProvider contextProvider) { return new GrpcOzgCloudUserProfileService(userProfileServiceGrpc, mapper, contextProvider); diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemCustomRepository.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemCustomRepository.java index 60d9b646de80c056a7f4df16a06c69861ab571e8..0fcd73c898e729dc7d06ef4f9f862976e4f60676 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemCustomRepository.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemCustomRepository.java @@ -27,6 +27,10 @@ import java.util.Map; interface VorgangAttachedItemCustomRepository { + void update(String itemId, long version, Map<String, Object> properyMap); + + void forceUpdate(String itemId, Map<String, Object> propertyMap); + void patch(String itemId, long version, Map<String, Object> propertyMap); void forcePatch(String itemId, Map<String, Object> propertyMap); diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemCustomRepositoryImpl.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemCustomRepositoryImpl.java index 66c828d24149e338c647d9eff9d1f7e34347c033..c3bb8ce0a775e518f791f245f5d0b3cb7614ad27 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemCustomRepositoryImpl.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemCustomRepositoryImpl.java @@ -60,14 +60,32 @@ public class VorgangAttachedItemCustomRepositoryImpl implements VorgangAttachedI } boolean doVorgangAttachedItemExist(String itemId) { - return mongoOperations.exists(query(isId(itemId)), VorgangAttachedItem.COLLECTION_NAME); + return mongoOperations.exists(createQueryById(itemId), VorgangAttachedItem.class); + } + + @Override + public void update(String itemId, long version, Map<String, Object> properyMap) { + var updateResult = mongoOperations.updateFirst(query(byIdAndVersion(itemId, version)), buildUpdateForItemAndVersion(properyMap), + VorgangAttachedItem.class); + collisionVerifier.verify(updateResult, itemId); + } + + @Override + public void forceUpdate(String itemId, Map<String, Object> propertyMap) { + mongoOperations.updateFirst(createQueryById(itemId), buildUpdateForItemAndVersion(propertyMap), VorgangAttachedItem.class); + } + + private Update buildUpdateForItemAndVersion(Map<String, Object> propertyMap) { + var item = new Document(); + item.putAll(propertyMap); + return new Update().set(VorgangAttachedItem.FIELDNAME_ITEM, item); } @Override public void patch(String itemId, long version, Map<String, Object> propertyMap) { mongoOperations.updateFirst( query(byIdAndVersion(itemId, version)), - createUpdateForItemAndVersion(propertyMap), + createPatchUpdateForItemAndVersion(propertyMap), VorgangAttachedItem.class); } @@ -76,7 +94,7 @@ public class VorgangAttachedItemCustomRepositoryImpl implements VorgangAttachedI public void forcePatch(String itemId, Map<String, Object> propertiesMap) { mongoOperations.updateFirst( createQueryById(itemId), - createUpdateForItemAndVersion(propertiesMap), + createPatchUpdateForItemAndVersion(propertiesMap), VorgangAttachedItem.class); } @@ -100,7 +118,7 @@ public class VorgangAttachedItemCustomRepositoryImpl implements VorgangAttachedI return where(VorgangAttachedItem.FIELDNAME_IS_DELETED).is(false); } - private Update createUpdateForItemAndVersion(Map<String, Object> propertyMap) { + private Update createPatchUpdateForItemAndVersion(Map<String, Object> propertyMap) { return Update.fromDocument(buildItemPatchDocument(propertyMap)).inc(VorgangAttachedItem.FIELDNAME_VERSION, 1); } diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemService.java index cf25401a6607aa4468ddad30e456b9f35924904f..2a6d887bac4dd22eae1201d9a1a28f9b64a20732 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemService.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemService.java @@ -74,13 +74,13 @@ public class VorgangAttachedItemService { } public void update(String commandId, VorgangAttachedItem item) { - repository.patch(item.getId(), item.getVersion(), item.getItem()); + repository.update(item.getId(), item.getVersion(), item.getItem()); publisher.publishEvent(new VorgangAttachedItemUpdatedEvent(commandId)); } public void forceUpdate(String commandId, VorgangAttachedItem item) { - repository.forcePatch(item.getId(), item.getItem()); + repository.forceUpdate(item.getId(), item.getItem()); publisher.publishEvent(new VorgangAttachedItemUpdatedEvent(commandId)); } diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/CommandRepository.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/CommandRepository.java index 6222d4569a6002727397d8df8c4a7b185f711219..da2142d42b5c41d75b9b0266bfe04edb2cef21f9 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/CommandRepository.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/CommandRepository.java @@ -36,10 +36,17 @@ import java.util.stream.Stream; import org.apache.commons.collections.MapUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.FindAndModifyOptions; import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.ComparisonOperators.Eq; +import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Switch; +import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Switch.CaseOperator; +import org.springframework.data.mongodb.core.aggregation.SetOperation; 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.stereotype.Repository; import com.google.common.collect.ObjectArrays; @@ -209,10 +216,6 @@ class CommandRepository { return findOne(query).map(Command::getBodyObject).map(this::getParentIdProperty); } - private Query queryById(String commandId) { - return query(criteriaById(commandId)); - } - private Criteria criteriaById(String commandId) { return where(MONGODB_ID).is(commandId); } @@ -258,4 +261,22 @@ class CommandRepository { query.fields().include(MONGODB_CLASS, MONGODB_ID); return query; } + + public Command setRevokeStatus(String id) { + return mongoOperations.findAndModify(queryById(id), buildUpdateStatusRevoke(), FindAndModifyOptions.options().returnNew(true), Command.class); + } + + private Query queryById(String commandId) { + return query(criteriaById(commandId)); + } + + private UpdateDefinition buildUpdateStatusRevoke() { + var switchOperation = Switch.switchCases( + CaseOperator.when(Eq.valueOf(MONGODB_STATUS).equalToValue(CommandStatus.NEW)).then(CommandStatus.CANCELED), + CaseOperator.when(Eq.valueOf(MONGODB_STATUS).equalToValue(CommandStatus.PENDING)).then(CommandStatus.REVOKE_PENDING), + CaseOperator.when(Eq.valueOf(MONGODB_STATUS).equalToValue(CommandStatus.FINISHED)).then(CommandStatus.REVOKE_PENDING) + ).defaultTo("$status"); + return Aggregation.newUpdate(SetOperation.set(MONGODB_STATUS).toValue(switchOperation)); + } + } diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/CommandService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/CommandService.java index 9b2e19c7f3172cea9a1dcc813bcc4e37774f7793..aa5c3102a12e16109046248cb4f3b6d34117c8d6 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/CommandService.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/CommandService.java @@ -24,6 +24,7 @@ package de.ozgcloud.vorgang.command; import static de.ozgcloud.vorgang.command.PersistedCommand.*; +import static java.util.Objects.*; import java.time.ZonedDateTime; import java.util.Collection; @@ -36,7 +37,6 @@ import java.util.UUID; import java.util.function.Consumer; import java.util.stream.Stream; -import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; @@ -109,10 +109,6 @@ public class CommandService { return command; } - public Optional<Command> findCommand(String commandId) { - return repository.getById(commandId); - } - public Stream<Command> findFinishedVorgangLoeschenCommandsOlderThen(@NonNull ZonedDateTime createdAfter) { return repository.findCommands(Order.VORGANG_LOESCHEN, CommandStatus.FINISHED, createdAfter); } @@ -122,6 +118,11 @@ public class CommandService { } public void setCommandFinished(String commandId, String createdResource) { + var command = getCommand(commandId); + if (command.getStatus() == CommandStatus.REVOKE_PENDING) { + publishRevokeCommandEvent(command); + return; + } Optional.ofNullable(createdResource).ifPresentOrElse( resource -> repository.finishCommand(commandId, resource), () -> repository.finishCommand(commandId)); @@ -150,34 +151,24 @@ public class CommandService { } public void setCommandRevoked(String commandId) { - updateCommandStatus(commandId, CommandStatus.REVOKED); - } - - public void setCommandRevokePending(final String commandId) { - updateCommandStatus(commandId, CommandStatus.REVOKE_PENDING); - - publishRevokeCommandEvent(commandId); + repository.updateCommandStatus(commandId, CommandStatus.REVOKED); } - void publishRevokeCommandEvent(final String commandId) { - var commandOptional = repository.getById(commandId); - commandOptional.ifPresentOrElse(command -> { - if (MapUtils.isEmpty(((PersistedCommand) command).getPreviousState())) { - handleError(commandId, "Command %s can not be revoked because it has no previous state."); - } - publisher.publishEvent(new RevokeCommandEvent(command)); - }, () -> handleError(commandId, "No Command with id %s found.")); + public Command revokeCommand(final String commandId) { + var updatedCommand = setRevokeStatus(commandId); + if (isNull(updatedCommand)) { + throw new NotFoundException(Command.class, commandId); + } + publishRevokeCommandEvent(updatedCommand); + return updatedCommand; } - private void handleError(String commandId, String messageTemplate) { - var message = messageTemplate.formatted(commandId); - LOG.error(message); - publisher.publishEvent(new CommandFailedEvent(commandId, message)); - throw new TechnicalException(message); + public Command setRevokeStatus(String commandId) { + return repository.setRevokeStatus(commandId); } - void updateCommandStatus(String commandId, CommandStatus status) { - repository.updateCommandStatus(commandId, status); + void publishRevokeCommandEvent(Command command) { + publisher.publishEvent(new RevokeCommandEvent(command)); } public boolean existsPendingCommands(String vorgangId) { @@ -212,13 +203,21 @@ public class CommandService { } Command getNotFailedCommand(String commandId) { - var command = findCommand(commandId).orElseThrow(() -> new NotFoundException(Command.class, commandId)); + var command = getCommand(commandId); if (command.getStatus() == CommandStatus.ERROR) { throw new TechnicalException("Cannot add sub commands to failed command ('%s')".formatted(commandId)); } return command; } + public Command getCommand(String commandId) { + return findCommand(commandId).orElseThrow(() -> new NotFoundException(Command.class, commandId)); + } + + public Optional<Command> findCommand(String commandId) { + return repository.getById(commandId); + } + Map<String, Object> buildSubCommandValues(CreateSubCommandsRequest request) { var executionMode = request.getExecutionMode(); return Map.of( diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/GrpcCommandService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/GrpcCommandService.java index 4237a4c2f986161b0b76880b2aa50409e751fd68..2a63348c24d5b4370aaa94c0a16f3ca7dc91c167 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/GrpcCommandService.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/GrpcCommandService.java @@ -36,7 +36,6 @@ import org.springframework.context.ApplicationEventPublisher; import de.ozgcloud.command.Command; import de.ozgcloud.command.CommandExecutedEvent; import de.ozgcloud.command.CommandStatus; -import de.ozgcloud.common.errorhandling.TechnicalException; import de.ozgcloud.vorgang.command.CommandResponse.ResponseCode; import de.ozgcloud.vorgang.common.errorhandling.NotFoundException; import de.ozgcloud.vorgang.common.security.PolicyService; @@ -92,11 +91,6 @@ public class GrpcCommandService extends CommandServiceImplBase { responseObserver.onCompleted(); } - // TODO check - is this function still in use? - boolean isStatusChangeOrder(String order) { - return order.startsWith("VORGANG"); - } - @Override public void getCommand(GrpcGetCommandRequest request, StreamObserver<GrpcCommand> responseObserver) { policyService.checkPermissionByCommand(request.getId()); @@ -110,19 +104,9 @@ public class GrpcCommandService extends CommandServiceImplBase { @Override public void revokeCommand(GrpcRevokeCommandRequest request, StreamObserver<GrpcCommandResponse> responseObserver) { policyService.checkPermissionByCommand(request.getId()); - - String commandId = request.getId(); - - try { - commandService.setCommandRevokePending(commandId); - - Command updatedCommand = getCommand(commandId); - - responseObserver.onNext(mapToGrpcResponse(updatedCommand)); - responseObserver.onCompleted(); - } catch (TechnicalException e) { - responseObserver.onError(e); - } + var updatedCommand = commandService.revokeCommand(request.getId()); + responseObserver.onNext(mapToGrpcResponse(updatedCommand)); + responseObserver.onCompleted(); } Command getCommand(String commandId) { diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/errorhandling/ExceptionHandler.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/errorhandling/ExceptionHandler.java index d5a1c169125ab7a84e1d734ad6e85f6adf5adccf..11318a0cf33d16cc95296cef55b61c1f08a878b8 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/errorhandling/ExceptionHandler.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/errorhandling/ExceptionHandler.java @@ -41,11 +41,14 @@ import net.devh.boot.grpc.server.advice.GrpcExceptionHandler; @GrpcAdvice public class ExceptionHandler { + private static final String ERROR_MESSAGE = "Grpc internal server error"; + static final String KEY_ERROR_CODE = "ERROR_CODE"; static final String KEY_EXCEPTION_ID = "EXCEPTION_ID"; @GrpcExceptionHandler public StatusException handleNotFoundException(NotFoundException e) { + LOG.error(ERROR_MESSAGE, e); return createStatusException(buildNotFoundStatus(e), buildMetadata(e)); } @@ -55,6 +58,7 @@ public class ExceptionHandler { @GrpcExceptionHandler public StatusException handleFunctionalException(FunctionalException e) { + LOG.error(ERROR_MESSAGE, e); return createStatusException(buildInternalStatus(e), buildMetadata(e)); } @@ -77,6 +81,7 @@ public class ExceptionHandler { @GrpcExceptionHandler public StatusException handleTechnicalException(TechnicalException e) { + LOG.error(ERROR_MESSAGE, e); return createStatusException(buildInternalStatus(e), buildMetadata(e.getExceptionId())); } @@ -88,7 +93,7 @@ public class ExceptionHandler { public StatusException handleRuntimeException(RuntimeException e) { var exceptionId = createExceptionId(); var messageWithExceptionId = ExceptionUtil.formatMessageWithExceptionId(e.getMessage(), exceptionId); - LOG.error("Grpc internal server error: " + messageWithExceptionId, e); + LOG.error("Grpc internal server error: {}", messageWithExceptionId, e); return createStatusException(buildInternalStatus(e, messageWithExceptionId), buildMetadata(exceptionId)); } @@ -123,7 +128,7 @@ public class ExceptionHandler { public StatusException handleAccessDeniedException(AccessDeniedException e) { var exceptionId = createExceptionId(); var messageWithExceptionId = ExceptionUtil.formatMessageWithExceptionId(e.getMessage(), exceptionId); - LOG.error("Grpc permission denied error: " + messageWithExceptionId, e); + LOG.error("Grpc permission denied error: {}", messageWithExceptionId, e); return createStatusException(buildPermissionDeniedStatus(e, messageWithExceptionId), buildMetadata(exceptionId)); } @@ -134,6 +139,7 @@ public class ExceptionHandler { @GrpcExceptionHandler public StatusException handleSearchServiceUnavailableException(SearchServiceUnavailableException e) { + LOG.error(ERROR_MESSAGE, e); return createStatusException(buildUnavailableStatus(e), buildMetadata(e.getExceptionId())); } diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/migration/M010_CreateIndexesInVorgang.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/migration/M010_CreateIndexesInVorgang.java new file mode 100644 index 0000000000000000000000000000000000000000..172953ca2820282a18a978e7ac012e7379cd55eb --- /dev/null +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/migration/M010_CreateIndexesInVorgang.java @@ -0,0 +1,68 @@ +/* + * 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 org.bson.Document; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.index.CompoundIndexDefinition; +import org.springframework.data.mongodb.core.index.IndexDefinition; + +import io.mongock.api.annotations.ChangeUnit; +import io.mongock.api.annotations.Execution; +import io.mongock.api.annotations.RollbackExecution; + +@ChangeUnit(id = "2024-06-12 17:00:00 OZG-5661", order = "M010", author = "ebardin") +public class M010_CreateIndexesInVorgang { // NOSONAR + + static final String COLLECTION_NAME = "vorgang"; + static final String COMPOUND_INDEX_NAME = "inCreation_OrgaId_assignedTo_status_createdAt"; + + static final String FIELD_IN_CREATION = "inCreation"; + static final String FIELD_STATUS = "status"; + static final String FIELD_CREATED_AT = "createdAt"; + static final String FIELD_ASSIGNED_TO = "assignedTo"; + static final String FIELD_EINGANGS = "eingangs"; + static final String FIELD_ORGANISATIONSEINHEITEN_ID = FIELD_EINGANGS + ".zustaendigeStelle.organisationseinheitenId"; + + @Execution + public void doMigration(MongoTemplate template) { + template.indexOps(COLLECTION_NAME).ensureIndex(buildCompoundIndexDefinition()); + } + + IndexDefinition buildCompoundIndexDefinition() { + var indexKeys = new Document(); + indexKeys.put(FIELD_IN_CREATION, 1); + indexKeys.put(FIELD_ORGANISATIONSEINHEITEN_ID, 1); + indexKeys.put(FIELD_ASSIGNED_TO, 1); + indexKeys.put(FIELD_STATUS, 1); + indexKeys.put(FIELD_CREATED_AT, 1); + return new CompoundIndexDefinition(indexKeys).named(COMPOUND_INDEX_NAME); + } + + @RollbackExecution + public void rollback() { + // kein rollback implementiert + } + +} diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/search/MongodbFullTextSearchIndexInitializer.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/search/MongodbFullTextSearchIndexInitializer.java new file mode 100644 index 0000000000000000000000000000000000000000..105b8bdffbb95a96514c9fe65c0100da5820a12a --- /dev/null +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/search/MongodbFullTextSearchIndexInitializer.java @@ -0,0 +1,59 @@ +/* + * 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.search; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.index.TextIndexDefinition; +import org.springframework.stereotype.Component; + +import de.ozgcloud.vorgang.vorgang.Vorgang; +import lombok.RequiredArgsConstructor; + +@ConditionalOnProperty(prefix = "ozgcloud.mongodbsearch", name = "enabled", havingValue = "true") +@Component +@RequiredArgsConstructor +public class MongodbFullTextSearchIndexInitializer { + + static final String TEXT_INDEX_NAME = "textIndex"; + static final String TEXT_INDEX_LANGUAGE = "german"; + + private final MongoOperations mongoOperations; + + @EventListener(ContextRefreshedEvent.class) + public void initWildcardTextIndex() { + mongoOperations.indexOps(Vorgang.COLLECTION_NAME).ensureIndex(buildWildcardTextIndexDefinition()); + } + + TextIndexDefinition buildWildcardTextIndexDefinition() { + return new TextIndexDefinition.TextIndexDefinitionBuilder() + .named(TEXT_INDEX_NAME) + .onAllFields() + .withDefaultLanguage(TEXT_INDEX_LANGUAGE) + .build(); + } + +} diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/search/SearchService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/search/SearchService.java index 19533cbeee1904c697846e124744baf09a841c31..1d7a92f2e8d7be97c40a63f717454a23c3702942 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/search/SearchService.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/search/SearchService.java @@ -23,10 +23,12 @@ */ package de.ozgcloud.vorgang.common.search; +import static java.util.Objects.*; + import java.util.Collections; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; @@ -37,10 +39,10 @@ import de.ozgcloud.vorgang.vorgang.Vorgang; import de.ozgcloud.vorgang.vorgang.VorgangHeader; @Service -@ConditionalOnProperty(prefix = "ozgcloud.elasticsearch", name = "address") +@ConditionalOnExpression("!'${ozgcloud.elasticsearch.address:}'.isEmpty() || ${ozgcloud.mongodbsearch.enabled:false}") public class SearchService { - @Autowired + @Autowired(required = false) private SearchVorgangRepository repository; @Autowired @@ -49,16 +51,15 @@ public class SearchService { @Autowired private IndexedVorgangMapper mapper; + // TODO die Methode scheint überfüssig zu sein. Man könnte stattsie und ' updateVorgang' eine Methode z.B. 'addOrUpdateVorgang' nutzen. public void addVorgang(Vorgang vorgang) { - repository.save(mapper.fromVorgang(vorgang)); + updateVorgang(vorgang); } public void updateVorgang(Vorgang vorgang) { - repository.save(mapper.fromVorgang(vorgang)); - } - - public boolean existsById(String vorgangId) { - return repository.existsById(vorgangId); + if (nonNull(repository)) { + repository.save(mapper.fromVorgang(vorgang)); + } } public Page<VorgangHeader> find(FindVorgangRequest findVorgangRequest) { @@ -70,7 +71,9 @@ public class SearchService { } public void deleteVorgang(String vorgangId) { - repository.deleteById(vorgangId); + if (nonNull(repository)) { + repository.deleteById(vorgangId); + } } public void setAktenzeichen(String vorgangId, String aktenzeichen) { diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/search/SearchVorgangCustomRepository.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/search/SearchVorgangCustomRepository.java index 7c1a2a19abae7c802e7054c3713d1dfdd6255879..3a58f8f74cfc1c23b8fdc5f217ddc64e488d4244 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/search/SearchVorgangCustomRepository.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/search/SearchVorgangCustomRepository.java @@ -25,13 +25,11 @@ package de.ozgcloud.vorgang.common.search; import java.util.Map; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.domain.Page; import de.ozgcloud.vorgang.vorgang.FindVorgangRequest; import de.ozgcloud.vorgang.vorgang.VorgangHeader; -@ConditionalOnProperty(prefix = "ozgcloud.elasticsearch", name = "address") interface SearchVorgangCustomRepository { Page<VorgangHeader> searchBy(FindVorgangRequest request); diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/search/SearchVorgangCustomRepositoryImpl.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/search/SearchVorgangCustomRepositoryImpl.java index cfd983a2828f8edc7d56c2f0457292615becbaca..47fe7b83020c3fbd880df9c0abd3aceaeef23ccd 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/search/SearchVorgangCustomRepositoryImpl.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/search/SearchVorgangCustomRepositoryImpl.java @@ -39,6 +39,7 @@ import java.util.stream.Stream; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder; @@ -61,6 +62,7 @@ import de.ozgcloud.vorgang.vorgang.FindVorgangRequest; import de.ozgcloud.vorgang.vorgang.Vorgang; import de.ozgcloud.vorgang.vorgang.VorgangHeader; +@ConditionalOnProperty(prefix = "ozgcloud.elasticsearch", name = "address") @Repository class SearchVorgangCustomRepositoryImpl implements SearchVorgangCustomRepository { diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/search/SearchVorgangCustomRepositoryMongo.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/search/SearchVorgangCustomRepositoryMongo.java new file mode 100644 index 0000000000000000000000000000000000000000..51570c813a3d09f2ba0373b598384429f736dbaa --- /dev/null +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/common/search/SearchVorgangCustomRepositoryMongo.java @@ -0,0 +1,76 @@ +/* + * 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.search; + +import java.util.Arrays; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.TextCriteria; +import org.springframework.data.mongodb.core.query.TextQuery; +import org.springframework.data.support.PageableExecutionUtils; +import org.springframework.stereotype.Repository; + +import de.ozgcloud.vorgang.vorgang.FindVorgangRequest; +import de.ozgcloud.vorgang.vorgang.VorgangHeader; +import lombok.RequiredArgsConstructor; + +@ConditionalOnProperty(prefix = "ozgcloud.mongodbsearch", name = "enabled", havingValue = "true") +@RequiredArgsConstructor +@Repository +public class SearchVorgangCustomRepositoryMongo implements SearchVorgangCustomRepository { + + private final MongoOperations mongoOperations; + + @Override + public Page<VorgangHeader> searchBy(final FindVorgangRequest request) { + var query = buildQuery(request); + var vorgangs = mongoOperations.stream(query, VorgangHeader.class).skip(request.getOffset()).limit(request.getLimit()).toList(); + return PageableExecutionUtils.getPage(vorgangs, buildPageable(request), vorgangs::size); + } + + private Query buildQuery(FindVorgangRequest request) { + return TextQuery.queryText(buildTextCriteria(request.getSearchBy())).sortByScore(); + } + + TextCriteria buildTextCriteria(String searchBy) { + var textCriteria = TextCriteria.forDefaultLanguage(); + Arrays.stream(searchBy.split(StringUtils.SPACE)).forEach(textCriteria::matchingPhrase); + return textCriteria; + } + + PageRequest buildPageable(FindVorgangRequest request) { + return PageRequest.of(request.getOffset() / request.getLimit(), request.getLimit()); + } + + @Override + public void update(final String vorangId, final Map<String, Object> patch) { + // update implemented in vorgang.vorgang.VorgangRepository + } +} diff --git a/vorgang-manager-server/src/main/resources/application-local.yml b/vorgang-manager-server/src/main/resources/application-local.yml index b1aa3707359cf0cc5a250ea5b76b2a86016d7c06..3c001700cf121f982f772d96606412189ad50db3 100644 --- a/vorgang-manager-server/src/main/resources/application-local.yml +++ b/vorgang-manager-server/src/main/resources/application-local.yml @@ -65,9 +65,6 @@ ozgcloud: user: cleanup: cron: 0 */5 * * * * - elasticsearch: - initEnabled: true - index: test-index feature: bescheid: enable-dummy-document-processor: true @@ -85,10 +82,14 @@ spring: - with-elastic ozgcloud: elasticsearch: + initEnabled: true + index: test-index address: localhost:9200 username: elastic password: password useSsl: false + mongodbsearch: + enabled: false --- @@ -126,3 +127,14 @@ grpc: bayern-id: address: static://127.0.0.1:9099 negotiationType: PLAINTEXT + +--- + +spring: + config: + activate: + on-profile: + - with-mongodbsearch +ozgcloud: + mongodbsearch: + enabled: true diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/bescheid/BescheidDocumentITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/bescheid/BescheidDocumentITCase.java index 54cfc7b0679835e54404911bcb0c8945533ed8d7..9a57cb6a5f7b082ba783569f1daf7c765b183d5c 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/bescheid/BescheidDocumentITCase.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/bescheid/BescheidDocumentITCase.java @@ -90,8 +90,8 @@ class BescheidDocumentITCase { .vorgangId("vorgang-id") .relationId("bescheidItem-id") .order(BescheidEventListener.CREATE_BESCHEID_DOCUMENT_ORDER) - .bodyObject(Map.of(BESCHEID_VOM_BODYKEY, "2024-01-01", - GENEHMIGT_BODYKEY, true)) + .bodyObject(Map.of(BESCHEID_VOM_BODY_KEY, "2024-01-01", + GENEHMIGT_BODY_KEY, true)) .build(); } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/bescheid/BescheidEventListenerITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/bescheid/BescheidITCase.java similarity index 95% rename from vorgang-manager-server/src/test/java/de/ozgcloud/bescheid/BescheidEventListenerITCase.java rename to vorgang-manager-server/src/test/java/de/ozgcloud/bescheid/BescheidITCase.java index 17e8ba106578761e7b8965205d94831df337f411..0926bd140fa344c6435b74befbda978e161dbf81 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/bescheid/BescheidEventListenerITCase.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/bescheid/BescheidITCase.java @@ -73,8 +73,8 @@ import de.ozgcloud.vorgang.VorgangManagerServerApplication; import de.ozgcloud.vorgang.attached_item.VorgangAttachedItem; import de.ozgcloud.vorgang.attached_item.VorgangAttachedItemTestFactory; import de.ozgcloud.vorgang.callcontext.CallContextAuthenticationTokenTestFactory; +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.servicekonto.ServiceKontoTestFactory; import de.ozgcloud.vorgang.status.StatusChangedEvent; @@ -93,7 +93,8 @@ import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; }) @DataITCase @DirtiesContext -class BescheidEventListenerITCase { +@WithMockCustomUser +class BescheidITCase { private static final String USER_ID = "user-id"; @@ -133,7 +134,7 @@ class BescheidEventListenerITCase { var vorgangId = mongoOperations.save(VorgangTestFactory.createBuilder().id(null).build()).getId(); var bescheidItemId = mongoOperations.save(createBescheidAttachedItem(vorgangId)).getId(); - eventPublisher.publishEvent(buildCommandCreatedEvent(vorgangId, bescheidItemId)); + commandService.createCommand(buildCreatedCommandRequest(vorgangId, bescheidItemId)); await().atMost(60, TimeUnit.SECONDS).untilAsserted(() -> { verify(bescheiTestEventListener).onBescheidDocumentCreated(bescheidDocumentCreatedEventCaptor.capture()); @@ -146,7 +147,7 @@ class BescheidEventListenerITCase { var vorgangId = mongoOperations.save(VorgangTestFactory.createBuilder().id(null).build()).getId(); var bescheidItemId = mongoOperations.save(createBescheidAttachedItem(vorgangId)).getId(); - eventPublisher.publishEvent(buildCommandCreatedEvent(vorgangId, bescheidItemId)); + commandService.createCommand(buildCreatedCommandRequest(vorgangId, bescheidItemId)); await().atMost(60, TimeUnit.SECONDS).untilAsserted(() -> { verify(bescheiTestEventListener).onBescheidDocumentCreated(bescheidDocumentCreatedEventCaptor.capture()); @@ -164,15 +165,14 @@ class BescheidEventListenerITCase { .build(); } - private CommandCreatedEvent buildCommandCreatedEvent(String vorgangId, String bescheidItemId) { - var command = CommandTestFactory.createBuilder() + private CreateCommandRequest buildCreatedCommandRequest(String vorgangId, String bescheidItemId) { + return CreateCommandRequest.builder() .vorgangId(vorgangId) .relationId(bescheidItemId) .order(BescheidEventListener.CREATE_BESCHEID_DOCUMENT_ORDER) - .bodyObject(Map.of(BESCHEID_VOM_BODYKEY, "2024-01-01", - GENEHMIGT_BODYKEY, true)) + .bodyObject(Map.of(BESCHEID_VOM_BODY_KEY, "2024-01-01", + GENEHMIGT_BODY_KEY, true)) .build(); - return new CommandCreatedEvent(command); } private GridFSFile getBescheidDocumentFile() { @@ -236,15 +236,15 @@ class BescheidEventListenerITCase { } private CommandCreatedEvent buildCommandCreatedEvent(String vorgangId, VorgangAttachedItem bescheidItem) { - var command = CommandTestFactory.createBuilder() + var createCommandRequest = CreateCommandRequest.builder() .vorgangId(vorgangId) .relationId(bescheidItem.getId()) .relationVersion(bescheidItem.getVersion()) .order(BescheidEventListener.SEND_BESCHEID_ORDER) - .bodyObject(Map.of(BESCHEID_VOM_BODYKEY, "2024-01-01", - GENEHMIGT_BODYKEY, true)) + .bodyObject(Map.of(BESCHEID_VOM_BODY_KEY, "2024-01-01", + GENEHMIGT_BODY_KEY, true)) .build(); - return new CommandCreatedEvent(command); + return new CommandCreatedEvent(commandService.createCommand(createCommandRequest)); } private VorgangAttachedItem loadBescheid(String bescheidId) { @@ -405,8 +405,8 @@ class BescheidEventListenerITCase { .relationId(bescheidItem.getId()) .relationVersion(bescheidItem.getVersion()) .order(SEND_BESCHEID_ORDER) - .bodyObject(Map.of(BESCHEID_VOM_BODYKEY, "2024-01-01", - GENEHMIGT_BODYKEY, true)) + .bodyObject(Map.of(BESCHEID_VOM_BODY_KEY, "2024-01-01", + GENEHMIGT_BODY_KEY, true)) .build(); } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/VorgangManagerCallContextProviderTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/VorgangManagerCallContextProviderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1ab89f43444d623ec62d3cd493d4b2155e06c276 --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/VorgangManagerCallContextProviderTest.java @@ -0,0 +1,118 @@ +/* + * 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; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.Optional; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; + +import de.ozgcloud.apilib.user.OzgCloudUserId; +import de.ozgcloud.vorgang.callcontext.CallContextUser; +import de.ozgcloud.vorgang.callcontext.CurrentUserService; +import de.ozgcloud.vorgang.callcontext.VorgangManagerClientCallContextAttachingInterceptor; + +class VorgangManagerCallContextProviderTest { + + private static final String USER_ID = "userId"; + + @Spy + @InjectMocks + private VorgangManagerCallContextProvider contextProvider; + + @Mock + private CurrentUserService currentUserService; + + @Nested + class TestProvideContext { + + @Test + void shouldSetClientName() { + var result = contextProvider.provideContext(); + + assertThat(result.getClientName()).isEqualTo(VorgangManagerClientCallContextAttachingInterceptor.VORGANG_MANAGER_CLIENT_NAME); + } + + @Test + void shouldCallFindUserId() { + contextProvider.provideContext(); + + verify(contextProvider).findUserId(); + } + + @Test + void shouldSetUserId() { + var userId = OzgCloudUserId.from(USER_ID); + when(contextProvider.findUserId()).thenReturn(Optional.of(userId)); + + var result = contextProvider.provideContext(); + + assertThat(result.getUserId()).isEqualTo(userId); + } + } + + @Nested + class TestFindUserId { + + @Test + void shouldCallCurrentUserService() { + contextProvider.findUserId(); + + verify(currentUserService).findUser(); + } + + @Test + void shouldReturnUserId() { + var callContextUser = CallContextUser.builder().userId(Optional.of(USER_ID)).build(); + when(currentUserService.findUser()).thenReturn(Optional.of(callContextUser)); + + var result = contextProvider.findUserId(); + + assertThat(result).contains(OzgCloudUserId.from(USER_ID)); + } + + @Test + void shouldReturnIfNoUser() { + var result = contextProvider.findUserId(); + + assertThat(result).isEmpty(); + } + + @Test + void shouldReturnIfNoUserId() { + var callContextUser = CallContextUser.builder().userId(Optional.empty()).build(); + when(currentUserService.findUser()).thenReturn(Optional.of(callContextUser)); + + var result = contextProvider.findUserId(); + + assertThat(result).isEmpty(); + } + } +} \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemITCase.java index 79ce07e580ab83de2d02838ad6837fc009116650..2612de99af4c87b621fdbd554082ffe1f0fd6ae8 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemITCase.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemITCase.java @@ -45,13 +45,13 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.security.access.AccessDeniedException; -import de.ozgcloud.command.Command; import de.ozgcloud.command.CommandCreatedEvent; import de.ozgcloud.common.test.DataITCase; import de.ozgcloud.vorgang.callcontext.CallContextUserTestFactory; import de.ozgcloud.vorgang.callcontext.CurrentUserService; -import de.ozgcloud.vorgang.command.CommandCreatedEventTestFactory; -import de.ozgcloud.vorgang.command.CommandTestFactory; +import de.ozgcloud.vorgang.command.CommandService; +import de.ozgcloud.vorgang.command.CreateCommandRequest; +import de.ozgcloud.vorgang.command.CreateCommandRequestTestFactory; import de.ozgcloud.vorgang.command.Order; import de.ozgcloud.vorgang.vorgang.Vorgang; import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; @@ -73,6 +73,8 @@ class VorgangAttachedItemITCase { private ApplicationEventPublisher publisher; @Autowired private MongoOperations mongoOperations; + @Autowired + private CommandService commandService; @BeforeEach void prepareDatabase() { @@ -83,13 +85,16 @@ class VorgangAttachedItemITCase { @Nested class TestCreateItem { - private Command command = CommandTestFactory.createBuilder().order(Order.CREATE_ATTACHED_ITEM.toString()) - .relationId(VorgangTestFactory.ID).bodyObject(VorgangAttachedItemTestFactory.asMap()).build(); - private CommandCreatedEvent event = CommandCreatedEventTestFactory.create(command); + private final CreateCommandRequest createCommandRequest = CreateCommandRequestTestFactory.createBuilder() + .order(Order.CREATE_ATTACHED_ITEM.name()) + .relationId(VorgangTestFactory.ID) + .bodyObject(VorgangAttachedItemTestFactory.asMap()) + .build(); @Test void shouldPersistInDatabase() { - publisher.publishEvent(event); + commandService.createCommand(createCommandRequest); + await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { var loadedItems = mongoOperations.findAll(VorgangAttachedItem.class); @@ -106,7 +111,7 @@ class VorgangAttachedItemITCase { @Nested class TestUpdateItem { - private Command command; + private CreateCommandRequest createCommandRequest; private CommandCreatedEvent event; private VorgangAttachedItem persistedItem; @@ -123,18 +128,16 @@ class VorgangAttachedItemITCase { var bodyObjectMap = new HashMap<>(VorgangAttachedItemTestFactory.asMap()); bodyObjectMap.put(VorgangAttachedItem.FIELDNAME_ITEM, itemMap); - command = CommandTestFactory.createBuilder() + createCommandRequest = CreateCommandRequestTestFactory.createBuilder() .order(Order.UPDATE_ATTACHED_ITEM.name()) .relationId(persistedItem.getId()) .relationVersion(persistedItem.getVersion()) .bodyObject(bodyObjectMap).build(); - - event = CommandCreatedEventTestFactory.create(command); } @Test void shouldIncrementVersion() { - publisher.publishEvent(event); + commandService.createCommand(createCommandRequest); await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { var loaded = mongoOperations.findAll(VorgangAttachedItem.class); diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemRepositoryITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemRepositoryITCase.java index 8145e0f6cd15c9f4c51d9323b9e2f2fae33ff819..f9c325212a072878c07fa8e88769812d229fa510 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemRepositoryITCase.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemRepositoryITCase.java @@ -28,6 +28,7 @@ import static org.junit.jupiter.api.Assertions.*; import java.util.Collections; import java.util.ConcurrentModificationException; +import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.BeforeEach; @@ -43,6 +44,8 @@ import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; @DataITCase class VorgangAttachedItemRepositoryITCase { + private static final Map<String, Object> UPDATE_ITEM_MAP = Map.of("dontTouch", 600); + @Autowired private VorgangAttachedItemRepository repository; @Autowired @@ -145,19 +148,90 @@ class VorgangAttachedItemRepositoryITCase { } } + @Nested + class TestUpdate { + + @BeforeEach + void init() { + persistedItem = mongoOperations.save(VorgangAttachedItemTestFactory.createBuilder().id(null).version(0).build()); + } + + @Test + void shouldUpdate() { + var result = updateItem(); + + assertThat(result.getItem()).containsExactlyEntriesOf(UPDATE_ITEM_MAP); + assertThat(result.getVersion()).isEqualTo(2); + } + + @Test + void shouldNotUpdateDeletedItem() { + persistedItem = mongoOperations.save(VorgangAttachedItemTestFactory.createBuilder().id(null).version(0).deleted(true).build()); + + var result = updateItem(); + + assertThat(result.getItem()).isEqualTo(VorgangAttachedItemTestFactory.STRING_MAP); + assertThat(result.getVersion()).isEqualTo(1); + } + + @Test + void shouldFailOnCollision() { + var itemId = persistedItem.getId(); + + assertThrows(ConcurrentModificationException.class, () -> repository.update(itemId, 0, UPDATE_ITEM_MAP)); + } + + VorgangAttachedItem updateItem() { + repository.update(persistedItem.getId(), persistedItem.getVersion(), UPDATE_ITEM_MAP); + return mongoOperations.findById(persistedItem.getId(), VorgangAttachedItem.class); + } + + } + + @Nested + class TestForceUpdate { + + @BeforeEach + void init() { + persistedItem = mongoOperations.save(VorgangAttachedItemTestFactory.createBuilder().id(null).version(0).build()); + } + + @Test + void shouldUpdate() { + var result = forceUpdateItem(); + + assertThat(result.getItem()).containsExactlyEntriesOf(UPDATE_ITEM_MAP); + assertThat(result.getVersion()).isEqualTo(2); + } + + @Test + void shouldNotUpdateDeletedItem() { + persistedItem = mongoOperations.save(VorgangAttachedItemTestFactory.createBuilder().id(null).version(0).deleted(true).build()); + + var result = forceUpdateItem(); + + assertThat(result.getItem()).isEqualTo(VorgangAttachedItemTestFactory.STRING_MAP); + assertThat(result.getVersion()).isEqualTo(1); + } + + VorgangAttachedItem forceUpdateItem() { + repository.forceUpdate(persistedItem.getId(), UPDATE_ITEM_MAP); + return mongoOperations.findById(persistedItem.getId(), VorgangAttachedItem.class); + } + + } + @Nested class TestPatch { @BeforeEach void prepareDatabase() { - mongoOperations.dropCollection(VorgangAttachedItem.COLLECTION_NAME); - persistedItem = mongoOperations.save(VorgangAttachedItemTestFactory.createBuilder().id(null).version(0).build()); } @Test void shouldUpdateFieldValue() { - repository.patch(persistedItem.getId(), persistedItem.getVersion(), buildPatchMap()); + repository.patch(persistedItem.getId(), persistedItem.getVersion(), UPDATE_ITEM_MAP); var loaded = findVorgangAttachedItem(); assertThat(loaded).isNotNull(); @@ -178,7 +252,7 @@ class VorgangAttachedItemRepositoryITCase { @Test void shouldIncreaseVersion() { - repository.patch(persistedItem.getId(), persistedItem.getVersion(), buildPatchMap()); + repository.patch(persistedItem.getId(), persistedItem.getVersion(), UPDATE_ITEM_MAP); var loaded = findVorgangAttachedItem(); assertThat(loaded).isNotNull(); @@ -187,7 +261,7 @@ class VorgangAttachedItemRepositoryITCase { @Test void shouldNotUpdateOnWrongVersion() { - repository.patch(persistedItem.getId(), 42, buildPatchMap()); + repository.patch(persistedItem.getId(), 42, UPDATE_ITEM_MAP); var loaded = findVorgangAttachedItem(); @@ -198,33 +272,40 @@ class VorgangAttachedItemRepositoryITCase { void shouldNotPatchDeletedItem() { var deletedItem = mongoOperations.save(VorgangAttachedItemTestFactory.createBuilder().id(null).version(0).deleted(true).build()); - repository.patch(deletedItem.getId(), deletedItem.getVersion(), buildPatchMap()); + repository.patch(deletedItem.getId(), deletedItem.getVersion(), UPDATE_ITEM_MAP); var loaded = mongoOperations.findById(deletedItem.getId(), VorgangAttachedItem.class); assertThat(loaded).usingRecursiveComparison().isEqualTo(deletedItem); } - private Map<String, Object> buildPatchMap() { - return Map.of("dontTouch", 600); - } } @Nested - class TestForceUpdate { + class TestForcePatch { + + @Test + void shouldPatch() { + var expectedItemMap = new HashMap<>(VorgangAttachedItemTestFactory.STRING_MAP); + expectedItemMap.putAll(UPDATE_ITEM_MAP); + var attachedItem = mongoOperations.save(VorgangAttachedItemTestFactory.createBuilder().id(null).version(0).build()); + + repository.forcePatch(attachedItem.getId(), UPDATE_ITEM_MAP); + + var result = mongoOperations.findById(attachedItem.getId(), VorgangAttachedItem.class); + assertThat(result.getItem()).containsExactlyEntriesOf(expectedItemMap); + assertThat(result.getVersion()).isEqualTo(2); + } @Test void shouldNotPatchDeletedItem() { var deletedItem = mongoOperations.save(VorgangAttachedItemTestFactory.createBuilder().id(null).version(0).deleted(true).build()); - repository.forcePatch(deletedItem.getId(), buildPatchMap()); + repository.forcePatch(deletedItem.getId(), UPDATE_ITEM_MAP); var loaded = mongoOperations.findById(deletedItem.getId(), VorgangAttachedItem.class); assertThat(loaded).usingRecursiveComparison().isEqualTo(deletedItem); } - private Map<String, Object> buildPatchMap() { - return Map.of("dontTouch", 600); - } } @Nested diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemServiceTest.java index 126d872284154d990daa7e429e7d866ca4988fae..e7d01613b0258fb7da57ad9b862b463e4f6ebaf7 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemServiceTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItemServiceTest.java @@ -36,6 +36,8 @@ import java.util.UUID; 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 org.mockito.Spy; @@ -114,36 +116,43 @@ class VorgangAttachedItemServiceTest { @Nested class TestUpdateItem { + @Captor + private ArgumentCaptor<VorgangAttachedItemUpdatedEvent> eventCaptor; @Test void shouldCallRepository() { service.update(CommandTestFactory.ID, VorgangAttachedItemTestFactory.create()); - verify(repository).patch(any(), anyLong(), any()); + verify(repository).update(VorgangAttachedItemTestFactory.ID, VERSION, STRING_MAP); } @Test void shouldPublishEvent() { service.update(CommandTestFactory.ID, VorgangAttachedItemTestFactory.create()); - verify(publisher).publishEvent(any(VorgangAttachedItemUpdatedEvent.class)); + verify(publisher).publishEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue()).extracting(VorgangAttachedItemUpdatedEvent::getSource).isEqualTo(CommandTestFactory.ID); } } @Nested class TestForceUpdateItem { + @Captor + private ArgumentCaptor<VorgangAttachedItemUpdatedEvent> eventCaptor; + @Test void shouldCallRepository() { service.forceUpdate(CommandTestFactory.ID, VorgangAttachedItemTestFactory.create()); - verify(repository).forcePatch(eq(VorgangAttachedItemTestFactory.ID), any()); + verify(repository).forceUpdate(VorgangAttachedItemTestFactory.ID, STRING_MAP); } @Test void shouldPublishEvent() { service.forceUpdate(CommandTestFactory.ID, VorgangAttachedItemTestFactory.create()); - verify(publisher).publishEvent(any(VorgangAttachedItemUpdatedEvent.class)); + verify(publisher).publishEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue()).extracting(VorgangAttachedItemUpdatedEvent::getSource).isEqualTo(CommandTestFactory.ID); } } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/CommandEventListenerTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/CommandEventListenerTest.java index 153af77891fc1df9973ceb7aee9b90251fd32bc7..fe0a42a246cc4018afe82709c0da52cb3a6109cd 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/CommandEventListenerTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/CommandEventListenerTest.java @@ -95,4 +95,5 @@ class CommandEventListenerTest { verify(commandService).setCommandRevoked(CommandTestFactory.ID); } } + } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/CommandRepositoryITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/CommandRepositoryITCase.java index cebb7e89bf49c7a6b6e4f4e71df53c4ec6f22020..ffb8365e0580e683c02e836748516f4f0d9a5214 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/CommandRepositoryITCase.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/CommandRepositoryITCase.java @@ -599,4 +599,50 @@ class CommandRepositoryITCase { assertThat(result).isEmpty(); } } + + @Nested + class TestSetRevokeStatus { + + @Test + void shouldSetCanceled() { + var command = mongoOperations.save(CommandTestFactory.createBuilder().id(null).status(CommandStatus.NEW).build()); + + repository.setRevokeStatus(command.getId()); + + var result = repository.getById(command.getId()); + assertThat(result).isPresent().get().extracting(Command::getStatus).isEqualTo(CommandStatus.CANCELED); + } + + @Test + void shouldSetRevokePendingWhenPending() { + var command = mongoOperations.save(CommandTestFactory.createBuilder().id(null).build()); + + repository.setRevokeStatus(command.getId()); + + var result = repository.getById(command.getId()); + assertThat(result).isPresent().get().extracting(Command::getStatus).isEqualTo(CommandStatus.REVOKE_PENDING); + } + + @Test + void shouldSetRevokePendingWhenFinished() { + var command = mongoOperations.save(CommandTestFactory.createBuilder().id(null).status(CommandStatus.FINISHED).build()); + + repository.setRevokeStatus(command.getId()); + + var result = repository.getById(command.getId()); + assertThat(result).isPresent().get().extracting(Command::getStatus).isEqualTo(CommandStatus.REVOKE_PENDING); + } + + @DisplayName("should not update when") + @ParameterizedTest(name = "status is {0}") + @EnumSource(value = CommandStatus.class, names = { "NEW", "PENDING", "FINISHED" }, mode = EnumSource.Mode.EXCLUDE) + void shouldNotUpdate(CommandStatus status) { + var command = mongoOperations.save(CommandTestFactory.createBuilder().id(null).status(status).build()); + + repository.setRevokeStatus(command.getId()); + + var result = repository.getById(command.getId()); + assertThat(result).isPresent().get().extracting(Command::getStatus).isEqualTo(status); + } + } } \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/CommandServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/CommandServiceTest.java index b34413c437df90ab5766b7421d7a527dd81c8319..7e5383aa33fd5870f925b8e33f9a0b194e15b191 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/CommandServiceTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/CommandServiceTest.java @@ -203,73 +203,124 @@ class CommandServiceTest { class TestSetCommandFinished { @Test - void shouldCallFinishCommand() { - service.setCommandFinished(CommandTestFactory.ID); + void shouldThrowExceptionIfCommandNotFound() { + doThrow(NotFoundException.class).when(service).getCommand(anyString()); - verify(service).setCommandFinished(CommandTestFactory.ID, null); + assertThrows(NotFoundException.class, () -> service.setCommandFinished(CommandTestFactory.ID)); } - @Test - void shouldCallRepositoryFinishCommand() { - service.setCommandFinished(CommandTestFactory.ID, null); + @Nested + class TestRevokeCommand { - verify(repository).finishCommand(CommandTestFactory.ID); - } + private Command command; - @Test - void shouldCallRepositoryFinishCommandWithCreatedResource() { - service.setCommandFinished(CommandTestFactory.ID, CommandTestFactory.CREATED_RESOURCE); + @BeforeEach + void init() { + command = CommandTestFactory.createBuilder().status(CommandStatus.REVOKE_PENDING).build(); + doReturn(command).when(service).getCommand(anyString()); + } - verify(repository).finishCommand(CommandTestFactory.ID, CommandTestFactory.CREATED_RESOURCE); - } + @Test + void shouldCallPublishRevokeCommandEvent() { + service.setCommandFinished(CommandTestFactory.ID); - @Test - void shouldCallGetCompletableParentId() { - service.setCommandFinished(CommandTestFactory.ID); + verify(service).publishRevokeCommandEvent(command); + } + + @Test + void shouldNotCallFinishCommandIfRevokePending() { + service.setCommandFinished(CommandTestFactory.ID); + + verify(repository, never()).finishCommand(anyString()); + verify(repository, never()).finishCommand(anyString(), anyString()); + } - verify(service).getCompletableParentId(CommandTestFactory.ID); } - @Test - void shouldCallExistsNotFinishedSubCommands() { - var parentId = "parent-id"; - doReturn(Optional.of(parentId)).when(service).getCompletableParentId(any()); + @Nested + class TestFinishCommand { - service.setCommandFinished(CommandTestFactory.ID, CommandTestFactory.CREATED_RESOURCE); + @BeforeEach + void init() { + doReturn(CommandTestFactory.create()).when(service).getCommand(anyString()); + } - verify(repository).existsNotFinishedSubCommands(parentId); - } + @Test + void shouldCallGetCommand() { + service.setCommandFinished(CommandTestFactory.ID); - @Test - void shouldCallPublishCommandExecutedEvent() { - var parentId = "parent-id"; - doReturn(Optional.of(parentId)).when(service).getCompletableParentId(any()); + verify(service).getCommand(CommandTestFactory.ID); + } - service.setCommandFinished(CommandTestFactory.ID, CommandTestFactory.CREATED_RESOURCE); + @Test + void shouldCallFinishCommand() { + service.setCommandFinished(CommandTestFactory.ID); - verify(service).publishCommandExecutedEvent(parentId); - } + verify(service).setCommandFinished(CommandTestFactory.ID, null); + } - @DisplayName("should not call publishCommandExecutedEvent when no parent id") - @Test - void shouldNotCallPublishCommandExecutedEvent() { - doReturn(Optional.empty()).when(service).getCompletableParentId(any()); + @Test + void shouldCallRepositoryFinishCommand() { + service.setCommandFinished(CommandTestFactory.ID, null); - service.setCommandFinished(CommandTestFactory.ID, CommandTestFactory.CREATED_RESOURCE); + verify(repository).finishCommand(CommandTestFactory.ID); + } - verify(service, never()).publishCommandExecutedEvent(anyString()); - } + @Test + void shouldCallRepositoryFinishCommandWithCreatedResource() { + service.setCommandFinished(CommandTestFactory.ID, CommandTestFactory.CREATED_RESOURCE); - @DisplayName("should not call publishCommandExecutedEvent when not all subcommands are finished") - @Test - void shouldNotCallPublishCommandExecutedEvent1() { - var parentId = "parent-id"; - doReturn(Optional.of(parentId)).when(service).getCompletableParentId(any()); - when(repository.existsNotFinishedSubCommands(any())).thenReturn(true); + verify(repository).finishCommand(CommandTestFactory.ID, CommandTestFactory.CREATED_RESOURCE); + } + + @Test + void shouldCallGetCompletableParentId() { + service.setCommandFinished(CommandTestFactory.ID); + + verify(service).getCompletableParentId(CommandTestFactory.ID); + } + + @Test + void shouldCallExistsNotFinishedSubCommands() { + var parentId = "parent-id"; + doReturn(Optional.of(parentId)).when(service).getCompletableParentId(any()); + + service.setCommandFinished(CommandTestFactory.ID, CommandTestFactory.CREATED_RESOURCE); + + verify(repository).existsNotFinishedSubCommands(parentId); + } + + @Test + void shouldCallPublishCommandExecutedEvent() { + var parentId = "parent-id"; + doReturn(Optional.of(parentId)).when(service).getCompletableParentId(any()); + + service.setCommandFinished(CommandTestFactory.ID, CommandTestFactory.CREATED_RESOURCE); + + verify(service).publishCommandExecutedEvent(parentId); + } + + @DisplayName("should not call publishCommandExecutedEvent when no parent id") + @Test + void shouldNotCallPublishCommandExecutedEvent() { + doReturn(Optional.empty()).when(service).getCompletableParentId(any()); + + service.setCommandFinished(CommandTestFactory.ID, CommandTestFactory.CREATED_RESOURCE); + + verify(service, never()).publishCommandExecutedEvent(anyString()); + } + + @DisplayName("should not call publishCommandExecutedEvent when not all subcommands are finished") + @Test + void shouldNotCallPublishCommandExecutedEvent1() { + var parentId = "parent-id"; + doReturn(Optional.of(parentId)).when(service).getCompletableParentId(any()); + when(repository.existsNotFinishedSubCommands(any())).thenReturn(true); - service.setCommandFinished(CommandTestFactory.ID, CommandTestFactory.CREATED_RESOURCE); + service.setCommandFinished(CommandTestFactory.ID, CommandTestFactory.CREATED_RESOURCE); - verify(service, never()).publishCommandExecutedEvent(anyString()); + verify(service, never()).publishCommandExecutedEvent(anyString()); + } } } @@ -471,62 +522,60 @@ class CommandServiceTest { } @Nested - class TestRevokeCommandPending { + class TestRevokeCommand { final String commandId = CommandTestFactory.ID; @Test void shouldThrowException() { - assertThatExceptionOfType(TechnicalException.class).isThrownBy(() -> service.setCommandRevokePending(commandId)); + assertThatExceptionOfType(NotFoundException.class).isThrownBy(() -> service.revokeCommand(commandId)); } @Test - void shouldUpdateCommandStatus() { - when(repository.getById(commandId)).thenReturn(Optional.of(CommandTestFactory.create())); + void shouldCallSetRevokeStatus() { + doReturn(CommandTestFactory.create()).when(repository).setRevokeStatus(anyString()); - service.setCommandRevokePending(commandId); + service.revokeCommand(commandId); - verify(repository).updateCommandStatus(anyString(), any(CommandStatus.class)); + verify(service).setRevokeStatus(CommandTestFactory.ID); } @Test void shouldCallPublishRevokeCommandEvent() { - when(repository.getById(commandId)).thenReturn(Optional.of(CommandTestFactory.create())); + var command = CommandTestFactory.create(); + doReturn(command).when(repository).setRevokeStatus(anyString()); - service.setCommandRevokePending(commandId); + service.revokeCommand(commandId); - verify(service).publishRevokeCommandEvent(any(String.class)); + verify(service).publishRevokeCommandEvent(command); } - } - - @Nested - class TestFiringRevokeCommand { - - final String commandId = CommandTestFactory.ID; @Test - void shoudlFireEvent() { - when(repository.getById(commandId)).thenReturn(Optional.of(CommandTestFactory.create())); + void shouldReturnCommand() { + var command = CommandTestFactory.create(); + doReturn(command).when(repository).setRevokeStatus(anyString()); - service.publishRevokeCommandEvent(commandId); + var result = service.revokeCommand(commandId); - verify(publisher).publishEvent(any(RevokeCommandEvent.class)); + assertThat(result).isSameAs(command); } + } - @Test - void shoudlThrowTechnicalExceptionBecauseCommandNotFound() { - when(repository.getById(commandId)).thenReturn(Optional.empty()); + @Nested + class TestPublishRevokeCommandEvent { - assertThatExceptionOfType(TechnicalException.class).isThrownBy(() -> service.publishRevokeCommandEvent(commandId)); - } + @Captor + private ArgumentCaptor<RevokeCommandEvent> eventCaptor; @Test - void shoudlThrowTechnicalExceptionBecauseNoPreviousState() { - when(repository.getById(commandId)).thenReturn(Optional.of(CommandTestFactory.createBuilder().previousState(null).build())); + void shouldPublishEvent() { + var command = CommandTestFactory.create(); - assertThatExceptionOfType(TechnicalException.class).isThrownBy(() -> service.publishRevokeCommandEvent(commandId)); - } + service.publishRevokeCommandEvent(command); + verify(publisher).publishEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getSource()).isSameAs(command); + } } @Nested @@ -807,4 +856,25 @@ class CommandServiceTest { assertThat(result).containsAllEntriesOf(BODY_OBJ); } } + + @Nested + class TestSetStatusRevokePending { + + @Test + void shouldCallRepository() { + service.setRevokeStatus(CommandTestFactory.ID); + + verify(repository).setRevokeStatus(CommandTestFactory.ID); + } + + @Test + void shouldReturnUpdatedCommand() { + var expectedCommand = CommandTestFactory.create(); + when(repository.setRevokeStatus(anyString())).thenReturn(expectedCommand); + + var result = service.setRevokeStatus(CommandTestFactory.ID); + + assertThat(result).isSameAs(expectedCommand); + } + } } \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/GrpcCommandServiceITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/GrpcCommandServiceITCase.java index 3343f674fda73345c563128d368532ee235f60d1..3053fad919a61cbdc56dfc0a5e0a46fd92a193e8 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/GrpcCommandServiceITCase.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/GrpcCommandServiceITCase.java @@ -39,6 +39,7 @@ import de.ozgcloud.vorgang.grpc.command.GrpcCreateCommand; import de.ozgcloud.vorgang.grpc.command.GrpcCreateCommandRequest; import de.ozgcloud.vorgang.grpc.command.GrpcFindCommandsRequest; import de.ozgcloud.vorgang.grpc.command.GrpcGetCommandRequest; +import de.ozgcloud.vorgang.grpc.command.GrpcRevokeCommandRequest; import de.ozgcloud.vorgang.vorgang.Vorgang; import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; import io.grpc.StatusRuntimeException; @@ -111,6 +112,29 @@ class GrpcCommandServiceITCase { } } + @Nested + class TestRevokeCommand { + + @Test + void shouldHaveSetStatus() { + var vorgang = mongoOperations.save(VorgangTestFactory.createBuilder().id(null).build()); + var command = mongoOperations.save(CommandTestFactory.createBuilder().id(null).status(CommandStatus.FINISHED).vorgangId(vorgang.getId()) + .previousState(Map.of(Vorgang.MONGODB_FIELDNAME_STATUS, "NEW")).build()); + + var result = serviceBlockingStub.revokeCommand(GrpcRevokeCommandRequest.newBuilder().setId(command.getId()).build()); + + assertThat(result.getCommand().getStatus()).isEqualTo(CommandStatus.REVOKE_PENDING.name()); + } + + @Test + void shouldThrowExceptionIfCommandNotFound() { + var revokeRequest = GrpcRevokeCommandRequest.newBuilder().setId("not-existing-id").build(); + + assertThrows(StatusRuntimeException.class, () -> serviceBlockingStub.revokeCommand(revokeRequest)); + } + + } + @Nested class TestLoadOutdatedOrders { diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/GrpcCommandServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/GrpcCommandServiceTest.java index 3924b31aeb817ab048897bf4de28ddec744b416e..dcc534d8db4fbf2dceb44d5ebcdaae1865c4db12 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/GrpcCommandServiceTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/GrpcCommandServiceTest.java @@ -36,9 +36,6 @@ 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.EnumSource; -import org.junit.jupiter.params.provider.EnumSource.Mode; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InjectMocks; @@ -66,7 +63,6 @@ import de.ozgcloud.vorgang.grpc.command.GrpcRevokeCommandRequest; import de.ozgcloud.vorgang.grpc.command.GrpcSetCommandExecutedRequest; import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; import io.grpc.stub.StreamObserver; -import lombok.SneakyThrows; class GrpcCommandServiceTest { @@ -147,29 +143,6 @@ class GrpcCommandServiceTest { } } - @Nested - class TestIsStatusChangeOrder { - - @ParameterizedTest - @EnumSource(mode = Mode.INCLUDE, names = { "VORGANG_ANNEHMEN", "VORGANG_VERWERFEN", "VORGANG_ZURUECKHOLEN", "VORGANG_BEARBEITEN", - "VORGANG_BESCHEIDEN", "VORGANG_ZURUECKSTELLEN", "VORGANG_ABSCHLIESSEN", "VORGANG_WIEDEREROEFFNEN" }) - void shouldReturnTrue(Order order) { - var result = service.isStatusChangeOrder(order.name()); - - assertThat(result).isTrue(); - } - - @ParameterizedTest - @EnumSource(mode = Mode.EXCLUDE, names = { "VORGANG_ANNEHMEN", "VORGANG_VERWERFEN", "VORGANG_ZURUECKHOLEN", "VORGANG_BEARBEITEN", - "VORGANG_BESCHEIDEN", "VORGANG_ZURUECKSTELLEN", "VORGANG_ABSCHLIESSEN", "VORGANG_WIEDEREROEFFNEN", "VORGANG_ZUM_LOESCHEN_MARKIEREN", - "VORGANG_LOESCHEN" }) - void shouldReturnFalse(Order order) { - var result = service.isStatusChangeOrder(order.name()); - - assertThat(result).isFalse(); - } - } - @DisplayName("Get command") @Nested class TestGetCommand { @@ -228,56 +201,55 @@ class GrpcCommandServiceTest { @Mock private StreamObserver<GrpcCommandResponse> responseObserver; - private final GrpcRevokeCommandRequest request = GrpcRevokeCommandRequest.newBuilder().setId(CommandTestFactory.ID).build(); - - @BeforeEach - void mockCommandService() { - Command vorgangCommand = CommandTestFactory.createBuilder().id(CommandTestFactory.ID).build(); + private final Command updatedCommand = CommandTestFactory.createBuilder().id(CommandTestFactory.ID).build(); + @Mock + private GrpcCommandResponse commandsResponse; - when(commandService.findCommand(any())).thenReturn(Optional.of(vorgangCommand)); - } + private final GrpcRevokeCommandRequest request = GrpcRevokeCommandRequest.newBuilder().setId(CommandTestFactory.ID).build(); + @Captor + private ArgumentCaptor<CommandResponse> responseCaptor; @Test - void shouldCallPolicyService() throws Exception { + void shouldCallPolicyService() { callRevokeCommand(); - verify(policyService).checkPermissionByCommand(anyString()); + verify(policyService).checkPermissionByCommand(CommandTestFactory.ID); } @Test - void shouldCallServiceRevokeCommand() throws Exception { + void shouldCallServiceRevokeCommand() { callRevokeCommand(); - verify(commandService).setCommandRevokePending(request.getId()); + verify(commandService).revokeCommand(CommandTestFactory.ID); } @Test - void shouldCallMapper() throws Exception { + void shouldCallMapper() { + when(commandService.revokeCommand(any())).thenReturn(updatedCommand); + callRevokeCommand(); - verify(commandResponseMapper).toGrpc(any()); + verify(commandResponseMapper).toGrpc(responseCaptor.capture()); + assertThat(responseCaptor.getValue()).extracting(CommandResponse::getCommand).isSameAs(updatedCommand); } - @Nested - class TestProceedRevokeCommand { + @Test + void shouldCallOnNext() { + when(commandResponseMapper.toGrpc(any())).thenReturn(commandsResponse); - private final PersistedCommand command = CommandTestFactory.create(); + callRevokeCommand(); - @BeforeEach - void init() { - when(commandService.findCommand(anyString())).thenReturn(Optional.of(command)); - } + verify(responseObserver).onNext(commandsResponse); + } - @Test - @SneakyThrows - void shouldLoadCommand() { - callRevokeCommand(); + @Test + void shouldCloseStream() { + callRevokeCommand(); - verify(service).getCommand(CommandTestFactory.ID); - } + verify(responseObserver).onCompleted(); } - private void callRevokeCommand() throws Exception { + private void callRevokeCommand() { service.revokeCommand(request, responseObserver); } } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/migration/M010_CreateIndexesInVorgangTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/migration/M010_CreateIndexesInVorgangTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d2751ccceaf4480dec515641851e051de8eba4f1 --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/migration/M010_CreateIndexesInVorgangTest.java @@ -0,0 +1,132 @@ +/* + * 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 static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.index.IndexDefinition; +import org.springframework.data.mongodb.core.index.IndexOperations; + +class M010_CreateIndexesInVorgangTest { + + @Spy + @InjectMocks + private M010_CreateIndexesInVorgang migration; + + @Mock + private MongoTemplate template; + @Mock + private IndexOperations indexOperations; + + @Nested + class TestDoMigration { + + @Mock + private IndexDefinition indexDefinition; + + @BeforeEach + void init() { + when(template.indexOps(anyString())).thenReturn(indexOperations); + } + + @Test + void shouldGetIndexOperations() { + migration.doMigration(template); + + verify(template).indexOps(M010_CreateIndexesInVorgang.COLLECTION_NAME); + } + + @Test + void shouldCallBuildCompoundIndexDefinition() { + migration.doMigration(template); + + verify(migration).buildCompoundIndexDefinition(); + } + + @Test + void shouldEnsureComponentIndex() { + doReturn(indexDefinition).when(migration).buildCompoundIndexDefinition(); + + migration.doMigration(template); + + verify(indexOperations).ensureIndex(indexDefinition); + } + + } + + @Nested + class TestBuildCompoundIndex { + + @Test + void shouldSetInCreation() { + var indexDefinition = migration.buildCompoundIndexDefinition(); + + assertThat(indexDefinition.getIndexKeys()).containsEntry(M010_CreateIndexesInVorgang.FIELD_IN_CREATION, 1); + } + + @Test + void shouldSetOrganisationseinheitenId() { + var indexDefinition = migration.buildCompoundIndexDefinition(); + + assertThat(indexDefinition.getIndexKeys()).containsEntry(M010_CreateIndexesInVorgang.FIELD_ORGANISATIONSEINHEITEN_ID, 1); + } + + @Test + void shouldSetAssignedTo() { + var indexDefinition = migration.buildCompoundIndexDefinition(); + + assertThat(indexDefinition.getIndexKeys()).containsEntry(M010_CreateIndexesInVorgang.FIELD_ASSIGNED_TO, 1); + } + + @Test + void shouldSetStatus() { + var indexDefinition = migration.buildCompoundIndexDefinition(); + + assertThat(indexDefinition.getIndexKeys()).containsEntry(M010_CreateIndexesInVorgang.FIELD_STATUS, 1); + } + + @Test + void shouldSetCreatedAt() { + var indexDefinition = migration.buildCompoundIndexDefinition(); + + assertThat(indexDefinition.getIndexKeys()).containsEntry(M010_CreateIndexesInVorgang.FIELD_CREATED_AT, 1); + } + + @Test + void shouldSetName() { + var indexDefinition = migration.buildCompoundIndexDefinition(); + + assertThat(indexDefinition.getIndexOptions()).containsEntry("name", M010_CreateIndexesInVorgang.COMPOUND_INDEX_NAME); + } + } + +} \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/search/MongodbFullTextSearchIndexInitializerITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/search/MongodbFullTextSearchIndexInitializerITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..c623040a7e824441be13e2bcb82676e0f19e2ec2 --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/search/MongodbFullTextSearchIndexInitializerITCase.java @@ -0,0 +1,63 @@ +/* + * 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.search; + +import static org.assertj.core.api.Assertions.*; +import static org.awaitility.Awaitility.*; + +import java.time.Duration; +import java.util.Optional; + +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.index.IndexInfo; + +import de.ozgcloud.common.test.DataITCase; +import de.ozgcloud.vorgang.vorgang.Vorgang; + +@SpringBootTest(properties = { + "ozgcloud.mongodbsearch.enabled=true" +}) +@DataITCase +class MongodbFullTextSearchIndexInitializerITCase { + + @Autowired + private MongoOperations mongoOperations; + + @Test + void shouldEnsureIndex() { + await().atMost(Duration.ofSeconds(60)).untilAsserted(() -> + assertThat(mongoOperations.indexOps(Vorgang.COLLECTION_NAME).getIndexInfo()).isNotEmpty()); + + assertThat(getTextIndex()).isPresent(); + } + + private Optional<IndexInfo> getTextIndex() { + return mongoOperations.indexOps(Vorgang.COLLECTION_NAME).getIndexInfo().stream() + .filter(index -> MongodbFullTextSearchIndexInitializer.TEXT_INDEX_NAME.equals(index.getName())).findFirst(); + } + +} \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/search/MongodbFullTextSearchIndexInitializerTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/search/MongodbFullTextSearchIndexInitializerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6e93887973173fa3a6e986891adfe6c5fd28e9b9 --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/search/MongodbFullTextSearchIndexInitializerTest.java @@ -0,0 +1,111 @@ +/* + * 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.search; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.index.IndexOperations; +import org.springframework.data.mongodb.core.index.TextIndexDefinition; + +import de.ozgcloud.vorgang.vorgang.Vorgang; + +class MongodbFullTextSearchIndexInitializerTest { + + @Spy + @InjectMocks + private MongodbFullTextSearchIndexInitializer indexInitializer; + + @Mock + private MongoOperations mongoOperations; + + @Nested + class TestInitWildcardTextIndex { + + @Mock + private IndexOperations indexOperations; + @Mock + private TextIndexDefinition textIndexDefinition; + + @BeforeEach + void init() { + when(mongoOperations.indexOps(anyString())).thenReturn(indexOperations); + } + + @Test + void shouldCallEnsureIndex() { + indexInitializer.initWildcardTextIndex(); + + verify(mongoOperations).indexOps(Vorgang.COLLECTION_NAME); + } + + @Test + void shouldCallBuildWildcardTextIndexDefinition() { + indexInitializer.initWildcardTextIndex(); + + verify(indexInitializer).buildWildcardTextIndexDefinition(); + } + + @Test + void shouldCallEnsureIndexWithBuildWildcardTextIndexDefinition() { + doReturn(textIndexDefinition).when(indexInitializer).buildWildcardTextIndexDefinition(); + + indexInitializer.initWildcardTextIndex(); + + verify(indexOperations).ensureIndex(textIndexDefinition); + } + } + + @Nested + class TestBuildWildcardTextIndexDefinition { + + @Test + void shouldSetIndexName() { + var result = indexInitializer.buildWildcardTextIndexDefinition(); + + assertThat(result.getIndexOptions()).containsEntry("name", MongodbFullTextSearchIndexInitializer.TEXT_INDEX_NAME); + } + + @Test + void shouldSetDefaultLanguage() { + var result = indexInitializer.buildWildcardTextIndexDefinition(); + + assertThat(result.getIndexOptions()).containsEntry("default_language", MongodbFullTextSearchIndexInitializer.TEXT_INDEX_LANGUAGE); + } + + @Test + void shouldCreateWildcardTextIndex() { + var result = indexInitializer.buildWildcardTextIndexDefinition(); + + assertThat(result.getIndexKeys()).containsEntry("$**", "text"); + } + } +} \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/search/SearchServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/search/SearchServiceTest.java index 65228c43a6fffd553c3520cb7d08ab8a0de3c69a..185953de74bc5b83361db559720fb272c8712fe9 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/search/SearchServiceTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/search/SearchServiceTest.java @@ -80,18 +80,6 @@ class SearchServiceTest { } } - @DisplayName("Exists by id") - @Nested - class TestExistsById { - - @Test - void shouldCallRepositoryExistsById() { - searchService.existsById(VorgangTestFactory.ID); - - verify(repository).existsById(VorgangTestFactory.ID); - } - } - @DisplayName("Search") @Nested class TestSearch { diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/search/SearchVorgangCustomRepositoryMongoITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/search/SearchVorgangCustomRepositoryMongoITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..751afdf88ad5545f59f9838787490ddfec73a703 --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/common/search/SearchVorgangCustomRepositoryMongoITCase.java @@ -0,0 +1,151 @@ +/* + * 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.search; + +import static org.awaitility.Awaitility.*; + +import java.time.Duration; +import java.util.Optional; + +import org.assertj.core.api.Assertions; +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.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.index.IndexInfo; + +import de.ozgcloud.common.test.DataITCase; +import de.ozgcloud.vorgang.vorgang.AntragstellerTestFactory; +import de.ozgcloud.vorgang.vorgang.EingangTestFactory; +import de.ozgcloud.vorgang.vorgang.FilterCriteria; +import de.ozgcloud.vorgang.vorgang.FindVorgangRequest; +import de.ozgcloud.vorgang.vorgang.Vorgang; +import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; + +@SpringBootTest(properties = { "ozgcloud.mongodbsearch.enabled=true" }) +@DataITCase +public class SearchVorgangCustomRepositoryMongoITCase { + + @Autowired + private SearchVorgangCustomRepositoryMongo repostitory; + + @Autowired + private MongoOperations mongoOperations; + + @Nested + class TestSpecialCharacters { + + @BeforeEach + void init() { + await().atMost(Duration.ofSeconds(10)).until(() -> getTextIndex().isPresent()); + mongoOperations.remove(Vorgang.class).all(); + } + + @DisplayName("should find vorgang when vorgangName") + @ParameterizedTest(name = "contains {0}") + @ValueSource(strings = { "_", "+", "-", "=", "&&", "||", "!", "(", ")", "{", "}", "[", "]", "^", "\"", "~", "*", "?", ":", "\\", "/", "<", + ">", "$", "%", "&", "#", "@" }) + void shouldFindInVorgangName(String character) { + var vorgangName = "AbC%sTest".formatted(character); + mongoOperations.save(VorgangTestFactory.createBuilder().name(vorgangName).build()); + + var vorgangHeaders = repostitory.searchBy(searchRequest(character)); + + Assertions.assertThat(vorgangHeaders.getContent()).hasSize(1).first() + .extracting("name").isEqualTo(vorgangName); + } + + @Test + void shouldFindInVorgangNummer() { + mongoOperations.save(VorgangTestFactory.create()); + + var vorgangHeaders = repostitory.searchBy(buildSearchRequest("VOR-GANG")); + + Assertions.assertThat(vorgangHeaders.getContent()).hasSize(1).first() + .extracting("nummer").isEqualTo(VorgangTestFactory.VORGANG_NUMMER); + } + + @Test + void shouldFindInAntragstellerName() { + var antragsteller = AntragstellerTestFactory.createBuilder().nachname("MusterMann").build(); + var eingang = EingangTestFactory.createBuilder().antragsteller(antragsteller).build(); + mongoOperations.save(VorgangTestFactory.createBuilder().clearEingangs().eingang(eingang).build()); + + var vorgangHeaders = repostitory.searchBy(buildSearchRequest("mustermann")); + + Assertions.assertThat(vorgangHeaders.getContent()).hasSize(1); + } + + @Test + void shouldFindInAntragstellerVorname() { + var antragsteller = AntragstellerTestFactory.createBuilder().vorname("Alexander").build(); + var eingang = EingangTestFactory.createBuilder().antragsteller(antragsteller).build(); + mongoOperations.save(VorgangTestFactory.createBuilder().clearEingangs().eingang(eingang).build()); + + var vorgangHeaders = repostitory.searchBy(buildSearchRequest("alexand")); + + Assertions.assertThat(vorgangHeaders.getContent()).hasSize(1); + } + + @Test + void shouldFindInAktenzeichen() { + var foundVorgang = mongoOperations.save(VorgangTestFactory.createBuilder().id(null).aktenzeichen("123-456").build()); + mongoOperations.save(VorgangTestFactory.createBuilder().id(null).build()); + + var vorgangHeaders = repostitory.searchBy(buildSearchRequest("123-")); + + Assertions.assertThat(vorgangHeaders.getContent()).hasSize(1).first().extracting("id").isEqualTo(foundVorgang.getId()); + } + + @Test + void shouldFindInMultipleFields() { + var antragsteller = AntragstellerTestFactory.createBuilder().nachname("Müller").build(); + mongoOperations.save(VorgangTestFactory.createBuilder().id(null).name("Mäuse") + .eingang(EingangTestFactory.createBuilder().antragsteller(antragsteller).build()) + .build(), Vorgang.COLLECTION_NAME); + + var result = repostitory.searchBy(buildSearchRequest("mull mau")); + + Assertions.assertThat(result.getContent()).hasSize(1); + } + + private Optional<IndexInfo> getTextIndex() { + return mongoOperations.indexOps(Vorgang.COLLECTION_NAME).getIndexInfo().stream() + .filter(index -> MongodbFullTextSearchIndexInitializer.TEXT_INDEX_NAME.equals(index.getName())).findFirst(); + } + + private FindVorgangRequest searchRequest(String character) { + return FindVorgangRequest.builder().filterBy(FilterCriteria.builder().build()).searchBy("abc%st".formatted(character)).build(); + } + + private FindVorgangRequest buildSearchRequest(String searchBy) { + return FindVorgangRequest.builder().filterBy(FilterCriteria.builder().build()).searchBy(searchBy).build(); + } + } +} diff --git a/vorgang-manager-utils/pom.xml b/vorgang-manager-utils/pom.xml index 920c88119e82ddd5b6e6717b8b12bfabfe9d5a0d..8e78361b0e1cae1ecef07ee4a41cf1e1ed1b0315 100644 --- a/vorgang-manager-utils/pom.xml +++ b/vorgang-manager-utils/pom.xml @@ -30,14 +30,14 @@ <parent> <groupId>de.ozgcloud.common</groupId> <artifactId>ozgcloud-common-dependencies</artifactId> - <version>4.2.0</version> + <version>4.3.0</version> <relativePath/> </parent> <groupId>de.ozgcloud.vorgang</groupId> <artifactId>vorgang-manager-utils</artifactId> <name>OZG-Cloud Vorgang Manager Utils</name> - <version>2.9.0-SNAPSHOT</version> + <version>2.11.0-SNAPSHOT</version> <properties> <maven-compiler-plugin.version>3.10.1</maven-compiler-plugin.version> diff --git a/vorgang-manager-utils/src/main/java/de/ozgcloud/vorgang/common/grpc/FormDataMappingUtils.java b/vorgang-manager-utils/src/main/java/de/ozgcloud/vorgang/common/grpc/FormDataMappingUtils.java index e2d04eb7aa6802d558e320c719f574a1fd27c142..26ce9a4bfd51d6599523467afd695711263b441f 100644 --- a/vorgang-manager-utils/src/main/java/de/ozgcloud/vorgang/common/grpc/FormDataMappingUtils.java +++ b/vorgang-manager-utils/src/main/java/de/ozgcloud/vorgang/common/grpc/FormDataMappingUtils.java @@ -9,6 +9,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Predicate; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.tuple.Pair; @@ -17,6 +18,11 @@ import de.ozgcloud.vorgang.vorgang.GrpcFormField; class FormDataMappingUtils { + private static final Predicate<Object> IS_VALUE = obj -> obj instanceof String + || obj instanceof Number + || obj instanceof LocalDate + || obj instanceof Boolean; + static final String VALUE_KEY = "value"; static final String LABEL_KEY = "label"; static final String CONTROL_DATA_KEY = "controlData"; @@ -72,13 +78,9 @@ class FormDataMappingUtils { static boolean isValue(Object value) { if (value instanceof Map<?, ?> valueMap) { - return valueMap.containsKey(VALUE_KEY) && - (valueMap.get(VALUE_KEY) instanceof String - || valueMap.get(VALUE_KEY) instanceof Number - || valueMap.get(VALUE_KEY) instanceof LocalDate - || valueMap.get(VALUE_KEY) instanceof Boolean); + return valueMap.containsKey(VALUE_KEY) && IS_VALUE.test(valueMap.get(VALUE_KEY)); } else { - return value instanceof String; + return IS_VALUE.test(value); } } diff --git a/vorgang-manager-utils/src/test/java/de/ozgcloud/vorgang/common/grpc/GrpcFormDataMapperTest.java b/vorgang-manager-utils/src/test/java/de/ozgcloud/vorgang/common/grpc/GrpcFormDataMapperTest.java index 3e9143321168141832df7261dc8b3069c5606acb..7694a8c144e0af1fd16d10ad76accb914103e471 100644 --- a/vorgang-manager-utils/src/test/java/de/ozgcloud/vorgang/common/grpc/GrpcFormDataMapperTest.java +++ b/vorgang-manager-utils/src/test/java/de/ozgcloud/vorgang/common/grpc/GrpcFormDataMapperTest.java @@ -115,6 +115,13 @@ class GrpcFormDataMapperTest { assertThat(grpcField.getLabel()).isEqualTo(FIELD_LABEL); } + + @Test + void shouldHaveNumeric() { + var grpcField = mapper.mapToField(FIELD_NAME, Map.of(VALUE_KEY, 42, LABEL_KEY, FIELD_LABEL)); + + assertThat(grpcField.getValue()).isEqualTo("42"); + } } } @@ -167,7 +174,6 @@ class GrpcFormDataMapperTest { @Test void shouldHaveSubFormField() { - GrpcFormData formData = mapper.mapToFormData(Map.of("key", Map.of("subKey", "value"))); assertThat(formData.getForm(0).getFieldList()).hasSize(1); @@ -175,6 +181,15 @@ class GrpcFormDataMapperTest { assertThat(formData.getForm(0).getField(0).getName()).isEqualTo("subKey"); assertThat(formData.getForm(0).getField(0).getValue()).isEqualTo("value"); } + + @Test + void shouldHaveNumericSubFormField() { + GrpcFormData formData = mapper.mapToFormData(Map.of("key", Map.of("subKey", 42))); + + assertThat(formData.getForm(0).getFieldList()).hasSize(1); + assertThat(formData.getForm(0).getField(0).getValue()).isEqualTo("42"); + } + } @DisplayName("Mapped SubFormField")