diff --git a/bescheid-manager/pom.xml b/bescheid-manager/pom.xml index 5f5e43c0d1204185e1f1473897ea2e7d76babb0e..07ca67426a2042ed3f702a4c3ef65e621c984e13 100644 --- a/bescheid-manager/pom.xml +++ b/bescheid-manager/pom.xml @@ -15,7 +15,7 @@ <version>1.10.0-SNAPSHOT</version> <properties> - <vorgang-manager.version>2.5.0</vorgang-manager.version> + <vorgang-manager.version>2.6.0-SNAPSHOT</vorgang-manager.version> <api-lib.version>0.7.0-SNAPSHOT</api-lib.version> </properties> diff --git a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/Bescheid.java b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/Bescheid.java index e4d7d675f1a11f287f7610aae8682357336bd907..c08d2a90c97761f8da7e20ee63bdbeec6fe20569 100644 --- a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/Bescheid.java +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/Bescheid.java @@ -3,17 +3,29 @@ package de.ozgcloud.bescheid; import java.io.File; import java.util.Optional; +import org.apache.commons.lang3.StringUtils; + import de.ozgcloud.bescheid.vorgang.Vorgang; import de.ozgcloud.bescheid.vorgang.VorgangId; import de.ozgcloud.common.binaryfile.FileId; import lombok.Builder; import lombok.Getter; +import lombok.RequiredArgsConstructor; import lombok.With; @Builder(toBuilder = true) @Getter public class Bescheid { + public static final String FIELD_STATUS = "status"; + public static final String FIELD_BESCHIEDEN_AM = "beschiedenAm"; + public static final String FIELD_BEWILLIGT = "bewilligt"; + public static final String FIELD_BESCHEID_DOCUMENT = "bescheidDocument"; + public static final String FIELD_ATTACHMENTS = "attachments"; + public static final String FIELD_SEND_BY = "sendBy"; + public static final String FIELD_NACHRICHT_TEXT = "nachrichtText"; + public static final String FIELD_NACHRICHT_SUBJECT = "nachrichtSubject"; + private VorgangId vorgangId; private boolean genehmigt; @@ -26,8 +38,38 @@ public class Bescheid { private String contentType; private long size; + @Builder.Default + private Optional<String> nachrichtSubject = Optional.empty(); @Builder.Default private Optional<String> nachrichtText = Optional.empty(); private Vorgang.ServiceKonto serviceKonto; + + public enum Status { + DRAFT, BESCHEID, SEND; + + public boolean not(String value) { + return !hasValue(value); + } + + public boolean hasValue(String value) { + return this.name().equalsIgnoreCase(value); + } + } + + @RequiredArgsConstructor + public enum SendBy { + NACHRICHT("NACHRICHT"), MANUAL("MANUAL"); + + private final String value; + + public boolean notValue(Object sendByValue) { + return !hasValue(sendByValue); + } + + public boolean hasValue(Object sendByValue) { + return StringUtils.equalsIgnoreCase(value, String.valueOf(sendByValue)); + } + + } } 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 b663692ecd5ab4364b432572e57d4faca1ba2511..5a16db15e398136d8f61e4091532f8a0d0b717b3 100644 --- a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/BescheidEventListener.java +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/BescheidEventListener.java @@ -25,6 +25,7 @@ package de.ozgcloud.bescheid; import java.time.LocalDate; import java.util.Optional; +import java.util.function.Consumer; import java.util.function.Predicate; import org.apache.commons.lang3.StringUtils; @@ -42,6 +43,8 @@ import de.ozgcloud.bescheid.vorgang.VorgangId; import de.ozgcloud.command.Command; import de.ozgcloud.command.CommandCreatedEvent; import de.ozgcloud.command.CommandFailedEvent; +import de.ozgcloud.document.BescheidDocumentCreatedEvent; +import de.ozgcloud.document.DocumentService; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; @@ -54,30 +57,41 @@ class BescheidEventListener { public static final String CREATE_BESCHEID_ORDER = "CREATE_BESCHEID"; public static final String DELETE_BESCHEID_ORDER = "DELETE_BESCHEID"; public static final String UPDATE_BESCHEID_ORDER = "UPDATE_BESCHEID"; + public static final String CREATE_BESCHEID_DOCUMENT_ORDER = "CREATE_BESCHEID_DOCUMENT"; + public static final String SEND_BESCHEID_ORDER = "SEND_BESCHEID"; + public static final String SEND_POSTFACH_MAIL_ORDER = "SEND_POSTFACH_MAIL"; - public static final Predicate<Command> IS_CREATE_BESCHEID_COMMAND = command -> command.getOrder().equals(CREATE_BESCHEID_ORDER); + public static final Predicate<Command> IS_CREATE_BESCHEID_COMMAND = command -> CREATE_BESCHEID_ORDER.equals(command.getOrder()); private static final String IS_CREATE_BESCHEID = "{T(de.ozgcloud.bescheid.BescheidEventListener).IS_CREATE_BESCHEID_COMMAND.test(event.getSource())}"; - public static final Predicate<Command> IS_DELETE_BESCHEID_COMMAND = command -> command.getOrder().equals(DELETE_BESCHEID_ORDER); + public static final Predicate<Command> IS_DELETE_BESCHEID_COMMAND = command -> DELETE_BESCHEID_ORDER.equals(command.getOrder()); private static final String IS_DELETE_BESCHEID = "{T(de.ozgcloud.bescheid.BescheidEventListener).IS_DELETE_BESCHEID_COMMAND.test(event.getSource())}"; - public static final Predicate<Command> IS_UPDATE_BESCHEID_COMMAND = command -> command.getOrder().equals(UPDATE_BESCHEID_ORDER); + public static final Predicate<Command> IS_UPDATE_BESCHEID_COMMAND = command -> UPDATE_BESCHEID_ORDER.equals(command.getOrder()); private static final String IS_UPDATE_BESCHEID = "{T(de.ozgcloud.bescheid.BescheidEventListener).IS_UPDATE_BESCHEID_COMMAND.test(event.getSource())}"; + public static final Predicate<Command> IS_CREATE_BESCHEID_DOCUMENT_COMMAND = command -> CREATE_BESCHEID_DOCUMENT_ORDER.equals(command.getOrder()); + private static final String IS_CREATE_BESCHEID_DOCUMENT = "{T(de.ozgcloud.bescheid.BescheidEventListener).IS_CREATE_BESCHEID_DOCUMENT_COMMAND.test(event.getSource())}"; + + 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())}"; + + public static final Predicate<Command> IS_SEND_POSTFACH_MAIL_COMMAND = command -> SEND_POSTFACH_MAIL_ORDER.equals(command.getOrder()); + private static final String IS_SEND_POSTFACH_MAIL = "{T(de.ozgcloud.bescheid.BescheidEventListener).IS_SEND_POSTFACH_MAIL_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"; private static final String LOG_MESSAGE_TEMPLATE = "{}. Command failed."; - private static final String CREATE_BESCHEID_ERROR_MESSAGE = "Error on executing Create Bescheid Command."; - private static final String DELETE_BESCHEID_ERROR_MESSAGE = "Error on executing Delete Bescheid Command."; - private static final String UPDATE_BESCHEID_ERROR_MESSAGE = "Error on executing Update Bescheid Command."; + private static final String ERROR_MESSAGE_TEMPLATE = "Error on executing %s Command."; private final BescheidService service; private final BinaryFileService fileService; private final NachrichtService nachrichtService; private final AttachedItemService attachedItemService; + private final DocumentService documentService; private final ApplicationEventPublisher eventPublisher; private final CurrentUserService userService; @@ -85,27 +99,13 @@ class BescheidEventListener { @EventListener(condition = IS_CREATE_BESCHEID) public void onCreateBescheidCommand(CommandCreatedEvent event) { - Command command = event.getSource(); - - doCreateBescheid(command); - } - - private void doCreateBescheid(Command command) { - SecurityContext prevContext = null; - try { - prevContext = userService.startSecurityContext(command); - execute(command); - } catch (Exception e) { - LOG.error(LOG_MESSAGE_TEMPLATE, CREATE_BESCHEID_ERROR_MESSAGE, e); - eventPublisher.publishEvent(new CommandFailedEvent(command.getId(), buildErrorMessage(CREATE_BESCHEID_ERROR_MESSAGE, e))); - } finally { - userService.resetSecurityContext(prevContext); - } + runWithSecurityContext(event.getSource(), this::doCreateBescheid); } - void execute(Command command) { + void doCreateBescheid(Command command) { if (isKielEnvironment()) { - doCreateBescheidBiz(command); + var bescheid = doCreateBescheidBiz(command); + nachrichtService.createNachrichtDraft(bescheid); eventPublisher.publishEvent(new BescheidCreatedEvent(command)); return; } @@ -115,15 +115,63 @@ class BescheidEventListener { boolean isKielEnvironment() { Predicate<SmartDocumentsProperties> configuredForKiel = properties -> TEMPLATE_GROUP_KIEL.equals(properties.getTemplateGroup()); - var smartDocumentsProperties1 = smartDocumentsProperties.filter(configuredForKiel); - return smartDocumentsProperties1.isPresent(); + return smartDocumentsProperties.filter(configuredForKiel).isPresent(); + } + + @EventListener(condition = IS_DELETE_BESCHEID) + public void onDeleteBescheid(CommandCreatedEvent event) { + runWithSecurityContext(event.getSource(), this::doDeleteBescheid); + } + + void doDeleteBescheid(Command command) { + attachedItemService.deleteBescheidDraft(command); + eventPublisher.publishEvent(new BescheidDeletedEvent(command)); + } + + @EventListener(condition = IS_UPDATE_BESCHEID) + public void onUpdateBescheidCommand(CommandCreatedEvent event) { + runWithSecurityContext(event.getSource(), this::doUpdateBescheid); + } + + void doUpdateBescheid(Command command) { + attachedItemService.updateBescheidDraft(command); + eventPublisher.publishEvent(new BescheidUpdatedEvent(command)); } - public void doCreateBescheidBiz(@NonNull Command command) { + @EventListener(condition = IS_CREATE_BESCHEID_DOCUMENT) + public void onCreatedBescheidDocument(CommandCreatedEvent event) { + runWithSecurityContext(event.getSource(), this::doCreateBescheidDocument); + } + + void doCreateBescheidDocument(Command command) { + var bescheid = doCreateBescheidBiz(command); + var bescheidDocument = documentService.createBescheidDocument(command, bescheid); + eventPublisher.publishEvent(new BescheidDocumentCreatedEvent(command, bescheidDocument)); + } + + Bescheid doCreateBescheidBiz(@NonNull Command command) { var bescheid = service.createBescheid(createRequest(command)); - bescheid = fileService.uploadBescheidFile(bescheid); + return fileService.uploadBescheidFile(bescheid); + } + + @EventListener(condition = IS_SEND_BESCHEID) + public void onSendBescheidCommand(CommandCreatedEvent event) { + runWithSecurityContext(event.getSource(), this::doSendBescheid); + } + + void doSendBescheid(Command command) { + service.sendBescheidManually(command.getRelationId(), command.getRelationVersion()); + eventPublisher.publishEvent(new BescheidSentEvent(command)); + } - nachrichtService.createNachrichtDraft(bescheid); + @EventListener(condition = IS_SEND_POSTFACH_MAIL) + public void onSendPostfachMailCommand(CommandCreatedEvent event) { + runWithSecurityContext(event.getSource(), this::doSendPostfachMail); + } + + void doSendPostfachMail(Command command) { + service.sendBescheidPostfachMail(command.getRelationId(), command.getRelationVersion()); + eventPublisher.publishEvent(new BescheidSentEvent(command)); } BescheidRequest createRequest(Command command) { @@ -141,36 +189,20 @@ class BescheidEventListener { return builder.build(); } - @EventListener(condition = IS_DELETE_BESCHEID) - public void onDeleteBescheid(CommandCreatedEvent event) { - SecurityContext prevContext = null; - Command command = event.getSource(); - try { - prevContext = userService.startSecurityContext(command); - attachedItemService.deleteBescheidDraft(command); - eventPublisher.publishEvent(new BescheidDeletedEvent(command)); - } catch (Exception e) { - LOG.error(LOG_MESSAGE_TEMPLATE, DELETE_BESCHEID_ERROR_MESSAGE, e); - eventPublisher.publishEvent(new CommandFailedEvent(command.getId(), buildErrorMessage(DELETE_BESCHEID_ERROR_MESSAGE, e))); - } finally { - userService.resetSecurityContext(prevContext); - } - } - @EventListener(condition = IS_UPDATE_BESCHEID) - public void onUpdateBescheidCommand(CommandCreatedEvent event) { - Command command = event.getSource(); + void runWithSecurityContext(Command command, Consumer<Command> commandExecutor) { SecurityContext prevContext = null; try { prevContext = userService.startSecurityContext(command); - attachedItemService.updateBescheidDraft(command); - eventPublisher.publishEvent(new BescheidUpdatedEvent(command)); + commandExecutor.accept(command); } catch (Exception e) { - LOG.error(LOG_MESSAGE_TEMPLATE, UPDATE_BESCHEID_ERROR_MESSAGE, e); - eventPublisher.publishEvent(new CommandFailedEvent(command.getId(), buildErrorMessage(UPDATE_BESCHEID_ERROR_MESSAGE, e))); + var errorMessage = ERROR_MESSAGE_TEMPLATE.formatted(command.getOrder()); + LOG.error(LOG_MESSAGE_TEMPLATE, errorMessage, e); + eventPublisher.publishEvent(new CommandFailedEvent(command.getId(), buildErrorMessage(errorMessage, e))); } finally { userService.resetSecurityContext(prevContext); } } + private String buildErrorMessage(String message, Exception cause) { try { StringBuilder sb = new StringBuilder(message); @@ -182,7 +214,7 @@ class BescheidEventListener { return sb.toString(); } catch (Exception e2) { LOG.error("Error in building Error Message (sick).", e2); - return CREATE_BESCHEID_ERROR_MESSAGE; + return message; } } diff --git a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/BescheidFeatureProperties.java b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/BescheidFeatureProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..538ae8150df7b0214c8a4cfdb619d1cb1cda3305 --- /dev/null +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/BescheidFeatureProperties.java @@ -0,0 +1,37 @@ +/* + * 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.bescheid; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import lombok.Getter; + +@Configuration +@ConfigurationProperties(prefix = "ozgcloud.feature.bescheid") +@Getter +public class BescheidFeatureProperties { + + private boolean storeAsDocument = false; +} diff --git a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/BescheidSentEvent.java b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/BescheidSentEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..8bfb22f6eec2e1cdee3bef6f7414dedfad82bd26 --- /dev/null +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/BescheidSentEvent.java @@ -0,0 +1,34 @@ +/* + * 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.bescheid; + +import de.ozgcloud.command.Command; +import de.ozgcloud.command.CommandExecutedEvent; + +public class BescheidSentEvent extends CommandExecutedEvent { + + public BescheidSentEvent(final Command command) { + super(command); + } +} diff --git a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/BescheidService.java b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/BescheidService.java index 3ee94d186e7973d2bfd1a484688cfb223be23e55..8eb392fbf673fc706751da2b68f3ef27df5fdefb 100644 --- a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/BescheidService.java +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/BescheidService.java @@ -1,34 +1,46 @@ package de.ozgcloud.bescheid; -import java.util.Objects; +import java.util.Map; +import java.util.Optional; import jakarta.annotation.PostConstruct; -import org.springframework.beans.factory.annotation.Autowired; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; +import de.ozgcloud.bescheid.attacheditem.AttachedItem; +import de.ozgcloud.bescheid.attacheditem.AttachedItemService; +import de.ozgcloud.bescheid.attributes.ClientAttributeService; +import de.ozgcloud.bescheid.common.callcontext.CurrentUserService; +import de.ozgcloud.bescheid.nachricht.NachrichtService; +import de.ozgcloud.bescheid.vorgang.Vorgang; +import de.ozgcloud.bescheid.vorgang.VorgangId; import de.ozgcloud.bescheid.vorgang.VorgangService; +import de.ozgcloud.common.binaryfile.FileId; import de.ozgcloud.common.errorhandling.TechnicalException; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; @Service @Log4j2 +@RequiredArgsConstructor class BescheidService { - private static final String ERROR_MESSAGE_NO_SERVICE = "'CREATE_BESCHEID' Command received but no Bescheid Endpoint is configured."; + private static final String ERROR_MESSAGE_NO_SERVICE = "No Bescheid Endpoint is configured."; - @Autowired - private VorgangService vorgangService; - @Autowired(required = false) - private BescheidRemoteService remoteService; + private final VorgangService vorgangService; + private final AttachedItemService attachedItemService; + private final NachrichtService nachrichtService; + private final CurrentUserService currentUserService; + private final ClientAttributeService bescheidClientAttributeService; + private final Optional<BescheidRemoteService> remoteService; @PostConstruct void logStatus() { - if (Objects.isNull(ERROR_MESSAGE_NO_SERVICE)) { - LOG.info("No BescheidRemoteService configured - Bescheid creation is not possible."); - } else { - LOG.info("Bescheid-Manager is configured."); - } + remoteService.ifPresentOrElse( + service -> LOG.info("No BescheidRemoteService configured - Bescheid creation is not possible."), + () -> LOG.info("Bescheid-Manager is configured.")); } public Bescheid createBescheid(BescheidRequest request) { @@ -39,16 +51,108 @@ class BescheidService { private Bescheid doCreateBescheid(BescheidRequest request) { var vorgang = vorgangService.getById(request.getVorgangId()); - return remoteService.create(request, vorgang) + return remoteService.get().create(request, vorgang) .toBuilder().vorgangId(vorgang.getId()).serviceKonto(vorgang.getServiceKonto()) .build(); } private void checkRemoteService() { - if (Objects.isNull(remoteService)) { + if (remoteService.isEmpty()) { LOG.error(ERROR_MESSAGE_NO_SERVICE); throw new TechnicalException(ERROR_MESSAGE_NO_SERVICE); } } + public void sendBescheidManually(String id, long version) { + var bescheidItem = attachedItemService.getItem(id); + validateBescheidSendManually(bescheidItem, version); + sendBescheid(bescheidItem); + } + + void validateBescheidSendManually(AttachedItem bescheidItem, long version) { + validateBescheid(bescheidItem, version); + var sendBy = bescheidItem.getItem().get(Bescheid.FIELD_SEND_BY); + if (Bescheid.SendBy.MANUAL.notValue(sendBy)) { + throw new TechnicalException("Bescheid has unexpected sendBy value: '%s'. Expected is %s" .formatted(sendBy, Bescheid.SendBy.MANUAL)); + } + } + + public void sendBescheidPostfachMail(String id, long version) { + var bescheidItem = attachedItemService.getItem(id); + validateBescheidSendPostfach(bescheidItem, version); + var vorgang = vorgangService.getById(VorgangId.from(bescheidItem.getVorgangId())); + nachrichtService.createNachrichtDraft(buildBescheid(bescheidItem, vorgang.getServiceKonto())); + sendBescheid(bescheidItem); + } + + public void validateBescheidSendPostfach(AttachedItem bescheidItem, long version) { + validateBescheid(bescheidItem, version); + var sendBy = bescheidItem.getItem().get(Bescheid.FIELD_SEND_BY); + if (Bescheid.SendBy.NACHRICHT.notValue(sendBy)) { + throw new TechnicalException("Bescheid has unexpected sendBy value: '%s'. Expected is %s".formatted(sendBy, Bescheid.SendBy.NACHRICHT)); + } + if (StringUtils.isBlank(getNachrichtSubject(bescheidItem))) { + throw new TechnicalException("Bescheid has no nachricht subject"); + } + if (StringUtils.isBlank(getNachrichtText(bescheidItem))) { + throw new TechnicalException("Bescheid has no nachricht text"); + } + } + + void validateBescheid(AttachedItem bescheidItem, long version) { + if (bescheidItem.getVersion() != version) { + throw new TechnicalException("Bescheid has different version. Expected: %d, but was: %d" .formatted(version, bescheidItem.getVersion())); + } + var status = MapUtils.getString(bescheidItem.getItem(), Bescheid.FIELD_STATUS); + if (Bescheid.Status.DRAFT.not(status)) { + throw new TechnicalException("Bescheid has status '%s'. Bescheid must have status DRAFT" .formatted(status)); + } + if (StringUtils.isBlank(MapUtils.getString(bescheidItem.getItem(), Bescheid.FIELD_BESCHEID_DOCUMENT))) { + throw new TechnicalException("Bescheid has no document"); + } + } + + Bescheid buildBescheid(AttachedItem bescheidItem, Vorgang.ServiceKonto serviceKonto) { + return Bescheid.builder() + .vorgangId(VorgangId.from(bescheidItem.getVorgangId())) + .genehmigt(getBewilligt(bescheidItem)) + .bescheidFileId(FileId.from(MapUtils.getString(bescheidItem.getItem(), Bescheid.FIELD_BESCHEID_DOCUMENT))) + .nachrichtSubject(Optional.ofNullable(getNachrichtSubject(bescheidItem))) + .nachrichtText(Optional.ofNullable(getNachrichtText(bescheidItem))) + .createdBy(currentUserService.getUserProfile().getId()) + .serviceKonto(serviceKonto) + .build(); + } + + String getNachrichtSubject(AttachedItem bescheidItem) { + return MapUtils.getString(bescheidItem.getItem(), Bescheid.FIELD_NACHRICHT_SUBJECT); + } + + String getNachrichtText(AttachedItem bescheidItem) { + return MapUtils.getString(bescheidItem.getItem(), Bescheid.FIELD_NACHRICHT_TEXT); + } + + void sendBescheid(AttachedItem bescheidItem) { + attachedItemService.patch(setBescheidSendStatus(bescheidItem)); + try { + vorgangService.bescheiden(bescheidItem.getVorgangId()); + } catch (Exception e) { + var item = attachedItemService.getItem(bescheidItem.getId()); + attachedItemService.patch(setBescheidDraftStatus(item)); + throw e; + } + bescheidClientAttributeService.setAntragResult(bescheidItem.getVorgangId(), getBewilligt(bescheidItem)); + } + + boolean getBewilligt(AttachedItem bescheidItem) { + return MapUtils.getBooleanValue(bescheidItem.getItem(), Bescheid.FIELD_BEWILLIGT, false); + } + + AttachedItem setBescheidSendStatus(AttachedItem bescheidItem) { + return bescheidItem.toBuilder().item(Map.of(Bescheid.FIELD_STATUS, Bescheid.Status.SEND.name())).build(); + } + + AttachedItem setBescheidDraftStatus(AttachedItem bescheidItem) { + return bescheidItem.toBuilder().item(Map.of(Bescheid.FIELD_STATUS, Bescheid.Status.DRAFT.name())).build(); + } } diff --git a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/AttachedItem.java b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/AttachedItem.java new file mode 100644 index 0000000000000000000000000000000000000000..c3407d72ea4d4a13e26d68571dc329ad174dcdbd --- /dev/null +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/AttachedItem.java @@ -0,0 +1,54 @@ +/* + * 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.bescheid.attacheditem; + +import java.util.Map; + +import lombok.Builder; +import lombok.Getter; +import lombok.Singular; + +@Builder(toBuilder = true) +@Getter +public class AttachedItem { + + public static final String PROPERTY_ID = "id"; + public static final String PROPERTY_CLIENT = "client"; + public static final String PROPERTY_VORGANG_ID = "vorgangId"; + public static final String PROPERTY_ITEM_NAME = "itemName"; + public static final String PROPERTY_VERSION = "version"; + public static final String PROPERTY_ITEM = "item"; + + private String id; + @Builder.Default + private long version = 0L; + + private String client; + private String vorgangId; + private String itemName; + + @Singular("itemEntry") + private Map<String, Object> item; + +} diff --git a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/AttachedItemMapper.java b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/AttachedItemMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..c2b373a33138cc4df45ea98c5f516921713f4863 --- /dev/null +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/AttachedItemMapper.java @@ -0,0 +1,36 @@ +/* + * 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.bescheid.attacheditem; + +import org.mapstruct.Mapper; + +import de.ozgcloud.vorgang.common.grpc.GrpcObjectMapper; +import de.ozgcloud.vorgang.vorgangAttachedItem.GrpcVorgangAttachedItem; + +@Mapper(uses = { GrpcObjectMapper.class}) +interface AttachedItemMapper { + + AttachedItem mapFromVorgangAttachedItem(GrpcVorgangAttachedItem item); + +} 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 cf8d8bf0e80e6a905b5e2c5097382462744acf74..0b2b6340757011822e3bb0778d23a66d22851f8b 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 @@ -47,10 +47,11 @@ import lombok.extern.log4j.Log4j2; @Log4j2 public class AttachedItemService { - static final String BESCHEID_ITEM_NAME = "Bescheid"; + public static final String BESCHEID_ITEM_NAME = "Bescheid"; static final String CREATE_ATTACHED_ITEM_ORDER = "CREATE_ATTACHED_ITEM"; static final String UPDATE_ATTACHED_ITEM_ORDER = "UPDATE_ATTACHED_ITEM"; 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 -> !BescheidItem.ACCEPTED_SEND_BY_VALUES.contains(sendBy); @@ -151,14 +152,14 @@ public class AttachedItemService { void validateBescheidData(Map<String, Object> bodyObject) { if (isNull(bodyObject.get(BescheidItem.FIELD_BESCHIEDEN_AM))) { - throw new TechnicalException("Fields '%s' is required for bescheid creation".formatted(BescheidItem.FIELD_BESCHIEDEN_AM)); + throw new TechnicalException("Fields '%s' is required for bescheid creation" .formatted(BescheidItem.FIELD_BESCHIEDEN_AM)); } if (isNull(bodyObject.get(BescheidItem.FIELD_BEWILLIGT))) { - throw new TechnicalException("Fields '%s' is required for bescheid creation".formatted(BescheidItem.FIELD_BEWILLIGT)); + throw new TechnicalException("Fields '%s' is required for bescheid creation" .formatted(BescheidItem.FIELD_BEWILLIGT)); } Optional.ofNullable(MapUtils.getString(bodyObject, BescheidItem.FIELD_SEND_BY)).filter(notExpectedSendByValue) .ifPresent(sendBy -> - LOG.warn("Unexpected value for field '%s': %s. Allowed are: %s".formatted(BescheidItem.FIELD_SEND_BY, sendBy, + LOG.warn("Unexpected value for field '%s': %s. Allowed are: %s" .formatted(BescheidItem.FIELD_SEND_BY, sendBy, String.join(", ", BescheidItem.ACCEPTED_SEND_BY_VALUES))) ); } @@ -172,7 +173,7 @@ public class AttachedItemService { void validateBescheidStatus(BescheidItem bescheid) { var bescheidStatus = MapUtils.getString(bescheid.getBescheidData(), BescheidItem.FIELD_STATUS); if (BescheidItem.Status.DRAFT.not(bescheidStatus)) { - throw new TechnicalException("Bescheid draft with ID '%s' has an unexpected status: '%s'".formatted(bescheid.getId(), bescheidStatus)); + throw new TechnicalException("Bescheid draft with ID '%s' has an unexpected status: '%s'" .formatted(bescheid.getId(), bescheidStatus)); } } @@ -185,4 +186,32 @@ public class AttachedItemService { .build(); } + public AttachedItem getItem(String id) { + return remoteService.getItem(id); + } + + public void patch(AttachedItem item) { + commandService.createAndWaitUntilDone(buildPatchBescheidCommand(item)); + } + + OzgCloudCommand buildPatchBescheidCommand(AttachedItem bescheidItem) { + return OzgCloudCommand.builder() + .vorgangId(commandMapper.toOzgCloudVorgangId(bescheidItem.getVorgangId())) + .relationId(commandMapper.mapRelationId(bescheidItem.getId())) + .relationVersion(bescheidItem.getVersion()) + .order(PATCH_ATTACHED_ITEM) + .bodyObject(buildObjectMap(bescheidItem)) + .build(); + } + + Map<String, Object> buildObjectMap(AttachedItem bescheidItem) { + var bodyObject = new HashMap<String, Object>(); + bodyObject.put(AttachedItem.PROPERTY_ID, bescheidItem.getId()); + bodyObject.put(AttachedItem.PROPERTY_CLIENT, bescheidItem.getClient()); + bodyObject.put(AttachedItem.PROPERTY_VORGANG_ID, bescheidItem.getVorgangId()); + bodyObject.put(AttachedItem.PROPERTY_ITEM_NAME, bescheidItem.getItemName()); + bodyObject.put(AttachedItem.PROPERTY_VERSION, bescheidItem.getVersion()); + bodyObject.put(AttachedItem.PROPERTY_ITEM, bescheidItem.getItem()); + return bodyObject; + } } diff --git a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/BescheidItem.java b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/BescheidItem.java index 84b6f8aff6a040ab619ccaed13d19c08c7d3b4f7..a018068459db579d4c480c4bf7223919d848fd6c 100644 --- a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/BescheidItem.java +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/BescheidItem.java @@ -31,6 +31,7 @@ import lombok.Getter; @Builder(toBuilder = true) @Getter +@Deprecated(since = "2.6.0") public class BescheidItem { static final Set<String> ACCEPTED_SEND_BY_VALUES = Set.of("POSTFACH", "MANUAL"); @@ -62,7 +63,7 @@ public class BescheidItem { private Map<String, Object> bescheidData; public enum Status { - DRAFT, BESCHEID; + DRAFT, BESCHEID, SEND; public boolean not(String value) { return !hasValue(value); diff --git a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/BescheidItemMapper.java b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/BescheidItemMapper.java index ff92f8c2f43b6525dbac9a44db3e573c4931e62e..2c915d65d934abab2636433b02e30a1eaf8e764d 100644 --- a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/BescheidItemMapper.java +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attacheditem/BescheidItemMapper.java @@ -42,6 +42,7 @@ import lombok.RequiredArgsConstructor; @Component @RequiredArgsConstructor +@Deprecated(since = "2.6.0") public class BescheidItemMapper { private final GrpcObjectMapper grpcObjectMapper; 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 e0ee53970c6e8b22f1e66b9060e7cd505847b987..aa6709149d8d968e263c86e0197e281ff99c3a8e 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 @@ -57,6 +57,8 @@ class VorgangAttachedItemRemoteService { private ClientInterceptor bescheidCallContextInterceptor; @Autowired private BescheidItemMapper bescheidItemMapper; + @Autowired + private AttachedItemMapper attachedItemMapper; public Optional<BescheidItem> findBescheidDraft(String vorgangId) { return findBescheidDraft(buildFindRequest(vorgangId)); @@ -90,6 +92,11 @@ class VorgangAttachedItemRemoteService { return bescheidItemMapper.mapFromVorgangAttachedItem(grpcVorgangAttachedItemResponse.getVorgangAttachedItem()); } + public AttachedItem getItem(String id) { + var grpcVorgangAttachedItemResponse = getServiceStub().getById(buildGetByIdRequest(id)); + return attachedItemMapper.mapFromVorgangAttachedItem(grpcVorgangAttachedItemResponse.getVorgangAttachedItem()); + } + GrpcVorgangAttachedItemRequest buildGetByIdRequest(String bescheidId) { return GrpcVorgangAttachedItemRequest.newBuilder().setId(bescheidId).build(); } diff --git a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attributes/ClientAttributeRemoteService.java b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attributes/ClientAttributeRemoteService.java new file mode 100644 index 0000000000000000000000000000000000000000..d87ae7496591bc23df9ba65148ca8c5f2ba24559 --- /dev/null +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attributes/ClientAttributeRemoteService.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.bescheid.attributes; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import de.ozgcloud.bescheid.BescheidCallContextAttachingInterceptor; +import de.ozgcloud.vorgang.grpc.clientAttribute.ClientAttributeServiceGrpc.ClientAttributeServiceBlockingStub; +import de.ozgcloud.vorgang.grpc.clientAttribute.GrpcAccessPermission; +import de.ozgcloud.vorgang.grpc.clientAttribute.GrpcClientAttribute; +import de.ozgcloud.vorgang.grpc.clientAttribute.GrpcClientAttributeValue; +import de.ozgcloud.vorgang.grpc.clientAttribute.GrpcSetClientAttributeRequest; +import io.grpc.ClientInterceptor; +import net.devh.boot.grpc.client.inject.GrpcClient; + +@Service("bescheid_ClientAttributeRemoteService") +class ClientAttributeRemoteService { + + @GrpcClient("vorgang-manager") + private ClientAttributeServiceBlockingStub serviceBlockingStub; + + @Autowired + private ClientInterceptor bescheidCallContextInterceptor; + + public void setBooleanReadOnlyClientAttribute(String vorgangId, String attributeName, boolean value) { + serviceBlockingStub.withInterceptors(bescheidCallContextInterceptor).set(buildRequest(vorgangId, attributeName, value)); + } + + GrpcSetClientAttributeRequest buildRequest(String vorgangId, String attributeName, boolean value) { + return GrpcSetClientAttributeRequest.newBuilder() + .setVorgangId(vorgangId) + .setAttribute(buildClientAttribute(attributeName, value)) + .build(); + } + + GrpcClientAttribute buildClientAttribute(String attributeName, boolean value) { + return GrpcClientAttribute.newBuilder() + .setClientName(BescheidCallContextAttachingInterceptor.BESCHEID_MANAGER_CLIENT_NAME) + .setAccess(GrpcAccessPermission.READ_ONLY) + .setAttributeName(attributeName) + .setValue(GrpcClientAttributeValue.newBuilder().setBoolValue(value).build()) + .build(); + } +} diff --git a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attributes/ClientAttributeService.java b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attributes/ClientAttributeService.java new file mode 100644 index 0000000000000000000000000000000000000000..1bd31a95ad2bcafe9628557b233b0cb5799f0060 --- /dev/null +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/attributes/ClientAttributeService.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.bescheid.attributes; + +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; + +@Service("bescheid_ClientAttributeService") +@RequiredArgsConstructor +public class ClientAttributeService { + + public static final String ATTRIBUTE_NAME_ANTRAG_BEWILLIGT = "ANTRAG_BEWILLIGT"; + + private final ClientAttributeRemoteService bescheidClientAttributeRemoteService; + + public void setAntragResult(String vorgangId, boolean antragResult) { + bescheidClientAttributeRemoteService.setBooleanReadOnlyClientAttribute(vorgangId, ATTRIBUTE_NAME_ANTRAG_BEWILLIGT, antragResult); + } + +} diff --git a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/dummy/DummyBescheidRemoteService.java b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/dummy/DummyBescheidRemoteService.java index 403defced86ffae1733612770f145c5f1581ca27..7a1152e6663a98cb91510d776b920aa3d12fa725 100644 --- a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/dummy/DummyBescheidRemoteService.java +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/dummy/DummyBescheidRemoteService.java @@ -1,6 +1,8 @@ package de.ozgcloud.bescheid.dummy; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import java.util.Optional; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import com.google.common.net.MediaType; @@ -12,7 +14,7 @@ import de.ozgcloud.bescheid.vorgang.Vorgang; import de.ozgcloud.common.binaryfile.TempFileUtils; @Service -@ConditionalOnMissingBean(BescheidRemoteService.class) +@ConditionalOnProperty("ozgcloud.feature.bescheid.enable-dummy-document-processor") class DummyBescheidRemoteService implements BescheidRemoteService { private static final String DUMMY_BESCHEID_FILE_NAME = "dummy-bescheid.pdf"; @@ -26,6 +28,8 @@ class DummyBescheidRemoteService implements BescheidRemoteService { .bescheidFile(file) .bescheidFileName(DUMMY_BESCHEID_FILE_NAME) .contentType(DUMMY_BESCHEID_CONTENT_TYPE) + .nachrichtText(Optional.of("Dummy Bescheid")) + .nachrichtSubject(Optional.of("Nachricht Subject")) .size(file.length()) .createdBy(request.getCreateFor().getId()) .build(); 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 a63d72018784b9b2f628a6267074f0d3345b5bd8..fc4a6e29d4b5b32820dc96b93c8d93e36eea985c 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 @@ -38,7 +38,7 @@ public class NachrichtService { return getAddress(bescheid).map(address -> Nachricht.builder() .vorgangId(bescheid.getVorgangId()) .postfachAddress(address) - .subject(SUBJECT) + .subject(bescheid.getNachrichtSubject().orElse(SUBJECT)) .mailBody(buildMessage(bescheid)) .createdBy(bescheid.getCreatedBy()) .bescheidFileId(bescheid.getBescheidFileId()) diff --git a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/smartdocuments/SmartDocumentsBescheidRemoteService.java b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/smartdocuments/SmartDocumentsBescheidRemoteService.java index 87bd9af60ce30a442052172f5b14d7aeb6f21e9d..6dd87bd4b8bb2423fe933e8d4e098e056890f8fc 100644 --- a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/smartdocuments/SmartDocumentsBescheidRemoteService.java +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/smartdocuments/SmartDocumentsBescheidRemoteService.java @@ -126,7 +126,7 @@ class SmartDocumentsBescheidRemoteService implements BescheidRemoteService { var expr = xPath.compile("/root/SmartDocument/Fields/NachrichtenText/text()"); var text = (Text) expr.evaluate(document, XPathConstants.NODE); - return Optional.of(text.getTextContent()); + return Optional.ofNullable(text.getTextContent()); } catch (XPathExpressionException | SAXException | IOException | ParserConfigurationException e) { LOG.error("XML-Parsing error on extracting Nachricht-Text: {}", e.getMessage(), e); } catch (ClassCastException e) { diff --git a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/vorgang/Vorgang.java b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/vorgang/Vorgang.java index d266fd84953b50a95fdc3780772f17f91aaf21bc..f8f076017157263a433cd7c582153e9bdbb4ea5d 100644 --- a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/vorgang/Vorgang.java +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/vorgang/Vorgang.java @@ -20,6 +20,7 @@ public class Vorgang { @JsonIgnore private VorgangId id; + private long version; private String vorgangName; diff --git a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/vorgang/VorgangService.java b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/vorgang/VorgangService.java index c479421c36775b9a98db6dd5cf84106faafa68f3..15ab8a87d9ffee2ef4c5a532a924c16d2405a5da 100644 --- a/bescheid-manager/src/main/java/de/ozgcloud/bescheid/vorgang/VorgangService.java +++ b/bescheid-manager/src/main/java/de/ozgcloud/bescheid/vorgang/VorgangService.java @@ -1,17 +1,46 @@ package de.ozgcloud.bescheid.vorgang; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import de.ozgcloud.apilib.common.command.OzgCloudCommand; +import de.ozgcloud.apilib.common.command.OzgCloudCommandService; +import de.ozgcloud.apilib.common.command.grpc.CommandMapper; import lombok.NonNull; +import lombok.RequiredArgsConstructor; @Service("bescheid_VorgangService") +@RequiredArgsConstructor public class VorgangService { - @Autowired - public VorgangRemoteService remoteService; + static final String VORGANG_BESCHEIDEN = "VORGANG_BESCHEIDEN"; + + private final VorgangRemoteService remoteService; + private final OzgCloudCommandService commandService; + private final CommandMapper commandMapper; public Vorgang getById(@NonNull VorgangId id) { return remoteService.getById(id); } + + public void bescheiden(String vorgangId) { + bescheiden(getById(VorgangId.from(vorgangId))); + } + + public void bescheiden(Vorgang vorgang) { + commandService.createAndWaitUntilDone(buildBescheidenCommand(vorgang)); + } + + OzgCloudCommand buildBescheidenCommand(Vorgang vorgang) { + return OzgCloudCommand.builder() + .vorgangId(commandMapper.toOzgCloudVorgangId(vorgang.getId().toString())) + .relationId(commandMapper.mapRelationId(vorgang.getId().toString())) + .relationVersion(vorgang.getVersion()) + .order(VORGANG_BESCHEIDEN) + .build(); + } + + public Vorgang.ServiceKonto getServiceKonto(String vorgangId) { + var vorgang = getById(VorgangId.from(vorgangId)); + return vorgang.getServiceKonto(); + } } diff --git a/bescheid-manager/src/main/java/de/ozgcloud/document/Document.java b/bescheid-manager/src/main/java/de/ozgcloud/document/Document.java new file mode 100644 index 0000000000000000000000000000000000000000..fc0d09b53f1d9cbc4d3792dca94c429f46d3e5e1 --- /dev/null +++ b/bescheid-manager/src/main/java/de/ozgcloud/document/Document.java @@ -0,0 +1,44 @@ +/* + * 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.document; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class Document { + + public static final String FIELD_DOCUMENT_TYPE = "type"; + public static final String FIELD_DOCUMENT_FILE = "documentFile"; + public static final String FIELD_NACHRICHT_TEXT = "nachrichtText"; + public static final String FIELD_NACHRICHT_SUBJECT = "nachrichtSubject"; + + private String id; + private String type; + private String fileId; + private String nachrichtSubject; + private String nachrichtText; + +} diff --git a/bescheid-manager/src/main/java/de/ozgcloud/document/DocumentGrpcService.java b/bescheid-manager/src/main/java/de/ozgcloud/document/DocumentGrpcService.java new file mode 100644 index 0000000000000000000000000000000000000000..673935d696777f3708fafbe134a8a496541b0d02 --- /dev/null +++ b/bescheid-manager/src/main/java/de/ozgcloud/document/DocumentGrpcService.java @@ -0,0 +1,48 @@ +/* + * 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.document; + +import de.ozgcloud.document.DocumentServiceGrpc.DocumentServiceImplBase; +import io.grpc.stub.StreamObserver; +import lombok.RequiredArgsConstructor; +import net.devh.boot.grpc.server.service.GrpcService; + +@GrpcService +@RequiredArgsConstructor +public class DocumentGrpcService extends DocumentServiceImplBase { + + private final DocumentService documentService; + private final DocumentMapper documentMapper; + + @Override + public void getDocument(GrpcGetDocumentRequest request, StreamObserver<GrpcGetDocumentResponse> responseObserver) { + var document = documentService.getDocument(request.getId()); + responseObserver.onNext(buildGetDocumentResponse(document)); + responseObserver.onCompleted(); + } + + GrpcGetDocumentResponse buildGetDocumentResponse(Document document) { + return GrpcGetDocumentResponse.newBuilder().setDocument(documentMapper.toGrpcDocument(document)).build(); + } +} diff --git a/bescheid-manager/src/main/java/de/ozgcloud/document/DocumentMapper.java b/bescheid-manager/src/main/java/de/ozgcloud/document/DocumentMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..41180bab88ef3c302300bd1e51ca34e3ed89619b --- /dev/null +++ b/bescheid-manager/src/main/java/de/ozgcloud/document/DocumentMapper.java @@ -0,0 +1,58 @@ +/* + * 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.document; + +import org.apache.commons.collections.MapUtils; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValueCheckStrategy; + +import de.ozgcloud.bescheid.attacheditem.AttachedItem; + +@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) +interface DocumentMapper { + + @Mapping(target = "allFields", ignore = true) + @Mapping(target = "unknownFields", ignore = true) + @Mapping(target = "typeBytes", ignore = true) + @Mapping(target = "nachrichtTextBytes", ignore = true) + @Mapping(target = "nachrichtSubjectBytes", ignore = true) + @Mapping(target = "mergeUnknownFields", ignore = true) + @Mapping(target = "mergeFrom", ignore = true) + @Mapping(target = "idBytes", ignore = true) + @Mapping(target = "fileIdBytes", ignore = true) + @Mapping(target = "clearOneof", ignore = true) + @Mapping(target = "clearField", ignore = true) + GrpcDocument toGrpcDocument(Document document); + + default Document fromAttachedItem(AttachedItem attachedItem) { + return Document.builder() + .id(attachedItem.getId()) + .type(MapUtils.getString(attachedItem.getItem(), Document.FIELD_DOCUMENT_TYPE)) + .fileId(MapUtils.getString(attachedItem.getItem(), Document.FIELD_DOCUMENT_FILE)) + .nachrichtText(MapUtils.getString(attachedItem.getItem(), Document.FIELD_NACHRICHT_TEXT)) + .nachrichtSubject(MapUtils.getString(attachedItem.getItem(), Document.FIELD_NACHRICHT_SUBJECT)) + .build(); + } +} 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 0a4e4090ec4f656f47d34d612a1cd72ef3474c56..25838cf4939ce8d9b2db28ac1f2bf404a83304e7 100644 --- a/bescheid-manager/src/main/java/de/ozgcloud/document/DocumentService.java +++ b/bescheid-manager/src/main/java/de/ozgcloud/document/DocumentService.java @@ -23,16 +23,20 @@ */ package de.ozgcloud.document; +import static java.util.Objects.*; + import java.util.HashMap; import java.util.Map; import java.util.Optional; import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; 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.BescheidCallContextAttachingInterceptor; import de.ozgcloud.bescheid.attacheditem.AttachedItemService; import de.ozgcloud.bescheid.attacheditem.BescheidItem; @@ -44,19 +48,30 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public class DocumentService { + public static final String DOCUMENT_ITEM_NAME = "Document"; + public static final String DOCUMENT_TYPE = "BESCHEID"; + static final String CREATE_ATTACHED_ITEM_ORDER = "CREATE_ATTACHED_ITEM"; - static final String DOCUMENT_ITEM_NAME = "Document"; static final String FIELD_DOCUMENT_TYPE = "type"; - static final String DOCUMENT_TYPE = "BESCHEID"; static final String FIELD_DOCUMENT_FILE = "documentFile"; + static final String FIELD_NACHRICHT_TEXT = "nachrichtText"; private final AttachedItemService attachedItemService; private final OzgCloudCommandService commandService; private final CommandMapper commandMapper; + private final DocumentMapper documentMapper; public String createBescheidDocument(Command command) { + return createBescheidDocument(command, buildItemMap(command)); + } + + public String createBescheidDocument(Command command, Bescheid bescheid) { + return createBescheidDocument(command, buildItemMap(bescheid)); + } + + String createBescheidDocument(Command command, Map<String, Object> itemMap) { validateBescheidItem(command.getRelationId()); - var ozgCloudCommand = buildCreateDocumentOzgCommand(command); + var ozgCloudCommand = buildCreateDocumentOzgCommand(command, buildAttachedItem(command, itemMap)); var executedCommand = commandService.createAndWaitUntilDone(ozgCloudCommand); return executedCommand.getCreatedResource(); } @@ -69,21 +84,21 @@ public class DocumentService { } } - OzgCloudCommand buildCreateDocumentOzgCommand(Command command) { + OzgCloudCommand buildCreateDocumentOzgCommand(Command command, Map<String, Object> bodyObject) { return OzgCloudCommand.builder() .order(CREATE_ATTACHED_ITEM_ORDER) .vorgangId(commandMapper.toOzgCloudVorgangId(command.getVorgangId())) .relationId(commandMapper.mapRelationId(command.getVorgangId())) - .bodyObject(buildAttachedItem(command)) + .bodyObject(bodyObject) .build(); } - Map<String, Object> buildAttachedItem(Command command) { + Map<String, Object> buildAttachedItem(Command command, Map<String, Object> itemMap) { var result = new HashMap<String, Object>(); result.put(BescheidItem.PROPERTY_VORGANG_ID, command.getVorgangId()); result.put(BescheidItem.PROPERTY_CLIENT, BescheidCallContextAttachingInterceptor.BESCHEID_MANAGER_CLIENT_NAME); result.put(BescheidItem.PROPERTY_ITEM_NAME, DOCUMENT_ITEM_NAME); - result.put(BescheidItem.PROPERTY_ITEM, buildItemMap(command)); + result.put(BescheidItem.PROPERTY_ITEM, itemMap); return result; } @@ -93,4 +108,16 @@ public class DocumentService { return Map.of(FIELD_DOCUMENT_TYPE, DOCUMENT_TYPE, FIELD_DOCUMENT_FILE, fileId); } + Map<String, Object> buildItemMap(Bescheid bescheid) { + if (isNull(bescheid.getBescheidFileId())) { + throw new TechnicalException("Bescheid file id is missing."); + } + return Map.of(FIELD_DOCUMENT_TYPE, DOCUMENT_TYPE, + FIELD_DOCUMENT_FILE, bescheid.getBescheidFileId().toString(), + FIELD_NACHRICHT_TEXT, bescheid.getNachrichtText().orElse(StringUtils.EMPTY)); + } + + public Document getDocument(String id) { + return documentMapper.fromAttachedItem(attachedItemService.getItem(id)); + } } 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 5964ad0da6fd99680853a85519c0958a150607ae..654825eb6bf6070b6651da59af1554a6e4669227 100644 --- a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/BescheidEventListenerTest.java +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/BescheidEventListenerTest.java @@ -8,8 +8,10 @@ import static org.mockito.Mockito.*; import java.util.Map; import java.util.Optional; +import java.util.function.Consumer; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -22,17 +24,19 @@ import org.springframework.security.core.context.SecurityContext; import org.springframework.test.util.ReflectionTestUtils; 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.callcontext.UserProfile; import de.ozgcloud.bescheid.common.callcontext.UserProfileTestFactory; import de.ozgcloud.bescheid.nachricht.NachrichtService; import de.ozgcloud.bescheid.smartdocuments.SmartDocumentsProperties; -import de.ozgcloud.bescheid.vorgang.VorgangId; import de.ozgcloud.command.Command; import de.ozgcloud.command.CommandCreatedEventTestFactory; import de.ozgcloud.command.CommandFailedEvent; import de.ozgcloud.command.CommandTestFactory; +import de.ozgcloud.document.BescheidDocumentCreatedEvent; +import de.ozgcloud.document.DocumentService; class BescheidEventListenerTest { @@ -48,6 +52,10 @@ class BescheidEventListenerTest { private NachrichtService nachrichtService; @Mock private AttachedItemService attachedItemService; + @Mock + private BescheidFeatureProperties featureProperties; + @Mock + private DocumentService documentService; @Mock private ApplicationEventPublisher eventPublisher; @@ -57,91 +65,36 @@ class BescheidEventListenerTest { @Nested class TestOnCreateBescheidCommand { - private Command command = CommandTestFactory.createBuilder() - .bodyObject( - Map.of(VORGANG_ID_BODYKEY, VORGANG_ID.toString(), - BESCHEID_VOM_BODYKEY, BESCHEID_VOM_STRING, - GENEHMIGT_BODYKEY, GENEHMIGT)) - .build(); - - @Mock - private SecurityContext secContext; + private final Command command = CommandTestFactory.create(); @Test - void shouldCreateBescheid() { - doReturn(true).when(listener).isKielEnvironment(); - + void shouldCallRunWithSecurityContext() { listener.onCreateBescheidCommand(CommandCreatedEventTestFactory.withCommand(command)); - verify(listener).execute(command); + verify(listener).runWithSecurityContext(eq(command), any()); } @Test - void shouldPublishErrorEventOnException() { - doReturn(true).when(listener).isKielEnvironment(); - doThrow(new RuntimeException("ups")).when(listener).doCreateBescheidBiz(any()); - + void shouldExecuteCreateBescheid() { listener.onCreateBescheidCommand(CommandCreatedEventTestFactory.withCommand(command)); - verify(eventPublisher).publishEvent(any(CommandFailedEvent.class)); + verify(listener).doCreateBescheid(command); } - - @Nested - class HandleSecurityContext { - - @BeforeEach - void init() { - when(userService.startSecurityContext(any())).thenReturn(secContext); - } - - @Test - void shouldStartSecurityContext() { - listener.onCreateBescheidCommand(CommandCreatedEventTestFactory.withCommand(command)); - - verify(userService).startSecurityContext(command); - } - - @Test - void shouldResetSecurityContext() { - listener.onCreateBescheidCommand(CommandCreatedEventTestFactory.withCommand(command)); - - verify(userService).resetSecurityContext(secContext); - } - - @Test - void shouldResetSecurityContextAfterException() { - doReturn(true).when(listener).isKielEnvironment(); - doThrow(new RuntimeException("ups")).when(listener).doCreateBescheidBiz(any()); - - listener.onCreateBescheidCommand(CommandCreatedEventTestFactory.withCommand(command)); - - verify(userService).resetSecurityContext(secContext); - } - } - - @Nested - class CreateBescheidRequest { - @Test - void shouldContainUserProfile() { - UserProfile user = UserProfileTestFactory.create(); - when(userService.getUserProfile()).thenReturn(user); - - var request = listener.createRequest(command); - - assertThat(request.getCreateFor()).isSameAs(user); - } - } - } @Nested - class TestExecute { + class TestDoCreateBescheid { + + 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)) + .build(); @Captor private ArgumentCaptor<BescheidCreatedEvent> eventCaptor; - private Command command = CommandTestFactory.create(); - @Nested class TestKielConfigured { @@ -152,26 +105,29 @@ class BescheidEventListenerTest { @Test void shouldCallDoCreateBescheidBiz() { - listener.execute(command); + listener.doCreateBescheid(COMMAND); - verify(listener).doCreateBescheidBiz(command); + verify(listener).doCreateBescheidBiz(COMMAND); + verify(attachedItemService, never()).createBescheidDraft(any()); } @Test - void shouldNotCallVorgangAttachedItemService() { - listener.execute(command); + void shouldCallNachrichtService() { + var bescheid = BescheidTestFactory.create(); + doReturn(bescheid).when(listener).doCreateBescheidBiz(any()); - verify(attachedItemService, never()).createBescheidDraft(any()); + listener.doCreateBescheid(COMMAND); + + verify(nachrichtService).createNachrichtDraft(bescheid); } @Test - void shouldCallPublishEvent() { - listener.execute(command); + void shouldPublishBescheidCreatedEvent() { + listener.doCreateBescheid(COMMAND); verify(eventPublisher).publishEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getCreatedResource()).isNull(); + assertThat(eventCaptor.getValue().getSource()).isEqualTo(CommandTestFactory.ID); } - } @Nested @@ -183,82 +139,40 @@ class BescheidEventListenerTest { } @Test - void shouldNotCallDoCreateBescheidBiz() { - listener.execute(command); + void shouldCallCreateBescheidDraft() { + listener.doCreateBescheid(COMMAND); + verify(attachedItemService).createBescheidDraft(COMMAND); verify(listener, never()).doCreateBescheidBiz(any()); } @Test - void shouldCallVorgangAttachedItemService() { - listener.execute(command); + @DisplayName("should publish BescheidCreatedEvent after creating BescheidDraft") + void shouldPublishBescheidCreatedEventWithCommand() { + var createdResource = "item-id"; + when(attachedItemService.createBescheidDraft(any())).thenReturn(createdResource); + + listener.doCreateBescheid(COMMAND); - verify(attachedItemService).createBescheidDraft(command); + verify(eventPublisher).publishEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getCommand()).isSameAs(COMMAND); } @Test - void shouldCallPublishEvent() { - var expectedItemId = "createdItemId"; - when(attachedItemService.createBescheidDraft(any())).thenReturn(expectedItemId); + @DisplayName("should publish BescheidCreatedEvent with created resource") + void shouldPublishBescheidCreatedEventWithCreatedResource() { + var createdResource = "item-id"; + when(attachedItemService.createBescheidDraft(any())).thenReturn(createdResource); - listener.execute(command); + listener.doCreateBescheid(COMMAND); verify(eventPublisher).publishEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getCreatedResource()).isEqualTo(expectedItemId); + assertThat(eventCaptor.getValue().getCreatedResource()).isEqualTo(createdResource); } } } - @Nested - class CreateBescheid { - - private Command command = CommandTestFactory.createBuilder() - .bodyObject( - Map.of(VORGANG_ID_BODYKEY, VORGANG_ID.toString(), - BESCHEID_VOM_BODYKEY, BESCHEID_VOM_STRING, - GENEHMIGT_BODYKEY, GENEHMIGT)) - .build(); - - @Captor - private ArgumentCaptor<BescheidRequest> requestCaptor; - - private Bescheid bescheid = BescheidTestFactory.create(); - private Bescheid bescheidWithFileId = BescheidTestFactory.create(); - - @BeforeEach - void init() { - when(service.createBescheid(any())).thenReturn(bescheid); - when(fileService.uploadBescheidFile(any())).thenReturn(bescheidWithFileId); - when(userService.getUserProfile()).thenReturn(UserProfileTestFactory.create()); - } - - @Test - void shouldCreateBescheid() { - listener.doCreateBescheidBiz(command); - - verify(service).createBescheid(requestCaptor.capture()); - assertThat(requestCaptor.getValue()).usingRecursiveComparison() - .isEqualTo(BescheidRequestTestFactory.createBuilder() - .vorgangId(VorgangId.from(CommandTestFactory.VORGANG_ID)) - .build()); - } - - @Test - void shouldSaveFile() { - listener.doCreateBescheidBiz(command); - - verify(fileService).uploadBescheidFile(bescheid); - } - - @Test - void shouldSaveNachricht() { - listener.doCreateBescheidBiz(command); - - verify(nachrichtService).createNachrichtDraft(bescheidWithFileId); - } - } - @Nested class TestIsKielEnvironment { @@ -295,149 +209,419 @@ class BescheidEventListenerTest { } } + @Nested + class TestCreateBescheidRequest { + + private static final Command COMMAND = CommandTestFactory.createBuilder() + .vorgangId(VORGANG_ID.toString()) + .bodyObject( + Map.of(BESCHEID_VOM_BODYKEY, BESCHEID_VOM_STRING, + GENEHMIGT_BODYKEY, false)) + .build(); + + @Test + void shouldSetVorgangId() { + var request = createBescheidRequest(); + + assertThat(request.getVorgangId()).isEqualTo(VORGANG_ID); + } + + @Test + void shouldSetBescheidVom() { + var request = createBescheidRequest(); + + assertThat(request.getBescheidVom()).isEqualTo(BESCHEID_VOM); + } + + @Test + void shouldSetGenehmigt() { + var request = createBescheidRequest(); + + assertThat(request.isGenehmigt()).isFalse(); + } + + @Test + void shouldSetDefaultGenehmigt() { + Command command = CommandTestFactory.createBuilder() + .vorgangId(VORGANG_ID.toString()) + .bodyObject( + Map.of(BESCHEID_VOM_BODYKEY, BESCHEID_VOM_STRING)) + .build(); + + var request = listener.createRequest(command); + + assertThat(request.isGenehmigt()).isTrue(); + } + + @Test + void shouldContainUserProfile() { + UserProfile user = UserProfileTestFactory.create(); + when(userService.getUserProfile()).thenReturn(user); + + var request = listener.createRequest(COMMAND); + + assertThat(request.getCreateFor()).isSameAs(user); + } + + private BescheidRequest createBescheidRequest() { + return listener.createRequest(COMMAND); + } + } + @Nested class TestOnDeleteBescheid { + private final Command command = CommandTestFactory.create(); + + @Test + void shouldCallRunWithSecurityContext() { + listener.onDeleteBescheid(CommandCreatedEventTestFactory.withCommand(command)); + + verify(listener).runWithSecurityContext(eq(command), any()); + } + + @Test + void shouldExecuteCreateBescheid() { + listener.onDeleteBescheid(CommandCreatedEventTestFactory.withCommand(command)); + + verify(listener).doDeleteBescheid(command); + } + } + + @Nested + class TestDoDeleteBescheid { + @Captor private ArgumentCaptor<BescheidDeletedEvent> bescheidDeletedEventCaptor; - @Captor - private ArgumentCaptor<CommandFailedEvent> commandFailedEventCaptor; - private Command command = CommandTestFactory.create(); + private final Command command = CommandTestFactory.create(); @Test void shouldCallAttachedItemService() { - listener.onDeleteBescheid(CommandCreatedEventTestFactory.withCommand(command)); + listener.doDeleteBescheid(command); verify(attachedItemService).deleteBescheidDraft(command); } @Test void shouldPublishCommandExecutedEvent() { - listener.onDeleteBescheid(CommandCreatedEventTestFactory.withCommand(command)); + listener.doDeleteBescheid(command); verify(eventPublisher).publishEvent(bescheidDeletedEventCaptor.capture()); assertThat(bescheidDeletedEventCaptor.getValue().getCommand()).isSameAs(command); } + } + + @Nested + class TestOnUpdateBescheid { + + private final Command command = CommandTestFactory.create(); + @Test - void shouldPublishErrorEventOnException() { - doThrow(new RuntimeException("ups")).when(attachedItemService).deleteBescheidDraft(any()); + void shouldCallRunWithSecurityContext() { + listener.onUpdateBescheidCommand(CommandCreatedEventTestFactory.withCommand(command)); - listener.onDeleteBescheid(CommandCreatedEventTestFactory.withCommand(command)); + verify(listener).runWithSecurityContext(eq(command), any()); + } - verify(eventPublisher).publishEvent(commandFailedEventCaptor.capture()); - assertThat(commandFailedEventCaptor.getValue().getSource()).isEqualTo(command.getId()); - assertThat(commandFailedEventCaptor.getValue().getErrorMessage()).isNotEmpty(); + @Test + void shouldExecuteCreateBescheid() { + listener.onUpdateBescheidCommand(CommandCreatedEventTestFactory.withCommand(command)); + + verify(listener).doUpdateBescheid(command); } - @Nested - class HandleSecurityContext { + } - @Mock - private SecurityContext secContext; + @Nested + class TestDoUpdateBescheid { - @BeforeEach - void init() { - when(userService.startSecurityContext(any())).thenReturn(secContext); - } + @Captor + private ArgumentCaptor<BescheidUpdatedEvent> bescheidUpdatedEventCaptor; - @Test - void shouldStartSecurityContext() { - listener.onCreateBescheidCommand(CommandCreatedEventTestFactory.withCommand(command)); + private final Command command = CommandTestFactory.createBuilder().build(); - verify(userService).startSecurityContext(command); - } + @Test + void shouldCallUpdateBescheidDraft() { + listener.doUpdateBescheid(command); - @Test - void shouldResetSecurityContext() { - listener.onCreateBescheidCommand(CommandCreatedEventTestFactory.withCommand(command)); + verify(attachedItemService).updateBescheidDraft(command); + } - verify(userService).resetSecurityContext(secContext); - } + @Test + void shouldPublishBescheidUpdatedEvent() { + listener.doUpdateBescheid(command); - @Test - void shouldResetSecurityContextAfterException() { - doReturn(true).when(listener).isKielEnvironment(); - doThrow(new RuntimeException("ups")).when(listener).doCreateBescheidBiz(any()); + verify(eventPublisher).publishEvent(bescheidUpdatedEventCaptor.capture()); + assertThat(bescheidUpdatedEventCaptor.getValue().getCommand()).isSameAs(command); + } - listener.onCreateBescheidCommand(CommandCreatedEventTestFactory.withCommand(command)); + } - verify(userService).resetSecurityContext(secContext); - } + @Nested + class TestOnCreateBescheidDocument { + + private final Command command = CommandTestFactory.create(); + + @Test + void shouldCallRunWithSecurityContext() { + listener.onCreatedBescheidDocument(CommandCreatedEventTestFactory.withCommand(command)); + + verify(listener).runWithSecurityContext(eq(command), any()); + } + + @Test + void shouldExecuteCreateBescheidDocument() { + listener.onCreatedBescheidDocument(CommandCreatedEventTestFactory.withCommand(command)); + + verify(listener).doCreateBescheidDocument(command); } } @Nested - class TestOnUpdateBescheid { + class TestDoCreateBescheidDocument { + + private static final Command COMMAND = CommandTestFactory.create(); + private static final Bescheid BESCHEID = BescheidTestFactory.create(); @Captor - private ArgumentCaptor<BescheidUpdatedEvent> bescheidUpdatedEventCaptor; + private ArgumentCaptor<BescheidDocumentCreatedEvent> eventCaptor; + + @BeforeEach + void init() { + doReturn(BESCHEID).when(listener).doCreateBescheidBiz(any()); + } + + @Test + void shouldCallCreateBescheidBiz() { + listener.doCreateBescheidDocument(COMMAND); + + verify(listener).doCreateBescheidBiz(COMMAND); + } + + @Test + void shouldCallCreateBescheidDocument() { + listener.doCreateBescheidDocument(COMMAND); + + verify(documentService).createBescheidDocument(COMMAND, BESCHEID); + } + + @Test + @DisplayName("should publish BescheidDocumentCreatedEvent with command") + void shouldPublishEventWithCommand() { + listener.doCreateBescheidDocument(COMMAND); + + verify(eventPublisher).publishEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getCommand()).isEqualTo(COMMAND); + } + + @Test + @DisplayName("should publish BescheidDocumentCreatedEvent with createdResource") + void shouldPublishEventWithCreatedResource() { + var bescheidDocument = "document-id"; + when(documentService.createBescheidDocument(any(), any(Bescheid.class))).thenReturn(bescheidDocument); + + listener.doCreateBescheidDocument(COMMAND); + + verify(eventPublisher).publishEvent(eventCaptor.capture()); + assertThat(eventCaptor.getValue().getCreatedResource()).isEqualTo(bescheidDocument); + } + } + + @Nested + class TestCreateBescheidBiz { + + private final Command command = CommandTestFactory.create(); + + @Test + void shouldCallCreateRequest() { + listener.doCreateBescheidBiz(command); + + verify(listener).createRequest(command); + } + + @Test + void shouldCallBescheidService() { + var bescheidRequest = BescheidRequestTestFactory.create(); + doReturn(bescheidRequest).when(listener).createRequest(any()); + + listener.doCreateBescheidBiz(command); + + verify(service).createBescheid(bescheidRequest); + } + + @Test + void shouldCallFileService() { + var bescheid = BescheidTestFactory.create(); + when(service.createBescheid(any())).thenReturn(bescheid); + + listener.doCreateBescheidBiz(command); + + verify(fileService).uploadBescheidFile(bescheid); + } + + @Test + void shouldReturnBescheid() { + var bescheid = BescheidTestFactory.create(); + when(fileService.uploadBescheidFile(any())).thenReturn(bescheid); + + var result = listener.doCreateBescheidBiz(command); + + assertThat(result).isSameAs(bescheid); + } + } + + @Nested + class TestOnSendBescheidCommand { + + private final Command command = CommandTestFactory.create(); + + @Test + void shouldCallRunWithSecurityContext() { + listener.onSendBescheidCommand(CommandCreatedEventTestFactory.withCommand(command)); + + verify(listener).runWithSecurityContext(eq(command), any()); + } + + @Test + void shouldExecuteDoSendBescheid() { + listener.onSendBescheidCommand(CommandCreatedEventTestFactory.withCommand(command)); + + verify(listener).doSendBescheid(command); + } + } + + @Nested + class TestDoSendBescheid { + + private static final Command COMMAND = CommandTestFactory.createBuilder().relationId(AttachedItemTestFactory.ID) + .relationVersion(AttachedItemTestFactory.VERSION).build(); + @Captor - private ArgumentCaptor<CommandFailedEvent> updateFailedEventCaptor; + private ArgumentCaptor<BescheidSentEvent> bescheidSentEventCaptor; + + @Test + void shouldCallSendBescheid() { + + listener.doSendBescheid(COMMAND); - private Command command = CommandTestFactory.createBuilder().build(); + verify(service).sendBescheidManually(AttachedItemTestFactory.ID, AttachedItemTestFactory.VERSION); + } @Test - void shouldCallUpdateBescheidDraft() { - listener.onUpdateBescheidCommand(CommandCreatedEventTestFactory.withCommand(command)); + void shouldPublishEvent() { + listener.doSendBescheid(COMMAND); - verify(attachedItemService).updateBescheidDraft(command); + verify(eventPublisher).publishEvent(bescheidSentEventCaptor.capture()); + assertThat(bescheidSentEventCaptor.getValue().getCommand()).isEqualTo(COMMAND); + } + } + + @Nested + class TestOnSendPostfachMailCommand { + + private final Command command = CommandTestFactory.create(); + + @Test + void shouldCallRunWithSecurityContext() { + listener.onSendPostfachMailCommand(CommandCreatedEventTestFactory.withCommand(command)); + + verify(listener).runWithSecurityContext(eq(command), any()); } @Test - void shouldPublishBescheidUpdatedEvent() { - listener.onUpdateBescheidCommand(CommandCreatedEventTestFactory.withCommand(command)); + void shouldExecuteDoSendPostfachMail() { + listener.onSendPostfachMailCommand(CommandCreatedEventTestFactory.withCommand(command)); - verify(eventPublisher).publishEvent(bescheidUpdatedEventCaptor.capture()); - assertThat(bescheidUpdatedEventCaptor.getValue().getCommand()).isSameAs(command); + verify(listener).doSendPostfachMail(command); } + } + + @Nested + class TestDoSendPostfachMail { + + @Captor + private ArgumentCaptor<BescheidSentEvent> bescheidSentEventCaptor; @Test - void shouldPublishCommandFailedEvent() { - doThrow(new RuntimeException("ups")).when(attachedItemService).updateBescheidDraft(any()); + void shouldCallBescheidService() { + var bescheidId = "bescheid-id"; + var bescheidVersion = 1L; + var command = CommandTestFactory.createBuilder().relationId(bescheidId).relationVersion(bescheidVersion).build(); - listener.onUpdateBescheidCommand(CommandCreatedEventTestFactory.withCommand(command)); + listener.doSendPostfachMail(command); - verify(eventPublisher).publishEvent(updateFailedEventCaptor.capture()); - assertThat(updateFailedEventCaptor.getValue().getSource()).isEqualTo(command.getId()); + verify(service).sendBescheidPostfachMail(bescheidId, bescheidVersion); } - @Nested - class HandleSecurityContext { + @Test + void shouldPublishEvent() { + var command = CommandTestFactory.createBuilder().relationVersion(0L).build(); - @Mock - private SecurityContext secContext; + listener.doSendPostfachMail(command); - @BeforeEach - void init() { - when(userService.startSecurityContext(any())).thenReturn(secContext); - } + verify(eventPublisher).publishEvent(bescheidSentEventCaptor.capture()); + assertThat(bescheidSentEventCaptor.getValue().getCommand()).isEqualTo(command); + } + } - @Test - void shouldStartSecurityContext() { - listener.onUpdateBescheidCommand(CommandCreatedEventTestFactory.withCommand(command)); + @Nested + class TestRunWithSecurityContext { - verify(userService).startSecurityContext(command); - } + private final Command command = CommandTestFactory.createBuilder().build(); - @Test - void shouldResetSecurityContext() { - listener.onUpdateBescheidCommand(CommandCreatedEventTestFactory.withCommand(command)); + @Mock + private Consumer<Command> commandExecutor; + @Mock + private SecurityContext secContext; + @Captor + private ArgumentCaptor<CommandFailedEvent> commandFailedEventCaptor; - verify(userService).resetSecurityContext(secContext); - } + @BeforeEach + void init() { + when(userService.startSecurityContext(any())).thenReturn(secContext); + } - @Test - void shouldResetSecurityContextAfterException() { - doThrow(new RuntimeException("ups")).when(attachedItemService).updateBescheidDraft(any()); + @Test + void shouldStartSecurityContext() { + listener.runWithSecurityContext(command, commandExecutor); - listener.onUpdateBescheidCommand(CommandCreatedEventTestFactory.withCommand(command)); + verify(userService).startSecurityContext(command); + } - verify(userService).resetSecurityContext(secContext); - } + @Test + void shouldExecuteCommand() { + listener.runWithSecurityContext(command, commandExecutor); + + verify(commandExecutor).accept(command); + } + + @Test + void shouldResetSecurityContext() { + listener.runWithSecurityContext(command, commandExecutor); + + verify(userService).resetSecurityContext(secContext); + } + + @Test + void shouldResetSecurityContextAfterException() { + doThrow(new RuntimeException("ups")).when(commandExecutor).accept(any()); + + listener.runWithSecurityContext(command, commandExecutor); + + verify(userService).resetSecurityContext(secContext); } + @Test + void shouldPublishCommandFailedEvent() { + doThrow(new RuntimeException("ups")).when(commandExecutor).accept(any()); + + listener.runWithSecurityContext(command, commandExecutor); + verify(eventPublisher).publishEvent(commandFailedEventCaptor.capture()); + assertThat(commandFailedEventCaptor.getValue().getSource()).isEqualTo(command.getId()); + assertThat(commandFailedEventCaptor.getValue().getErrorMessage()).isNotEmpty(); + } } } diff --git a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/BescheidServiceTest.java b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/BescheidServiceTest.java index afbc1a5a925c54528878259074c6b64c1b78bc81..2d80f5de69e2af46c5a3d1ad6dba7229f4e2932d 100644 --- a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/BescheidServiceTest.java +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/BescheidServiceTest.java @@ -1,21 +1,45 @@ package de.ozgcloud.bescheid; 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.Collections; +import java.util.Map; +import java.util.Optional; + 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.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Spy; +import org.springframework.test.util.ReflectionTestUtils; +import de.ozgcloud.bescheid.attacheditem.AttachedItem; +import de.ozgcloud.bescheid.attacheditem.AttachedItemService; +import de.ozgcloud.bescheid.attacheditem.AttachedItemTestFactory; +import de.ozgcloud.bescheid.attributes.ClientAttributeService; +import de.ozgcloud.bescheid.common.callcontext.CurrentUserService; +import de.ozgcloud.bescheid.common.callcontext.UserProfile; +import de.ozgcloud.bescheid.nachricht.NachrichtService; +import de.ozgcloud.bescheid.vorgang.ServiceKontoTestFactory; import de.ozgcloud.bescheid.vorgang.Vorgang; +import de.ozgcloud.bescheid.vorgang.VorgangId; import de.ozgcloud.bescheid.vorgang.VorgangService; import de.ozgcloud.bescheid.vorgang.VorgangTestFactory; +import de.ozgcloud.command.CommandTestFactory; +import de.ozgcloud.common.errorhandling.TechnicalException; class BescheidServiceTest { + @Spy @InjectMocks private BescheidService service; @@ -23,12 +47,27 @@ class BescheidServiceTest { private VorgangService vorgangService; @Mock private BescheidRemoteService remoteService; + @Mock + private AttachedItemService attachedItemService; + @Mock + private NachrichtService nachrichtService; + @Mock + private CurrentUserService currentUserService; + @Mock + private UserProfile callContextUser; + @Mock + private ClientAttributeService clientAttributeService; + + @BeforeEach + void init() { + ReflectionTestUtils.setField(service, "remoteService", Optional.of(remoteService)); + } @Nested class TestCreateBescheid { - private Vorgang vorgang = VorgangTestFactory.create(); - private Bescheid bescheid = BescheidTestFactory.createBuilder().vorgangId(null).build(); + private final Vorgang vorgang = VorgangTestFactory.create(); + private final Bescheid bescheid = BescheidTestFactory.createBuilder().vorgangId(null).build(); @BeforeEach void initMocks() { @@ -60,4 +99,499 @@ class BescheidServiceTest { } } + @Nested + class TestSendBescheidManually { + + private final AttachedItem bescheidItem = AttachedItemTestFactory.createBescheid(); + + @BeforeEach + void init() { + doNothing().when(service).validateBescheidSendManually(any(), anyLong()); + doReturn(bescheidItem).when(attachedItemService).getItem(anyString()); + } + + @Test + void shouldCallGetItem() { + sendBescheid(); + + verify(attachedItemService).getItem(AttachedItemTestFactory.ID); + } + + @Test + void shouldCallValidateBescheid() { + sendBescheid(); + + verify(service).validateBescheidSendManually(bescheidItem, AttachedItemTestFactory.VERSION); + } + + @Test + void shouldCallSendBescheid() { + sendBescheid(); + + verify(service).sendBescheid(bescheidItem); + } + + private void sendBescheid() { + service.sendBescheidManually(AttachedItemTestFactory.ID, AttachedItemTestFactory.VERSION); + } + } + + @Nested + class TestValidateBescheidSendManually { + + @Test + void shouldCallValidateBescheid() { + var bescheidItem = AttachedItemTestFactory.createBescheid(); + + service.validateBescheidSendManually(bescheidItem, AttachedItemTestFactory.VERSION); + + verify(service).validateBescheid(bescheidItem, AttachedItemTestFactory.VERSION); + } + + @Test + void shouldValidate() { + doNothing().when(service).validateBescheid(any(), anyLong()); + var bescheidItem = AttachedItemTestFactory.createBescheid(); + + assertDoesNotThrow(() -> service.validateBescheidSendManually(bescheidItem, AttachedItemTestFactory.VERSION)); + } + + @DisplayName("should decline when") + @ParameterizedTest(name = "sendBy = {0}") + @EnumSource(value = Bescheid.SendBy.class, names = "MANUAL", mode = EnumSource.Mode.EXCLUDE) + void shouldDeclineSendByNotManual(Bescheid.SendBy sendBy) { + var bescheidItem = AttachedItemTestFactory.createBescheidBuilder().itemEntry(Bescheid.FIELD_SEND_BY, sendBy.name()).build(); + + var message = assertThrows(TechnicalException.class, + () -> service.validateBescheidSendManually(bescheidItem, AttachedItemTestFactory.VERSION)).getMessage(); + assertThat(message).contains("sendBy"); + } + } + + @Nested + class TestSendBescheidPostfachMail { + + private final AttachedItem bescheidItem = AttachedItemTestFactory.createBescheid(); + @Mock + private AttachedItem bescheidSendItem; + @Captor + private ArgumentCaptor<VorgangId> vorgangIdCaptor; + + @BeforeEach + void init() { + doNothing().when(service).validateBescheidSendPostfach(any(), anyLong()); + doReturn(bescheidItem).when(attachedItemService).getItem(anyString()); + lenient().when(callContextUser.getId()).thenReturn(UserId.from("user-id")); + lenient().when(currentUserService.getUserProfile()).thenReturn(callContextUser); + } + + @Test + void shouldCallGetItem() { + when(vorgangService.getById(any())).thenReturn(VorgangTestFactory.create()); + + sendBescheid(); + + verify(attachedItemService).getItem(AttachedItemTestFactory.ID); + } + + @Test + void shouldCallValidateBescheid() { + when(vorgangService.getById(any())).thenReturn(VorgangTestFactory.create()); + + sendBescheid(); + + verify(service).validateBescheidSendPostfach(bescheidItem, AttachedItemTestFactory.VERSION); + } + + @Test + void shouldCallVorgangService() { + when(vorgangService.getById(any())).thenReturn(VorgangTestFactory.create()); + + sendBescheid(); + + verify(vorgangService).getById(vorgangIdCaptor.capture()); + assertThat(vorgangIdCaptor.getValue()).hasToString(CommandTestFactory.VORGANG_ID); + } + + @Test + void shouldCallBuildBescheid() { + var serviceKonto = ServiceKontoTestFactory.create(); + var vorgang = VorgangTestFactory.createBuilder().serviceKonto(serviceKonto).build(); + doReturn(vorgang).when(vorgangService).getById(any()); + + sendBescheid(); + + verify(service).buildBescheid(bescheidItem, serviceKonto); + } + + @Test + void shouldCallNachrichtService() { + var bescheid = BescheidTestFactory.create(); + doReturn(bescheid).when(service).buildBescheid(any(), any()); + when(vorgangService.getById(any())).thenReturn(VorgangTestFactory.create()); + + sendBescheid(); + + verify(nachrichtService).createNachrichtDraft(bescheid); + } + + @Test + void shouldCallSendBescheid() { + when(vorgangService.getById(any())).thenReturn(VorgangTestFactory.create()); + + sendBescheid(); + + verify(service).sendBescheid(bescheidItem); + } + + private void sendBescheid() { + service.sendBescheidPostfachMail(AttachedItemTestFactory.ID, AttachedItemTestFactory.VERSION); + } + } + + @Nested + class TestValidateBescheidSendPostfach { + + @Test + void shouldCallValidateBescheid() { + var bescheidItem = AttachedItemTestFactory.createBescheidBuilder() + .itemEntry(Bescheid.FIELD_SEND_BY, Bescheid.SendBy.NACHRICHT.name()) + .itemEntry(Bescheid.FIELD_NACHRICHT_SUBJECT, AttachedItemTestFactory.NACHRICHT_SUBJECT) + .itemEntry(Bescheid.FIELD_NACHRICHT_TEXT, AttachedItemTestFactory.NACHRICHT_TEXT).build(); + + service.validateBescheidSendPostfach(bescheidItem, AttachedItemTestFactory.VERSION); + + verify(service).validateBescheid(bescheidItem, AttachedItemTestFactory.VERSION); + } + + @Test + void shouldValidate() { + doNothing().when(service).validateBescheid(any(), anyLong()); + var bescheidItem = AttachedItemTestFactory.createBescheidBuilder() + .itemEntry(Bescheid.FIELD_SEND_BY, Bescheid.SendBy.NACHRICHT.name()) + .itemEntry(Bescheid.FIELD_NACHRICHT_SUBJECT, AttachedItemTestFactory.NACHRICHT_SUBJECT) + .itemEntry(Bescheid.FIELD_NACHRICHT_TEXT, AttachedItemTestFactory.NACHRICHT_TEXT).build(); + + assertDoesNotThrow(() -> service.validateBescheidSendPostfach(bescheidItem, AttachedItemTestFactory.VERSION)); + } + + @DisplayName("should decline when") + @ParameterizedTest(name = "sendBy = {0}") + @EnumSource(value = Bescheid.SendBy.class, names = "NACHRICHT", mode = EnumSource.Mode.EXCLUDE) + void shouldDeclineSendByNotPostfach(Bescheid.SendBy sendBy) { + var bescheidItem = AttachedItemTestFactory.createBescheidBuilder() + .itemEntry(Bescheid.FIELD_SEND_BY, sendBy.name()) + .itemEntry(Bescheid.FIELD_NACHRICHT_SUBJECT, AttachedItemTestFactory.NACHRICHT_SUBJECT) + .itemEntry(Bescheid.FIELD_NACHRICHT_TEXT, AttachedItemTestFactory.NACHRICHT_TEXT).build(); + + var message = assertThrows(TechnicalException.class, + () -> service.validateBescheidSendPostfach(bescheidItem, AttachedItemTestFactory.VERSION)).getMessage(); + assertThat(message).contains("sendBy"); + } + + @Test + void shouldDeclineWhenNoSubject() { + var bescheidItem = AttachedItemTestFactory.createBescheidBuilder() + .itemEntry(Bescheid.FIELD_SEND_BY, Bescheid.SendBy.NACHRICHT.name()) + .itemEntry(Bescheid.FIELD_NACHRICHT_SUBJECT, null) + .itemEntry(Bescheid.FIELD_NACHRICHT_TEXT, AttachedItemTestFactory.NACHRICHT_TEXT).build(); + + var message = assertThrows(TechnicalException.class, + () -> service.validateBescheidSendPostfach(bescheidItem, AttachedItemTestFactory.VERSION)).getMessage(); + assertThat(message).contains("nachricht subject"); + } + + @Test + void shouldDeclineWhenNoText() { + var bescheidItem = AttachedItemTestFactory.createBescheidBuilder() + .itemEntry(Bescheid.FIELD_SEND_BY, Bescheid.SendBy.NACHRICHT.name()) + .itemEntry(Bescheid.FIELD_NACHRICHT_SUBJECT, AttachedItemTestFactory.NACHRICHT_SUBJECT) + .itemEntry(Bescheid.FIELD_NACHRICHT_TEXT, null).build(); + + var message = assertThrows(TechnicalException.class, + () -> service.validateBescheidSendPostfach(bescheidItem, AttachedItemTestFactory.VERSION)).getMessage(); + assertThat(message).contains("nachricht text"); + } + } + + @Nested + class TestValidateBescheid { + + @Test + void shouldValidateBescheid() { + var bescheidItem = AttachedItemTestFactory.createBescheid(); + + assertDoesNotThrow(() -> service.validateBescheidSendManually(bescheidItem, AttachedItemTestFactory.VERSION)); + } + + @DisplayName("should decline when") + @ParameterizedTest(name = "status = {0}") + @EnumSource(value = Bescheid.Status.class, names = "DRAFT", mode = EnumSource.Mode.EXCLUDE) + void shouldDeclineNotDraft(Bescheid.Status status) { + var bescheidItem = AttachedItemTestFactory.createBescheidBuilder().itemEntry(Bescheid.FIELD_STATUS, status.name()).build(); + + var message = assertThrows(TechnicalException.class, + () -> service.validateBescheidSendManually(bescheidItem, AttachedItemTestFactory.VERSION)).getMessage(); + assertThat(message).contains("has status"); + } + + @Test + void shouldDeclineWhenNoDocument() { + var bescheidItem = AttachedItemTestFactory.createBescheidBuilder().itemEntry(Bescheid.FIELD_BESCHEID_DOCUMENT, null).build(); + + var message = assertThrows(TechnicalException.class, + () -> service.validateBescheidSendManually(bescheidItem, AttachedItemTestFactory.VERSION)).getMessage(); + assertThat(message).contains("document"); + } + + @Test + void shouldDeclineWhenVersionMismatch() { + var bescheidItem = AttachedItemTestFactory.createBescheid(); + + var message = assertThrows(TechnicalException.class, + () -> service.validateBescheidSendManually(bescheidItem, AttachedItemTestFactory.VERSION + 1)).getMessage(); + assertThat(message).contains("version"); + } + } + + @Nested + class TestBuildBescheid { + + private static final AttachedItem BESCHEID_ITEM = AttachedItemTestFactory.createBescheid(); + private static final Vorgang.ServiceKonto SERVICE_KONTO = ServiceKontoTestFactory.create(); + private static final String CREATED_BY = "user-id"; + + @BeforeEach + void init() { + when(callContextUser.getId()).thenReturn(UserId.from(CREATED_BY)); + when(currentUserService.getUserProfile()).thenReturn(callContextUser); + } + + @Test + void shouldSetVorgangId() { + var result = buildBescheid(); + + assertThat(result.getVorgangId()).hasToString(CommandTestFactory.VORGANG_ID); + } + + @Test + void shouldCallGetBewilligt() { + buildBescheid(); + + verify(service).getBewilligt(BESCHEID_ITEM); + } + + @Test + void shouldSetGenehmigt() { + doReturn(true).when(service).getBewilligt(any()); + + var result = buildBescheid(); + + assertThat(result.isGenehmigt()).isTrue(); + } + + @Test + void shouldSetBescheidFileId() { + var result = buildBescheid(); + + assertThat(result.getBescheidFileId()).hasToString(AttachedItemTestFactory.BESCHEID_DOCUMENT); + } + + @Test + void shouldSetNachrichtSubject() { + var result = buildBescheid(); + + assertThat(result.getNachrichtSubject()).contains(AttachedItemTestFactory.NACHRICHT_SUBJECT); + } + + @Test + void shouldSetNachrichtText() { + var result = buildBescheid(); + + assertThat(result.getNachrichtText()).contains(AttachedItemTestFactory.NACHRICHT_TEXT); + } + + @Test + void shouldCallCurrentUserService() { + buildBescheid(); + + verify(currentUserService).getUserProfile(); + } + + @Test + void shouldSetCreatedBy() { + var result = buildBescheid(); + + assertThat(result.getCreatedBy()).hasToString(CREATED_BY); + } + + @Test + void shouldSetServiceKonto() { + var result = buildBescheid(); + + assertThat(result.getServiceKonto()).usingRecursiveComparison().isEqualTo(SERVICE_KONTO); + } + + private Bescheid buildBescheid() { + return service.buildBescheid(BESCHEID_ITEM, SERVICE_KONTO); + } + } + + @Nested + class TestGetNachrichtSubject { + + @Test + void shouldReturnSubject() { + var bescheidItem = AttachedItemTestFactory.createBescheidBuilder().item(Map.of(Bescheid.FIELD_NACHRICHT_SUBJECT, + AttachedItemTestFactory.NACHRICHT_SUBJECT)).build(); + + var result = service.getNachrichtSubject(bescheidItem); + + assertThat(result).isEqualTo(AttachedItemTestFactory.NACHRICHT_SUBJECT); + } + } + + @Nested + class TestGetNachrichtText { + + @Test + void shouldReturnText() { + var bescheidItem = AttachedItemTestFactory.createBescheidBuilder().item(Map.of(Bescheid.FIELD_NACHRICHT_TEXT, + AttachedItemTestFactory.NACHRICHT_TEXT)).build(); + + var result = service.getNachrichtText(bescheidItem); + + assertThat(result).isEqualTo(AttachedItemTestFactory.NACHRICHT_TEXT); + } + } + + @Nested + class TestSendBescheid { + + private final AttachedItem bescheidItem = AttachedItemTestFactory.createBescheid(); + @Mock + private AttachedItem bescheidSendItem; + + @Test + void shouldCallBuildBescheidSend() { + sendBescheid(); + + verify(service).setBescheidSendStatus(bescheidItem); + } + + @Test + void shouldCallPatch() { + doReturn(bescheidSendItem).when(service).setBescheidSendStatus(any()); + + sendBescheid(); + + verify(attachedItemService).patch(bescheidSendItem); + } + + @Test + void shouldCallBescheiden() { + sendBescheid(); + + verify(vorgangService).bescheiden(CommandTestFactory.VORGANG_ID); + } + + @Test + void shouldCallGetItem() { + when(attachedItemService.getItem(any())).thenReturn(AttachedItemTestFactory.createBescheid()); + doThrow(new TechnicalException("error")).when(vorgangService).bescheiden(anyString()); + + assertThrows(TechnicalException.class, this::sendBescheid); + + verify(attachedItemService).getItem(AttachedItemTestFactory.ID); + } + + @Test + void shouldCallSetBescheidDraftStatus() { + doThrow(new TechnicalException("error")).when(vorgangService).bescheiden(anyString()); + var updatedBescheidUitem = AttachedItemTestFactory.createBescheid(); + when(attachedItemService.getItem(any())).thenReturn(updatedBescheidUitem); + + assertThrows(TechnicalException.class, this::sendBescheid); + + verify(service).setBescheidDraftStatus(updatedBescheidUitem); + } + + @Test + void shouldCallPatchWhenBescheidenFails() { + doReturn(bescheidSendItem).when(service).setBescheidDraftStatus(any()); + doThrow(new TechnicalException("error")).when(vorgangService).bescheiden(anyString()); + + assertThrows(TechnicalException.class, this::sendBescheid); + + verify(attachedItemService).patch(bescheidSendItem); + } + + @Test + void shouldCallGetBewilligt() { + sendBescheid(); + + verify(service).getBewilligt(bescheidItem); + } + + @Test + void shouldCallClientAttributeService() { + doReturn(true).when(service).getBewilligt(any()); + + sendBescheid(); + + verify(clientAttributeService).setAntragResult(CommandTestFactory.VORGANG_ID, true); + } + + private void sendBescheid() { + service.sendBescheid(bescheidItem); + } + } + + @Nested + class TestGetBewilligt { + + @Test + void shouldReturnBewilligt() { + var bescheidItem = AttachedItemTestFactory.createBescheidBuilder().itemEntry(Bescheid.FIELD_BEWILLIGT, true).build(); + + var result = service.getBewilligt(bescheidItem); + + assertThat(result).isTrue(); + } + + @Test + void shouldReturnDefault() { + var bescheidItem = AttachedItemTestFactory.createBescheidBuilder().clearItem().build(); + + var result = service.getBewilligt(bescheidItem); + + assertThat(result).isFalse(); + } + } + + @Nested + class TestSetBescheidSendStatus { + + @Test + void shouldSetSendStatus() { + var bescheidItem = AttachedItemTestFactory.createBescheid(); + + var result = service.setBescheidSendStatus(bescheidItem); + + assertThat(result.getItem()).containsEntry(Bescheid.FIELD_STATUS, Bescheid.Status.SEND.name()); + } + } + + @Nested + class TestSetBescheidDraftStatus { + + @Test + void shouldSetDraftStatus() { + var bescheidItem = AttachedItemTestFactory.createBescheidBuilder().item(Collections.emptyMap()).build(); + + var result = service.setBescheidDraftStatus(bescheidItem); + + assertThat(result.getItem()).containsEntry(Bescheid.FIELD_STATUS, Bescheid.Status.DRAFT.name()); + } + } } diff --git a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/BescheidTestFactory.java b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/BescheidTestFactory.java index e4ccee014906c47e7bf442205002675ca054cad5..a0fac1f66bc68d8fe0056ca38c34f551ac163e63 100644 --- a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/BescheidTestFactory.java +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/BescheidTestFactory.java @@ -19,8 +19,10 @@ public class BescheidTestFactory { public static final byte[] TEST_BESCHEID = "testbescheid".getBytes(); public static final File BESCHEID_FILE = TempFileUtils.writeTmpFile(TEST_BESCHEID); + public static final String BESCHEID_FILE_ID = "bescheid-file-id"; public static final String CONTENT_TYPE = MediaType.APPLICATION_PDF_VALUE; + public static final String NACHRICHT_SUBJECT = LoremIpsum.getInstance().getWords(5); public static final String NACHRICHT_TEXT = LoremIpsum.getInstance().getWords(5); public static Bescheid create() { @@ -34,6 +36,7 @@ public class BescheidTestFactory { .contentType(CONTENT_TYPE) .bescheidFileName(FILE_NAME) .bescheidFile(BESCHEID_FILE) + .nachrichtSubject(Optional.of(NACHRICHT_SUBJECT)) .nachrichtText(Optional.of(NACHRICHT_TEXT)) .genehmigt(true) .size(TEST_BESCHEID.length) diff --git a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attacheditem/AttachedItemMapperTest.java b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attacheditem/AttachedItemMapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..782c98d8cc4bc4066b43d34e9a45334d724ca7d5 --- /dev/null +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attacheditem/AttachedItemMapperTest.java @@ -0,0 +1,61 @@ +/* + * 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.bescheid.attacheditem; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import de.ozgcloud.document.DocumentService; +import de.ozgcloud.vorgang.common.grpc.GrpcObjectMapper; + +class AttachedItemMapperTest { + + @InjectMocks + private AttachedItemMapper mapper = Mappers.getMapper(AttachedItemMapper.class); + + @Mock + private GrpcObjectMapper grpcObjectMapper; + + @BeforeEach + void init() { + when(grpcObjectMapper.mapFromGrpc(any())).thenReturn(AttachedItemTestFactory.createDocumentItem()); + } + + @Test + void shouldMapFromVorgangAttachedItem() { + var grpcVorgangAttachedItem = GrpcVorgangAttachedItemTestFactory.createBuilder().setItemName(DocumentService.DOCUMENT_ITEM_NAME) + .setVersion(AttachedItemTestFactory.VERSION).build(); + + var result = mapper.mapFromVorgangAttachedItem(grpcVorgangAttachedItem); + + assertThat(result).usingRecursiveComparison().isEqualTo(AttachedItemTestFactory.createDocument()); + } + +} \ No newline at end of file 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 3c10b5cdb070f9c0cbd60590cf805170c0491632..abb9739abd06cf487fe5e8d1919eb186f22394ea 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 @@ -115,7 +115,7 @@ class AttachedItemServiceTest { private static final String CREATED_ATTACHED_ITEM_ID = "attached-item-id"; private Command command = CommandTestFactory.createBuilder().bodyObject(BescheidItemTestFactory.createBescheidBody()).build(); - private BescheidItem bescheidItem = BescheidItemTestFactory.create(); + private final BescheidItem bescheidItem = BescheidItemTestFactory.create(); @Mock private OzgCloudCommand ozgCloudCommand; @@ -295,8 +295,8 @@ class AttachedItemServiceTest { @Mock private OzgCloudCommand updateAttachedItemCommand; - private Command command = CommandTestFactory.createBuilder().relationId(BescheidItemTestFactory.ID).build(); - private BescheidItem bescheidItem = BescheidItemTestFactory.create(); + private final Command command = CommandTestFactory.createBuilder().relationId(BescheidItemTestFactory.ID).build(); + private final BescheidItem bescheidItem = BescheidItemTestFactory.create(); @BeforeEach void init() { @@ -365,7 +365,7 @@ class AttachedItemServiceTest { @Nested class TestBuildAttachedItemAsMap { - private Command command = CommandTestFactory.createBuilder().bodyObject(BescheidItemTestFactory.createBescheidBody()).build(); + private final Command command = CommandTestFactory.createBuilder().bodyObject(BescheidItemTestFactory.createBescheidBody()).build(); @Test void shouldSetVorgangId() { @@ -405,7 +405,7 @@ class AttachedItemServiceTest { @Mock private OzgCloudCommand updateItemCommand; - private Command command = CommandTestFactory.createBuilder().bodyObject(BescheidItemTestFactory.createBescheidBody()).build(); + private final Command command = CommandTestFactory.createBuilder().bodyObject(BescheidItemTestFactory.createBescheidBody()).build(); @BeforeEach void init() { @@ -499,7 +499,7 @@ class AttachedItemServiceTest { @Nested class TestBuildBescheidMap { - private Command command = CommandTestFactory.createBuilder().bodyObject(BescheidItemTestFactory.createBescheidBody()).build(); + private final Command command = CommandTestFactory.createBuilder().bodyObject(BescheidItemTestFactory.createBescheidBody()).build(); @Test void shouldSetStatus() { @@ -626,7 +626,7 @@ class AttachedItemServiceTest { @Mock private OzgCloudCommand deleteItemOzgCloudCommand; - private Command command = CommandTestFactory.createBuilder().relationId(BescheidItemTestFactory.ID).build(); + private final Command command = CommandTestFactory.createBuilder().relationId(BescheidItemTestFactory.ID).build(); @Test void shouldCallGetBescheid() { @@ -727,4 +727,171 @@ class AttachedItemServiceTest { } } + + @Nested + class TestGetItem { + + @Test + void shouldCallRemoteService() { + service.getItem(BescheidItemTestFactory.ID); + + verify(service).getItem(BescheidItemTestFactory.ID); + } + + @Test + void shouldReturnValue() { + var expectedItem = AttachedItemTestFactory.createDocument(); + doReturn(expectedItem).when(remoteService).getItem(any()); + + var result = service.getItem(BescheidItemTestFactory.ID); + + assertThat(result).isEqualTo(expectedItem); + } + } + + @Nested + class TestPatch { + + @Mock + private OzgCloudCommand command; + + @Test + void shouldCallBuildPatchBescheidCommand() { + var item = AttachedItemTestFactory.createDocument(); + + service.patch(item); + + verify(service).buildPatchBescheidCommand(item); + } + + @Test + void shouldCallCommandService() { + doReturn(command).when(service).buildPatchBescheidCommand(any()); + + service.patch(AttachedItemTestFactory.createDocument()); + + verify(commandService).createAndWaitUntilDone(command); + } + } + + @Nested + class TestBuildPatchBescheidCommand { + + @Test + void shouldCallVorgangIdMapper() { + service.buildPatchBescheidCommand(AttachedItemTestFactory.createDocument()); + + verify(commandMapper).toOzgCloudVorgangId(CommandTestFactory.VORGANG_ID); + } + + @Test + void shouldSetVorgangId() { + var expectedVorgangId = OzgCloudVorgangId.from(CommandTestFactory.VORGANG_ID); + when(commandMapper.toOzgCloudVorgangId(any())).thenReturn(expectedVorgangId); + + var result = service.buildPatchBescheidCommand(AttachedItemTestFactory.createDocument()); + + assertThat(result.getVorgangId()).isEqualTo(expectedVorgangId); + } + + @Test + void shouldCallRelationIdMapper() { + service.buildPatchBescheidCommand(AttachedItemTestFactory.createDocument()); + + verify(commandMapper).mapRelationId(AttachedItemTestFactory.ID); + } + + @Test + void shouldSetRelationId() { + var expectedId = GenericId.from(AttachedItemTestFactory.ID); + when(commandMapper.mapRelationId(any())).thenReturn(expectedId); + + var result = service.buildPatchBescheidCommand(AttachedItemTestFactory.createDocument()); + + assertThat(result.getRelationId()).isEqualTo(expectedId); + } + + @Test + void shouldSetRelationVersion() { + var result = service.buildPatchBescheidCommand(AttachedItemTestFactory.createDocument()); + + assertThat(result.getRelationVersion()).isEqualTo(AttachedItemTestFactory.VERSION); + } + + @Test + void shouldSetOrder() { + var result = service.buildPatchBescheidCommand(AttachedItemTestFactory.createDocument()); + + assertThat(result.getOrder()).isEqualTo(AttachedItemService.PATCH_ATTACHED_ITEM); + } + + @Test + void shouldCallBuildObjectMap() { + var bescheidItem = AttachedItemTestFactory.createDocument(); + + service.buildPatchBescheidCommand(bescheidItem); + + verify(service).buildObjectMap(bescheidItem); + } + + @Test + void shouldSetBodyObject() { + var expectedMap = Map.of("key", (Object) "value"); + doReturn(expectedMap).when(service).buildObjectMap(any()); + + var result = service.buildPatchBescheidCommand(AttachedItemTestFactory.createDocument()); + + assertThat(result.getBodyObject()).containsAllEntriesOf(expectedMap); + } + } + + @Nested + class TestBuildBodyObject { + + @Test + void shouldSetId() { + var result = buildObjectMap(); + + assertThat(result).containsEntry(AttachedItem.PROPERTY_ID, AttachedItemTestFactory.ID); + } + + @Test + void shouldSetClient() { + var result = buildObjectMap(); + + assertThat(result).containsEntry(AttachedItem.PROPERTY_CLIENT, AttachedItemTestFactory.CLIENT); + } + + @Test + void shouldSetVorgangId() { + var result = buildObjectMap(); + + assertThat(result).containsEntry(AttachedItem.PROPERTY_VORGANG_ID, CommandTestFactory.VORGANG_ID); + } + + @Test + void shouldSetItemName() { + var result = buildObjectMap(); + + assertThat(result).containsEntry(AttachedItem.PROPERTY_ITEM_NAME, AttachedItemService.BESCHEID_ITEM_NAME); + } + + @Test + void shouldSetVersion() { + var result = buildObjectMap(); + + assertThat(result).containsEntry(AttachedItem.PROPERTY_VERSION, AttachedItemTestFactory.VERSION); + } + + @Test + void shouldSetItem() { + var result = buildObjectMap(); + + assertThat(result).containsEntry(AttachedItem.PROPERTY_ITEM, AttachedItemTestFactory.createBescheidItem()); + } + + private Map<String, Object> buildObjectMap() { + return service.buildObjectMap(AttachedItemTestFactory.createBescheid()); + } + } } \ No newline at end of file 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 new file mode 100644 index 0000000000000000000000000000000000000000..71fdad4db515fc47f523b215e415d2f1d5bcf8d4 --- /dev/null +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attacheditem/AttachedItemTestFactory.java @@ -0,0 +1,101 @@ +/* + * 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.bescheid.attacheditem; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import de.ozgcloud.bescheid.Bescheid; +import de.ozgcloud.bescheid.Bescheid.Status; +import de.ozgcloud.bescheid.attacheditem.AttachedItem.AttachedItemBuilder; +import de.ozgcloud.command.CommandTestFactory; +import de.ozgcloud.document.Document; +import de.ozgcloud.document.DocumentService; +import de.ozgcloud.document.DocumentTestFactory; + +public class AttachedItemTestFactory { + + public static final String ID = "bescheid-item-id"; + public static final long VERSION = 10L; + public static final String CLIENT = "client"; + + public static final String BESCHEIDEN_AM = "2024-01-01"; + 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; + public static final String NACHRICHT_TEXT = "nachricht-text"; + public static final String NACHRICHT_SUBJECT = "nachricht-subject"; + + public static AttachedItem createBescheid() { + return createBescheidBuilder().build(); + } + + public static AttachedItem.AttachedItemBuilder createBescheidBuilder() { + return createBuilder() + .itemName(AttachedItemService.BESCHEID_ITEM_NAME) + .item(createBescheidItem()); + } + + public static Map<String, Object> createBescheidItem() { + 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_BESCHEID_DOCUMENT, BESCHEID_DOCUMENT); + item.put(Bescheid.FIELD_ATTACHMENTS, List.of(ATTACHMENT)); + item.put(Bescheid.FIELD_SEND_BY, SEND_BY.name()); + item.put(Bescheid.FIELD_NACHRICHT_TEXT, NACHRICHT_TEXT); + item.put(Bescheid.FIELD_NACHRICHT_SUBJECT, NACHRICHT_SUBJECT); + return item; + } + + public static AttachedItem createDocument() { + return createDocumentBuilder().build(); + } + + public static AttachedItem.AttachedItemBuilder createDocumentBuilder() { + return createBuilder() + .itemName(DocumentService.DOCUMENT_ITEM_NAME) + .item(createDocumentItem()); + } + + public static HashMap<String, Object> createDocumentItem() { + var map = new HashMap<String, Object>(); + map.put(Document.FIELD_DOCUMENT_TYPE, DocumentService.DOCUMENT_TYPE); + map.put(Document.FIELD_DOCUMENT_FILE, DocumentTestFactory.DOCUMENT_FILE); + map.put(Document.FIELD_NACHRICHT_TEXT, DocumentTestFactory.NACHRICHT_TEXT); + map.put(Document.FIELD_NACHRICHT_SUBJECT, DocumentTestFactory.NACHRICHT_SUBJECT); + return map; + } + + public static AttachedItemBuilder createBuilder() { + return AttachedItem.builder() + .id(ID) + .version(VERSION) + .client(CLIENT) + .vorgangId(CommandTestFactory.VORGANG_ID); + } + +} 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 88259ab1300ec4b86712144b5c196f761c7a231a..444249e06cc391cd8b6d05304ab33f93134cb899 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,7 +26,6 @@ package de.ozgcloud.bescheid.attacheditem; import static org.assertj.core.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; import java.util.Map; @@ -51,7 +50,6 @@ import de.ozgcloud.vorgang.vorgangAttachedItem.GrpcVorgangAttachedItemRequest; import de.ozgcloud.vorgang.vorgangAttachedItem.GrpcVorgangAttachedItemResponse; import de.ozgcloud.vorgang.vorgangAttachedItem.VorgangAttachedItemServiceGrpc.VorgangAttachedItemServiceBlockingStub; import io.grpc.ClientInterceptor; -import io.grpc.StatusRuntimeException; class VorgangAttachedItemRemoteServiceTest { @@ -67,6 +65,8 @@ class VorgangAttachedItemRemoteServiceTest { private ClientInterceptor bescheidCallContextInterceptor; @Mock private BescheidItemMapper bescheidItemMapper; + @Mock + private AttachedItemMapper attachedItemMapper; @Nested class TestFindBescheidDraft { @@ -308,15 +308,77 @@ class VorgangAttachedItemRemoteServiceTest { assertThat(result).isEqualTo(expectedBescheid); } + private BescheidItem getBescheid() { + return service.getBescheid(BescheidItemTestFactory.ID); + } + } + + @Nested + class TestGetItem { + + @Mock + private GrpcVorgangAttachedItemRequest grpcVorgangAttachedItemRequest; + @Mock + private GrpcVorgangAttachedItemResponse grpcVorgangAttachedItemResponse; + @Mock + private GrpcVorgangAttachedItem grpcVorgangAttachedItem; + + @BeforeEach + void init() { + when(serviceStub.getById(any())).thenReturn(grpcVorgangAttachedItemResponse); + doReturn(serviceStub).when(service).getServiceStub(); + } + @Test - void shouldThrowExceptionIfNotFound() { - when(serviceStub.getById(any())).thenThrow(StatusRuntimeException.class); + void shouldCallGetServiceStab() { + when(grpcVorgangAttachedItemResponse.getVorgangAttachedItem()).thenReturn(grpcVorgangAttachedItem); + + getItem(); - assertThrows(StatusRuntimeException.class, this::getBescheid); + verify(service).getServiceStub(); } - private BescheidItem getBescheid() { - return service.getBescheid(BescheidItemTestFactory.ID); + @Test + void shouldCallBuildRequest() { + when(grpcVorgangAttachedItemResponse.getVorgangAttachedItem()).thenReturn(grpcVorgangAttachedItem); + + getItem(); + + verify(service).buildGetByIdRequest(AttachedItemTestFactory.ID); + } + + @Test + void shouldCallGetById() { + when(grpcVorgangAttachedItemResponse.getVorgangAttachedItem()).thenReturn(grpcVorgangAttachedItem); + doReturn(grpcVorgangAttachedItemRequest).when(service).buildGetByIdRequest(any()); + + getItem(); + + verify(serviceStub).getById(grpcVorgangAttachedItemRequest); + } + + @Test + void shouldCallMapper() { + when(grpcVorgangAttachedItemResponse.getVorgangAttachedItem()).thenReturn(grpcVorgangAttachedItem); + + getItem(); + + verify(attachedItemMapper).mapFromVorgangAttachedItem(grpcVorgangAttachedItem); + } + + @Test + void shouldReturnFoundBescheid() { + var expectedItem = AttachedItemTestFactory.createDocument(); + when(attachedItemMapper.mapFromVorgangAttachedItem(any())).thenReturn(expectedItem); + when(grpcVorgangAttachedItemResponse.getVorgangAttachedItem()).thenReturn(grpcVorgangAttachedItem); + + var result = getItem(); + + assertThat(result).isEqualTo(expectedItem); + } + + private AttachedItem getItem() { + return service.getItem(BescheidItemTestFactory.ID); } } diff --git a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attributes/ClientAttributeRemoteServiceTest.java b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attributes/ClientAttributeRemoteServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..cfa18131b58c3ccc123ba07d4848625fcbddfdfb --- /dev/null +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attributes/ClientAttributeRemoteServiceTest.java @@ -0,0 +1,161 @@ +/* + * 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.bescheid.attributes; + +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.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; + +import de.ozgcloud.bescheid.BescheidCallContextAttachingInterceptor; +import de.ozgcloud.vorgang.grpc.clientAttribute.ClientAttributeServiceGrpc.ClientAttributeServiceBlockingStub; +import de.ozgcloud.vorgang.grpc.clientAttribute.GrpcAccessPermission; +import de.ozgcloud.vorgang.grpc.clientAttribute.GrpcClientAttribute; +import de.ozgcloud.vorgang.grpc.clientAttribute.GrpcSetClientAttributeRequest; + +class ClientAttributeRemoteServiceTest { + + private static final String VORGANG_ID = "vorgangId"; + private static final String ATTRIBUTE_NAME = "attributeName"; + + @Spy + @InjectMocks + private ClientAttributeRemoteService service; + + @Mock + private ClientAttributeServiceBlockingStub serviceBlockingStub; + @Mock + private BescheidCallContextAttachingInterceptor bescheidCallContextInterceptor; + + @Nested + class TestSetBooleanReadOnlyClientAttribute { + + @Captor + private ArgumentCaptor<BescheidCallContextAttachingInterceptor> interceptorCaptor; + + @BeforeEach + void setUp() { + when(serviceBlockingStub.withInterceptors(any())).thenReturn(serviceBlockingStub); + } + + @Test + void shouldSetInterceptors() { + service.setBooleanReadOnlyClientAttribute(VORGANG_ID, ATTRIBUTE_NAME, true); + + verify(serviceBlockingStub).withInterceptors(interceptorCaptor.capture()); + assertThat(interceptorCaptor.getValue()).isEqualTo(bescheidCallContextInterceptor); + } + + @Test + void shouldCallBuildRequest() { + service.setBooleanReadOnlyClientAttribute(VORGANG_ID, ATTRIBUTE_NAME, true); + + verify(service).buildRequest(VORGANG_ID, ATTRIBUTE_NAME, true); + } + + @Test + void shouldCallSetMethod() { + var request = GrpcSetClientAttributeRequest.newBuilder().build(); + doReturn(request).when(service).buildRequest(any(), any(), anyBoolean()); + + service.setBooleanReadOnlyClientAttribute(VORGANG_ID, ATTRIBUTE_NAME, true); + + verify(serviceBlockingStub).set(request); + + } + } + + @Nested + class TestBuildRequest { + + @Mock + private GrpcClientAttribute clientAttribute; + + @Test + void shouldSetVorgangId() { + var result = buildRequest(); + + assertThat(result.getVorgangId()).isEqualTo(VORGANG_ID); + } + + @Test + void shouldCallBuildClientAttribute() { + buildRequest(); + + verify(service).buildClientAttribute(ATTRIBUTE_NAME, true); + } + + @Test + void shouldSetClientAttribute() { + doReturn(clientAttribute).when(service).buildClientAttribute(any(), anyBoolean()); + + var result = buildRequest(); + + assertThat(result.getAttribute()).isEqualTo(clientAttribute); + } + + private GrpcSetClientAttributeRequest buildRequest() { + return service.buildRequest(VORGANG_ID, ATTRIBUTE_NAME, true); + } + } + + @Nested + class TestBuildClientAttribute { + + @Test + void shouldSetClientName() { + var result = service.buildClientAttribute(ATTRIBUTE_NAME, true); + + assertThat(result.getClientName()).isEqualTo(BescheidCallContextAttachingInterceptor.BESCHEID_MANAGER_CLIENT_NAME); + } + + @Test + void shouldSetAccess() { + var result = service.buildClientAttribute(ATTRIBUTE_NAME, true); + + assertThat(result.getAccess()).isEqualTo(GrpcAccessPermission.READ_ONLY); + } + + @Test + void shouldSetAttributeName() { + var result = service.buildClientAttribute(ATTRIBUTE_NAME, true); + + assertThat(result.getAttributeName()).isEqualTo(ATTRIBUTE_NAME); + } + + @Test + void shouldSetValue() { + var result = service.buildClientAttribute(ATTRIBUTE_NAME, true); + + assertThat(result.getValue().getBoolValue()).isTrue(); + } + } +} \ No newline at end of file diff --git a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attributes/ClientAttributeServiceTest.java b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attributes/ClientAttributeServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..03d31e54d9df4e505ac172fff010acf8f94f996a --- /dev/null +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/attributes/ClientAttributeServiceTest.java @@ -0,0 +1,48 @@ +/* + * 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.bescheid.attributes; + +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +class ClientAttributeServiceTest { + + @InjectMocks + private ClientAttributeService service; + + @Mock + private ClientAttributeRemoteService remoteService; + + @Test + void shouldCallRemoteService() { + var vorgangId = "vorgangId"; + + service.setAntragResult(vorgangId, true); + + verify(remoteService).setBooleanReadOnlyClientAttribute(vorgangId, ClientAttributeService.ATTRIBUTE_NAME_ANTRAG_BEWILLIGT, true); + } +} \ 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 fb2bd022762adfa8402df8fc8d1964469e68b521..84ded81a81d00efedee18241e553e943e68cb919 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 @@ -28,7 +28,7 @@ class NachrichtServiceTest { @Nested class TestCreateNachrichtDraft { - private Nachricht nachricht = NachrichtTestFactory.create(); + private final Nachricht nachricht = NachrichtTestFactory.create(); @Test void shouldCallRemoteService() { @@ -58,6 +58,15 @@ class NachrichtServiceTest { void shouldSetSubject() { var nachricht = service.buildNachricht(BescheidTestFactory.create()).get(); + assertThat(nachricht.getSubject()).isEqualTo(BescheidTestFactory.NACHRICHT_SUBJECT); + } + + @Test + void shouldSetDefaultSubject() { + var bescheid = BescheidTestFactory.createBuilder().nachrichtSubject(Optional.empty()).build(); + + var nachricht = service.buildNachricht(bescheid).get(); + assertThat(nachricht.getSubject()).isEqualTo(NachrichtService.SUBJECT); } diff --git a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/smartdocuments/SmartDocumentsBescheidRemoteServiceITCase.java b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/smartdocuments/SmartDocumentsBescheidRemoteServiceITCase.java index 6bbf4083b967a58576187f18ff047699cc6b8f24..3126ecfe5b5f98c41749c406cd426253a65b053e 100644 --- a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/smartdocuments/SmartDocumentsBescheidRemoteServiceITCase.java +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/smartdocuments/SmartDocumentsBescheidRemoteServiceITCase.java @@ -1,21 +1,33 @@ package de.ozgcloud.bescheid.smartdocuments; +import static org.assertj.core.api.Assertions.*; + import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.ActiveProfiles; -import de.ozgcloud.common.test.ITCase; +import de.ozgcloud.apilib.common.command.OzgCloudCommandService; +import de.ozgcloud.apilib.common.command.grpc.CommandMapper; import de.ozgcloud.bescheid.BescheidRequestTestFactory; +import de.ozgcloud.bescheid.BescheidTestApplication; import de.ozgcloud.bescheid.vorgang.VorgangTestFactory; +import de.ozgcloud.common.test.ITCase; @Disabled +@SpringBootTest(classes = BescheidTestApplication.class) @ITCase @ActiveProfiles({ "itcase", "local" }) class SmartDocumentsBescheidRemoteServiceITCase { @Autowired private SmartDocumentsBescheidRemoteService remoteService; + @MockBean + private OzgCloudCommandService ozgCloudCommandService; + @MockBean + private CommandMapper commandMapper; @Test void createBescheid() { @@ -23,6 +35,7 @@ class SmartDocumentsBescheidRemoteServiceITCase { System.out.println(bescheid.getBescheidFileName()); System.out.println(bescheid.getBescheidFile().getAbsolutePath()); + assertThat(bescheid.getBescheidFileName()).isNotEmpty(); } } diff --git a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/smartdocuments/SmartDocumentsBescheidRemoteServiceTest.java b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/smartdocuments/SmartDocumentsBescheidRemoteServiceTest.java index 24d0f5ec2574b1ada388baeb44be8a8d3dfb827f..a19b60778f59f8a9fd72d3f2ce80706692a7b283 100644 --- a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/smartdocuments/SmartDocumentsBescheidRemoteServiceTest.java +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/smartdocuments/SmartDocumentsBescheidRemoteServiceTest.java @@ -37,7 +37,7 @@ class SmartDocumentsBescheidRemoteServiceTest { void shouldFillBescheid() { var bescheid = service.buildBescheid(BescheidRequestTestFactory.create(), SmartDocumentsResponseTestFactory.create()); - assertThat(bescheid).usingRecursiveComparison().ignoringFields("serviceKonto") + assertThat(bescheid).usingRecursiveComparison().ignoringFields("serviceKonto", "nachrichtSubject") .isEqualTo(BescheidTestFactory.createBuilder().nachrichtText(Optional.empty()).build()); } } @@ -56,7 +56,7 @@ class SmartDocumentsBescheidRemoteServiceTest { @Nested class TestGetNachrichtText { - private File xmlFile = TempFileUtils.writeTmpFile(TestUtils.loadFile("SD_answer.xml")); + private final File xmlFile = TempFileUtils.writeTmpFile(TestUtils.loadFile("SD_answer.xml")); @Test void shouldCallExtractText() { diff --git a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/vorgang/BescheidVorgangMapperTest.java b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/vorgang/BescheidVorgangMapperTest.java index d430ba2760da66241a263864f147a6325366a699..6f64a48933ed0351295ef3415111f149ca3f6c7d 100644 --- a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/vorgang/BescheidVorgangMapperTest.java +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/vorgang/BescheidVorgangMapperTest.java @@ -27,5 +27,12 @@ class BescheidVorgangMapperTest { assertThat(result.getId()).isEqualTo(VorgangTestFactory.ID); } + + @Test + void shouldMapVersion() { + var result = mapper.mapVorgang(GrpcVorgangWithEingangTestFactory.create()); + + assertThat(result.getVersion()).isEqualTo(VorgangTestFactory.VERSION); + } } } diff --git a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/vorgang/GrpcVorgangWithEingangTestFactory.java b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/vorgang/GrpcVorgangWithEingangTestFactory.java index bad828ffad1f7e9b38b11fd16b42657ffb513848..63c8a880f63c6d9ceffc9f2a8a5028cce949ed34 100644 --- a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/vorgang/GrpcVorgangWithEingangTestFactory.java +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/vorgang/GrpcVorgangWithEingangTestFactory.java @@ -14,6 +14,7 @@ class GrpcVorgangWithEingangTestFactory { static GrpcVorgangWithEingang.Builder createBuilder() { return GrpcVorgangWithEingang.newBuilder() .setId(ID.toString()) + .setVersion(VERSION) .setEingang(GrpcEingangTestFactory.create()); } diff --git a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/vorgang/VorgangServiceTest.java b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/vorgang/VorgangServiceTest.java index 375bd58887096ea7f101b9d07a0c1e1579ce75b1..424a4330012bc1fd605dede82c56eac40c435f67 100644 --- a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/vorgang/VorgangServiceTest.java +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/vorgang/VorgangServiceTest.java @@ -9,19 +9,31 @@ 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.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; class VorgangServiceTest { + @Spy @InjectMocks private VorgangService service; @Mock private VorgangRemoteService remoteService; + @Mock + private OzgCloudCommandService commandService; + @Mock + private CommandMapper commandMapper; @Nested class TestGetById { - private Vorgang vorgang = VorgangTestFactory.create(); + private final Vorgang vorgang = VorgangTestFactory.create(); @BeforeEach void init() { @@ -43,4 +55,98 @@ class VorgangServiceTest { } } + @Nested + class TestBescheiden { + + private final static Vorgang VORGANG = VorgangTestFactory.create(); + + @Mock + private OzgCloudCommand ozgCloudCommand; + + @BeforeEach + void init() { + doReturn(VORGANG).when(service).getById(any(VorgangId.class)); + } + + @Test + void shouldCallGetBeyId() { + service.bescheiden(VorgangTestFactory.ID.toString()); + + verify(service).getById(VorgangTestFactory.ID); + } + + @Test + void shouldCallBuildBescheidenCommand() { + service.bescheiden(VorgangTestFactory.ID.toString()); + + verify(service).buildBescheidenCommand(VORGANG); + } + + @Test + void shouldCallCommandService() { + doReturn(ozgCloudCommand).when(service).buildBescheidenCommand(any()); + + service.bescheiden(VorgangTestFactory.ID.toString()); + + verify(commandService).createAndWaitUntilDone(ozgCloudCommand); + } + } + + @Nested + class TestBuildBescheidenCommand { + + private final static Vorgang VORGANG = VorgangTestFactory.create(); + + @Test + void shouldCallCommandMapperToOzgCloudVorgangId() { + service.buildBescheidenCommand(VORGANG); + + verify(commandMapper).toOzgCloudVorgangId(VorgangTestFactory.ID.toString()); + } + + @Test + void shouldSetVorgangId() { + var expectedId = OzgCloudVorgangId.from(VorgangTestFactory.ID.toString()); + when(commandMapper.toOzgCloudVorgangId(any())).thenReturn(expectedId); + + var result = service.buildBescheidenCommand(VORGANG); + + assertThat(result.getVorgangId()).isEqualTo(expectedId); + } + + @Test + void shouldCallCommandMapperMapRelationId() { + service.buildBescheidenCommand(VORGANG); + + verify(commandMapper).mapRelationId(VorgangTestFactory.ID.toString()); + } + + @Test + void shouldSetRelationId() { + var expectedId = GenericId.from(VorgangTestFactory.ID.toString()); + when(commandMapper.mapRelationId(any())).thenReturn(expectedId); + + var result = service.buildBescheidenCommand(VORGANG); + + assertThat(result.getRelationId()).isEqualTo(expectedId); + } + + @Test + void shouldSetRelationVersion() { + var expectedVersion = VORGANG.getVersion(); + + var result = service.buildBescheidenCommand(VORGANG); + + assertThat(result.getRelationVersion()).isEqualTo(expectedVersion); + } + + @Test + void shouldSetOrder() { + var expectedOrder = VorgangService.VORGANG_BESCHEIDEN; + + var result = service.buildBescheidenCommand(VORGANG); + + assertThat(result.getOrder()).isEqualTo(expectedOrder); + } + } } diff --git a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/vorgang/VorgangTestFactory.java b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/vorgang/VorgangTestFactory.java index d6c02552c3b866edb6e8de5d2c20439d09815d01..62009fca650eef4534876936e5ec328f0ddd593c 100644 --- a/bescheid-manager/src/test/java/de/ozgcloud/bescheid/vorgang/VorgangTestFactory.java +++ b/bescheid-manager/src/test/java/de/ozgcloud/bescheid/vorgang/VorgangTestFactory.java @@ -5,6 +5,7 @@ import java.util.UUID; public class VorgangTestFactory { public static final VorgangId ID = VorgangId.from(UUID.randomUUID().toString()); + public static final long VERSION = 1L; public static Vorgang create() { return createBuilder().build(); @@ -13,6 +14,7 @@ public class VorgangTestFactory { public static Vorgang.VorgangBuilder createBuilder() { return Vorgang.builder() .id(ID) + .version(VERSION) .serviceKonto(ServiceKontoTestFactory.create()) .vorgangName("KFAS_LIVE_KI_10_Haltverbot_befristet") .vorgangNummer("ABC-123-XY") diff --git a/bescheid-manager/src/test/java/de/ozgcloud/document/DocumentGrpcServiceTest.java b/bescheid-manager/src/test/java/de/ozgcloud/document/DocumentGrpcServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e2157ed90a4161e3e67bfd1dcc30f0010c5ca390 --- /dev/null +++ b/bescheid-manager/src/test/java/de/ozgcloud/document/DocumentGrpcServiceTest.java @@ -0,0 +1,120 @@ +/* + * 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.document; + +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 io.grpc.stub.StreamObserver; + +class DocumentGrpcServiceTest { + + @Spy + @InjectMocks + private DocumentGrpcService service; + + @Mock + private DocumentService documentService; + @Mock + private DocumentMapper documentMapper; + + @Nested + class TestGetDocument { + + private final static GrpcGetDocumentRequest REQUEST = GrpcGetDocumentRequest.newBuilder().setId(DocumentTestFactory.ID).build(); + + @Mock + private GrpcGetDocumentResponse response; + @Mock + private StreamObserver<GrpcGetDocumentResponse> responseObserver; + + @BeforeEach + void init() { + doReturn(response).when(service).buildGetDocumentResponse(any()); + } + + @Test + void shouldCallDocumentService() { + service.getDocument(REQUEST, responseObserver); + + verify(documentService).getDocument(DocumentTestFactory.ID); + } + + @Test + void shouldCallBuildResponse() { + service.getDocument(REQUEST, responseObserver); + + verify(service).buildGetDocumentResponse(any()); + } + + @Test + void shouldCallOnNext() { + service.getDocument(REQUEST, responseObserver); + + verify(responseObserver).onNext(response); + } + + @Test + void shouldCallOnCompleted() { + service.getDocument(REQUEST, responseObserver); + + verify(responseObserver).onCompleted(); + } + } + + @Nested + class TestBuildGetDocumentResponse { + + @Mock + private GrpcDocument grpcDocument; + + @BeforeEach + void init() { + when(documentMapper.toGrpcDocument(any())).thenReturn(grpcDocument); + } + + @Test + void shouldCallDocumentMapper() { + var document = DocumentTestFactory.create(); + + service.buildGetDocumentResponse(document); + + verify(documentMapper).toGrpcDocument(document); + } + + @Test + void shouldSetDocument() { + var result = service.buildGetDocumentResponse(DocumentTestFactory.create()); + + assertThat(result.getDocument()).isEqualTo(grpcDocument); + } + } +} \ No newline at end of file diff --git a/bescheid-manager/src/test/java/de/ozgcloud/document/DocumentMapperTest.java b/bescheid-manager/src/test/java/de/ozgcloud/document/DocumentMapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9ba02e1e11c796f19fc771e9ed29000f911690cd --- /dev/null +++ b/bescheid-manager/src/test/java/de/ozgcloud/document/DocumentMapperTest.java @@ -0,0 +1,107 @@ +/* + * 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.document; + +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.mapstruct.factory.Mappers; + +import de.ozgcloud.bescheid.attacheditem.AttachedItemTestFactory; + +class DocumentMapperTest { + + private DocumentMapper mapper = Mappers.getMapper(DocumentMapper.class); + + @DisplayName("To grpc document") + @Nested + class TestToGrpcDocument { + + @Test + void shouldMapDocument() { + var result = mapper.toGrpcDocument(DocumentTestFactory.create()); + + assertThat(result).usingRecursiveComparison().isEqualTo(GrpcDocumentTestFactory.create()); + } + + @Test + void shouldMapEmptyDocument() { + var result = mapper.toGrpcDocument(Document.builder().build()); + + assertThat(result).isEqualTo(GrpcDocument.newBuilder().build()); + } + } + + @DisplayName("From attached item") + @Nested + class TestFromAttachedItem { + + @Test + void shouldMapId() { + var document = AttachedItemTestFactory.createDocument(); + + var result = mapper.fromAttachedItem(document); + + assertThat(result.getId()).isEqualTo(AttachedItemTestFactory.ID); + } + + @Test + void shouldMapType() { + var document = AttachedItemTestFactory.createDocument(); + + var result = mapper.fromAttachedItem(document); + + assertThat(result.getType()).isEqualTo(DocumentService.DOCUMENT_TYPE); + } + + @Test + void shouldMapFileId() { + var document = AttachedItemTestFactory.createDocument(); + + var result = mapper.fromAttachedItem(document); + + assertThat(result.getFileId()).isEqualTo(DocumentTestFactory.DOCUMENT_FILE); + } + + @Test + void shouldMapNachrichtText() { + var document = AttachedItemTestFactory.createDocument(); + + var result = mapper.fromAttachedItem(document); + + assertThat(result.getNachrichtText()).isEqualTo(DocumentTestFactory.NACHRICHT_TEXT); + } + + @Test + void shouldMapNachrichtSubject() { + var document = AttachedItemTestFactory.createDocument(); + + var result = mapper.fromAttachedItem(document); + + assertThat(result.getNachrichtSubject()).isEqualTo(DocumentTestFactory.NACHRICHT_SUBJECT); + } + } +} \ No newline at end of file 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 9ade6417382b7da3c7968fb1b994f2c568fd51eb..a9b9669ca351e63c42e259ec75d193b24c98355a 100644 --- a/bescheid-manager/src/test/java/de/ozgcloud/document/DocumentServiceTest.java +++ b/bescheid-manager/src/test/java/de/ozgcloud/document/DocumentServiceTest.java @@ -28,7 +28,9 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; import java.util.Map; +import java.util.Optional; +import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -42,12 +44,16 @@ 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.BescheidCallContextAttachingInterceptor; +import de.ozgcloud.bescheid.BescheidTestFactory; import de.ozgcloud.bescheid.attacheditem.AttachedItemService; +import de.ozgcloud.bescheid.attacheditem.AttachedItemTestFactory; import de.ozgcloud.bescheid.attacheditem.BescheidItem; import de.ozgcloud.bescheid.attacheditem.BescheidItemTestFactory; import de.ozgcloud.command.Command; import de.ozgcloud.command.CommandTestFactory; +import de.ozgcloud.common.binaryfile.FileId; import de.ozgcloud.common.errorhandling.TechnicalException; class DocumentServiceTest { @@ -65,10 +71,10 @@ class DocumentServiceTest { private CommandMapper commandMapper; @Mock private AttachedItemService attachedItemService; + @Mock + private DocumentMapper documentMapper; - private Command command = CommandTestFactory.createBuilder() - .relationId(RELATION_ID) - .bodyObject(Map.of(DocumentService.FIELD_DOCUMENT_FILE, FILE_ID)).build(); + private Command command = CommandTestFactory.createBuilder().relationId(RELATION_ID).build(); @Nested class TestCreateBescheidDocument { @@ -78,42 +84,112 @@ class DocumentServiceTest { @Mock private OzgCloudCommand ozgCommand; - @BeforeEach - void init() { - when(commandService.createAndWaitUntilDone(any())).thenReturn(ozgCommand); - doNothing().when(service).validateBescheidItem(any()); - } + @Nested + class TestWithCommand { - @Test - void shouldCallValidateBescheidItem() { - service.createBescheidDocument(command); + private Command command = CommandTestFactory.createBuilder().relationId(RELATION_ID) + .bodyObject(Map.of(DocumentService.FIELD_DOCUMENT_FILE, FILE_ID)).build(); - verify(service).validateBescheidItem(RELATION_ID); - } + @BeforeEach + void init() { + doReturn(CREATED_ATTACHED_ITEM_ID).when(service).createBescheidDocument(any(), anyMap()); + } - @Test - void shouldCallBuildOzgCommand() { - service.createBescheidDocument(command); + @Test + void shouldCallBuildItemMap() { + service.createBescheidDocument(command); + + verify(service).buildItemMap(command); + } + + @Test + void shouldCallCreateBescheidDocument() { + var itemMap = Map.of("key", (Object) "value"); + doReturn(itemMap).when(service).buildItemMap(any(Command.class)); + + service.createBescheidDocument(command); + + verify(service).createBescheidDocument(command, itemMap); + } - verify(service).buildCreateDocumentOzgCommand(command); + @Test + void shouldReturnDocumentId() { + String result = service.createBescheidDocument(command); + + assertThat(result).isEqualTo(CREATED_ATTACHED_ITEM_ID); + } } - @Test - void shouldCallCommandService() { - doReturn(ozgCommand).when(service).buildCreateDocumentOzgCommand(any()); + @Nested + class TestWithBescheid { + + private Bescheid bescheid = BescheidTestFactory.create().withBescheidFileId(FileId.from(BescheidTestFactory.BESCHEID_FILE_ID)); + + @BeforeEach + void init() { + doReturn(CREATED_ATTACHED_ITEM_ID).when(service).createBescheidDocument(any(), anyMap()); + } + + @Test + void shouldCallBuildItemMap() { + service.createBescheidDocument(command, bescheid); + + verify(service).buildItemMap(bescheid); + } + + @Test + void shouldCallCreateBescheidDocument() { + var itemMap = Map.of("key", (Object) "value"); + doReturn(itemMap).when(service).buildItemMap(any(Bescheid.class)); + + service.createBescheidDocument(command, bescheid); - service.createBescheidDocument(command); + verify(service).createBescheidDocument(command, itemMap); + } - verify(commandService).createAndWaitUntilDone(ozgCommand); + @Test + void shouldReturnDocumentId() { + String result = service.createBescheidDocument(command, bescheid); + + assertThat(result).isEqualTo(CREATED_ATTACHED_ITEM_ID); + } } - @Test - void shouldReturnDocumentId() { - when(ozgCommand.getCreatedResource()).thenReturn(CREATED_ATTACHED_ITEM_ID); + @Nested + class TestWithCommandAndItemMap { + + private static final Map<String, Object> BODY_OBJECT = Map.of(DocumentService.FIELD_DOCUMENT_FILE, FILE_ID); + + @BeforeEach + void init() { + when(commandService.createAndWaitUntilDone(any())).thenReturn(ozgCommand); + doNothing().when(service).validateBescheidItem(any()); + } + + @Test + void shouldCallValidateBescheidItem() { + service.createBescheidDocument(command, BODY_OBJECT); + + verify(service).validateBescheidItem(RELATION_ID); + } + + @Test + void shouldCallCommandService() { + doReturn(ozgCommand).when(service).buildCreateDocumentOzgCommand(any(), anyMap()); + + service.createBescheidDocument(command, BODY_OBJECT); + + verify(commandService).createAndWaitUntilDone(ozgCommand); + } - String documentId = service.createBescheidDocument(command); + @Test + void shouldReturnDocumentId() { + when(ozgCommand.getCreatedResource()).thenReturn(CREATED_ATTACHED_ITEM_ID); - assertThat(documentId).isEqualTo(CREATED_ATTACHED_ITEM_ID); + String documentId = service.createBescheidDocument(command, BODY_OBJECT); + + assertThat(documentId).isEqualTo(CREATED_ATTACHED_ITEM_ID); + } } } @@ -150,6 +226,7 @@ class DocumentServiceTest { assertDoesNotThrow(this::validateBescheidItem); } + void validateBescheidItem() { service.validateBescheidItem(BescheidItemTestFactory.ID); } @@ -158,16 +235,18 @@ class DocumentServiceTest { @Nested class TestBuildCreateDocumentOzgCommand { + private static final Map<String, Object> BODY_OBJECT = Map.of(DocumentService.FIELD_DOCUMENT_FILE, FILE_ID); + @Test void shouldSetOrder() { - var ozgCommand = service.buildCreateDocumentOzgCommand(command); + var ozgCommand = service.buildCreateDocumentOzgCommand(command, BODY_OBJECT); assertThat(ozgCommand.getOrder()).isEqualTo(DocumentService.CREATE_ATTACHED_ITEM_ORDER); } @Test void shouldCallVorgangIdMapper() { - service.buildCreateDocumentOzgCommand(command); + service.buildCreateDocumentOzgCommand(command, BODY_OBJECT); verify(commandMapper).toOzgCloudVorgangId(CommandTestFactory.VORGANG_ID); } @@ -177,14 +256,14 @@ class DocumentServiceTest { var expectedVorgangId = OzgCloudVorgangId.from("vorgang-id"); when(commandMapper.toOzgCloudVorgangId(any())).thenReturn(expectedVorgangId); - var ozgCommand = service.buildCreateDocumentOzgCommand(command); + var ozgCommand = buildCreateDocumentOzgCommand(); assertThat(ozgCommand.getVorgangId()).isEqualTo(expectedVorgangId); } @Test void shouldCallRelationIdMapper() { - service.buildCreateDocumentOzgCommand(command); + buildCreateDocumentOzgCommand(); verify(commandMapper).mapRelationId(CommandTestFactory.VORGANG_ID); } @@ -194,32 +273,28 @@ class DocumentServiceTest { var expectedRelationId = GenericId.from("relation-id"); when(commandMapper.mapRelationId(any())).thenReturn(expectedRelationId); - var ozgCommand = service.buildCreateDocumentOzgCommand(command); + var ozgCommand = buildCreateDocumentOzgCommand(); assertThat(ozgCommand.getRelationId()).isEqualTo(expectedRelationId); } @Test - void shouldCallBuildAttachedItem() { - service.buildCreateDocumentOzgCommand(command); + void shouldSetBodyObject() { + var ozgCommand = buildCreateDocumentOzgCommand(); - verify(service).buildAttachedItem(command); + assertThat(ozgCommand.getBodyObject()).isEqualTo(BODY_OBJECT); } - @Test - void shouldSetAttachedItem() { - var expectedAttachedItem = Map.of("key", (Object) "value"); - doReturn(expectedAttachedItem).when(service).buildAttachedItem(any()); - - var ozgCommand = service.buildCreateDocumentOzgCommand(command); - - assertThat(ozgCommand.getBodyObject()).isEqualTo(expectedAttachedItem); + private OzgCloudCommand buildCreateDocumentOzgCommand() { + return service.buildCreateDocumentOzgCommand(command, BODY_OBJECT); } } @Nested class TestBuildAttachedItem { + private static final Map<String, Object> ITEM_MAP = Map.of("key", (Object) "value"); + @Test void shouldSetVorgangId() { var attachedItem = buildAttachedItem(); @@ -243,54 +318,130 @@ class DocumentServiceTest { } @Test - void shouldCallBuildDocumentMap() { - buildAttachedItem(); + void shouldSetItem() { + var attachedItem = buildAttachedItem(); - verify(service).buildItemMap(command); + assertThat(attachedItem).containsEntry(BescheidItem.PROPERTY_ITEM, ITEM_MAP); } - @Test - void shouldSetDocument() { - var expectedDocument = Map.of("key", (Object) "value"); - doReturn(expectedDocument).when(service).buildItemMap(any()); + private Map<String, Object> buildAttachedItem() { + return service.buildAttachedItem(command, ITEM_MAP); + } + } - var attachedItem = buildAttachedItem(); + @Nested + class TestBuildItemMap { + + @Nested + class TestBuildFromCommand { + + private Command command = CommandTestFactory.createBuilder().relationId(RELATION_ID) + .bodyObject(Map.of(DocumentService.FIELD_DOCUMENT_FILE, FILE_ID)).build(); - assertThat(attachedItem).containsEntry(BescheidItem.PROPERTY_ITEM, expectedDocument); + @Test + void shouldDetDocumentType() { + var itemMap = buildItemMap(); + + assertThat(itemMap).containsEntry(DocumentService.FIELD_DOCUMENT_TYPE, DocumentService.DOCUMENT_TYPE); + } + + @Test + void shouldSetDocumentFile() { + var itemMap = buildItemMap(); + + assertThat(itemMap).containsEntry(DocumentService.FIELD_DOCUMENT_FILE, FILE_ID); + } + + @Test + void shouldThrowExceptionIfDocumentFileIsMissing() { + command = CommandTestFactory.createBuilder().bodyObject(Map.of()).build(); + + assertThrows(TechnicalException.class, () -> service.buildItemMap(command)); + } + + private Map<String, Object> buildItemMap() { + return service.buildItemMap(command); + } } - private Map<String, Object> buildAttachedItem() { - return service.buildAttachedItem(command); + @Nested + class TestBuildFromBescheid { + + private Bescheid bescheid = BescheidTestFactory.create().withBescheidFileId(FileId.from(BescheidTestFactory.BESCHEID_FILE_ID)); + + @Test + void shouldDetDocumentType() { + var itemMap = buildItemMap(); + + assertThat(itemMap).containsEntry(DocumentService.FIELD_DOCUMENT_TYPE, DocumentService.DOCUMENT_TYPE); + } + + @Test + void shouldSetDocumentFile() { + var itemMap = buildItemMap(); + + assertThat(itemMap).containsEntry(DocumentService.FIELD_DOCUMENT_FILE, BescheidTestFactory.BESCHEID_FILE_ID); + } + + @Test + void shouldSetNachrichtText() { + var itemMap = buildItemMap(); + + assertThat(itemMap).containsEntry(DocumentService.FIELD_NACHRICHT_TEXT, BescheidTestFactory.NACHRICHT_TEXT); + } + + @Test + void shouldSetEmptyNachrichtText() { + bescheid = BescheidTestFactory.createBuilder().bescheidFileId(FileId.from(BescheidTestFactory.BESCHEID_FILE_ID)) + .nachrichtText(Optional.empty()).build(); + + var itemMap = buildItemMap(); + + assertThat(itemMap).containsEntry(DocumentService.FIELD_NACHRICHT_TEXT, StringUtils.EMPTY); + } + + @Test + void shouldThrowExceptionIfFileIdMissing() { + bescheid = BescheidTestFactory.create(); + + assertThrows(TechnicalException.class, () -> service.buildItemMap(bescheid)); + + } + + private Map<String, Object> buildItemMap() { + return service.buildItemMap(bescheid); + } } } @Nested - class TestBuildItemMap { + class TestGetDocument { @Test - void shouldDetDocumentType() { - var itemMap = buildItemMap(); + void shouldCallAttachedItemService() { + service.getDocument(AttachedItemTestFactory.ID); - assertThat(itemMap).containsEntry(DocumentService.FIELD_DOCUMENT_TYPE, DocumentService.DOCUMENT_TYPE); + verify(attachedItemService).getItem(AttachedItemTestFactory.ID); } @Test - void shouldSetDocumentFile() { - var itemMap = buildItemMap(); + void shouldCallDocumentMapper() { + var expectedItem = AttachedItemTestFactory.createDocument(); + when(attachedItemService.getItem(any())).thenReturn(expectedItem); + + service.getDocument(AttachedItemTestFactory.ID); - assertThat(itemMap).containsEntry(DocumentService.FIELD_DOCUMENT_FILE, FILE_ID); + verify(documentMapper).fromAttachedItem(expectedItem); } @Test - void shouldThrowExceptionIfDocumentFileIsMissing() { - command = CommandTestFactory.createBuilder().bodyObject(Map.of()).build(); + void shouldReturnDocument() { + var expectedDocument = DocumentTestFactory.create(); + when(documentMapper.fromAttachedItem(any())).thenReturn(expectedDocument); - assertThrows(TechnicalException.class, () -> service.buildItemMap(command)); - } + Document document = service.getDocument(AttachedItemTestFactory.ID); - private Map<String, Object> buildItemMap() { - return service.buildItemMap(command); + assertThat(document).isEqualTo(expectedDocument); } } - } \ No newline at end of file diff --git a/bescheid-manager/src/test/java/de/ozgcloud/document/DocumentTestFactory.java b/bescheid-manager/src/test/java/de/ozgcloud/document/DocumentTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..0484d6f5492a0b4fe72bbd87b6980797c9bb6110 --- /dev/null +++ b/bescheid-manager/src/test/java/de/ozgcloud/document/DocumentTestFactory.java @@ -0,0 +1,48 @@ +/* + * 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.document; + +import de.ozgcloud.document.Document.DocumentBuilder; + +public class DocumentTestFactory { + + public static final String ID = "documentId"; + public static final String TYPE = "type"; + public static final String DOCUMENT_FILE = "file-id"; + public static final String NACHRICHT_SUBJECT = "subject"; + public static final String NACHRICHT_TEXT = "text"; + + public static Document create() { + return createBuilder().build(); + } + + public static DocumentBuilder createBuilder() { + return Document.builder() + .id(ID) + .type(TYPE) + .fileId(DOCUMENT_FILE) + .nachrichtSubject(NACHRICHT_SUBJECT) + .nachrichtText(NACHRICHT_TEXT); + } +} diff --git a/bescheid-manager/src/test/java/de/ozgcloud/document/GrpcDocumentTestFactory.java b/bescheid-manager/src/test/java/de/ozgcloud/document/GrpcDocumentTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..5c56530f7b13a986c3cd6fd0914bfc9201d152b7 --- /dev/null +++ b/bescheid-manager/src/test/java/de/ozgcloud/document/GrpcDocumentTestFactory.java @@ -0,0 +1,40 @@ +/* + * 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.document; + +public class GrpcDocumentTestFactory { + + public static GrpcDocument create() { + return createBuilder().build(); + } + + public static GrpcDocument.Builder createBuilder() { + return GrpcDocument.newBuilder() + .setId(DocumentTestFactory.ID) + .setType(DocumentTestFactory.TYPE) + .setFileId(DocumentTestFactory.DOCUMENT_FILE) + .setNachrichtSubject(DocumentTestFactory.NACHRICHT_SUBJECT) + .setNachrichtText(DocumentTestFactory.NACHRICHT_TEXT); + } +} diff --git a/nachrichten-bayernid-proxy/src/main/helm/templates/network_policy.yaml b/nachrichten-bayernid-proxy/src/main/helm/templates/network_policy.yaml index e0effc809597979a1cdb01c636f868c63574f617..95cd70f64a419cc1e585a2f977f48d45aae0eaee 100644 --- a/nachrichten-bayernid-proxy/src/main/helm/templates/network_policy.yaml +++ b/nachrichten-bayernid-proxy/src/main/helm/templates/network_policy.yaml @@ -44,6 +44,9 @@ spec: component: vorgang-manager {{- with (.Values.networkPolicy).additionalIngressConfig }} {{ toYaml . | indent 2 }} +{{- end }} +{{- with (.Values.networkPolicy).additionalIngressConfigNamespace }} +{{ toYaml . | indent 2 }} {{- end }} egress: - to: @@ -62,5 +65,8 @@ spec: {{- with (.Values.networkPolicy).additionalEgressConfig }} {{ toYaml . | indent 2 }} {{- end }} +{{- with (.Values.networkPolicy).additionalEgressConfigNamespace }} +{{ toYaml . | indent 2 }} +{{- end }} {{- end }} \ No newline at end of file diff --git a/nachrichten-bayernid-proxy/src/test/helm/network_policy_test.yaml b/nachrichten-bayernid-proxy/src/test/helm/network_policy_test.yaml index 7382ad0947be9db238ff1b5d7f9de08439716165..6942329e62a7ca2a0544cc56d6f3d6d76e9b2120 100644 --- a/nachrichten-bayernid-proxy/src/test/helm/network_policy_test.yaml +++ b/nachrichten-bayernid-proxy/src/test/helm/network_policy_test.yaml @@ -91,16 +91,15 @@ tests: - port: 5353 protocol: TCP - - it: add ingress rule by values + - it: should add additionalIngressConfig set: networkPolicy: - ssoPublicIp: 51.89.117.53/32 - dnsServerNamespace: test-namespace-dns + dnsServerNamespace: test-dns-namespace additionalIngressConfig: - from: - podSelector: matchLabels: - component: client2 + additionalIngressConfig: yes asserts: - contains: path: spec.ingress @@ -108,41 +107,62 @@ tests: from: - podSelector: matchLabels: - component: client2 + additionalIngressConfig: yes - - it: add egress rules by values + - it: should add additionalEgressConfig set: - networkPolicy: + networkPolicy: + dnsServerNamespace: test-dns-namespace additionalEgressConfig: - - to: - - ipBlock: - cidr: 1.2.3.4/32 - to: - podSelector: matchLabels: - component: ozg-testservice - ports: - - port: 12345 - protocol: TCP - - dnsServerNamespace: test-dns-namespace + additionalEgressConfig: yes asserts: - contains: path: spec.egress content: to: - - ipBlock: - cidr: 1.2.3.4/32 + - podSelector: + matchLabels: + additionalEgressConfig: yes + + + - it: should add additionalIngressConfigNamespace + set: + networkPolicy: + dnsServerNamespace: test-dns-namespace + additionalIngressConfigNamespace: + - from: + - podSelector: + matchLabels: + additionalIngressConfigNamespace: yes + asserts: + - contains: + path: spec.ingress + content: + from: + - podSelector: + matchLabels: + additionalIngressConfigNamespace: yes + + - it: should add additionalEgressConfigNamespace + set: + networkPolicy: + dnsServerNamespace: test-dns-namespace + additionalEgressConfigNamespace: + - to: + - podSelector: + matchLabels: + additionalEgressConfigNamespace: yes + asserts: - contains: path: spec.egress content: to: - podSelector: matchLabels: - component: ozg-testservice - ports: - - port: 12345 - protocol: TCP + additionalEgressConfigNamespace: yes - it: test network policy disabled set: diff --git a/nachrichten-manager/pom.xml b/nachrichten-manager/pom.xml index e569a9df0fd76405975f30c968f21291846c2da2..aeaac44692b56cda777a843885651a86dddccb0c 100644 --- a/nachrichten-manager/pom.xml +++ b/nachrichten-manager/pom.xml @@ -24,8 +24,9 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> <parent> <groupId>de.ozgcloud.common</groupId> @@ -39,278 +40,310 @@ <version>2.6.0-SNAPSHOT</version> <name>OZG-Cloud Nachrichten Manager</name> - <properties> - <java.version>21</java.version> - <!-- TODO version management --> - <shedlock.version>4.25.0</shedlock.version> - <logcaptor.version>2.7.10</logcaptor.version> - <ozgcloud.license.version>1.3.0</ozgcloud.license.version> - <jaxb-maven-plugin.version>3.0.1</jaxb-maven-plugin.version> - <ozg-info-manager-interface.version>0.10.0-22.8503e49-20240124.071709-1</ozg-info-manager-interface.version> - <bayernid-proxy-interface.version>0.1.0</bayernid-proxy-interface.version> - </properties> + <properties> + <java.version>17</java.version> + <!-- TODO version management --> + <shedlock.version>4.25.0</shedlock.version> + <logcaptor.version>2.7.10</logcaptor.version> + <ozgcloud.license.version>1.3.0</ozgcloud.license.version> + <jaxb-maven-plugin.version>3.0.1</jaxb-maven-plugin.version> + <ozg-info-manager-interface.version>0.10.0-22.8503e49-20240124.071709-1</ozg-info-manager-interface.version> + <bayernid-proxy-interface.version>0.1.0</bayernid-proxy-interface.version> + </properties> - <dependencies> - <dependency> - <groupId>de.ozgcloud.vorgang</groupId> - <artifactId>vorgang-manager-base</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>de.ozgcloud.vorgang</groupId> - <artifactId>vorgang-manager-interface</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>de.ozgcloud.command</groupId> - <artifactId>command-manager</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>de.ozgcloud.vorgang</groupId> - <artifactId>vorgang-manager-utils</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>de.ozgcloud.nachrichten</groupId> - <artifactId>bayernid-proxy-interface</artifactId> - <version>${bayernid-proxy-interface.version}</version> - </dependency> + <repositories> + <repository> + <id>shibboleth-releases</id> + <name>Shibboleth Releases Repository</name> + <url>https://build.shibboleth.net/maven/releases/</url> + <releases> + <enabled>true</enabled> + <checksumPolicy>warn</checksumPolicy> + </releases> + <snapshots> + <enabled>false</enabled> + </snapshots> + </repository> + <repository> + <id>shibboleth-thirdparty</id> + <name>Shibboleth Thirdparty Repository</name> + <url>https://build.shibboleth.net/maven/thirdparty/</url> + <releases> + <enabled>true</enabled> + <checksumPolicy>fail</checksumPolicy> + </releases> + <snapshots> + <enabled>false</enabled> + </snapshots> + </repository> + </repositories> - <dependency> - <groupId>com.mgmtp.bup.ozg</groupId> - <artifactId>ozg-info-manager-interface</artifactId> - <version>${ozg-info-manager-interface.version}</version> - </dependency> + <dependencies> + <dependency> + <groupId>de.ozgcloud.vorgang</groupId> + <artifactId>vorgang-manager-base</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>de.ozgcloud.vorgang</groupId> + <artifactId>vorgang-manager-interface</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>de.ozgcloud.command</groupId> + <artifactId>command-manager</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>de.ozgcloud.vorgang</groupId> + <artifactId>vorgang-manager-utils</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>de.ozgcloud.nachrichten</groupId> + <artifactId>bayernid-proxy-interface</artifactId> + <version>${bayernid-proxy-interface.version}</version> + </dependency> - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-mail</artifactId> - </dependency> + <dependency> + <groupId>com.mgmtp.bup.ozg</groupId> + <artifactId>ozg-info-manager-interface</artifactId> + <version>${ozg-info-manager-interface.version}</version> + </dependency> - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-json</artifactId> - <scope>compile</scope> - </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-mail</artifactId> + </dependency> - <dependency> - <groupId>org.springframework</groupId> - <artifactId>spring-web</artifactId> - </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-json</artifactId> + <scope>compile</scope> + </dependency> - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-validation</artifactId> - </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-web</artifactId> + </dependency> - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-web-services</artifactId> - </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-validation</artifactId> + </dependency> - <dependency> - <groupId>org.glassfish.jaxb</groupId> - <artifactId>jaxb-runtime</artifactId> - </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web-services</artifactId> + </dependency> - <dependency> - <groupId>net.javacrumbs.shedlock</groupId> - <artifactId>shedlock-spring</artifactId> - <version>${shedlock.version}</version> - </dependency> - <dependency> - <groupId>net.javacrumbs.shedlock</groupId> - <artifactId>shedlock-provider-mongo</artifactId> - <version>${shedlock.version}</version> - </dependency> + <dependency> + <groupId>org.glassfish.jaxb</groupId> + <artifactId>jaxb-runtime</artifactId> + </dependency> - <dependency> - <groupId>org.mapstruct</groupId> - <artifactId>mapstruct</artifactId> - </dependency> + <dependency> + <groupId>net.javacrumbs.shedlock</groupId> + <artifactId>shedlock-spring</artifactId> + <version>${shedlock.version}</version> + </dependency> + <dependency> + <groupId>net.javacrumbs.shedlock</groupId> + <artifactId>shedlock-provider-mongo</artifactId> + <version>${shedlock.version}</version> + </dependency> - <!-- grpc --> - <dependency> - <groupId>net.devh</groupId> - <artifactId>grpc-server-spring-boot-starter</artifactId> - </dependency> - <dependency> - <groupId>net.devh</groupId> - <artifactId>grpc-client-spring-boot-starter</artifactId> - </dependency> + <dependency> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct</artifactId> + </dependency> - <!-- commons --> - <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-lang3</artifactId> - </dependency> - <dependency> - <groupId>org.apache.httpcomponents.client5</groupId> - <artifactId>httpclient5</artifactId> - </dependency> - <dependency> - <groupId>commons-beanutils</groupId> - <artifactId>commons-beanutils</artifactId> - </dependency> + <dependency> + <groupId>org.springframework.security</groupId> + <artifactId>spring-security-saml2-service-provider</artifactId> + </dependency> - <!-- DEV --> - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-devtools</artifactId> - <scope>runtime</scope> - <optional>true</optional> - </dependency> - <dependency> - <groupId>org.projectlombok</groupId> - <artifactId>lombok</artifactId> - <optional>true</optional> - <scope>provided</scope> - </dependency> + + <dependency> + <groupId>net.devh</groupId> + <artifactId>grpc-server-spring-boot-starter</artifactId> + </dependency> + <dependency> + <groupId>net.devh</groupId> + <artifactId>grpc-client-spring-boot-starter</artifactId> + </dependency> - <!-- TEST --> - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-test</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-configuration-processor</artifactId> - <optional>true</optional> - </dependency> - <dependency> - <groupId>io.github.hakky54</groupId> - <artifactId>logcaptor</artifactId> - <version>${logcaptor.version}</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>de.ozgcloud.vorgang</groupId> - <artifactId>vorgang-manager-base</artifactId> - <version>${project.version}</version> - <type>test-jar</type> - <scope>test</scope> - </dependency> - </dependencies> + <!-- commons --> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + </dependency> + <dependency> + <groupId>org.apache.httpcomponents.client5</groupId> + <artifactId>httpclient5</artifactId> + </dependency> + <dependency> + <groupId>commons-beanutils</groupId> + <artifactId>commons-beanutils</artifactId> + </dependency> - <build> - <plugins> - <plugin> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-maven-plugin</artifactId> - <configuration> - <skip>true</skip> - </configuration> - </plugin> + <!-- DEV --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-devtools</artifactId> + <scope>runtime</scope> + <optional>true</optional> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <optional>true</optional> + <scope>provided</scope> + </dependency> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-jar-plugin</artifactId> - <executions> - <execution> - <goals> - <goal>test-jar</goal> - </goals> - </execution> - </executions> - </plugin> + <!-- TEST --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <optional>true</optional> + </dependency> + <dependency> + <groupId>io.github.hakky54</groupId> + <artifactId>logcaptor</artifactId> + <version>${logcaptor.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>de.ozgcloud.vorgang</groupId> + <artifactId>vorgang-manager-base</artifactId> + <version>${project.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + </dependencies> - <plugin> - <groupId>org.jvnet.jaxb</groupId> - <artifactId>jaxb-maven-plugin</artifactId> - <version>${jaxb-maven-plugin.version}</version> - <configuration> - <schemas> - <schema> - <fileset> - <directory>${basedir}/src/main/resources/bayernid</directory> - <includes> - <include>*.wsdl</include> - </includes> - </fileset> - </schema> - <schema> - <fileset> - <directory>${basedir}/src/main/resources/bayernid</directory> - <includes> - <include>*.xsd</include> - </includes> - </fileset> - </schema> - </schemas> - <episode>false</episode> - </configuration> - <executions> - <execution> - <goals> - <goal>generate</goal> - </goals> - </execution> - </executions> - </plugin> - - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-compiler-plugin</artifactId> - </plugin> + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <configuration> + <skip>true</skip> + </configuration> + </plugin> - <plugin> - <groupId>org.sonarsource.scanner.maven</groupId> - <artifactId>sonar-maven-plugin</artifactId> - </plugin> - <plugin> - <groupId>org.jacoco</groupId> - <artifactId>jacoco-maven-plugin</artifactId> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-failsafe-plugin</artifactId> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-surefire-plugin</artifactId> - </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>test-jar</goal> + </goals> + </execution> + </executions> + </plugin> - <plugin> - <groupId>com.mycila</groupId> - <artifactId>license-maven-plugin</artifactId> - <configuration> - <mapping> - <config>SCRIPT_STYLE</config> - </mapping> - <licenseSets> - <licenseSet> - <header>license/eupl_v1_2_de/header.txt</header> - <excludes> - <exclude>**/README</exclude> - <exclude>src/test/resources/**</exclude> - <exclude>src/main/resources/**</exclude> - </excludes> - </licenseSet> - </licenseSets> - </configuration> - <dependencies> - <dependency> - <groupId>de.ozgcloud.common</groupId> - <artifactId>ozgcloud-common-license</artifactId> - <version>${ozgcloud.license.version}</version> - </dependency> - </dependencies> - </plugin> - </plugins> - </build> + <plugin> + <groupId>org.jvnet.jaxb</groupId> + <artifactId>jaxb-maven-plugin</artifactId> + <version>${jaxb-maven-plugin.version}</version> + <configuration> + <schemas> + <schema> + <fileset> + <directory>${basedir}/src/main/resources/bayernid</directory> + <includes> + <include>*.wsdl</include> + </includes> + </fileset> + </schema> + <schema> + <fileset> + <directory>${basedir}/src/main/resources/bayernid</directory> + <includes> + <include>*.xsd</include> + </includes> + </fileset> + </schema> + </schemas> + <episode>false</episode> + </configuration> + <executions> + <execution> + <goals> + <goal>generate</goal> + </goals> + </execution> + </executions> + </plugin> - <distributionManagement> - <repository> - <id>ozg-nexus</id> - <name>ozg-releases</name> - <url>https://nexus.ozg-sh.de/repository/ozg-releases/</url> - </repository> - <snapshotRepository> - <id>ozg-snapshots-nexus</id> - <name>ozg-snapshots</name> - <url>https://nexus.ozg-sh.de/repository/ozg-snapshots/</url> - </snapshotRepository> - </distributionManagement> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + </plugin> + + <plugin> + <groupId>org.sonarsource.scanner.maven</groupId> + <artifactId>sonar-maven-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + </plugin> + + <plugin> + <groupId>com.mycila</groupId> + <artifactId>license-maven-plugin</artifactId> + <configuration> + <mapping> + <config>SCRIPT_STYLE</config> + </mapping> + <licenseSets> + <licenseSet> + <header>license/eupl_v1_2_de/header.txt</header> + <excludes> + <exclude>**/README</exclude> + <exclude>src/test/resources/**</exclude> + <exclude>src/main/resources/**</exclude> + </excludes> + </licenseSet> + </licenseSets> + </configuration> + <dependencies> + <dependency> + <groupId>de.ozgcloud.common</groupId> + <artifactId>ozgcloud-common-license</artifactId> + <version>${ozgcloud.license.version}</version> + </dependency> + </dependencies> + </plugin> + </plugins> + </build> + + <distributionManagement> + <repository> + <id>ozg-nexus</id> + <name>ozg-releases</name> + <url>https://nexus.ozg-sh.de/repository/ozg-releases/</url> + </repository> + <snapshotRepository> + <id>ozg-snapshots-nexus</id> + <name>ozg-snapshots</name> + <url>https://nexus.ozg-sh.de/repository/ozg-snapshots/</url> + </snapshotRepository> + </distributionManagement> </project> diff --git a/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumGrpcService.java b/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumGrpcService.java new file mode 100644 index 0000000000000000000000000000000000000000..4921595ed99fe131abde54ebfccb4f42ad2a8d3e --- /dev/null +++ b/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumGrpcService.java @@ -0,0 +1,60 @@ +/* + * 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.nachrichten.antragraum; + +import org.apache.commons.lang3.NotImplementedException; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +import de.ozgcloud.vorgang.grpc.command.GrpcCommand; +import io.grpc.stub.StreamObserver; +import lombok.RequiredArgsConstructor; +import net.devh.boot.grpc.server.service.GrpcService; + +@GrpcService +@RequiredArgsConstructor +@ConditionalOnProperty(AntragraumProperties.PROPERTY_ANTRAGSRAUM_URL) +class AntragraumGrpcService extends AntragraumServiceGrpc.AntragraumServiceImplBase { + private final AntragraumService antragraumService; + private final AntragraumNachrichtMapper mapper; + + @Override + public void findRueckfragen(GrpcFindRueckfragenRequest request, StreamObserver<GrpcFindRueckfragenResponse> streamObserver) { + var rueckfragen = antragraumService.findRueckfragen(request.getSamlToken()).map(mapper::toGrpc).toList(); + var response = GrpcFindRueckfragenResponse.newBuilder().addAllRueckfrage(rueckfragen).build(); + + streamObserver.onNext(response); + streamObserver.onCompleted(); + } + + @Override + public void sendRueckfrageAnswer(GrpcSendRueckfrageAnswerRequest request, StreamObserver<GrpcCommand> streamObserver) { + var answer = request.getAnswer(); + + var commandId = antragraumService.sendRueckfrageAnswer(request.getSamlToken(), answer.getRueckfrageId(), mapper.toPostfachNachricht(answer)); + + streamObserver.onNext(GrpcCommand.newBuilder().setId(commandId).build()); + streamObserver.onCompleted(); + } +} diff --git a/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumNachrichtMapper.java b/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumNachrichtMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..cb54984efbb573241ed2aeca1fc2f08fc56d2e8e --- /dev/null +++ b/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumNachrichtMapper.java @@ -0,0 +1,61 @@ +/* + * 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.nachrichten.antragraum; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValueCheckStrategy; + +import de.ozgcloud.nachrichten.postfach.PostfachNachricht; + +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) +interface AntragraumNachrichtMapper { + String DEFAULT_STATUS = "NEU"; + + @Mapping(source = "sentAt", target = "sentAt", dateFormat = "yyyy-MM-dd'T'HH:mm:ss") + @Mapping(source = "mailBody", target = "text") + @Mapping(source = "subject", target = "vorgangName") + @Mapping(source = "attachments", target = "attachmentFileIdList") + @Mapping(target = "status", constant = DEFAULT_STATUS) + GrpcRueckfrage toGrpc(PostfachNachricht postfachNachricht); + + @Mapping(target = "mailBody", source = "answerText") + @Mapping(target = "attachments", source = "attachmentFileIdList") + @Mapping(target = "createdAt", ignore = true) + @Mapping(target = "createdBy", ignore = true) + @Mapping(target = "direction", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "messageCode", ignore = true) + @Mapping(target = "messageId", source = "rueckfrageId") + @Mapping(target = "postfachAddress", ignore = true) + @Mapping(target = "postfachId", ignore = true) + @Mapping(target = "replyOption", ignore = true) + @Mapping(target = "sentAt", ignore = true) + @Mapping(target = "sentSuccessful", ignore = true) + @Mapping(target = "subject", ignore = true) + @Mapping(target = "vorgangId", ignore = true) + PostfachNachricht toPostfachNachricht(GrpcRueckfrageAnswer answer); +} diff --git a/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumProperties.java b/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumProperties.java index 860818fa9c7e82e7331fca1ef06bcb1939d73a8d..9db391f6c208a0aa6c1c1849d9a6c588a031ceb2 100644 --- a/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumProperties.java +++ b/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumProperties.java @@ -1,3 +1,26 @@ +/* + * 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.nachrichten.antragraum; import jakarta.validation.constraints.NotEmpty; @@ -5,6 +28,7 @@ import jakarta.validation.constraints.NotEmpty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; import org.springframework.validation.annotation.Validated; import lombok.Getter; @@ -23,4 +47,22 @@ public class AntragraumProperties { @NotEmpty private String url; + /** + * The entityId as defined the BayernId SAML Metadata + */ + @NotEmpty + private String entityId; + /** + * The uri where to load the idp Metadata from + */ + private Resource metadataUri; + /** + * The location of the private key for decrypting the saml token data + */ + private Resource decryptionPrivateKey; + /** + * The location of the certificate for decrypting the saml token data + */ + private Resource decryptionCertificate; + } diff --git a/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumService.java b/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumService.java index 0bbdf0ab26d16ad9f9c14f164f63f241995963f1..3d9ea7abd01eaa507573d2614f599654e0e2bfbf 100644 --- a/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumService.java +++ b/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumService.java @@ -1,19 +1,50 @@ +/* + * 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.nachrichten.antragraum; import static java.util.Objects.*; -import jakarta.annotation.PostConstruct; +import java.util.stream.Stream; +import org.apache.commons.collections.CollectionUtils; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import de.ozgcloud.nachrichten.NachrichtenManagerProperties; +import de.ozgcloud.nachrichten.postfach.PersistPostfachNachrichtService; +import de.ozgcloud.nachrichten.postfach.PostfachNachricht; +import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor @ConditionalOnProperty(AntragraumProperties.PROPERTY_ANTRAGSRAUM_URL) public class AntragraumService { + private final PersistPostfachNachrichtService postfachNachrichtService; + private final Saml2Verifier verifier; + private final Saml2Parser parser; + private final Saml2Decrypter decrypter; static final String USER_NOTIFICATION_TEMPLATE = """ Guten Tag, @@ -44,4 +75,28 @@ public class AntragraumService { return USER_NOTIFICATION_TEMPLATE.formatted(getAntragsraumUrl()); } + public Stream<PostfachNachricht> findRueckfragen(String samlToken) { + verifyToken(samlToken); + var postfachId = decrypter.decryptPostfachId(parser.parse(samlToken)); + + return postfachNachrichtService.findRueckfragen(postfachId); + } + + public Stream<PostfachNachricht> findRueckfrageAnswers(String rueckfrageId) { + return postfachNachrichtService.findAnswers(rueckfrageId); + } + + public String sendRueckfrageAnswer(String samlToken, String rueckfrageId, PostfachNachricht nachricht) { + verifyToken(samlToken); + + return postfachNachrichtService.persistAnswer(rueckfrageId, nachricht); + } + + void verifyToken(String token) { + var errors = verifier.verify(token); + if (CollectionUtils.isNotEmpty(errors)) { + throw new SecurityException("SAML Token verification failed. Errors: %s".formatted(errors)); + } + } + } diff --git a/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/BayernIdSamlConfiguration.java b/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/BayernIdSamlConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..0961155476fed54722e9572435b34c5225e67e80 --- /dev/null +++ b/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/BayernIdSamlConfiguration.java @@ -0,0 +1,242 @@ +/* + * 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.nachrichten.antragraum; + +import java.io.IOException; +import java.io.InputStream; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import jakarta.annotation.PostConstruct; + +import org.opensaml.core.config.ConfigurationService; +import org.opensaml.core.config.InitializationService; +import org.opensaml.core.criterion.EntityIdCriterion; +import org.opensaml.core.xml.XMLObject; +import org.opensaml.core.xml.config.XMLObjectProviderRegistry; +import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.criterion.ProtocolCriterion; +import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion; +import org.opensaml.saml.saml2.metadata.EntitiesDescriptor; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml.saml2.metadata.KeyDescriptor; +import org.opensaml.security.credential.Credential; +import org.opensaml.security.credential.CredentialResolver; +import org.opensaml.security.credential.UsageType; +import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion; +import org.opensaml.security.credential.criteria.impl.EvaluableUsageCredentialCriterion; +import org.opensaml.security.credential.impl.CollectionCredentialResolver; +import org.opensaml.security.criteria.UsageCriterion; +import org.opensaml.security.x509.BasicX509Credential; +import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap; +import org.opensaml.xmlsec.keyinfo.KeyInfoSupport; +import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; +import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.security.converter.RsaKeyConverters; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.util.Assert; + +import lombok.Getter; +import net.shibboleth.utilities.java.support.component.ComponentInitializationException; +import net.shibboleth.utilities.java.support.resolver.CriteriaSet; +import net.shibboleth.utilities.java.support.xml.BasicParserPool; +import net.shibboleth.utilities.java.support.xml.ParserPool; +import net.shibboleth.utilities.java.support.xml.XMLParserException; + +@Configuration +@ConditionalOnProperty(AntragraumProperties.PROPERTY_ANTRAGSRAUM_URL) +class BayernIdSamlConfiguration { + private XMLObjectProviderRegistry registry; + @Getter + private ParserPool parserPool; + @Autowired + private AntragraumProperties antragraumProperties; + + @PostConstruct + void initOpenSAML() { + try { + registry = new XMLObjectProviderRegistry(); + ConfigurationService.register(XMLObjectProviderRegistry.class, registry); + parserPool = initParserPool(); + + registry.setParserPool(parserPool); + InitializationService.initialize(); + } catch (Exception e) { + throw new RuntimeException("Initialization failed"); + } + } + + private ParserPool initParserPool() throws ComponentInitializationException { + var localParserPool = new BasicParserPool(); + + final var features = createFeatureMap(); + + localParserPool.setBuilderFeatures(features); + + localParserPool.setBuilderAttributes(new HashMap<>()); + + localParserPool.initialize(); + + return localParserPool; + } + + private Map<String, Boolean> createFeatureMap() { + final Map<String, Boolean> features = new HashMap<>(); + features.put("http://xml.org/sax/features/external-general-entities", Boolean.FALSE); + features.put("http://xml.org/sax/features/external-parameter-entities", Boolean.FALSE); + features.put("http://apache.org/xml/features/disallow-doctype-decl", Boolean.TRUE); + features.put("http://apache.org/xml/features/validation/schema/normalized-value", Boolean.FALSE); + features.put("http://javax.xml.XMLConstants/feature/secure-processing", Boolean.TRUE); + return features; + } + + SignatureTrustEngine getTrustEngine() { + Set<Credential> credentials = new HashSet<>(); + Collection<Saml2X509Credential> keys = getCertificatesFromMetadata(); + + for (Saml2X509Credential key : keys) { + var cred = new BasicX509Credential(key.getCertificate()); + cred.setUsageType(UsageType.SIGNING); + cred.setEntityId(antragraumProperties.getEntityId()); + credentials.add(cred); + } + + CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials); + return new ExplicitKeySignatureTrustEngine(credentialsResolver, + DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver()); + } + + CriteriaSet getVerificationCriteria() { + var criteria = new CriteriaSet(); + criteria.add(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(antragraumProperties.getEntityId()))); + criteria.add(new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion("urn:oasis:names:tc:SAML:2.0:protocol"))); + criteria.add(new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); + return criteria; + } + + Saml2X509Credential getDecryptionCredential() { + var privateKey = readPrivateKey(antragraumProperties.getDecryptionPrivateKey()); + var certificate = readCertificateFromResource(antragraumProperties.getDecryptionCertificate()); + return new Saml2X509Credential(privateKey, certificate, Saml2X509Credential.Saml2X509CredentialType.DECRYPTION); + } + + private RSAPrivateKey readPrivateKey(Resource location) { + Assert.state(location != null, "No private key location specified"); + Assert.state(location.exists(), () -> "Private key location '" + location + "' does not exist"); + try (var inputStream = location.getInputStream()) { + return RsaKeyConverters.pkcs8().convert(inputStream); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + private X509Certificate readCertificateFromResource(Resource location) { + Assert.state(location != null, "No certificate location specified"); + Assert.state(location.exists(), () -> "Certificate location '" + location + "' does not exist"); + try (var inputStream = location.getInputStream()) { + + return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(inputStream); + } catch (IOException | CertificateException e) { + throw new IllegalArgumentException(e); + } + } + + List<Saml2X509Credential> getCertificatesFromMetadata() { + try (var metadata = antragraumProperties.getMetadataUri().getInputStream()) { + var xmlObject = xmlObject(metadata); + if (xmlObject instanceof EntitiesDescriptor descriptors) { + for (EntityDescriptor descriptor : descriptors.getEntityDescriptors()) { + if (descriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS) != null) { + return getVerificationCertificates(descriptor); + } + } + } + } catch (IOException e) { + throw new Saml2Exception("Error reading idp metadata.", e); + } catch (ComponentInitializationException | XMLParserException e) { + throw new Saml2Exception("Error initializing parser pool.", e); + } + + throw new Saml2Exception("No IDPSSO Descriptors found"); + } + + private XMLObject xmlObject(InputStream inputStream) throws ComponentInitializationException, XMLParserException { + var document = getParserPool().parse(inputStream); + var element = document.getDocumentElement(); + var unmarshaller = this.registry.getUnmarshallerFactory().getUnmarshaller(element); + if (unmarshaller == null) { + throw new Saml2Exception("Unsupported element of type " + element.getTagName()); + } + try { + return unmarshaller.unmarshall(element); + } catch (Exception ex) { + throw new Saml2Exception(ex); + } + } + + List<Saml2X509Credential> getVerificationCertificates(EntityDescriptor descriptor) { + var idpssoDescriptor = descriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS); + if (idpssoDescriptor == null) { + throw new Saml2Exception("Metadata response is missing the necessary IDPSSODescriptor element"); + } + List<Saml2X509Credential> verification = new ArrayList<>(); + for (KeyDescriptor keyDescriptor : idpssoDescriptor.getKeyDescriptors()) { + if (keyDescriptor.getUse().equals(UsageType.SIGNING)) { + var certificates = certificates(keyDescriptor); + for (X509Certificate certificate : certificates) { + verification.add(Saml2X509Credential.verification(certificate)); + } + } + } + if (verification.isEmpty()) { + throw new Saml2Exception( + "Metadata response is missing verification certificates, necessary for verifying SAML assertions"); + } + + return verification; + } + + private List<X509Certificate> certificates(KeyDescriptor keyDescriptor) { + try { + return KeyInfoSupport.getCertificates(keyDescriptor.getKeyInfo()); + } catch (CertificateException ex) { + throw new Saml2Exception(ex); + } + } +} diff --git a/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/Saml2Decrypter.java b/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/Saml2Decrypter.java new file mode 100644 index 0000000000000000000000000000000000000000..9728ddba81bb0105dde77407ea24b44214e0b29d --- /dev/null +++ b/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/Saml2Decrypter.java @@ -0,0 +1,105 @@ +/* + * 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.nachrichten.antragraum; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +import jakarta.annotation.PostConstruct; + +import org.opensaml.core.xml.schema.XSString; +import org.opensaml.saml.saml2.core.AttributeStatement; +import org.opensaml.saml.saml2.core.EncryptedAssertion; +import org.opensaml.saml.saml2.core.Response; +import org.opensaml.saml.saml2.encryption.Decrypter; +import org.opensaml.saml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver; +import org.opensaml.security.credential.Credential; +import org.opensaml.security.credential.CredentialSupport; +import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver; +import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver; +import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver; +import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver; +import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver; +import org.opensaml.xmlsec.keyinfo.impl.CollectionKeyInfoCredentialResolver; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@ConditionalOnProperty(AntragraumProperties.PROPERTY_ANTRAGSRAUM_URL) +class Saml2Decrypter { + private final BayernIdSamlConfiguration configuration; + + public static final String LEGACY_POSTKORB_HANDLE_KEY = "legacyPostkorbHandle"; + private Decrypter decrypter; + private static final EncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver( + Arrays.asList(new InlineEncryptedKeyResolver(), new EncryptedElementTypeEncryptedKeyResolver(), + new SimpleRetrievalMethodEncryptedKeyResolver())); + + @PostConstruct + void init() { + Collection<Credential> credentials = new ArrayList<>(); + var decryptionX509Credential = configuration.getDecryptionCredential(); + + Credential cred = CredentialSupport.getSimpleCredential(decryptionX509Credential.getCertificate(), decryptionX509Credential.getPrivateKey()); + credentials.add(cred); + + KeyInfoCredentialResolver resolver = new CollectionKeyInfoCredentialResolver(credentials); + var setupDecrypter = new Decrypter(null, resolver, encryptedKeyResolver); + setupDecrypter.setRootInNewDocument(true); + + decrypter = setupDecrypter; + } + + void decryptResponseElements(Response response) { + for (EncryptedAssertion encryptedAssertion : response.getEncryptedAssertions()) { + try { + var assertion = decrypter.decrypt(encryptedAssertion); + response.getAssertions().add(assertion); + } catch (Exception ex) { + throw new Saml2Exception(ex); + } + } + } + + String decryptPostfachId(Response response) { + decryptResponseElements(response); + + var samlAssertion = response.getAssertions().get(0); + var statements = (AttributeStatement) samlAssertion.getStatements().get(1); + var attributes = statements.getAttributes(); + var postfachIdOptional = attributes.stream().filter(attribute -> LEGACY_POSTKORB_HANDLE_KEY.equals(attribute.getFriendlyName())).findFirst(); + + return postfachIdOptional.map(postfachIdAttribute -> { + var values = postfachIdAttribute.getAttributeValues(); + return ((XSString) values.get(0)).getValue(); + }).orElseThrow(); + + } +} diff --git a/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/Saml2Parser.java b/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/Saml2Parser.java new file mode 100644 index 0000000000000000000000000000000000000000..fa23252ab39606a73825a3981127f0731f0f7e49 --- /dev/null +++ b/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/Saml2Parser.java @@ -0,0 +1,74 @@ +/* + * 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.nachrichten.antragraum; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +import org.opensaml.core.xml.XMLObject; +import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; +import org.opensaml.core.xml.io.UnmarshallingException; +import org.opensaml.saml.saml2.core.Response; +import org.opensaml.saml.saml2.core.impl.ResponseUnmarshaller; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import net.shibboleth.utilities.java.support.xml.XMLParserException; + +@Service +@ConditionalOnProperty(AntragraumProperties.PROPERTY_ANTRAGSRAUM_URL) +@RequiredArgsConstructor +class Saml2Parser { + private final BayernIdSamlConfiguration configuration; + private ResponseUnmarshaller unmarshaller; + + Response parse(String request) { + return (Response) xmlObject(new ByteArrayInputStream(request.getBytes(StandardCharsets.UTF_8))); + } + + XMLObject xmlObject(InputStream inputStream) throws Saml2Exception { + try { + var document = configuration.getParserPool().parse(inputStream); + var element = document.getDocumentElement(); + return getUnmarshaller().unmarshall(element); + } catch (XMLParserException | UnmarshallingException e) { + throw new Saml2Exception("Failed to deserialize LogoutRequest", e); + } + } + + private ResponseUnmarshaller getUnmarshaller() { + if (Objects.nonNull(unmarshaller)) { + return unmarshaller; + } + + unmarshaller = (ResponseUnmarshaller) XMLObjectProviderRegistrySupport.getUnmarshallerFactory() + .getUnmarshaller(Response.DEFAULT_ELEMENT_NAME); + return unmarshaller; + } +} diff --git a/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/Saml2Verifier.java b/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/Saml2Verifier.java new file mode 100644 index 0000000000000000000000000000000000000000..e8cdf4d18e1ce1846294a165a1da80175aef452e --- /dev/null +++ b/nachrichten-manager/src/main/java/de/ozgcloud/nachrichten/antragraum/Saml2Verifier.java @@ -0,0 +1,90 @@ +/* + * 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.nachrichten.antragraum; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import jakarta.annotation.PostConstruct; + +import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; +import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.security.saml2.core.Saml2Error; +import org.springframework.security.saml2.core.Saml2ErrorCodes; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import net.shibboleth.utilities.java.support.resolver.CriteriaSet; + +@Log4j2 +@RequiredArgsConstructor +@Service +@ConditionalOnProperty(AntragraumProperties.PROPERTY_ANTRAGSRAUM_URL) +class Saml2Verifier { + public static final String INVALID_SIGNATURE = "Invalid signature for object [%s]: "; + public static final String SIGNATURE_MISSING = "Signature missing"; + private final Saml2Parser parser; + private final BayernIdSamlConfiguration configuration; + private SignatureTrustEngine trustEngine; + private CriteriaSet verificationCriteria; + + @PostConstruct + void init() { + trustEngine = configuration.getTrustEngine(); + verificationCriteria = configuration.getVerificationCriteria(); + } + + List<Saml2Error> verify(String samlToken) { + var response = parser.parse(samlToken); + + List<Saml2Error> errors = new ArrayList<>(); + var signature = response.getSignature(); + var profileValidator = new SAMLSignatureProfileValidator(); + if (Objects.nonNull(signature)) { + try { + profileValidator.validate(signature); + } catch (Exception ex) { + LOG.error("Error validating SAML Token: ", ex); + errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, INVALID_SIGNATURE.formatted(response.getID()))); + } + + try { + if (!trustEngine.validate(signature, verificationCriteria)) { + errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, INVALID_SIGNATURE.formatted(response.getID()))); + } + } catch (Exception ex) { + LOG.error("Error validating SAML Token: ", ex); + errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, INVALID_SIGNATURE.formatted(response.getID()))); + } + } else { + errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, SIGNATURE_MISSING)); + } + + return errors; + } +} diff --git a/nachrichten-manager/src/main/resources/application-bayern.yaml b/nachrichten-manager/src/main/resources/application-bayern.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f8fcba293cb9f21fd400b4f86a3c0320c0fa6aa3 --- /dev/null +++ b/nachrichten-manager/src/main/resources/application-bayern.yaml @@ -0,0 +1,3 @@ +ozgcloud: + antragraum: + entityId: https://antragsraum.ozgcloud.de/ \ No newline at end of file diff --git a/nachrichten-manager/src/main/resources/application-bayernlocal.yaml b/nachrichten-manager/src/main/resources/application-bayernlocal.yaml new file mode 100644 index 0000000000000000000000000000000000000000..898a6a6d7a85c34656f5ba0a34611a10025f4962 --- /dev/null +++ b/nachrichten-manager/src/main/resources/application-bayernlocal.yaml @@ -0,0 +1,7 @@ +ozgcloud: + antragraum: + url: https://dev.antragsraum.de/ + entityId: https://antragsraum.ozgcloud.de/ + metadataUri: "classpath:/bayernid/metadata/bayernid-idp-infra.xml" + decryptionPrivateKey: "classpath:/bayernid/bayernid-test-enc.key" + decryptionCertificate: "classpath:/bayernid/bayernid-test-enc.crt" \ No newline at end of file diff --git a/nachrichten-manager/src/main/resources/bayernid/metadata/bayernid-idp-infra.xml b/nachrichten-manager/src/main/resources/bayernid/metadata/bayernid-idp-infra.xml new file mode 100644 index 0000000000000000000000000000000000000000..ec1ed7ca7099b8be7a8cff7448a740f0b9404c34 --- /dev/null +++ b/nachrichten-manager/src/main/resources/bayernid/metadata/bayernid-idp-infra.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?><md:EntitiesDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"> + <md:EntityDescriptor entityID="https://infra-pre-id.bayernportal.de/idp"> + <md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> + <md:KeyDescriptor use="signing"> + <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> + <ds:X509Data> + <ds:X509Certificate>MIIFbzCCA1egAwIBAgIJAPdFXXarkBN2MA0GCSqGSIb3DQEBCwUAME4xCzAJBgNV + BAYTAkRFMQ8wDQYDVQQIDAZCYXllcm4xETAPBgNVBAcMCE11ZW5jaGVuMQ0wCwYD + VQQKDARBS0RCMQwwCgYDVQQLDANJRE0wHhcNMjAxMDI3MTMxODQxWhcNMjUxMDI2 + MTMxODQxWjBOMQswCQYDVQQGEwJERTEPMA0GA1UECAwGQmF5ZXJuMREwDwYDVQQH + DAhNdWVuY2hlbjENMAsGA1UECgwEQUtEQjEMMAoGA1UECwwDSURNMIICIjANBgkq + hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzDtWAEdC3J9FD+ti1exRhN1lzNgKWqO2 + gQNdJvlt7KGHA2VGGO7tqRogTuoqi/ydtiHJ8+lhp4kcWqyfv7i9HXOncvcsRRmR + dZjUY2Iui6ozJqD5LVm/vP5YfdP7vQPdbqyyfpoJhf3mbMEtdNDdGRnGIPUfDn+C + Fbo37f9tPwMgf3jgh4gxaujtLIhhr9gevVTEeZAFu9EvzLNd3kEtRb7MuXqIOdu1 + rW8HlGYFwwVLqEyBn8XG0QAIfhMmGjFMG7z+Kco2quwOmmZVzWQfeH/3AlN2KbcP + t7j+pl+6Bew2AAivP7O+95YKORqQjTu3rPWMF4txPId37MSjoytwBRyd5EACTvhQ + BOGrDFKQUOx6fTtRc8+7XGVz8MdQaZQWQXXh1ByU783twNdnRSrSVIyLdjiy1uCb + jvsSAtbzGBygPIvDo3skCNLNFXsChtHIfFFDK20KPGb0ghEDf2q3hDbFG3ZDGGyn + ZmJcZKuZhJqodJ/++sAXADyTJNAPVYDjKCF4ypELp2Eu/p1gaQPJEb74L/ZFZVOE + JFyXIiaqB9J+fcn/biqHHOmcCi8n9aIiNt1fatr1Z4lQRWoGtKaGU0+bzUSH4Bgs + 2EG4u1CI2MKDWqK2aEsHrtu8tbS9LrUmDVKtaEUOeul8xWVa036vp/YUIdiJNZSx + ZG4iTmSOATECAwEAAaNQME4wHQYDVR0OBBYEFFYeltslkaolOmcINXQeSe7nURwp + MB8GA1UdIwQYMBaAFFYeltslkaolOmcINXQeSe7nURwpMAwGA1UdEwQFMAMBAf8w + DQYJKoZIhvcNAQELBQADggIBAKqAlXoO41SAiycYUOrR90pfwTCysmbtHF5RWSCM + jF2aCG8URJ7bXwC0lBH8E5zCetFZwdqZziQtxzRkIOfhS5uWbH0RDhwuxZG+5RTP + yaHPAZI6e5xHDu8vHl/VbC3lnL/6K8l+Purr/yo8qkJqrPgThZRL9jBQyYRhDSsJ + UyIw5zcKKUQC/JWtMQAQcopbjekCs6xDT1HqIN90Sc/gOfYjNo0dGMNmro9mxcw8 + 2Iow18KNVdtEexfD+/6x4NPD61pzuQEe09TR+Cv3XyzBoGQ/2arijcPnGvth79ff + VFtRSf3fSs7wEKV9g3mEWXFDtPBhDj6K0kKU/kJfEZixkXl92MY+bmugrtTIrazj + tfrgMglIAHu9XCYWd/gef0J+PNfHsxgbTEr3XSC+5/xoFKPQSw3PgV8lkUDq4mJU + Ky/q4YmA37XQxourFR5pWvF03YACdtq6zPjtVeI7Cvkte6k0YW5S3cx9RmPv6YZh + laZ5ERpWNiv6IjokLsvNeemf2PApjO7Q2EDBIoHBYH31wwJSsyRDrSVmbaqLFI15 + fLXeh2A4YbaBDZdGvDiLOAk+dG1wdZ2aGw/uNBzMtc8VeKqI1HPcqIluBA3uUPpy + LLA+9hDPf6Pp4j0gkXxBikz+/h22bFxE1HmDiOSkEn+2NmOHuEFeA+D8jsCAL5VJ + 3emK</ds:X509Certificate> + </ds:X509Data> + </ds:KeyInfo> + </md:KeyDescriptor> + <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat> + <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://infra-pre-id.bayernportal.de/idp/profile/SAML2/POST/SSO"/> + <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://infra-pre-id.bayernportal.de/idp/profile/SAML2/Redirect/SSO"/> + </md:IDPSSODescriptor> + </md:EntityDescriptor> +</md:EntitiesDescriptor> \ No newline at end of file diff --git a/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumGrpcServiceTest.java b/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumGrpcServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..968c13b46cacfbf6528a6f3ac6d750e0154c08f5 --- /dev/null +++ b/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumGrpcServiceTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2024. + * 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.nachrichten.antragraum; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.util.UUID; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; + +import de.ozgcloud.nachrichten.postfach.PostfachNachricht; +import de.ozgcloud.nachrichten.postfach.PostfachNachrichtTestFactory; +import de.ozgcloud.vorgang.grpc.command.GrpcCommand; +import io.grpc.stub.StreamObserver; + +class AntragraumGrpcServiceTest { + @Spy + @InjectMocks + private AntragraumGrpcService antragsraumGrpcService; + @Mock + private AntragraumService antragraumService; + @Mock + private AntragraumNachrichtMapper mapper; + + @Nested + class TestFindRueckfragen { + + @Nested + class TestFindRueckfragenGrpc { + @Mock + private StreamObserver<GrpcFindRueckfragenResponse> streamObserver; + + @BeforeEach + void setup() { + when(antragraumService.findRueckfragen(any())).thenReturn(Stream.of(PostfachNachrichtTestFactory.create())); + when(mapper.toGrpc(any(PostfachNachricht.class))).thenReturn(GrpcRueckfrage.getDefaultInstance()); + } + + @Test + void shouldCallMapper() { + antragsraumGrpcService.findRueckfragen(GrpcFindRueckfrageRequestTestFactory.create(), streamObserver); + + verify(mapper).toGrpc(any(PostfachNachricht.class)); + } + + @Test + void shouldCallOnNext() { + antragsraumGrpcService.findRueckfragen(GrpcFindRueckfrageRequestTestFactory.create(), streamObserver); + + verify(streamObserver).onNext(any(GrpcFindRueckfragenResponse.class)); + } + + @Test + void shouldCallOnCompleted() { + antragsraumGrpcService.findRueckfragen(GrpcFindRueckfrageRequestTestFactory.create(), streamObserver); + + verify(streamObserver).onCompleted(); + } + } + } + + @Nested + class TestSendAnswer { + @Mock + private StreamObserver<GrpcCommand> streamObserver; + + @BeforeEach + void setup() { + when(antragraumService.sendRueckfrageAnswer(anyString(), anyString(), any(PostfachNachricht.class))) + .thenReturn(UUID.randomUUID().toString()); + when(mapper.toPostfachNachricht(any(GrpcRueckfrageAnswer.class))).thenReturn(PostfachNachrichtTestFactory.create()); + } + + @Test + void shouldCallMapper() { + antragsraumGrpcService.sendRueckfrageAnswer(GrpcSendRueckfrageAnswerRequestTestFactory.create(), streamObserver); + + verify(mapper).toPostfachNachricht(any(GrpcRueckfrageAnswer.class)); + } + + @Test + void shouldCallAntragraumService() { + antragsraumGrpcService.sendRueckfrageAnswer(GrpcSendRueckfrageAnswerRequestTestFactory.create(), streamObserver); + + verify(antragraumService).sendRueckfrageAnswer(anyString(), anyString(), any(PostfachNachricht.class)); + } + + @Test + void shouldCallOnNext() { + antragsraumGrpcService.sendRueckfrageAnswer(GrpcSendRueckfrageAnswerRequestTestFactory.create(), streamObserver); + + verify(streamObserver).onNext(any(GrpcCommand.class)); + } + + @Test + void shouldCallOnCompleted() { + antragsraumGrpcService.sendRueckfrageAnswer(GrpcSendRueckfrageAnswerRequestTestFactory.create(), streamObserver); + + verify(streamObserver).onCompleted(); + } + + } +} \ No newline at end of file diff --git a/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumNachrichtMapperTest.java b/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumNachrichtMapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5ff78a92ed2d37b75e8c380ea47c87d41a50790c --- /dev/null +++ b/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumNachrichtMapperTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2024. + * 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.nachrichten.antragraum; + +import static org.assertj.core.api.Assertions.*; + +import java.time.format.DateTimeFormatter; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; + +import de.ozgcloud.nachrichten.postfach.PostfachNachricht; +import de.ozgcloud.nachrichten.postfach.PostfachNachrichtTestFactory; +import de.ozgcloud.nachrichten.postfach.osi.MessageTestFactory; + +class AntragraumNachrichtMapperTest { + + AntragraumNachrichtMapper mapper = Mappers.getMapper(AntragraumNachrichtMapper.class); + + @Nested + class TestMapRueckfrage { + + @Test + void shouldMapVorgangId() { + var result = map(); + + assertThat(result.getVorgangId()).isEqualTo(MessageTestFactory.VORGANG_ID); + } + + @Test + void shouldMapId() { + var result = map(); + + assertThat(result.getId()).isEqualTo(PostfachNachrichtTestFactory.ID); + } + + @Test + void shouldMapVorgangName() { + var result = map(); + + assertThat(result.getVorgangName()).isEqualTo(MessageTestFactory.SUBJECT); + } + + @Test + void shouldMapStatus() { + var result = map(); + + assertThat(result.getStatus()).isEqualTo(AntragraumNachrichtMapper.DEFAULT_STATUS); + } + + @Test + void shouldMapSentAt() { + var result = map(); + + assertThat(result.getSentAt()).isEqualTo(PostfachNachrichtTestFactory.SENT_AT.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + } + + @Test + void shouldMapText() { + var result = map(); + + assertThat(result.getText()).isEqualTo(PostfachNachrichtTestFactory.MAIL_BODY); + } + + @Test + void shouldMapAttachments() { + var result = map(); + + assertThat(result.getAttachmentFileIdCount()).isEqualTo(1); + assertThat(result.getAttachmentFileId(0)).isEqualTo(PostfachNachrichtTestFactory.ATTACHMENTS.get(0)); + } + + private GrpcRueckfrage map() { + return mapper.toGrpc(PostfachNachrichtTestFactory.create()); + } + + } + + @Nested + class TestMapAnswerToPostfachNachricht { + @Test + void shouldMapText() { + var result = map(); + + assertThat(result.getMailBody()).isEqualTo(GrpcRueckfrageAnswerTestFactory.TEXT); + } + + @Test + void shouldMapMessageId() { + var result = map(); + + assertThat(result.getMessageId()).isEqualTo(GrpcRueckfrageAnswerTestFactory.RUECKFRAGE_ID); + } + + @Test + void shouldMapAttachmentIds() { + var result = map(); + + assertThat(result.getAttachments()).isEqualTo(GrpcRueckfrageAnswerTestFactory.ATTACHMENT_ID_LIST); + } + + private PostfachNachricht map() { + return mapper.toPostfachNachricht(GrpcRueckfrageAnswerTestFactory.create()); + } + } +} \ No newline at end of file diff --git a/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumServiceTest.java b/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumServiceTest.java index fb81269bda54f4e645399c75c7cf666bfaeaa11c..1b8cfe1ed00be12d1d23043ef422878b7283f254 100644 --- a/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumServiceTest.java +++ b/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumServiceTest.java @@ -1,19 +1,25 @@ package de.ozgcloud.nachrichten.antragraum; import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import java.util.UUID; + 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.opensaml.saml.saml2.core.Response; import de.ozgcloud.nachrichten.NachrichtenManagerProperties; -import de.ozgcloud.nachrichten.antragraum.AntragraumProperties; -import de.ozgcloud.nachrichten.antragraum.AntragraumService; +import de.ozgcloud.nachrichten.postfach.PersistPostfachNachrichtService; +import de.ozgcloud.nachrichten.postfach.PostfachNachricht; +import de.ozgcloud.nachrichten.postfach.PostfachNachrichtTestFactory; class AntragraumServiceTest { - + @Spy @InjectMocks private AntragraumService service; @@ -21,6 +27,14 @@ class AntragraumServiceTest { private AntragraumProperties properties; @Mock private NachrichtenManagerProperties nachrichtenManagerProperties; + @Mock + private PersistPostfachNachrichtService postfachNachrichtService; + @Mock + private Saml2Verifier verifier; + @Mock + private Saml2Parser parser; + @Mock + private Saml2Decrypter decrypter; @Nested class TestGetAntragsraumUrl { @@ -38,4 +52,68 @@ class AntragraumServiceTest { } + @Nested + class TestFindRueckfragen { + static final String SAML_TOKEN = "TOKEN"; + + @Test + void shouldCallVerify() { + service.findRueckfragen(SAML_TOKEN); + + verify(service).verifyToken(anyString()); + } + + @Test + void shouldCallVerifier() { + service.findRueckfragen(SAML_TOKEN); + + verify(verifier).verify(anyString()); + } + + @Test + void shouldCallDecrypt() { + when(parser.parse(anyString())).thenReturn(mock(Response.class)); + + service.findRueckfragen(SAML_TOKEN); + + verify(decrypter).decryptPostfachId(any(Response.class)); + } + + @Test + void shouldCallPostfachService() { + when(decrypter.decryptPostfachId(any())).thenReturn(GrpcFindRueckfrageRequestTestFactory.POSTFACH_ID); + + service.findRueckfragen(SAML_TOKEN); + + verify(postfachNachrichtService).findRueckfragen(anyString()); + } + } + + @Nested + class TestSendRueckfragenAnswers { + static final String SAML_TOKEN = "TOKEN"; + static final String RUECKFRAGE_ID = UUID.randomUUID().toString(); + static final PostfachNachricht NACHRICHT = PostfachNachrichtTestFactory.create(); + + @Test + void shouldCallVerify() { + service.sendRueckfrageAnswer(SAML_TOKEN, RUECKFRAGE_ID, NACHRICHT); + + verify(service).verifyToken(anyString()); + } + + @Test + void shouldCallVerifier() { + service.findRueckfragen(SAML_TOKEN); + + verify(verifier).verify(anyString()); + } + + @Test + void shouldCallPostfachService() { + service.sendRueckfrageAnswer(SAML_TOKEN, RUECKFRAGE_ID, NACHRICHT); + + verify(postfachNachrichtService).persistAnswer(anyString(), any(PostfachNachricht.class)); + } + } } \ No newline at end of file diff --git a/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/BayernIdSamlConfigurationTest.java b/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/BayernIdSamlConfigurationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..045f5caf46429366373499cfd279563895d3d9ad --- /dev/null +++ b/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/BayernIdSamlConfigurationTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024. + * 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.nachrichten.antragraum; + +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.springframework.core.io.InputStreamResource; + +import de.ozgcloud.common.test.TestUtils; + +class BayernIdSamlConfigurationTest { + @Mock + private AntragraumProperties properties; + @InjectMocks + private BayernIdSamlConfiguration bayernIdSamlConfiguration; + + @Nested + class TestCreationOfParserPool { + @BeforeEach + void setup() throws Exception { + bayernIdSamlConfiguration.initOpenSAML(); + } + + @Test + void shouldCreateParserPool() { + assertThat(bayernIdSamlConfiguration.getParserPool()).isNotNull(); + } + } + + @Nested + class TestCreatingTrustEngine { + @BeforeEach + void setup() throws Exception { + when(properties.getMetadataUri()).thenReturn(new InputStreamResource(TestUtils.loadFile("bayernid-idp-infra.xml"))); + when(properties.getEntityId()).thenReturn("https://antragsraum.ozgcloud.de/"); + bayernIdSamlConfiguration.initOpenSAML(); + } + + @Test + void shouldCreateTrustEngine() { + assertThat(bayernIdSamlConfiguration.getTrustEngine()).isNotNull(); + } + } + + @Nested + class TestLoadingVerficationData { + @Test + void shouldCreateVerificationCriteria() { + when(properties.getEntityId()).thenReturn("https://antragsraum.ozgcloud.de/"); + bayernIdSamlConfiguration.initOpenSAML(); + + assertThat(bayernIdSamlConfiguration.getVerificationCriteria()).isNotNull(); + } + + @Test + void shouldLoadVerificationCert() throws Exception { + when(properties.getMetadataUri()).thenReturn(new InputStreamResource(TestUtils.loadFile("bayernid-idp-infra.xml"))); + bayernIdSamlConfiguration.initOpenSAML(); + + assertThat(bayernIdSamlConfiguration.getCertificatesFromMetadata()).isNotNull(); + } + } +} \ No newline at end of file diff --git a/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/GrpcFindRueckfrageRequestTestFactory.java b/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/GrpcFindRueckfrageRequestTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..9795428ab0d6020b63251d765af3e25e0f89cfa4 --- /dev/null +++ b/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/GrpcFindRueckfrageRequestTestFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024. + * 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.nachrichten.antragraum; + +import de.ozgcloud.common.test.TestUtils; + +import java.util.UUID; + +public class GrpcFindRueckfrageRequestTestFactory { + static final String POSTFACH_ID = UUID.randomUUID().toString(); + static final String SAML_TOKEN = TestUtils.loadTextFile("SamlResponse.xml"); + + static GrpcFindRueckfragenRequest create() { + return createBuilder().build(); + } + + static GrpcFindRueckfragenRequest.Builder createBuilder() { + return GrpcFindRueckfragenRequest.newBuilder() + .setPostfachId(POSTFACH_ID) + .setSamlToken(SAML_TOKEN); + } +} diff --git a/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/GrpcRueckfrageAnswerTestFactory.java b/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/GrpcRueckfrageAnswerTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..6c0fb28f113bc5e179aff00d19a9e5cd5a7a6cc9 --- /dev/null +++ b/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/GrpcRueckfrageAnswerTestFactory.java @@ -0,0 +1,23 @@ +package de.ozgcloud.nachrichten.antragraum; + +import java.util.List; +import java.util.UUID; + +import com.thedeanda.lorem.LoremIpsum; + +public class GrpcRueckfrageAnswerTestFactory { + static final String RUECKFRAGE_ID = UUID.randomUUID().toString(); + static final String TEXT = LoremIpsum.getInstance().getParagraphs(2, 4); + static final List<String> ATTACHMENT_ID_LIST = List.of(UUID.randomUUID().toString()); + + static GrpcRueckfrageAnswer create() { + return createBuilder().build(); + } + + static GrpcRueckfrageAnswer.Builder createBuilder() { + return GrpcRueckfrageAnswer.newBuilder() + .setRueckfrageId(RUECKFRAGE_ID) + .setAnswerText(TEXT) + .addAllAttachmentFileId(ATTACHMENT_ID_LIST); + } +} diff --git a/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/GrpcSendRueckfrageAnswerRequestTestFactory.java b/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/GrpcSendRueckfrageAnswerRequestTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..f1fa9bcfd3877d5bdc17b611f06d373feb565205 --- /dev/null +++ b/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/GrpcSendRueckfrageAnswerRequestTestFactory.java @@ -0,0 +1,19 @@ +package de.ozgcloud.nachrichten.antragraum; + +import de.ozgcloud.common.test.TestUtils; + +public class GrpcSendRueckfrageAnswerRequestTestFactory { + static final String SAML_TOKEN = TestUtils.loadTextFile("SamlResponse.xml"); + static final GrpcRueckfrageAnswer ANSWER = GrpcRueckfrageAnswerTestFactory.create(); + + static GrpcSendRueckfrageAnswerRequest create() { + return createBuilder().build(); + } + + static GrpcSendRueckfrageAnswerRequest.Builder createBuilder() { + return GrpcSendRueckfrageAnswerRequest.newBuilder() + .setAnswer(ANSWER) + .setSamlToken(SAML_TOKEN); + } + +} diff --git a/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2DecrypterTest.java b/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2DecrypterTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1dd94cea3e8ef54e5b7d62ac936921ed583d748b --- /dev/null +++ b/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2DecrypterTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2024. + * 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.nachrichten.antragraum; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.opensaml.saml.saml2.core.Attribute; +import org.opensaml.saml.saml2.core.AttributeStatement; +import org.opensaml.saml.saml2.core.Response; +import org.springframework.core.io.InputStreamResource; +import org.springframework.test.util.ReflectionTestUtils; + +import de.ozgcloud.common.test.TestUtils; + +@Disabled("Passende certificat und key file muessen local vorhanden sein") +class Saml2DecrypterTest { + private Response samlResponse; + + @Mock + private AntragraumProperties properties; + + private BayernIdSamlConfiguration configuration; + + private Saml2Decrypter decrypter; + + @BeforeEach + void setup() throws Exception { + when(properties.getDecryptionCertificate()).thenReturn(new InputStreamResource(TestUtils.loadFile("bayernid-test-enc.crt"))); + when(properties.getDecryptionPrivateKey()).thenReturn(new InputStreamResource(TestUtils.loadFile("bayernid-test-enc.key"))); + + configuration = new BayernIdSamlConfiguration(); + ReflectionTestUtils.setField(configuration, "antragraumProperties", properties); + configuration.initOpenSAML(); + + var samlResponseString = TestUtils.loadTextFile("SamlResponse.xml"); + var parser = new Saml2Parser(configuration); + samlResponse = parser.parse(samlResponseString); + + decrypter = new Saml2Decrypter(configuration); + decrypter.init(); + } + + @Test + void shouldDecrypt() { + assertThat(samlResponse.getAssertions()).isEmpty(); + + decrypter.decryptResponseElements(samlResponse); + + assertThat(samlResponse.getAssertions()).isNotEmpty(); + } + + @Test + void shouldHaveSubject() { + decrypter.decryptResponseElements(samlResponse); + var samlAssertion = samlResponse.getAssertions().get(0); + + assertThat(samlAssertion.getSubject()).isNotNull(); + } + + @Test + void shouldHaveAuthnStatements() { + decrypter.decryptResponseElements(samlResponse); + var samlAssertion = samlResponse.getAssertions().get(0); + var authnStatements = samlAssertion.getAuthnStatements(); + + assertThat(authnStatements).isNotNull(); + } + + @Test + void shouldHaveStatements() { + decrypter.decryptResponseElements(samlResponse); + var samlAssertion = samlResponse.getAssertions().get(0); + var statements = samlAssertion.getStatements(); + + assertThat(statements).isNotNull(); + } + + @Test + void shouldHaveAttributes() { + decrypter.decryptResponseElements(samlResponse); + var samlAssertion = samlResponse.getAssertions().get(0); + var statements = (AttributeStatement) samlAssertion.getStatements().get(1); + var attributes = statements.getAttributes(); + assertThat(attributes).hasSize(7); + } + + @Test + void shouldHavePostfachId() { + decrypter.decryptResponseElements(samlResponse); + var samlAssertion = samlResponse.getAssertions().get(0); + var statements = (AttributeStatement) samlAssertion.getStatements().get(1); + var attributes = statements.getAttributes(); + var postfachIdOptional = attributes.stream().filter(attribute -> "legacyPostkorbHandle".equals(attribute.getFriendlyName())).findFirst(); + assertThat(postfachIdOptional).map(Attribute::getAttributeValues).isNotNull(); + } + + @Test + void shouldGetPostfachId() { + var postfachId = decrypter.decryptPostfachId(samlResponse); + + assertThat(postfachId).isEqualTo("28721c6f-b78f-4d5c-a048-19fd2fc429d2"); + } +} \ No newline at end of file diff --git a/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2ParserTest.java b/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2ParserTest.java new file mode 100644 index 0000000000000000000000000000000000000000..afcca2fbe9c930eebf72c015495ac38f3a3ed2bd --- /dev/null +++ b/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2ParserTest.java @@ -0,0 +1,72 @@ + +package de.ozgcloud.nachrichten.antragraum; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.opensaml.saml.saml2.core.Response; +import org.springframework.test.util.ReflectionTestUtils; + +import de.ozgcloud.common.test.TestUtils; + +class Saml2ParserTest { + private BayernIdSamlConfiguration configuration; + + @BeforeEach + void setup() { + var properties = mock(AntragraumProperties.class); + configuration = new BayernIdSamlConfiguration(); + ReflectionTestUtils.setField(configuration, "antragraumProperties", properties); + configuration.initOpenSAML(); + } + + @Test + void shouldInit() { + var parser = new Saml2Parser(configuration); + + assertThat(parser).isNotNull(); + } + + @Test + void shouldParseSamlToken() throws Exception { + var response = getResponse(); + assertThat(response).isNotNull(); + } + + @Test + void shouldHaveAssertions() throws Exception { + var response = getResponse(); + assertThat(response.getAssertions()).isNotNull(); + } + + @Test + void shouldHaveEncryptedAssertions() throws Exception { + var response = getResponse(); + assertThat(response.getEncryptedAssertions()).isNotNull(); + } + + @Test + void shouldHaveIssuer() throws Exception { + var response = getResponse(); + assertThat(response.getIssuer().getValue()).isEqualTo("https://infra-pre-id.bayernportal.de/idp"); + } + + @Test + void shouldGetXMLObject() throws Exception { + var parser = new Saml2Parser(configuration); + + try (var tokenStream = TestUtils.loadFile("SamlResponse.xml")) { + assertThat(parser.xmlObject(tokenStream)).isNotNull(); + } + } + + private Response getResponse() throws Exception { + var token = TestUtils.loadTextFile("SamlResponse.xml"); + var parser = new Saml2Parser(configuration); + + return parser.parse(token); + } + +} \ No newline at end of file diff --git a/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2VerifierTest.java b/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2VerifierTest.java new file mode 100644 index 0000000000000000000000000000000000000000..03dbcb5983d097f1516bdea5bc6f284345d43b15 --- /dev/null +++ b/nachrichten-manager/src/test/java/de/ozgcloud/nachrichten/antragraum/Saml2VerifierTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024. + * 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.nachrichten.antragraum; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.springframework.core.io.InputStreamResource; +import org.springframework.test.util.ReflectionTestUtils; + +import de.ozgcloud.common.test.TestUtils; + +class Saml2VerifierTest { + private String samlResponse; + + private Saml2Verifier verifier; + + @Mock + private AntragraumProperties properties; + + private BayernIdSamlConfiguration configuration; + + @BeforeEach + void setup() throws Exception { + when(properties.getMetadataUri()).thenReturn(new InputStreamResource(TestUtils.loadFile("bayernid-idp-infra.xml"))); + when(properties.getEntityId()).thenReturn("https://antragsraum.ozgcloud.de"); + samlResponse = TestUtils.loadTextFile("SamlResponse.xml"); + + configuration = new BayernIdSamlConfiguration(); + ReflectionTestUtils.setField(configuration, "antragraumProperties", properties); + configuration.initOpenSAML(); + + var parser = new Saml2Parser(configuration); + + verifier = new Saml2Verifier(parser, configuration); + verifier.init(); + } + + @Test + void shouldGetVerificationError() { + var res = verifier.verify(samlResponse); + + assertThat(res).isNotEmpty(); + } +} \ No newline at end of file diff --git a/nachrichten-manager/src/test/resources/SamlResponse.xml b/nachrichten-manager/src/test/resources/SamlResponse.xml new file mode 100644 index 0000000000000000000000000000000000000000..d28738585aa627bc0bd4e6b7288ff3346ea4029e --- /dev/null +++ b/nachrichten-manager/src/test/resources/SamlResponse.xml @@ -0,0 +1,128 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (c) 2024. + ~ 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. + --> + +<saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" + Destination="https://deep-touching-condor.ngrok-free.app/login/saml2/sso/bayernid" + ID="_d75103771f4e3869ca4bf743efb51320" InResponseTo="ARQf371368-b6eb-4708-b90d-e8a9c5fc0ffd" + IssueInstant="2024-02-07T10:27:18.456Z" Version="2.0"> + <saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://infra-pre-id.bayernportal.de/idp + </saml2:Issuer> + <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> + <ds:SignedInfo> + <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/> + <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/> + <ds:Reference URI="#_d75103771f4e3869ca4bf743efb51320"> + <ds:Transforms> + <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/> + <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/> + </ds:Transforms> + <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/> + <ds:DigestValue>y8O2/uKwgap3hb7Ym/sn+v0e3l+w0Z+wIFe11xXkSHU=</ds:DigestValue> + </ds:Reference> + </ds:SignedInfo> + <ds:SignatureValue> + G4fZCUS7Z+7PwDF7J+ZwXssM+iHBgxt34Uf4U3PWrbaYROrCZFD3hlVwCj35Z7RlQkDBi4q1m9RW6XGGVx2vpUDjT9dTkfbF7tB9PXa6l4mq3RyxuRELMwRpcbnamfe02qwtp0N7n9+gdjTVPb2xTMhp7FVG3OZ46OKwwJIm6jNLE+zVbKkNmxnv8XqGK+FgDS82CCG6Zi8nIZZkR80vHuRnSwrpStiInWSURoIYvG8nQfJ6u6IxbtMkDPtLrQHP6th9NMEyODe4RrjNwH8ERkbBl+rvtz406y3hngOW4uxNSTdQGOWj68t7LSn78S+Zc+5g/8up8gRIY6FWB5QxTl+GINIskcoWEfpyQcY932Jh9jGKFRBj2bcP0xALOeP+LTAz1O3hY0EZD0HpjILNhjp4/4Ki6SSeoVrp4UdEZGPpfFAMXdA9unjQGf5DqT3los5mH+KgkpAQoIU0725tIJuGojigXDIKgbNftB1oXjepcqcWvdnbRZlE9Kk4iU2YcVKGxHtEGi03+Qr2M37SqnooXw94Q0LxOQHU0jaOuw+nA8JbcvbpmHVbh7Qyg6OfrI/g+1pwhaQWrL6zEDDlgF3Fj6QxZGhMviCf43WJd8nPPwLIp0dFxXmbX5yBnpAPC4txJkf4idH8gze054O0Zf9G35vFH8oxELrA+d3qbPY= + </ds:SignatureValue> + <ds:KeyInfo> + <ds:X509Data> + <ds:X509Certificate>MIIFbzCCA1egAwIBAgIJAPdFXXarkBN2MA0GCSqGSIb3DQEBCwUAME4xCzAJBgNVBAYTAkRFMQ8w + DQYDVQQIDAZCYXllcm4xETAPBgNVBAcMCE11ZW5jaGVuMQ0wCwYDVQQKDARBS0RCMQwwCgYDVQQL + DANJRE0wHhcNMjAxMDI3MTMxODQxWhcNMjUxMDI2MTMxODQxWjBOMQswCQYDVQQGEwJERTEPMA0G + A1UECAwGQmF5ZXJuMREwDwYDVQQHDAhNdWVuY2hlbjENMAsGA1UECgwEQUtEQjEMMAoGA1UECwwD + SURNMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzDtWAEdC3J9FD+ti1exRhN1lzNgK + WqO2gQNdJvlt7KGHA2VGGO7tqRogTuoqi/ydtiHJ8+lhp4kcWqyfv7i9HXOncvcsRRmRdZjUY2Iu + i6ozJqD5LVm/vP5YfdP7vQPdbqyyfpoJhf3mbMEtdNDdGRnGIPUfDn+CFbo37f9tPwMgf3jgh4gx + aujtLIhhr9gevVTEeZAFu9EvzLNd3kEtRb7MuXqIOdu1rW8HlGYFwwVLqEyBn8XG0QAIfhMmGjFM + G7z+Kco2quwOmmZVzWQfeH/3AlN2KbcPt7j+pl+6Bew2AAivP7O+95YKORqQjTu3rPWMF4txPId3 + 7MSjoytwBRyd5EACTvhQBOGrDFKQUOx6fTtRc8+7XGVz8MdQaZQWQXXh1ByU783twNdnRSrSVIyL + djiy1uCbjvsSAtbzGBygPIvDo3skCNLNFXsChtHIfFFDK20KPGb0ghEDf2q3hDbFG3ZDGGynZmJc + ZKuZhJqodJ/++sAXADyTJNAPVYDjKCF4ypELp2Eu/p1gaQPJEb74L/ZFZVOEJFyXIiaqB9J+fcn/ + biqHHOmcCi8n9aIiNt1fatr1Z4lQRWoGtKaGU0+bzUSH4Bgs2EG4u1CI2MKDWqK2aEsHrtu8tbS9 + LrUmDVKtaEUOeul8xWVa036vp/YUIdiJNZSxZG4iTmSOATECAwEAAaNQME4wHQYDVR0OBBYEFFYe + ltslkaolOmcINXQeSe7nURwpMB8GA1UdIwQYMBaAFFYeltslkaolOmcINXQeSe7nURwpMAwGA1Ud + EwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAKqAlXoO41SAiycYUOrR90pfwTCysmbtHF5RWSCM + jF2aCG8URJ7bXwC0lBH8E5zCetFZwdqZziQtxzRkIOfhS5uWbH0RDhwuxZG+5RTPyaHPAZI6e5xH + Du8vHl/VbC3lnL/6K8l+Purr/yo8qkJqrPgThZRL9jBQyYRhDSsJUyIw5zcKKUQC/JWtMQAQcopb + jekCs6xDT1HqIN90Sc/gOfYjNo0dGMNmro9mxcw82Iow18KNVdtEexfD+/6x4NPD61pzuQEe09TR + +Cv3XyzBoGQ/2arijcPnGvth79ffVFtRSf3fSs7wEKV9g3mEWXFDtPBhDj6K0kKU/kJfEZixkXl9 + 2MY+bmugrtTIrazjtfrgMglIAHu9XCYWd/gef0J+PNfHsxgbTEr3XSC+5/xoFKPQSw3PgV8lkUDq + 4mJUKy/q4YmA37XQxourFR5pWvF03YACdtq6zPjtVeI7Cvkte6k0YW5S3cx9RmPv6YZhlaZ5ERpW + Niv6IjokLsvNeemf2PApjO7Q2EDBIoHBYH31wwJSsyRDrSVmbaqLFI15fLXeh2A4YbaBDZdGvDiL + OAk+dG1wdZ2aGw/uNBzMtc8VeKqI1HPcqIluBA3uUPpyLLA+9hDPf6Pp4j0gkXxBikz+/h22bFxE + 1HmDiOSkEn+2NmOHuEFeA+D8jsCAL5VJ3emK + </ds:X509Certificate> + </ds:X509Data> + </ds:KeyInfo> + </ds:Signature> + <saml2p:Status xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"> + <saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/> + </saml2p:Status> + <saml2:EncryptedAssertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"> + <xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Id="_baed1174200b81b1bff3856cb4e6365c" + Type="http://www.w3.org/2001/04/xmlenc#Element"> + <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc" + xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"/> + <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> + <xenc:EncryptedKey Id="_5a164760d15a61d269e1f7fdd9872a10" Recipient="https://antragsraum.ozgcloud.de/" + xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"> + <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p" + xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"> + <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" + xmlns:ds="http://www.w3.org/2000/09/xmldsig#"/> + </xenc:EncryptionMethod> + <ds:KeyInfo> + <ds:X509Data> + <ds:X509Certificate> + MIIDsTCCApmgAwIBAgIUdw/27be5+2vj+MhGtoJjDsMsdDEwDQYJKoZIhvcNAQELBQAwaDELMAkG + A1UEBhMCREUxDzANBgNVBAgMBkJheWVybjERMA8GA1UEBwwITXVlbmNoZW4xDzANBgNVBAoMBm1n + bSB0cDEkMCIGCSqGSIb3DQEJARYVamVucy5yZWVzZUBtZ20tdHAuY29tMB4XDTI0MDExNjEyMjI0 + OVoXDTI1MDExNTEyMjI0OVowaDELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJheWVybjERMA8GA1UE + BwwITXVlbmNoZW4xDzANBgNVBAoMBm1nbSB0cDEkMCIGCSqGSIb3DQEJARYVamVucy5yZWVzZUBt + Z20tdHAuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/HBBWBDSrEgdwXkSy15V + 00EaVTyLgc4vh/JcDiGIYZSqmcMwBd+B1u36xbdBf/duEtCUymMNP48OMjgFZtR6xn0meuR4NR6Y + kn9mYGdU/GhldGuGv9XLAEAkVuTlo0H1QYyBS/6JwKQoSsHDkJ3YwDwKcyOt7QtpSadRZjQEN3gD + vWoRYjgXTxj2I1ovllmi0zOHsFi5PBIuiPWUdJvBrHxpD/XVS9R/qzJpHPu3bjQ6UVRmhiZCUF7H + 5F/PQNwk+qXvjV0ooBeSWWO5hywhk4OP4QEgbYMOSo20YukYX8TJEsum1pwIcQrw7kW4GyKaAycy + Rsa1fbM3tEkj+TiBKwIDAQABo1MwUTAdBgNVHQ4EFgQUfDL/6R33SJodsONCvxKy96AtU18wHwYD + VR0jBBgwFoAUfDL/6R33SJodsONCvxKy96AtU18wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B + AQsFAAOCAQEA+PCnvSwKU+bArTCIg5lfrwONbzKkjvPUymDNYX3oj1wVEN75hNf0RD7Rr0//ZYT3 + Rt0G193gjDcH1gbGIYhMLeGGkxEous2l3O+pRIQRR+hprjr6HzF8IphaJy1RbDwyGsXyLcyOylPL + 4cX9IjUdhklHiLZusBq95LSyw7hsCOAL2+vn816O7yv+28EWXXbnP2XEUjW36nxcZvR6oTJUplXy + HRuuJJTsOxGRNuXA3UVgNbkdm1HnoSGpnsGdUKsUFoEmEJkcSdQRwxeH21WzYGOZmKMcvx2gObaS + P8tafWh5z4Jx+Z7z5WP72Jt44/lnVjaV8aGo0KHXwgqQOtYftQ== + </ds:X509Certificate> + </ds:X509Data> + </ds:KeyInfo> + <xenc:CipherData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"> + <xenc:CipherValue> + ffr9pG/yL4QGQ4o1z/t6HH5XRG8pMHHjzlVTq6uC4eRpVvaNMz8XpUXqNAFGiB0Xbpkm++qOhGsOuz5Wffq5Qo78fMBfU95L1Lk9cVH1pUFfYyz5GV1LqlhStAZrCGHUdv5d0O7JLKgbi45JxxTc7ErAwPlOMqKLs95ZJuhl8Fp9XcYrdzW9IjuwmkB/HyPyjBWV066gaCMLImeBdCzBZc0pxuvH9jq8eX7h1B1eCd5F1LIoj35YDeU3PA/P/E6tLBxdGLFws+nYqNU3B5R2FPPoW+LP9zM7Q+SR20ti1Uh6TEMha05sJjWXFJU78PpJAtEl978ifqqO/23lYXYCrA== + </xenc:CipherValue> + </xenc:CipherData> + </xenc:EncryptedKey> + </ds:KeyInfo> + <xenc:CipherData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"> + <xenc:CipherValue> + YvHcO5br8vWMo/vreVcU8MQylgUmU6vxmvEoUJ07+8kSV0prHD46TtuXHXsLnsYKQMs+SM6bnTWMooNxIy8b0/BvIG4lg/l3m+SwoP56+lOMx26mTEwz9dJT+4BTFH25i3GbWQd86AbD4wT2X9juExE1t6uq1bhACqgeA1erhy/fjTS+1fKKbOTdC0DF35idejruc/YFyV/BNRMHncfj5BnFQFUmXT0b2Fg+WyUAIzflqwXmUhdzmQNl2uqZDL2Vaw4vQp1a9S0ePD3t+QCSGtCYcmqvg+FhooS1KfhqITNinzi07KaQNW6sY0uXpnq3/BGWxXrU58rmU24AQ5ozAgwPfpmOnZhyUURSikvKmvcpD8DPy8jMOxi+rd/tYlxokjPjFSV8ixh0fGZl+7mDAt/YvxBOSSln3nUEMVoKMNgUT3ax6cyUTBXOxt1EMLYuAcHRIBpF2R9MYasmaaR1RntlkKPiomaPdGqIgh1HtkmuwkS6npAH68d0ObD/SpRqu+vPuu++RxHPlbIjShbJWYUJIqPJlhtG7/p9VkC3514t/H1cgxALKstC95OXNH8S6b4m+atl1Wf4PELfHD7lT/QgYHwZk2/xhoWduKc18nauB4kc3v3FZ8nIO3iCgBkXT8qq+/1wJgOnVqAEFWs8w8AC99gHoTgm04q+JUw9XramIXEOdJ10/2Y8HW97XCt53KG+kbaJZe7DISx+2eh79ed3BxEzwgqcXlsrIVcezXWJAl2myFxUggn3c/N1ckgOWFwENOSDFXS6WAtSo/8dlXYvgZTYyKVvVO7l5oq5al2ESrFkHqB3WKu0q9LaWEKw7jMy6FnOawSOjJ2t5o43hfqs9+Wg3J7bgM8YjDuuNZIJ55SO7NIdeeFOkkgGMgcmF0svsxGKbHhdkvqa0t0BLtase7dNIgfWY5laXU7xBn+HEhrz9ZR1xnk8gK28KZwR1wO72rpcV9igsbek5cxEJ/hAk9NSHw7VXAgXZJ6VKZapJfoOqanE50FfK8z0TaoKBUbc1kfOAFnB0n9nUKpUXyyqfiu12w4n3lAs3tBB2S4w3UW5Mef1ZGdF8dqs/T0GUs819vrwYENVUM8u1usP44MnMdTw2i3eo6/VYrCOo1tsa31t46BbLEnercEdGDgpOOBjuda+C3G4NtlDEFzM8rN2FspLS5wphCvKiTB867Q9nrsXOQdDTiwXs2wOpVq1utASdV82Yam1ucVVfriDWlLlhFsFpZnUmwG4zwCPqah2t8inSwx9jramTbJnXpihRw75mFGFC5QAalnV/EAtZ58ecIz4PVTRq4zP7OKtwnN+qIOMm//8AOl6e5H/At7qWB5/dVjw8BUdIxaUF+e33NdQG6x8IEbMhlGFTGCjv5E0h27D/AbqCBU6PKrkGZowZHtKNtz7nITysa0hpyefM0rQkFHwf4NmR8uSHZ9YoT8VLStTtrF6daHSYlUT45EDXkiSA7NCAX8BIDbZNGxPGNCWR3reZxtQoJ89YPPdQngyqMn7sB1T4sP+euWpnAOOF5VDA2xyM2WtPzuKDZFD0XH5r3AqKf2jl+E1/jSBlhW1Y677WVLTLJm7+lMYYLK6YVaSsKQp10lOdhcNSoNF2iWUwmdCTfkrYIqM6Dz6FiYSrg1TTlOJb7Mg/z54eO6utHzIRFepBTL+pu9qP38Y21NtpUWu//jh0vpniXu1rzGQvuiuoNGIiqYA/dL39cpLB5JIwEVWNsnlHvzRcYQnqg2mhdkFMNZPhZwb3f+BV3GNA/dTziazHEgUN5G0esGl85PlZM7oLrPkXe1mYKh3pVRVHx9bScTQ/KR/Dll0NI1d5ul2SZa80Pmj29voelfMUa5Ml5q4zHzXK6N1E70ujMzWWJB025gLIb3e7aE9eN/5SJegL2ctob8uNQCTSouel6ko+Ykw/pstPL8IYWgsAwrckmzNJHasZi9MB9re639Zf+bDvflpigNboH6Jgzs7us5C2dozH1s5IzGP4XzgeALu8XP/T2coQxhj9xfaAJFrtqWRse9MM9gKIWCBEIzzIPeMa0+S7QAKAd7XRS/oZSDeqUFK2jRkrpbEnUxRErfX6lmHWlaY4CU8XKbSYU13LkuJcyzCm9EN/BU7f5cOwaVwSLMfOQeOx0GwJ5KRF4F30pVQIKfQmXt627TEmrutHr2mIgBLcte+S052JdumcRFMnlNLlmpl/rnOrU0bBeNDtXHQuYCWwfgiyZ47J2OZ+R68v0lA8mSdHFtJ81mbGnYQZn6PnZ/gbKz8uTu5WZE6CQ+n8IY6uvd8B0EYOjaoJK/FTQRjnYe6cBWTQFfmFzH441Ao3vcfKh6co4d5Ec1L9PXQWXidhCv4aJw1G06zgOTIcLCqvE3rbBpYOFUOmKlQbBmaIiKWqG+Xqk34jbQgzfHmcFO96sqh6B64/OzjPdjQupzCTM9hBNwSUABWoOAb9Yn0wtCUU5tRMTXf2DessFKbm5Jygvbh7KrBIQX3SGLQ1sHY+MYVbDpRTyi6/cATCCSWcekyT08mEh52KDKbrTR4CFdu2B7wsIqKxoiW4XiHOOc7DGtgVaCXgoce0nd9YXrtXbIjriMddx7wo/A5xvQbTOyaBuL/j3893Ny6eU10c18p8tZCzdBO1VOWH+R+8TkdvhkA995xzrdvjw7RoSdO7eqdyiOqkHBJGAp+OVSMWf0+/kHKRQf3LTT+CcwYN+7mx+Qz6Uw6Squ6/9JsxsEfIQ3VRsa84hUjcmmKf4QTpzy54RIteqPZc0kNQPobpKmvyWUUjldV0jgYi98XfErrdLQF3gyU/TG0kOsEPjuskIHoLEEka+nGXOjbtxj7HZe7onJ54kIluMkHeKfLWOMoZ2Qx9tqYMoGtbvLf6RrKh7METcaenirAS0waZfYWbIKS/XQQnlG0kWmK2VbUtKpKwAkjlEFEkYo8NtYRTqB1gYp7iyOhOcYT8Hdd6y+276P+sF+FuuXBAd1WJ8GWosOGcBOSMlvbxOyuaa58K10CUcBu3UNYMXfbifsNunrPPaJClyHK279gtweail/SVEc9XoyUh2uP/sL2eU3edeOBnscRj+0TXCkr+8auzW2BlMHrzDniQBXnFQelm94yDB4Ue3BfqKlkPmfC9QO+X/Jsjl6RtIQ6kqvwz9ZQ9uzFi4ZS5xd0I/IBp621GEXFkqIZhx13ohoiGa1KlhXS1AkDSRptzy7DlkW/ZDR3rh4WKGYsIF7/09WGtSPzicF8E3Mc9BS1OBksTuDuC1ayo6o0qxgEED0s3tNKE4GMQ5Kr9glfvoTbkMtgJst2R+Tq0/HozNCp5Dr4cqTcOZwOTtyPtZbdmzmdfT66mkdC4P1e8JPGugtHv+5HC8u0PSyBQGEewIo7g417mxC/5mf84Cp+1a/TLTe5mb4xqycaUexYM8+jLy4YdStpvR0NuTcx3ZzTwsi357S3NfACfLnU2BnD5WyvID9Y3uNd7Mawa4b+631WfuMdFWBOa3qQYifzdnzo98CpkpatCn5HSse87LqTxZ8Cm8ziu42g/uKcHuvGJXDzcFSD4wk0qclvpcdrSfhRZfU9H/YFK4JuW6q9aAvL24DgdD9mBDI2Gz94xFwu7u9iMivTQXEC5vgHD2lvdH9XtnpHxElqTBN4whFTujk9ZhjcEY50ntoaplQOs0N3S9XhQhLMjNz+g6CvY77jTq+3pdoGffuoecbD/hOWZqebdqaC5Oa7ytnH/YY2J6L3tCTaFloqNOetWvre3uaz5aNh4i6F4f4Z4CkTWGksHPcKdukMQDMNb0gm+JiWt9Wa2bHhQ1KmK+XQHZEIEM6GAhzIDCk0kMeKiyyE3zOwIJomqjHZVb5O4GmpJ1oONZzBSyvdBusaRYl1r8MT5BWgh0f8l34wc5pMxmvZUT11hZPDzawPfKLYCqHDAPvAEOR7JDkOBb3X8ILMRUIGslzzu17y0gP9qiOOuwadoyTcTHxvPJaEc6lQYn3j9E44C3TxDGpNOHl9rgT0EkRT4o2QNLNXXAoKPl2lFw/8goob9s73dMqtzeRwVVJdoC9kGv7TBzDJcs6yIXMM9EvExeuotANi99JdL4xLUZiRrtcv3+fJnIsJPdgczGYV/9ScODFIsa8N3fNFiIOe9b+5gPDwutGVYoSf2Fs+mzsbrxLv7/D+7vRAQxlya9WCvWtw8YjkTaBTEX+y8FUOHtHDBczu4XXEHaznZ1chnV/Pb5FkX78BiEO86ai1dB6v5+F2J/MLdEWhqEvEW8QYAPB6AYwfdEeVZX5igCF17mqbCqS/sg3z/hdW0iLSnjVeYqHXwTHbuFWM/c0MLCddnqvO49vZacWzXHk86wRyghbQTJUx3HUa/mJDGACZysvHLGzv2qjmlsKviokYLWA/Wsg2G1UV3eAmVq7VXt9s3v8nFvTRyqeWeHIkBpw45NFOR/H1sUjWAklBWOdiT/db0y6SOCvaTxVWqnk/6nL+/7vhhF3Zp9HrImt8+lk2vpeAS12YD8mHS/LJPWY+nLBWu1t0yI4aca70gJsBGJI5NvZbDGGe6f5SGfxbVHCr5NS/cBOUMOo+Oh87/IYv2XGZPwvD+7S9b84VnZVXdj9lQh0Et+v+zbaRkB4lquJMoGnF5UwcLtV0/RQybThfIpOYjNMs1LuW/6jPS6PCKlxeUGNwDvlKDCQlDhZkAQ1PPVPMlhA7QlMceaSYXnRLzI+IHJBQb6freSwWF6zBHYFI9H39CxxUUjIiR5sv5ViOi8tO5RgHtF3bsj3i8yQS43SdFTe0C4cxIuBuQMuSz7laE8/gZl3yGo7Wjgy/3RknSjla2sUt0d6PLQ2QQndxcWHKCNt19i6sYTttLqrywCHrjSyYwEBlWEC2nioEDONieaFc3iticbgSD/a5xzW9aefnQVFZWCRWkP0wVtBkf/tGtHl9PXau4+TV66rTq5X9aY3jEeU83OCnsuY5zBHFm3S5blMlvA+ebCcEWhyHDVwLfI7FAQ7fmcz3xO9cQf0waI6CK70yV+poRFAKDiCD1HELm6ij1KR/y2uWPF3LZ5P3pMGTr2W5hnC7Kvh+yqGvaID2L1athALmORgJHv1CDQzx0AzxfGMvUkgtQq8bCdjf0XvX1H4SKiPylc/2AwFM1PwS8eR429YnIMwJ6yyAQ68u5VyEGaFMdyrXX3I/vYStWcuUBrsN4oheWxIcNVKO1fFC0f6O0N5bRf8Kjyz9siRZK3z+vsU/zD1l6ShheQdoDeiXQKYgv91C7X3dJEx7fo60lRHVurySIUq1J5omLBMSl8b8F7Eqw1jTmZlVvSHcgzulhNYbv88MA1reVj6RQOfRA8LfaNID/RA89xAPYuEadd92uAjbQqExObqj5ELcNmhqOX/JrB+hn2aef3pWzSJmy98C9FdJuvF2l2XnZ5sDS25I9hnuCRAHeyHoAk4/VfjqEIjTN8BG1b0sTjSwJFBlHJCwBsKaaJviwXl5qynN1Z2f0IjoZ4gzwl9QDtcZDqYxk+qxbWDFqc9Nz/dhFLjGYTpX8VNEedNaa4/c/wTOpq7KioMEzASNlfwoIk9Ha6Xpph4PDoumFoeE6ONFEHNK3LgPDzEDfjj8HykTkq/VZIA7uN4BaaGFAi5Ah66FuVFFT7Mp9vUMvCfSH/Wdx/AYijS5WNBULlDpLJeLQ79oJfZ1ItrBOfD7oBSt5hIK7qoVM2aCMXwdOVSE6M34SJfgMPC9MCfcJudvQjDvYX31ZA42ZGGDgD4Iab75KMOXMdOR4aqlxCTb6FoLqqahzFTh6bMWtHEdqCz4uTVeHcH/FGUC/2j1BQE5FEKg3EIKl0uja5ztpSZa+3e59cmOHAPcyhlYE9ov+tU6GVzThHqBGs9Wd4zw6oNMvBt+YbbepNJotoU7YtuST1Z5mZIjpJaD+kDHqmmLyDYxlpWaHJFsPETkRsGj1Ko6lzgz1NYVMuejwOw2EwUtWQ+YKy7c3Y54dVk0hWD8oRaqOo8HNQ9YKURUjeIXXZ8OGst6WE5KtvoV/ga8VZQ8gE8SRc/XEWdXGrdDKc8tMxnVN03pSJQGIYquLQGU7QwUUftlJ6MYA8jgQFpGcW0FVBmsxl03zyxSL+FemGrtEoVwAOlfAiLaHuLV2QZ84j+dJAoWjdOFIF+uXwOzyeQNMPTwFg+ivSAkE9D23HFQJOnC34NYENFHBLCZfK+oICubzmXnFKHbLJrlNvlvbCONp+LiFK2iXMda3p8oGojI5o/92vlxF4mn5ZH4JZ8jRX6Xalj18Cw6H6j8UzO6w4cI5OV0RvFEKOwKmGvN/W7OvdHPMBMmg1oxFhAX6p8KdyULTdU+1C4JkX7mBND+Y5mjLv4/M3GIzyEcwyL6sxVTz0aqkKSCltyedUU/g9JmiuEp11d2Pk0skRo1o/gqPdJ5lbVG/fJXGcvxG6sIhAGcCOLIjHG873yWf2rJY7RRWk0W8lX5IjgvmDIWY9Tf8W8dNUWoRTbJ8IzAqL9YtYyOKDt4W8fdXKSRP+cljxQhmHmXTzyJ9mQkGpy0TWsf7VE96ki5aAhbRikjQ5mYN6xLyiLC1YKz8c/tZOb0bPH897bhFgjRHOtfzRbRm9p/Sqk4AMsStChqcVkZDM2bz3WOjNpBLI0/K8OCd+PDT+xg9ehjzBcM2WNjspKmch9rK6T58YRulEmbQbc9QT1+A2lPTnb2aXccptRrP+CwkQ2bsT/OMbUnE3NHvw0/G5Z3pSU0Qy6L10+lJ8SXzFGf4XTPoLuK+fNKj05IWFxJiHINtjo3WbwLdKZp8Kd97X+TW+z7XpP2qKqaRMfeYwzoc56y7w1NnOGzH48qicaC+DXipszT5mYmW6e1bxjwqNpo+E1JmXZQR/LeFi84XEGUYNxaaAciQfRX/6jO4b81TmdvWjyADWHwMonDXXHjO4YoSbh1M6pRKR2Psuw066/O/WvJh+WrIhlsJC6D9W2HtDK8gIU2F8SrpXOxd3hlntVFYsMo37s6kPAtFm2hu+eWN+NtX9/EO8psgAdiXvMNczCSFxV9cdEEXfDHHbQBmH/g+XilwtXYcjoZas9LqLS4xBSU4vLMnCqrtQ0qauh2g52wDFz6mAj7Z6OydXcVDwjK4QD2P2zfAYsfOqIhhg7xEzDJoumTy+eRbkGbFHraOet+LF5N8lKKplstYqiVamzJ56geEsZ6QP1NYv/KXdKk+OjtRC/KdoOZjrBXsoR8/CS8Az6JV/DyGqbTkwlo9gA5hnKaVKtRy9q7uNASl9asjj82WN/0VYIhMfLoR1CbW+vUrOSbAh/ilqbcq3HJ8c5+E2HuhKIbYRJRMQrfJrxKk7xKTRAgeSbA5dRzQACm31Ch2FSvoS5KnIpYXyqm4828lGGNApJiZ1aU0Q2mLUI/ZHAHrzMacshZtckhWG9n9MKIcDc8ld0gl7Tl5QFMOswgHvWyzuEqgTnuBxJqfhTprv6tbLJ4xcpNz2/v4fHE743YuVN5/lJG0SEfwUUq53MtXUqII9PRcjnTAN4TT3Y+cz6QWaW/l/tW0uSvniLha36gw3WlfA5qWdBuwE2uR0XMyYXE5d7WEkHUnAYzzJsRqrvhkqNwQO0V1jF14ApnUltMxqK3pbyWO3tw1o5+PIjcvKFeOFYCUucRa2lS0h4peA8FdiOcMkUabgHdtsAJuUrlxa+Vi39Y6PVg24XRyupLFIy80QmIF5H4GUY/aW5Ig687karDPyHVnr9M6PEJ3q3acV4yKwZSMBvlxqlvUFYgLm8NELucYtQvyI7CYRDl6LvSpdro+FK9IJSaOLXFghk90IlcJz4Ic74UY8a8+l+8tQKEjhVzFeYbLvrHGTv5A0OAs43fxIxAe5q/iqRFZUAXE+h0PUfky5af3xo/7zZXGhnzfz1Um8L58WUa5hTavUhbD0+zRqiajkJ7nyqIKj5b6nRTzHwG+lr7bxs5P5SPYycAoDdEqjT3TUqY3dz2tONxBh1lU7WX3BTxfO/WSqi0Zz+sEAOrvGu8io4SmIM4/Ka5RTXQscbG1I04g28x0fTm6KSXDFhAcNsNw0Mw8o2u5Lxa42szqPdapNqo6lrOP2N6RQ0pzOUJ6d/f/4Yqt1/r6XtoTqCPE70IwyZvaAWjqTX1XoLmvQ4uajXSpxDA84kZ1fFNd96Y892wh4ygX6RMg6vUd67kkRKcrmAckL8UmAklWAJryol61L8fPBt8o32hDuVCE2itZBjVn/4vEZVMARe0U7X/NOb6NDdrbP8TCM4g4+XeV7Jej+MnqvdA0c19fSUqxU1fhrSQL4cuNcdPKbdPSyPzptcmrxngxL9Yotv6/Ah2c4kL8dt1m7uQE8cdunn3qMB176itdJQu6lEF3p/gg/kuBO70VtY6gVISN1cNPnv8EiKYKzLhCf89np5f/fCDWOhciRKDwWRzULf3jZvHDJ8cd25p5JA3UppQIqy/8C9g6UrJwmYWZ8f8z0+sGCY25gGwsQs5UREaq9GQKE//vFneh09XAVdDhzDkqspkWjL6RmLDWdIpttc0N5NTdX2DdqR9JVikvEkg9OBHpYxkxYr0RQMp5bphj0ee6KsYcbZ4x06gbJFeGSgKZjuR4EpDJ8yPJXFFxldxmUtsrCMr/z9NA9olxU7+l4OD+dXd0AEAfMtpIK+g98cuLM0HNfslZ5/YgWBxFF2zmKJFunA9bb/agMyUR9AGAVr/HER9lxJEwdbwKtHQy9vwiMF8G+452JLK/t0/j0mvspENFA9z2ZJPL9jFbSRuF34ppQ3mV/E14IZeGP29NIv2uOHOxL1uzKwNydi1YbLIJRDE25zm0Zh/ulUBJWEnMqUfmzDqo1V8qy4VxfXeym4xccedSOiEyyLna/lP08giG6hUmQLxnvzrMHK2AEe+ggm3RGoS8Rown66LX+See1iohZqnbqxISRf6rNBpM45XPvxph3Q3pKcjDvoeq9eaUFSA92VfxpnVBO/tXtiQGBLYuBSZpqxTWd6G4RP/PFdrmUOhVkRsUXBufoj2MKSgNx6Ab+KMUg5qY52yrkYGKt8O6CubJt0/GJnIlCRODd3ZdcuCNaVWx2PN5+LK6Jy4jEFKtw/f+Ub+piXcpBL02TiO1SCI7qU0MznFxbs5CYrYOo5uQe/CaxWIN8Pq57WuPJ4UT+FHcRW6eiVYxa5hQTAf/ai3XsH5xP2LT/CWdWB175xA8uN8iZ6ai3yJysIENsHcRD6bpGEZ6gc+IIFu+JEhm4zMxIhu9CqjscKnyUIIzasUxdo2Ioa6hZaq4exO0dC/5gQbL4vR23nf5/PfxEzggWNQyDhM+UupTXtBIO2yqV6/wotUPibw5rOcqU+Amua0hAt+7RVXHwvfnFVfCo8E6mdD8wv8xSK2i1mElL9KiW/eVZWBRNE8gSBYQ8DpaNF193CPDyWKxPHz3Hg/aehKa5P4GPE7ugkqhjsYVMUo9Pvq7wXrjAs6U8EKDEh9zsRfFyg91RJaRSqSMNaUbuTkqKYq7KalV3WErKQRL6SxCZa3xGwJ8H5t5bYU+J3lCbtQjtDZ8O66PwKPPvyx1zuiiMjQcwv+JNGNIlFraoEgz5MlGkrBEgIRGXZDuS6ZVAa8L8lqE+/en4pHcj6HNlKtriDeosSDduttT+zi45k4bkgXWgO0a8d7gdqZ3UJR29T2KFIOSrm2Eqat9SF4S8lc33+LUUePfo82x/nQilk3+Eu+HhupyjfM5PGq1dglQ4wG/lJYazW0LlJ774pf/fwHYuPGjVC0CgsOjDzN8o2F6koQV+o90KCDPNkmqpGuAx8+43UGbtxxZGt0OpiFwi3tp8JyYExjdfni19OpI6SI48mM4pZCQXpkhvbZnbvD9hHigF+ERZJfzZZRGExPctz5QL8eQNAFI/KuL4DC23teZmSi1RNxU26PZTrfmtXwgciXXMyvm8j2ACq5n3+x4PyKdvh+SAVEvOQpc1SO7JDZf4+1L+fmB31RWMENRock5xOJ6H2UdMncn+nU9plECElvoLRnImET+mfWHT1fAfrR6iA1Yks9UXu3F2KHIc/wAeChopbz8RCkHQGxOQEVSbujBdd/Hjod1X8g4kF4MsefT+gd4n0UGoUgd30zeGmabC7h7W29PhizM8IZvfZbzVFdLPLU5X8s+wmdN65Woyld4B+BVEQ+w1GA4J4K6WVcSki7AeL4Gv6v3DXYBgdHl5aHXI1epDe0MxtdxvyPhHxoO3pHodmCkdJ1udrGu1167EDHXzknCB5r5U9cxI/UFt54Xxr5LPwrfONSxhMj3DqfGLSQQfxf2cSWWFig3jGf2mOfQ+XzH62mfn3Dv0KSS2M12rilop/c5iFK5ApaiFse2g== + </xenc:CipherValue> + </xenc:CipherData> + </xenc:EncryptedData> + </saml2:EncryptedAssertion> +</saml2p:Response> diff --git a/nachrichten-manager/src/test/resources/application-bayern.yaml b/nachrichten-manager/src/test/resources/application-bayern.yaml new file mode 100644 index 0000000000000000000000000000000000000000..765940c40972f684097389caf6331114bfbdf1e0 --- /dev/null +++ b/nachrichten-manager/src/test/resources/application-bayern.yaml @@ -0,0 +1,15 @@ +ozgcloud: + antragraum: + url: https://dev.antragsraum.de/ + entityId: https://antragsraum.ozgcloud.de/ + metadataUri: "classpath:/bayernid/metadata/bayernid-idp-infra.xml" + decryptionPrivateKey: "classpath:/bayernid/bayernid-test-enc.key" + decryptionCertificate: "classpath:/bayernid/bayernid-test-enc.crt" + nachrichten-manager: + url: static://localhost:9091 +grpc: + client: + info-manager: + address: static://localhost:9091 + server: + port: 9092 \ No newline at end of file diff --git a/nachrichten-manager/src/test/resources/application.yaml b/nachrichten-manager/src/test/resources/application.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/nachrichten-manager/src/test/resources/bayernid-idp-infra.xml b/nachrichten-manager/src/test/resources/bayernid-idp-infra.xml new file mode 100644 index 0000000000000000000000000000000000000000..ec1ed7ca7099b8be7a8cff7448a740f0b9404c34 --- /dev/null +++ b/nachrichten-manager/src/test/resources/bayernid-idp-infra.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?><md:EntitiesDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"> + <md:EntityDescriptor entityID="https://infra-pre-id.bayernportal.de/idp"> + <md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> + <md:KeyDescriptor use="signing"> + <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> + <ds:X509Data> + <ds:X509Certificate>MIIFbzCCA1egAwIBAgIJAPdFXXarkBN2MA0GCSqGSIb3DQEBCwUAME4xCzAJBgNV + BAYTAkRFMQ8wDQYDVQQIDAZCYXllcm4xETAPBgNVBAcMCE11ZW5jaGVuMQ0wCwYD + VQQKDARBS0RCMQwwCgYDVQQLDANJRE0wHhcNMjAxMDI3MTMxODQxWhcNMjUxMDI2 + MTMxODQxWjBOMQswCQYDVQQGEwJERTEPMA0GA1UECAwGQmF5ZXJuMREwDwYDVQQH + DAhNdWVuY2hlbjENMAsGA1UECgwEQUtEQjEMMAoGA1UECwwDSURNMIICIjANBgkq + hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzDtWAEdC3J9FD+ti1exRhN1lzNgKWqO2 + gQNdJvlt7KGHA2VGGO7tqRogTuoqi/ydtiHJ8+lhp4kcWqyfv7i9HXOncvcsRRmR + dZjUY2Iui6ozJqD5LVm/vP5YfdP7vQPdbqyyfpoJhf3mbMEtdNDdGRnGIPUfDn+C + Fbo37f9tPwMgf3jgh4gxaujtLIhhr9gevVTEeZAFu9EvzLNd3kEtRb7MuXqIOdu1 + rW8HlGYFwwVLqEyBn8XG0QAIfhMmGjFMG7z+Kco2quwOmmZVzWQfeH/3AlN2KbcP + t7j+pl+6Bew2AAivP7O+95YKORqQjTu3rPWMF4txPId37MSjoytwBRyd5EACTvhQ + BOGrDFKQUOx6fTtRc8+7XGVz8MdQaZQWQXXh1ByU783twNdnRSrSVIyLdjiy1uCb + jvsSAtbzGBygPIvDo3skCNLNFXsChtHIfFFDK20KPGb0ghEDf2q3hDbFG3ZDGGyn + ZmJcZKuZhJqodJ/++sAXADyTJNAPVYDjKCF4ypELp2Eu/p1gaQPJEb74L/ZFZVOE + JFyXIiaqB9J+fcn/biqHHOmcCi8n9aIiNt1fatr1Z4lQRWoGtKaGU0+bzUSH4Bgs + 2EG4u1CI2MKDWqK2aEsHrtu8tbS9LrUmDVKtaEUOeul8xWVa036vp/YUIdiJNZSx + ZG4iTmSOATECAwEAAaNQME4wHQYDVR0OBBYEFFYeltslkaolOmcINXQeSe7nURwp + MB8GA1UdIwQYMBaAFFYeltslkaolOmcINXQeSe7nURwpMAwGA1UdEwQFMAMBAf8w + DQYJKoZIhvcNAQELBQADggIBAKqAlXoO41SAiycYUOrR90pfwTCysmbtHF5RWSCM + jF2aCG8URJ7bXwC0lBH8E5zCetFZwdqZziQtxzRkIOfhS5uWbH0RDhwuxZG+5RTP + yaHPAZI6e5xHDu8vHl/VbC3lnL/6K8l+Purr/yo8qkJqrPgThZRL9jBQyYRhDSsJ + UyIw5zcKKUQC/JWtMQAQcopbjekCs6xDT1HqIN90Sc/gOfYjNo0dGMNmro9mxcw8 + 2Iow18KNVdtEexfD+/6x4NPD61pzuQEe09TR+Cv3XyzBoGQ/2arijcPnGvth79ff + VFtRSf3fSs7wEKV9g3mEWXFDtPBhDj6K0kKU/kJfEZixkXl92MY+bmugrtTIrazj + tfrgMglIAHu9XCYWd/gef0J+PNfHsxgbTEr3XSC+5/xoFKPQSw3PgV8lkUDq4mJU + Ky/q4YmA37XQxourFR5pWvF03YACdtq6zPjtVeI7Cvkte6k0YW5S3cx9RmPv6YZh + laZ5ERpWNiv6IjokLsvNeemf2PApjO7Q2EDBIoHBYH31wwJSsyRDrSVmbaqLFI15 + fLXeh2A4YbaBDZdGvDiLOAk+dG1wdZ2aGw/uNBzMtc8VeKqI1HPcqIluBA3uUPpy + LLA+9hDPf6Pp4j0gkXxBikz+/h22bFxE1HmDiOSkEn+2NmOHuEFeA+D8jsCAL5VJ + 3emK</ds:X509Certificate> + </ds:X509Data> + </ds:KeyInfo> + </md:KeyDescriptor> + <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat> + <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://infra-pre-id.bayernportal.de/idp/profile/SAML2/POST/SSO"/> + <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://infra-pre-id.bayernportal.de/idp/profile/SAML2/Redirect/SSO"/> + </md:IDPSSODescriptor> + </md:EntityDescriptor> +</md:EntitiesDescriptor> \ No newline at end of file diff --git a/src/main/helm/templates/deployment.yaml b/src/main/helm/templates/deployment.yaml index 01005831511b2253a348cc33f0ff4f6125a76b6f..6465ac5f120cc4ed9494484bc5e670820f3d9ff9 100644 --- a/src/main/helm/templates/deployment.yaml +++ b/src/main/helm/templates/deployment.yaml @@ -180,7 +180,23 @@ spec: - 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).antragraum).enabled }} + - name: ozgcloud_antragraum_enabled + value: {{ quote .Values.ozgcloud.antragraum.enabled }} + - name: ozgcloud_antragraum_url + value: {{ quote (required "ozgcloud.antragraum.url must be set if ozgcloud.antragraum is enabled" ((.Values.ozgcloud).antragraum).url) }} + - name: ozgcloud_antragraum_metadatauri + value: {{ quote (required "ozgcloud.antragraum.metadataUri must be set if ozgcloud.antragraum is enabled" ((.Values.ozgcloud).antragraum).metadataUri) }} + - name: ozgcloud_antragraum_decryptionprivatekey + value: {{ quote (required "ozgcloud.antragraum.decryptionPrivateKey must be set if ozgcloud.antragraum is enabled" ((.Values.ozgcloud).antragraum).decryptionPrivateKey) }} + - name: ozgcloud_antragraum_decryptioncertificate + value: {{ quote (required "ozgcloud.antragraum.decryptionCertificate must be set if ozgcloud.antragraum is enabled" ((.Values.ozgcloud).antragraum).decryptionCertificate) }} + {{- end }} + {{- if (((.Values.ozgcloud).feature).bescheid).enableDummyDocumentProcessor }} + - name: ozgcloud_feature_bescheid_enableDummyDocumentProcessor + value: {{ quote (((.Values.ozgcloud).feature).bescheid).enableDummyDocumentProcessor }} + {{- end }} {{- if (.Values.ozgcloud).processors}} {{- range $processor_index, $processor := (.Values.ozgcloud).processors }} diff --git a/src/main/helm/templates/network_policy.yaml b/src/main/helm/templates/network_policy.yaml index fb20373df7e5cf5c1045657b25fb3df4057f48cc..88ad059ed4898240276aa3f9a0dc05775705a87e 100644 --- a/src/main/helm/templates/network_policy.yaml +++ b/src/main/helm/templates/network_policy.yaml @@ -47,6 +47,9 @@ spec: ozg-component: eingangsadapter {{- with (.Values.networkPolicy).additionalIngressConfig }} {{ toYaml . | indent 2 }} +{{- end }} +{{- with (.Values.networkPolicy).additionalIngressConfigNamespace }} +{{ toYaml . | indent 2 }} {{- end }} egress: - to: @@ -101,5 +104,8 @@ spec: {{- with (.Values.networkPolicy).additionalEgressConfig }} {{ toYaml . | indent 2 }} {{- end }} +{{- with (.Values.networkPolicy).additionalEgressConfigNamespace }} +{{ toYaml . | indent 2 }} +{{- end }} {{- end }} \ No newline at end of file diff --git a/src/test/helm/deployment_antragraum_test.yaml b/src/test/helm/deployment_antragraum_test.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9d8cc1af7e263702ba1e354064c5459800a00269 --- /dev/null +++ b/src/test/helm/deployment_antragraum_test.yaml @@ -0,0 +1,124 @@ +# +# 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. +# + +suite: deployment antragraum +release: + name: vorgang-manager + namespace: sh-helm-test +templates: + - templates/deployment.yaml +set: + imagePullSecret: image-pull-secret + ozgcloud: + environment: dev + antragraum: + enabled: true + url: https://antragraum.address + metadataUri: "classpath:/bayernid/metadata/bayernid-idp-infra.xml" + decryptionPrivateKey: "decryptionPrivateKey_secret" + decryptionCertificate: "decryptionCertificate_secret" +tests: + - it: should enable antragraum + templates: + - templates/deployment.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_antragraum_enabled + value: "true" + - it: should fail if antragraum url is not set + set: + ozgcloud: + environment: dev + antragraum: + enabled: true + url: + asserts: + - failedTemplate: + errorMessage: "ozgcloud.antragraum.url must be set if ozgcloud.antragraum is enabled" + + - it: should set metadataUri + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_antragraum_metadatauri + value: "classpath:/bayernid/metadata/bayernid-idp-infra.xml" + - it: should fail if metadataUri is not set + set: + ozgcloud: + antragraum: + metadataUri: + asserts: + - failedTemplate: + errorMessage: "ozgcloud.antragraum.metadataUri must be set if ozgcloud.antragraum is enabled" + + - it: should set metadataUri + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_antragraum_metadatauri + value: "classpath:/bayernid/metadata/bayernid-idp-infra.xml" + - it: should fail if metadataUri is not set + set: + ozgcloud: + antragraum: + metadataUri: + asserts: + - failedTemplate: + errorMessage: "ozgcloud.antragraum.metadataUri must be set if ozgcloud.antragraum is enabled" + + - it: should set decryptionPrivateKey + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_antragraum_decryptionprivatekey + value: "decryptionPrivateKey_secret" + - it: should fail if decryptionPrivateKey is not set + set: + ozgcloud: + antragraum: + decryptionPrivateKey: + asserts: + - failedTemplate: + errorMessage: "ozgcloud.antragraum.decryptionPrivateKey must be set if ozgcloud.antragraum is enabled" + + - it: should set decryptionPrivateKey + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_antragraum_decryptioncertificate + value: "decryptionCertificate_secret" + - it: should fail if decryptionCertificate is not set + set: + ozgcloud: + antragraum: + decryptionCertificate: + asserts: + - failedTemplate: + errorMessage: "ozgcloud.antragraum.decryptionCertificate must be set if ozgcloud.antragraum is enabled" \ No newline at end of file diff --git a/src/test/helm/deployment_dummy_bescheid_document_test.yaml b/src/test/helm/deployment_dummy_bescheid_document_test.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8863f522e03eb89a1967528f116e5195ac42a473 --- /dev/null +++ b/src/test/helm/deployment_dummy_bescheid_document_test.yaml @@ -0,0 +1,45 @@ +# +# 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. +# + +suite: deployment dummy bescheid document processor +release: + name: vorgang-manager + namespace: sh-helm-test +templates: + - templates/deployment.yaml +set: + imagePullSecret: image-pull-secret + ozgcloud: + environment: dev + feature: + bescheid: + enableDummyDocumentProcessor: true +tests: + - it: should enable dummy bescheid document processor + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_feature_bescheid_enableDummyDocumentProcessor + value: "true" diff --git a/src/test/helm/network_policy_test.yaml b/src/test/helm/network_policy_test.yaml index 16661a34b0b43473c82029e7496f2867220dd785..ac7df6574a59eae14b64a88872f8f37a182dfa89 100644 --- a/src/test/helm/network_policy_test.yaml +++ b/src/test/helm/network_policy_test.yaml @@ -253,39 +253,78 @@ tests: matchLabels: component: client2 - - it: add egress rules by values + - it: should add additionalIngressConfig set: networkPolicy: + dnsServerNamespace: test-namespace-dns + additionalIngressConfig: + - from: + - podSelector: + matchLabels: + additionalIngressConfig: yes + asserts: + - contains: + path: spec.ingress + content: + from: + - podSelector: + matchLabels: + additionalIngressConfig: yes + + - it: should add additionalEgressConfig + set: + networkPolicy: + dnsServerNamespace: test-namespace-dns additionalEgressConfig: - - to: - - ipBlock: - cidr: 1.2.3.4/32 - to: - podSelector: matchLabels: - component: ozg-testservice - ports: - - port: 12345 - protocol: TCP - - dnsServerNamespace: test-dns-namespace + additionalEgressConfig: yes asserts: - contains: path: spec.egress content: to: - - ipBlock: - cidr: 1.2.3.4/32 + - podSelector: + matchLabels: + additionalEgressConfig: yes + + + - it: should add additionalIngressConfigNamespace + set: + networkPolicy: + dnsServerNamespace: test-namespace-dns + additionalIngressConfigNamespace: + - from: + - podSelector: + matchLabels: + additionalIngressConfigNamespace: yes + asserts: + - contains: + path: spec.ingress + content: + from: + - podSelector: + matchLabels: + additionalIngressConfigNamespace: yes + + - it: should add additionalEgressConfigNamespace + set: + networkPolicy: + dnsServerNamespace: test-dns-namespace + additionalEgressConfigNamespace: + - to: + - podSelector: + matchLabels: + additionalEgressConfigNamespace: yes + asserts: - contains: path: spec.egress content: to: - podSelector: matchLabels: - component: ozg-testservice - ports: - - port: 12345 - protocol: TCP + additionalEgressConfigNamespace: yes - it: test network policy disabled set: diff --git a/vorgang-manager-interface/src/main/protobuf/antragraum.model.proto b/vorgang-manager-interface/src/main/protobuf/antragraum.model.proto index 16eb419ccb78e483b10cdf075337b069008fa1cc..062c92b7c6e0d9cdc7c2f40902737f61bed20094 100644 --- a/vorgang-manager-interface/src/main/protobuf/antragraum.model.proto +++ b/vorgang-manager-interface/src/main/protobuf/antragraum.model.proto @@ -32,42 +32,33 @@ option java_package = "de.ozgcloud.nachrichten.antragraum"; option java_outer_classname = "AntragraumModelProto"; message GrpcFindRueckfragenRequest { - string samlToken = 1; - string postfachId = 2; + string samlToken = 1; + string postfachId = 2; } message GrpcFindRueckfragenResponse { - repeated GrpcRueckfrage rueckfrage = 1; -} - -message GrpcGetRueckfrageRequest { - string samlToken = 1; - string rueckfrageId = 2; -} - -message GrpcGetRueckfrageResponse { - GrpcRueckfrage rueckfrage = 1; + repeated GrpcRueckfrage rueckfrage = 1; } message GrpcRueckfrage { - string id = 1; - string vorgangId = 2; - string vorgangName = 3; - string sendAt = 4; - string answeredAt = 5; - string status = 6; - string text = 7; - repeated string answerAttachmentFileId = 8; - repeated GrpcRueckfrageAnswer answers = 9; + string id = 1; + string vorgangId = 2; + string vorgangName = 3; + string sentAt = 4; + string answeredAt = 5; + string status = 6; + string text = 7; + repeated string attachmentFileId = 8; + repeated GrpcRueckfrageAnswer answers = 9; } message GrpcSendRueckfrageAnswerRequest { - string samlToken = 1; - GrpcRueckfrageAnswer answer = 2; + string samlToken = 1; + GrpcRueckfrageAnswer answer = 2; } message GrpcRueckfrageAnswer { - string rueckfrageId = 1; - string answerText = 2; - repeated string answerAttachmentFileId = 3; + string rueckfrageId = 1; + string answerText = 2; + repeated string attachmentFileId = 3; } \ No newline at end of file diff --git a/vorgang-manager-interface/src/main/protobuf/antragraum.proto b/vorgang-manager-interface/src/main/protobuf/antragraum.proto index d5874e2a11806972c3ee66107ae2b616aefe878e..0fa6d67dffed44a28d58a531d8ff1ad03bf128ea 100644 --- a/vorgang-manager-interface/src/main/protobuf/antragraum.proto +++ b/vorgang-manager-interface/src/main/protobuf/antragraum.proto @@ -33,12 +33,9 @@ option java_package = "de.ozgcloud.nachrichten.antragraum"; option java_outer_classname = "AntragraumProto"; service AntragraumService { - rpc FindRueckfragen(GrpcFindRueckfragenRequest) returns (GrpcFindRueckfragenResponse) { - } + rpc FindRueckfragen(GrpcFindRueckfragenRequest) returns (GrpcFindRueckfragenResponse) { + } - rpc GetRueckfrage(GrpcGetRueckfrageRequest) returns (GrpcGetRueckfrageResponse) { - } - - rpc SendRueckfrageAnswer(GrpcSendRueckfrageAnswerRequest) returns (de.ozgcloud.vorgang.grpc.command.GrpcCommand) { - } + rpc SendRueckfrageAnswer(GrpcSendRueckfrageAnswerRequest) returns (de.ozgcloud.vorgang.grpc.command.GrpcCommand) { + } } \ No newline at end of file diff --git a/vorgang-manager-interface/src/main/protobuf/document.model.proto b/vorgang-manager-interface/src/main/protobuf/document.model.proto new file mode 100644 index 0000000000000000000000000000000000000000..ff0025a2197b3ba328547cd595f741ea95852310 --- /dev/null +++ b/vorgang-manager-interface/src/main/protobuf/document.model.proto @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ + syntax = "proto3"; + + package de.ozgcloud.document; + + option java_multiple_files = true; + option java_package = "de.ozgcloud.document"; + option java_outer_classname = "DocumentModelProto"; + +message GrpcGetDocumentRequest { + string id = 1; +} + +message GrpcGetDocumentResponse { + GrpcDocument document = 1; +} + +message GrpcDocument { + string id = 1; + string type = 2; + string fileId = 3; + string nachrichtSubject = 4; + string nachrichtText = 5; +} \ No newline at end of file diff --git a/vorgang-manager-interface/src/main/protobuf/document.proto b/vorgang-manager-interface/src/main/protobuf/document.proto new file mode 100644 index 0000000000000000000000000000000000000000..c2a903373b229e3461932fa15e50706a9e5b41af --- /dev/null +++ b/vorgang-manager-interface/src/main/protobuf/document.proto @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +syntax = "proto3"; + +package de.ozgcloud.document; + +import "document.model.proto"; + +option java_multiple_files = true; +option java_package = "de.ozgcloud.document"; +option java_outer_classname = "DocumentProto"; + +service DocumentService { + rpc GetDocument(GrpcGetDocumentRequest) returns (GrpcGetDocumentResponse) {} +} \ No newline at end of file diff --git a/vorgang-manager-server/pom.xml b/vorgang-manager-server/pom.xml index 06f4f37af8f931a634649d52ed1213713bc83e4b..cdc3e660057ab9c6802a58676b672a5077e2cfef 100644 --- a/vorgang-manager-server/pom.xml +++ b/vorgang-manager-server/pom.xml @@ -54,7 +54,7 @@ <user-manager-interface.version>2.1.0</user-manager-interface.version> <bescheid-manager.version>1.10.0-SNAPSHOT</bescheid-manager.version> - <processor-manager.version>0.3.0</processor-manager.version> + <processor-manager.version>0.4.0-SNAPSHOT</processor-manager.version> <ozgcloud-starter.version>0.7.0-SNAPSHOT</ozgcloud-starter.version> <zip.version>2.11.1</zip.version> diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItem.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItem.java index 7b96198ef4a1f2d0f062f32a5b102294721625a9..3757c40d46163e6fdb4a209873fb3c5b7bb0220c 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItem.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/attached_item/VorgangAttachedItem.java @@ -25,12 +25,13 @@ package de.ozgcloud.vorgang.attached_item; import java.util.Map; +import jakarta.validation.constraints.NotBlank; + import org.springframework.data.annotation.Id; import org.springframework.data.annotation.TypeAlias; import org.springframework.data.annotation.Version; import org.springframework.data.mongodb.core.mapping.Document; -import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -50,9 +51,9 @@ public class VorgangAttachedItem { static final String FIELDNAME_ID = "id"; static final String FIELDNAME_VERSION = "version"; static final String FIELDNAME_CLIENT = "client"; - static final String FIELDNAME_VORGANG_ID = "vorgangId"; - static final String FIELDNAME_ITEM_NAME = "itemName"; - static final String FIELDNAME_ITEM = "item"; + public static final String FIELDNAME_VORGANG_ID = "vorgangId"; + public static final String FIELDNAME_ITEM_NAME = "itemName"; + public static final String FIELDNAME_ITEM = "item"; public static final String FIELDNAME_IS_DELETED = "deleted"; @Id diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/PersistPostfachNachrichtByCommandService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/PersistPostfachNachrichtByCommandService.java index 2c981d73c499dcd683a2e456c538b4197650b9cf..63aee3c41f4aaa594572b35da58cd5270575336e 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/PersistPostfachNachrichtByCommandService.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/PersistPostfachNachrichtByCommandService.java @@ -1,9 +1,5 @@ /* - * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * + * Copyright (c) 2024. * Lizenziert unter der EUPL, Version 1.2 oder - sobald * diese von der Europäischen Kommission genehmigt wurden - * Folgeversionen der EUPL ("Lizenz"); @@ -256,7 +252,7 @@ class PersistPostfachNachrichtByCommandService implements PersistPostfachNachric // uses map file: src/main/resources/mime.types private String getByMimeTypes(String fileName) { - MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap(); + var fileTypeMap = new MimetypesFileTypeMap(); return fileTypeMap.getContentType(fileName); } @@ -268,10 +264,13 @@ class PersistPostfachNachrichtByCommandService implements PersistPostfachNachric return Stream.of(PostfachNachricht.builder() .attachments(Collections.emptyList()) .createdAt(ZonedDateTime.now()) + .sentAt(ZonedDateTime.now()) + .vorgangId("VORGANG_DUMMY_URI") .createdBy("Klaus") .direction(Direction.OUT) .replyOption(ReplyOption.POSSIBLE) .mailBody("Lorem ipsum dolres est") + .id(UUID.randomUUID().toString()) .subject("Test") .build()); } diff --git a/vorgang-manager-server/src/main/resources/application-local.yml b/vorgang-manager-server/src/main/resources/application-local.yml index 346ab37983c1d9a1e395e8ba52c0d31823caca68..f575ac6f04eaec08c1ea13a5f28d849eebe0576c 100644 --- a/vorgang-manager-server/src/main/resources/application-local.yml +++ b/vorgang-manager-server/src/main/resources/application-local.yml @@ -53,6 +53,9 @@ ozgcloud: elasticsearch: initEnabled: true index: test-index + feature: + bescheid: + enable-dummy-document-processor: true mongock: 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 new file mode 100644 index 0000000000000000000000000000000000000000..54cfc7b0679835e54404911bcb0c8945533ed8d7 --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/bescheid/BescheidDocumentITCase.java @@ -0,0 +1,99 @@ +/* + * 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.bescheid; + +import static de.ozgcloud.bescheid.BescheidEventListener.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.test.annotation.DirtiesContext; + +import de.ozgcloud.bescheid.common.callcontext.CurrentUserService; +import de.ozgcloud.command.Command; +import de.ozgcloud.command.CommandCreatedEvent; +import de.ozgcloud.command.CommandFailedEvent; +import de.ozgcloud.common.test.ITCase; +import de.ozgcloud.vorgang.VorgangManagerServerApplication; +import de.ozgcloud.vorgang.command.CommandTestFactory; + +class BescheidDocumentITCase { + + @Nested + @SpringBootTest(classes = VorgangManagerServerApplication.class) + @ITCase + @DirtiesContext + class TestDocumentProzessorNotConfigured { + + @Autowired + private ApplicationEventPublisher eventPublisher; + @SpyBean + private BescheidEventListener eventListener; + @SpyBean + private TestEventListener testEventListener; + + @MockBean + private CurrentUserService currentUserService; + + @Captor + private ArgumentCaptor<CommandFailedEvent> commandFailedEventCaptor; + + @Test + void shouldFailWhenNoDocumentProcessor() { + var command = buildCreateDocumentCommand(); + + eventPublisher.publishEvent(new CommandCreatedEvent(command)); + + Awaitility.await().atMost(60, TimeUnit.SECONDS).untilAsserted(() -> { + verify(testEventListener).onCommandFailed(commandFailedEventCaptor.capture()); + assertThat(commandFailedEventCaptor.getValue().getSource()).isEqualTo(command.getId()); + assertThat(commandFailedEventCaptor.getValue().getErrorMessage()).isNotEmpty(); + }); + + } + + private Command buildCreateDocumentCommand() { + return CommandTestFactory.createBuilder() + .vorgangId("vorgang-id") + .relationId("bescheidItem-id") + .order(BescheidEventListener.CREATE_BESCHEID_DOCUMENT_ORDER) + .bodyObject(Map.of(BESCHEID_VOM_BODYKEY, "2024-01-01", + GENEHMIGT_BODYKEY, 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/BescheidEventListenerITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..b7a2ad373088f30562bdd8876f6931a6492fda7a --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/bescheid/BescheidEventListenerITCase.java @@ -0,0 +1,397 @@ +/* + * 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.bescheid; + +import static de.ozgcloud.bescheid.BescheidEventListener.*; +import static org.assertj.core.api.Assertions.*; +import static org.awaitility.Awaitility.*; +import static org.mockito.Mockito.*; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.gridfs.GridFsTemplate; +import org.springframework.test.annotation.DirtiesContext; + +import com.mongodb.client.gridfs.model.GridFSFile; + +import de.ozgcloud.bescheid.attacheditem.AttachedItemService; +import de.ozgcloud.bescheid.common.callcontext.CallContextUser; +import de.ozgcloud.bescheid.common.callcontext.CurrentUserService; +import de.ozgcloud.bescheid.common.callcontext.UserProfile; +import de.ozgcloud.command.Command; +import de.ozgcloud.command.CommandCreatedEvent; +import de.ozgcloud.command.CommandStatus; +import de.ozgcloud.common.test.DataITCase; +import de.ozgcloud.document.BescheidDocumentCreatedEvent; +import de.ozgcloud.document.Document; +import de.ozgcloud.nachrichten.postfach.PostfachNachricht; +import de.ozgcloud.vorgang.VorgangManagerServerApplication; +import de.ozgcloud.vorgang.attached_item.VorgangAttachedItem; +import de.ozgcloud.vorgang.attached_item.VorgangAttachedItemTestFactory; +import de.ozgcloud.vorgang.command.CommandService; +import de.ozgcloud.vorgang.command.CommandTestFactory; +import de.ozgcloud.vorgang.command.CreateCommandRequest; +import de.ozgcloud.vorgang.status.StatusChangedEvent; +import de.ozgcloud.vorgang.vorgang.Vorgang; +import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; + +@SpringBootTest(classes = VorgangManagerServerApplication.class, properties = { + "grpc.server.inProcessName=test", + "grpc.server.port=-1", + "grpc.client.ozgcloud-command-manager.address=in-process:test", + "grpc.client.vorgang-manager.address=in-process:test", + "grpc.client.nachrichten-manager.address=in-process:test", + "grpc.client.pluto.address=in-process:test", + "ozgcloud.feature.bescheid.enable-dummy-document-processor=true", +}) +@DataITCase +@DirtiesContext +class BescheidEventListenerITCase { + + @Autowired + private CommandService commandService; + @Autowired + private ApplicationEventPublisher eventPublisher; + @SpyBean + private TestEventListener bescheiTestEventListener; + + @Autowired + private MongoOperations mongoOperations; + @Autowired + private GridFsTemplate gridFsTemplate; + + @MockBean + private CurrentUserService currentUserService; + @Mock + private CallContextUser callContextUser; + @Mock + private UserProfile userProfile; + + @Captor + private ArgumentCaptor<BescheidDocumentCreatedEvent> bescheidDocumentCreatedEventCaptor; + + @BeforeEach + void init() { + mongoOperations.dropCollection(VorgangAttachedItem.COLLECTION_NAME); + mongoOperations.dropCollection(Vorgang.COLLECTION_NAME); + when(currentUserService.getUser()).thenReturn(callContextUser); + when(currentUserService.getUserProfile()).thenReturn(userProfile); + } + + @Nested + class TestCreateBescheidDocument { + + @Test + void shouldCreateBescheidDocument() { + var vorgangId = mongoOperations.save(VorgangTestFactory.createBuilder().id(null).build()).getId(); + var bescheidItemId = mongoOperations.save(createBescheidAttachedItem(vorgangId)).getId(); + + eventPublisher.publishEvent(buildCommandCreatedEvent(vorgangId, bescheidItemId)); + + await().atMost(60, TimeUnit.SECONDS).untilAsserted(() -> { + verify(bescheiTestEventListener).onBescheidDocumentCreated(bescheidDocumentCreatedEventCaptor.capture()); + assertThat(getBescheidDocument()).isNotNull(); + }); + } + + @Test + void shouldCreateDocumentFile() { + var vorgangId = mongoOperations.save(VorgangTestFactory.createBuilder().id(null).build()).getId(); + var bescheidItemId = mongoOperations.save(createBescheidAttachedItem(vorgangId)).getId(); + + eventPublisher.publishEvent(buildCommandCreatedEvent(vorgangId, bescheidItemId)); + + await().atMost(60, TimeUnit.SECONDS).untilAsserted(() -> { + verify(bescheiTestEventListener).onBescheidDocumentCreated(bescheidDocumentCreatedEventCaptor.capture()); + assertThat(getBescheidDocumentFile()).isNotNull(); + }); + } + + private VorgangAttachedItem createBescheidAttachedItem(String vorgangId) { + return VorgangAttachedItemTestFactory.createBuilder() + .id(null) + .version(0L) + .vorgangId(vorgangId) + .itemName(AttachedItemService.BESCHEID_ITEM_NAME) + .item(Map.of(Bescheid.FIELD_STATUS, Bescheid.Status.DRAFT.name())) + .build(); + } + + private CommandCreatedEvent buildCommandCreatedEvent(String vorgangId, String bescheidItemId) { + var command = CommandTestFactory.createBuilder() + .vorgangId(vorgangId) + .relationId(bescheidItemId) + .order(BescheidEventListener.CREATE_BESCHEID_DOCUMENT_ORDER) + .bodyObject(Map.of(BESCHEID_VOM_BODYKEY, "2024-01-01", + GENEHMIGT_BODYKEY, true)) + .build(); + return new CommandCreatedEvent(command); + } + + private GridFSFile getBescheidDocumentFile() { + var fileId = getBescheidDocument().getItem().get(Document.FIELD_DOCUMENT_FILE).toString(); + return gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(fileId))); + } + + private VorgangAttachedItem getBescheidDocument() { + var documentId = bescheidDocumentCreatedEventCaptor.getValue().getCreatedResource(); + return mongoOperations.findById(documentId, VorgangAttachedItem.class); + } + + } + + @Nested + class TestSendBescheidManually { + + @Captor + private ArgumentCaptor<BescheidSentEvent> bescheidSentEventCaptor; + @Captor + private ArgumentCaptor<StatusChangedEvent> statusChangedEventCaptor; + + @Test + void shouldSetBescheidStatusToSent() { + var vorgangId = mongoOperations.save(VorgangTestFactory.createBuilder().id(null).build()).getId(); + var bescheidItem = mongoOperations.save(createBescheidAttachedItemWithDocument(vorgangId)); + + eventPublisher.publishEvent(buildCommandCreatedEvent(vorgangId, bescheidItem)); + + await().atMost(60, TimeUnit.SECONDS).untilAsserted(() -> { + verify(bescheiTestEventListener).onBescheidSentEvent(bescheidSentEventCaptor.capture()); + assertThat(loadBescheid(bescheidItem.getId()).getItem()).containsEntry(Bescheid.FIELD_STATUS, Bescheid.Status.SEND.name()); + }); + } + + @Test + void shouldSetVorgangStatusToBeschieden() { + var vorgangId = mongoOperations.save(VorgangTestFactory.createBuilder().id(null).build()).getId(); + var bescheidItem = mongoOperations.save(createBescheidAttachedItemWithDocument(vorgangId)); + var event = buildCommandCreatedEvent(vorgangId, bescheidItem); + + eventPublisher.publishEvent(event); + + await().atMost(60, TimeUnit.SECONDS).untilAsserted(() -> { + verify(bescheiTestEventListener).onBescheidStatusChangedEvent(statusChangedEventCaptor.capture()); + assertThat(loadVorgang(vorgangId).getStatus()).isEqualTo(Vorgang.Status.BESCHIEDEN); + }); + } + + private VorgangAttachedItem createBescheidAttachedItemWithDocument(String vorgangId) { + return VorgangAttachedItemTestFactory.createBuilder() + .id(null) + .version(0L) + .vorgangId(vorgangId) + .itemName(AttachedItemService.BESCHEID_ITEM_NAME) + .item(Map.of( + Bescheid.FIELD_STATUS, Bescheid.Status.DRAFT.name(), + Bescheid.FIELD_SEND_BY, Bescheid.SendBy.MANUAL.name(), + Bescheid.FIELD_BESCHEID_DOCUMENT, "documentId")) + .build(); + } + + private CommandCreatedEvent buildCommandCreatedEvent(String vorgangId, VorgangAttachedItem bescheidItem) { + var command = CommandTestFactory.createBuilder() + .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)) + .build(); + return new CommandCreatedEvent(command); + } + + private VorgangAttachedItem loadBescheid(String bescheidId) { + return mongoOperations.findById(bescheidId, VorgangAttachedItem.class); + } + + private Vorgang loadVorgang(String vorgangId) { + return mongoOperations.findById(vorgangId, Vorgang.class); + } + } + + @Nested + class TestSendBescheidPostfachMail { + + private static final String NACHRICHT_SUBJECT = "nachrichtSubject"; + private static final String NACHRICHT_TEXT = "nachrichtText"; + + @Captor + private ArgumentCaptor<BescheidSentEvent> bescheidSentEventCaptor; + @Captor + private ArgumentCaptor<StatusChangedEvent> statusChangedEventCaptor; + + @BeforeEach + void init() { + when(userProfile.getId()).thenReturn(UserId.from("user-id")); + } + + @Test + void shouldSuccessfullyCompleteCommand() { + var vorgangId = mongoOperations.save(VorgangTestFactory.createBuilder().id(null).build()).getId(); + var bescheidItem = mongoOperations.save(createBescheidAttachedItemWithDocument(vorgangId)); + + commandService.createCommand(buildCreateCommandRequest(vorgangId, bescheidItem)); + + await().atMost(60, TimeUnit.SECONDS).untilAsserted(() -> { + verify(bescheiTestEventListener).onBescheidSentEvent(bescheidSentEventCaptor.capture()); + assertThat(loadCommand(bescheidSentEventCaptor.getValue().getSource()).getStatus()).isEqualTo(CommandStatus.FINISHED); + }); + } + + @Test + void shouldSetBescheidStatusToSent() { + var vorgangId = mongoOperations.save(VorgangTestFactory.createBuilder().id(null).build()).getId(); + var bescheidItem = mongoOperations.save(createBescheidAttachedItemWithDocument(vorgangId)); + + eventPublisher.publishEvent(buildCommandCreatedEvent(vorgangId, bescheidItem)); + + await().atMost(60, TimeUnit.SECONDS).untilAsserted(() -> { + verify(bescheiTestEventListener).onBescheidSentEvent(bescheidSentEventCaptor.capture()); + assertThat(loadBescheid(bescheidItem.getId()).getItem()).containsEntry(Bescheid.FIELD_STATUS, Bescheid.Status.SEND.name()); + }); + } + + @Test + void shouldSetVorgangStatusToBeschieden() { + var vorgangId = mongoOperations.save(VorgangTestFactory.createBuilder().id(null).build()).getId(); + var bescheidItem = mongoOperations.save(createBescheidAttachedItemWithDocument(vorgangId)); + + commandService.createCommand(buildCreateCommandRequest(vorgangId, bescheidItem)); + + await().atMost(60, TimeUnit.SECONDS).untilAsserted(() -> { + verify(bescheiTestEventListener).onBescheidStatusChangedEvent(statusChangedEventCaptor.capture()); + assertThat(loadCommand(statusChangedEventCaptor.getValue().getSource()).getStatus()).isEqualTo(CommandStatus.FINISHED); + assertThat(loadVorgang(vorgangId).getStatus()).isEqualTo(Vorgang.Status.BESCHIEDEN); + }); + } + + @Test + void shouldSaveNachrichtDraft() { + var vorgangId = mongoOperations.save(VorgangTestFactory.createBuilder().id(null).build()).getId(); + var bescheidItem = mongoOperations.save(createBescheidAttachedItemWithDocument(vorgangId)); + + commandService.createCommand(buildCreateCommandRequest(vorgangId, bescheidItem)); + + await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { + verify(bescheiTestEventListener).onBescheidSentEvent(bescheidSentEventCaptor.capture()); + assertThat(loadCommand(bescheidSentEventCaptor.getValue().getSource()).getStatus()).isEqualTo(CommandStatus.FINISHED); + var nachrichtDraft = loadNachricht(vorgangId); + assertThat(nachrichtDraft.getItem()).containsEntry(PostfachNachricht.FIELD_SUBJECT, NACHRICHT_SUBJECT); + assertThat(nachrichtDraft.getItem()).containsEntry(PostfachNachricht.FIELD_MAIL_BODY, NACHRICHT_TEXT); + }); + } + + @Test + void shouldSetClientAttribute() { + var vorgangId = mongoOperations.save(VorgangTestFactory.createBuilder().id(null).build()).getId(); + var bescheidItem = mongoOperations.save(createBescheidAttachedItemWithDocument(vorgangId)); + + commandService.createCommand(buildCreateCommandRequest(vorgangId, bescheidItem)); + + await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { + verify(bescheiTestEventListener).onBescheidSentEvent(bescheidSentEventCaptor.capture()); + assertThat(loadCommand(bescheidSentEventCaptor.getValue().getSource()).getStatus()).isEqualTo(CommandStatus.FINISHED); + var vorgang = loadVorgang(vorgangId); + assertThat(vorgang.getClientAttributes()).containsKey(BescheidCallContextAttachingInterceptor.BESCHEID_MANAGER_CLIENT_NAME); + + }); + } + + private VorgangAttachedItem createBescheidAttachedItemWithDocument(String vorgangId) { + return VorgangAttachedItemTestFactory.createBuilder() + .id(null) + .version(0L) + .vorgangId(vorgangId) + .itemName(AttachedItemService.BESCHEID_ITEM_NAME) + .item(Map.of( + Bescheid.FIELD_STATUS, Bescheid.Status.DRAFT.name(), + Bescheid.FIELD_SEND_BY, Bescheid.SendBy.NACHRICHT.name(), + Bescheid.FIELD_BEWILLIGT, true, + Bescheid.FIELD_BESCHEID_DOCUMENT, "documentId", + Bescheid.FIELD_NACHRICHT_SUBJECT, NACHRICHT_SUBJECT, + Bescheid.FIELD_NACHRICHT_TEXT, NACHRICHT_TEXT)) + .build(); + } + + private CommandCreatedEvent buildCommandCreatedEvent(String vorgangId, VorgangAttachedItem bescheidItem) { + var command = CommandTestFactory.createBuilder() + .vorgangId(vorgangId) + .relationId(bescheidItem.getId()) + .relationVersion(bescheidItem.getVersion()) + .order(SEND_POSTFACH_MAIL_ORDER) + .bodyObject(Map.of(BESCHEID_VOM_BODYKEY, "2024-01-01", + GENEHMIGT_BODYKEY, true)) + .build(); + return new CommandCreatedEvent(command); + } + + private CreateCommandRequest buildCreateCommandRequest(String vorgangId, VorgangAttachedItem bescheidItem) { + return CreateCommandRequest.builder() + .vorgangId(vorgangId) + .relationId(bescheidItem.getId()) + .relationVersion(bescheidItem.getVersion()) + .order(SEND_POSTFACH_MAIL_ORDER) + .bodyObject(Map.of(BESCHEID_VOM_BODYKEY, "2024-01-01", + GENEHMIGT_BODYKEY, true)) + .build(); + } + + private Command loadCommand(String commandId) { + return mongoOperations.findById(commandId, Command.class); + } + + private VorgangAttachedItem loadBescheid(String bescheidId) { + return mongoOperations.findById(bescheidId, VorgangAttachedItem.class); + } + + private Vorgang loadVorgang(String vorgangId) { + return mongoOperations.findById(vorgangId, Vorgang.class); + } + + private VorgangAttachedItem loadNachricht(String vorgangId) { + var criteria = new Criteria().andOperator( + Criteria.where(VorgangAttachedItem.FIELDNAME_VORGANG_ID).is(vorgangId), + Criteria.where(VorgangAttachedItem.FIELDNAME_ITEM_NAME).is("PostfachMail") + ); + var vorgangAttachedItems = mongoOperations.find(Query.query(criteria), VorgangAttachedItem.class); + assertThat(vorgangAttachedItems).hasSize(1); + return vorgangAttachedItems.getFirst(); + } + } +} diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/bescheid/TestEventListener.java b/vorgang-manager-server/src/test/java/de/ozgcloud/bescheid/TestEventListener.java new file mode 100644 index 0000000000000000000000000000000000000000..1968b51b907dede97a4580038ac49b352729283d --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/bescheid/TestEventListener.java @@ -0,0 +1,49 @@ +/* + * 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.bescheid; + +import org.springframework.context.event.EventListener; + +import de.ozgcloud.command.CommandFailedEvent; +import de.ozgcloud.document.BescheidDocumentCreatedEvent; +import de.ozgcloud.vorgang.status.StatusChangedEvent; + +class TestEventListener { + + @EventListener + public void onBescheidDocumentCreated(BescheidDocumentCreatedEvent event) { + } + + @EventListener + public void onCommandFailed(CommandFailedEvent event) { + } + + @EventListener + public void onBescheidSentEvent(BescheidSentEvent event) { + } + + @EventListener + public void onBescheidStatusChangedEvent(StatusChangedEvent event) { + } +} diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/document/DocumentGrpcServiceITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/document/DocumentGrpcServiceITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..95720de7ca399b35508f54d47da2550cb858dadd --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/document/DocumentGrpcServiceITCase.java @@ -0,0 +1,110 @@ +/* + * 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.document; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.Map; +import java.util.Optional; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.test.annotation.DirtiesContext; + +import de.ozgcloud.bescheid.common.callcontext.CallContextUser; +import de.ozgcloud.bescheid.common.callcontext.CurrentUserService; +import de.ozgcloud.common.test.DataITCase; +import de.ozgcloud.document.DocumentServiceGrpc.DocumentServiceBlockingStub; +import de.ozgcloud.vorgang.VorgangManagerServerApplication; +import de.ozgcloud.vorgang.attached_item.VorgangAttachedItem; +import de.ozgcloud.vorgang.attached_item.VorgangAttachedItemTestFactory; +import io.grpc.StatusRuntimeException; +import net.devh.boot.grpc.client.inject.GrpcClient; + +@SpringBootTest(classes = VorgangManagerServerApplication.class, properties = { + "grpc.server.inProcessName=test", + "grpc.server.port=-1", + "grpc.client.vorgang-manager.address=in-process:test", + "grpc.client.inProcess.address=in-process:test", +}) +@DataITCase +@DirtiesContext +class DocumentGrpcServiceITCase { + + @GrpcClient("inProcess") + private DocumentServiceBlockingStub documentServiceStub; + + @Autowired + private MongoOperations mongoOperations; + + @MockBean + private CurrentUserService currentUserService; + @Mock + private CallContextUser callContextUser; + + @BeforeEach + void init() { + mongoOperations.dropCollection(VorgangAttachedItem.COLLECTION_NAME); + when(currentUserService.findUser()).thenReturn(Optional.of(callContextUser)); + when(currentUserService.getUser()).thenReturn(callContextUser); + } + + @Test + void shouldGetDocument() { + var documentId = saveDocument(); + + var response = documentServiceStub.getDocument(GrpcGetDocumentRequest.newBuilder().setId(documentId).build()); + + assertThat(response.hasDocument()).isTrue(); + } + + @Test + void shouldThrowExceptionWhenDocumentNotFound() { + saveDocument(); + var request = GrpcGetDocumentRequest.newBuilder().setId("not-found").build(); + + Assertions.assertThrows(StatusRuntimeException.class, () -> documentServiceStub.getDocument(request)); + } + + private String saveDocument() { + var document = VorgangAttachedItemTestFactory.createBuilder() + .id(null) + .version(0) + .itemName(DocumentService.DOCUMENT_ITEM_NAME) + .item(Map.of( + Document.FIELD_DOCUMENT_TYPE, DocumentService.DOCUMENT_TYPE, + Document.FIELD_DOCUMENT_FILE, "file-id", + Document.FIELD_NACHRICHT_TEXT, "nachricht-text", + Document.FIELD_NACHRICHT_SUBJECT, "nachricht-subject")) + .build(); + return mongoOperations.save(document, VorgangAttachedItem.COLLECTION_NAME).getId(); + } +} diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/nachrichten/postfach/PostfachMailITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/nachrichten/postfach/PostfachMailITCase.java index 055a48e234bab978031fcbfa3d91176df3c20640..8db9ce23a49f33ee2b2a058eb33906fd56882813 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/nachrichten/postfach/PostfachMailITCase.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/nachrichten/postfach/PostfachMailITCase.java @@ -150,7 +150,7 @@ class PostfachMailITCase { assertThat(mails.get(0).getCreatedAt()).isNotNull(); assertThat(mails.get(0).getDirection()).isEqualTo(GrpcDirection.OUT); assertThat(mails.get(0).getSentAt()).isNotNull(); - assertThat(ZonedDateTime.parse(mails.get(0).getSentAt())).isCloseTo(ZonedDateTime.now(), within(2, ChronoUnit.SECONDS)); + assertThat(ZonedDateTime.parse(mails.get(0).getSentAt())).isCloseTo(ZonedDateTime.now(), within(61, ChronoUnit.SECONDS)); assertThat(mails.get(0).getSentSuccessful()).isTrue(); } } @@ -175,7 +175,7 @@ class PostfachMailITCase { await().atMost(60, TimeUnit.SECONDS).untilAsserted(() -> { assertThat(callGrpcListEndpoint()).hasSize(1).first().satisfies(mail -> { assertThat(mail.getSentAt()).isNotNull(); - assertThat(ZonedDateTime.parse(mail.getSentAt())).isCloseTo(ZonedDateTime.now(), within(2, ChronoUnit.SECONDS)); + assertThat(ZonedDateTime.parse(mail.getSentAt())).isCloseTo(ZonedDateTime.now(), within(61, ChronoUnit.SECONDS)); assertThat(mail.getSentSuccessful()).isFalse(); }); });