diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/Order.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/Order.java index 5a4c25ad185ea39ac6722fad8e27e0cf96115d91..cdc8f9ed1fe03bd3c28231b45ba909c24500d5f0 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/Order.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/Order.java @@ -50,6 +50,7 @@ public enum Order { REDIRECT_VORGANG, FORWARD_SUCCESSFULL, FORWARD_FAILED, + FORWARD_VORGANG, SEND_POSTFACH_MAIL, RESEND_POSTFACH_MAIL, diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/status/StatusService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/status/StatusService.java index a87a9901cf0418b8a55306b51c878b4902800888..bd9df2149be5a9a02336c5916df7f6ce650302e5 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/status/StatusService.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/status/StatusService.java @@ -29,7 +29,6 @@ import java.util.Objects; import java.util.Optional; import org.apache.commons.collections4.MapUtils; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; @@ -41,18 +40,17 @@ import de.ozgcloud.common.errorhandling.TechnicalException; import de.ozgcloud.vorgang.command.PersistedCommand; import de.ozgcloud.vorgang.vorgang.Vorgang; import de.ozgcloud.vorgang.vorgang.Vorgang.Status; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; @Service +@RequiredArgsConstructor @Log4j2 public class StatusService { private static final String CONCURRENT_MODIFICATION_ERROR_CODE = "concurrent_modification"; - @Autowired - private ApplicationEventPublisher publisher; - - @Autowired - private StatusRepository repository; + private final ApplicationEventPublisher publisher; + private final StatusRepository repository; void setStatusNeu(Command command) { executeStatusChangeCommand(command, Status.NEU); @@ -100,6 +98,10 @@ public class StatusService { doUpdateStatus(vorgangId, version, Status.IN_BEARBEITUNG.name()); } + public void setStatusToWeitergeleitet(String vorgangId, long version) { + doUpdateStatus(vorgangId, version, Status.WEITERGELEITET.name()); + } + public void revokeStatusChange(Command command) { try { doUpdateStatus(command.getRelationId(), command.getRelationVersion() + 1, diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/Lock.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/Lock.java index a888e0ea88582596dc39af16e2007bc66a507c3e..51060326c1bad411cfa98d93886bdf33576c7d8e 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/Lock.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/Lock.java @@ -25,19 +25,23 @@ package de.ozgcloud.vorgang.vorgang; import java.time.ZonedDateTime; +import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; -@Builder +@Builder(toBuilder = true) @Getter -class Lock { +public class Lock { static final String FIELD_CLIENT_NAME = "clientName"; static final String FIELD_LOCKED_SINCE = "lockedSince"; static final String FIELD_REASON = "reason"; + @NotNull private final String clientName; + @NotNull private final ZonedDateTime lockedSince; + @NotNull private final String reason; } diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/LockMapper.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/LockMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..8260a3e36fbe4cb35e6f4ada6a932480ddb25c69 --- /dev/null +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/LockMapper.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.vorgang; + +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Map; + +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +import de.ozgcloud.command.Command; + +@Mapper +public interface LockMapper { + + @Mapping(target = "clientName", source = "createdByClientName") + @Mapping(target = "lockedSince", expression = "java(getLockedSince())") + @Mapping(target = "reason", source = "bodyObject") + Lock fromCommand(Command command); + + default String getReason(Map<String, Object> bodyObject) { + return StringUtils.trimToNull(MapUtils.getString(bodyObject, Lock.FIELD_REASON)); + } + + default ZonedDateTime getLockedSince() { + return ZonedDateTime.now(ZoneOffset.UTC); + } +} diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangEventListener.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangEventListener.java index be036bb8112e61cf4047e4591806ea13caf39282..d44544422fb18c593bf381db34cd515b612f116d 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangEventListener.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangEventListener.java @@ -37,6 +37,7 @@ import de.ozgcloud.command.CommandFailedEvent; import de.ozgcloud.command.CommandRevokeFailedEvent; import de.ozgcloud.command.CommandRevokedEvent; import de.ozgcloud.command.RevokeCommandEvent; +import de.ozgcloud.command.VorgangLockedEvent; import de.ozgcloud.command.VorgangUnlockedEvent; import de.ozgcloud.vorgang.command.CommandService; import de.ozgcloud.vorgang.command.Order; @@ -72,6 +73,7 @@ public class VorgangEventListener { private final VorgangHeaderService vorgangHeaderService; private final FileService fileService; private final CommandService commandService; + private final LockMapper lockMapper; private final ApplicationEventPublisher publisher; @@ -122,7 +124,8 @@ public class VorgangEventListener { public void onLockVorgang(CommandCreatedEvent event) { var command = event.getSource(); try { - vorgangService.lockVorgang(command); + vorgangService.lockVorgang(lockMapper.fromCommand(command), command.getVorgangId(), command.getRelationVersion()); + publisher.publishEvent(new VorgangLockedEvent(command)); } catch (RuntimeException e) { LOG.error("Error locking vorgang.", e); publisher.publishEvent(new CommandFailedEvent(command.getId(), e.getMessage())); diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangService.java index 3cbc342a0f46aad3c4131a1d2033996f0d372eb2..a4690f4c7a4ebc99ea5aaa2a25ce0b4d8f7b28a0 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangService.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangService.java @@ -23,13 +23,10 @@ */ package de.ozgcloud.vorgang.vorgang; -import java.time.ZoneId; -import java.time.ZonedDateTime; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; @@ -44,8 +41,6 @@ import org.springframework.validation.annotation.Validated; import de.ozgcloud.command.Command; import de.ozgcloud.command.VorgangAssignedEvent; import de.ozgcloud.command.VorgangCreatedEvent; -import de.ozgcloud.command.VorgangLockedEvent; -import de.ozgcloud.common.errorhandling.TechnicalException; import de.ozgcloud.vorgang.clientattribute.ClientAttributeMap; import de.ozgcloud.vorgang.clientattribute.ClientAttributeReadPermitted; import de.ozgcloud.vorgang.clientattribute.ClientAttributesMap; @@ -223,30 +218,15 @@ public class VorgangService { return repository.findById(vorgangId).orElseThrow(() -> new NotFoundException(Vorgang.class, vorgangId)); } - public void lockVorgang(Command command) { - validateLockCommand(command); - repository.patch(command.getVorgangId(), command.getRelationVersion(), buildLockPatch(command)); - publisher.publishEvent(new VorgangLockedEvent(command)); + public void lockVorgang(@Valid Lock lock, String vorgangId, Long version) { + repository.patch(vorgangId, version, buildLockPatch(lock)); } - void validateLockCommand(Command command) { - if (Objects.isNull(command.getCreatedByClientName())) { - throw new TechnicalException("Missing client name in lock command"); - } - if (Objects.isNull(getReason(command.getBodyObject()))) { - throw new TechnicalException("Missing reason in lock command"); - } - } - - Map<String, Object> buildLockPatch(Command command) { + Map<String, Object> buildLockPatch(Lock lock) { return Map.of(KEY_HEADER_LOCK, Map.of( - Lock.FIELD_CLIENT_NAME, command.getCreatedByClientName(), - Lock.FIELD_LOCKED_SINCE, ZonedDateTime.now(ZoneId.of("UTC")), - Lock.FIELD_REASON, getReason(command.getBodyObject()))); - } - - String getReason(Map<String, Object> commandBody) { - return StringUtils.trimToNull(MapUtils.getString(commandBody, Lock.FIELD_REASON)); + Lock.FIELD_CLIENT_NAME, lock.getClientName(), + Lock.FIELD_LOCKED_SINCE, lock.getLockedSince(), + Lock.FIELD_REASON, lock.getReason())); } public boolean isVorgangLocked(String vorgangId) { diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/Address.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/Address.java new file mode 100644 index 0000000000000000000000000000000000000000..b15442afab102d783a5b10787a274f87604a3a34 --- /dev/null +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/Address.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.vorgang.redirect; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +class Address { + + private String street; + private String houseNumber; + private String zipCode; + private String city; +} diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingEventListener.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingEventListener.java index d84484bc860b99521ce9a382437d96ee17c73b17..7303a7b8517aba740e58a4a295afc01bc6b4001e 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingEventListener.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingEventListener.java @@ -26,7 +26,6 @@ package de.ozgcloud.vorgang.vorgang.redirect; import java.util.ConcurrentModificationException; import java.util.function.Predicate; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; @@ -34,34 +33,41 @@ import org.springframework.stereotype.Component; import de.ozgcloud.command.Command; import de.ozgcloud.command.CommandCreatedEvent; import de.ozgcloud.command.CommandFailedEvent; +import de.ozgcloud.command.VorgangLockedEvent; import de.ozgcloud.nachrichten.email.MailSendErrorEvent; import de.ozgcloud.nachrichten.email.MailSendRequest; import de.ozgcloud.nachrichten.email.MailSentEvent; import de.ozgcloud.vorgang.command.Order; +import de.ozgcloud.vorgang.vorgang.LockMapper; +import de.ozgcloud.vorgang.vorgang.VorgangService; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; @Component +@RequiredArgsConstructor @Log4j2 public class ForwardingEventListener { private static final String IS_REDIRECT_EVENT_CONDITION = "{T(de.ozgcloud.vorgang.vorgang.redirect.ForwardingEventListener).IS_REDIRECT_MAIL_REQ.test(event.getSource())}"; private static final String IS_SUCCESSFULL_ORDER_CONDITION = "{T(de.ozgcloud.vorgang.vorgang.redirect.ForwardingEventListener).IS_SUCCESSFULL_ORDER.test(event.getSource())}"; private static final String IS_FAILED_ORDER_CONDITION = "{T(de.ozgcloud.vorgang.vorgang.redirect.ForwardingEventListener).IS_FAILED_ORDER.test(event.getSource())}"; - private static final String IS_FORWARD_ORDER_CONDITION = "{T(de.ozgcloud.vorgang.vorgang.redirect.ForwardingEventListener).IS_FORWARD_ORDER.test(event.getSource())}"; + private static final String IS_REDIRECT_VORGANG_ORDER_CONDITION = "{T(de.ozgcloud.vorgang.vorgang.redirect.ForwardingEventListener).IS_REDIRECT_VORGANG_ORDER.test(event.getSource())}"; + private static final String IS_FORWARD_VORGANG_ORDER_CONDITION = "{T(de.ozgcloud.vorgang.vorgang.redirect.ForwardingEventListener).IS_FORWARD_VORGANG_ORDER.test(event.getSource())}"; public static final Predicate<MailSendRequest> IS_REDIRECT_MAIL_REQ = req -> req.getRequestReference() instanceof Forwarding; public static final Predicate<Command> IS_SUCCESSFULL_ORDER = command -> Order.FORWARD_SUCCESSFULL.isMeant(command.getOrder()); public static final Predicate<Command> IS_FAILED_ORDER = command -> Order.FORWARD_FAILED.isMeant(command.getOrder()); - public static final Predicate<Command> IS_FORWARD_ORDER = command -> Order.REDIRECT_VORGANG.isMeant(command.getOrder()); + public static final Predicate<Command> IS_REDIRECT_VORGANG_ORDER = command -> Order.REDIRECT_VORGANG.isMeant(command.getOrder()); + public static final Predicate<Command> IS_FORWARD_VORGANG_ORDER = command -> Order.FORWARD_VORGANG.isMeant(command.getOrder()); - @Autowired - private ForwardingService service; + private final ForwardingService service; + private final ApplicationEventPublisher publisher; + private final ForwardingRequestMapper forwardingRequestMapper; + private final VorgangService vorgangService; + private final LockMapper lockMapper; - @Autowired - private ApplicationEventPublisher publisher; - - @EventListener(condition = IS_FORWARD_ORDER_CONDITION) - public void onForwardOrder(CommandCreatedEvent event) { + @EventListener(condition = IS_REDIRECT_VORGANG_ORDER_CONDITION) + public void onRedirectVorgangOrder(CommandCreatedEvent event) { handleException(() -> service.forwardByCommand(event.getSource()), event.getSource().getId()); } @@ -89,6 +95,26 @@ public class ForwardingEventListener { service.markAsFailed(event.getSource()); } + @EventListener(condition = IS_FORWARD_VORGANG_ORDER_CONDITION) + public void onForwardVorgangOrder(CommandCreatedEvent event) { + handleException(() -> handleForwardVorgangCommand(event.getSource()), event.getSource().getId()); + } + + void handleForwardVorgangCommand(Command command) { + lockVorgangOnForwarding(command); + service.forward(incrementVersion(forwardingRequestMapper.fromCommand(command))); + publisher.publishEvent(new VorgangLockedEvent(command)); + } + + private void lockVorgangOnForwarding(Command command) { + var lock = lockMapper.fromCommand(command).toBuilder().reason("Vorgang was forwarded").build(); + vorgangService.lockVorgang(lock, command.getVorgangId(), command.getRelationVersion()); + } + + private ForwardingRequest incrementVersion(ForwardingRequest request) { + return request.toBuilder().version(request.getVersion() + 1).build(); + } + private void handleException(Runnable runnable, String commandId) { try { runnable.run(); diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRequest.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..25179865679a4100697b767fb850f8be54a3c7dd --- /dev/null +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRequest.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.vorgang.redirect; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder(toBuilder = true) +public class ForwardingRequest { + + private String vorgangId; + private long version; + private String createdBy; + private String createdByName; + private OrganisationEinheit organisationEinheit; +} diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRequestMapper.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRequestMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..e47ae180e85e18dc7466278b994f02f1886ce28b --- /dev/null +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRequestMapper.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.vorgang.redirect; + +import java.util.Map; + +import org.apache.commons.collections4.MapUtils; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +import de.ozgcloud.command.Command; + +@Mapper(imports = MapUtils.class) +interface ForwardingRequestMapper { + + interface CommandBodyFields { + String ORGANISATION_EINHEIT_ID = "organisationEinheitId"; + String NAME = "name"; + String STRASSE = "strasse"; + String HAUSNUMMER = "hausnummer"; + String PLZ = "plz"; + String ORT = "ort"; + } + + @Mapping(target = "version", source = "relationVersion") + @Mapping(target = "organisationEinheit", source = "bodyObject") + ForwardingRequest fromCommand(Command command); + + @Mapping(target = "id", expression = "java(MapUtils.getString(body, CommandBodyFields.ORGANISATION_EINHEIT_ID))") + @Mapping(target = "name", expression = "java(MapUtils.getString(body, CommandBodyFields.NAME))") + @Mapping(target = "address", source = ".") + OrganisationEinheit toOrganisationEinheit(Map<String, Object> body); + + @Mapping(target = "street", expression = "java(MapUtils.getString(body, CommandBodyFields.STRASSE))") + @Mapping(target = "houseNumber", expression = "java(MapUtils.getString(body, CommandBodyFields.HAUSNUMMER))") + @Mapping(target = "zipCode", expression = "java(MapUtils.getString(body, CommandBodyFields.PLZ))") + @Mapping(target = "city", expression = "java(MapUtils.getString(body, CommandBodyFields.ORT))") + Address toAddress(Map<String, Object> body); +} diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingService.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingService.java index 7af3879a0b86aa557a864fc13b32889b986eeb0d..ed26c9e9850a1361531b70b36699b5ca92c08596 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingService.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingService.java @@ -329,4 +329,8 @@ public class ForwardingService { public Stream<Forwarding> findForwardings(String vorgangId) { return repository.findByVorgangId(vorgangId).stream(); } + + public void forward(ForwardingRequest request) { + statusService.setStatusToWeitergeleitet(request.getVorgangId(), request.getVersion()); + } } \ No newline at end of file diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/OrganisationEinheit.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/OrganisationEinheit.java new file mode 100644 index 0000000000000000000000000000000000000000..8465f4180d3f235292f899aa1bd1fe481411f10d --- /dev/null +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/redirect/OrganisationEinheit.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.vorgang.redirect; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class OrganisationEinheit { + + private String id; + private String name; + private Address address; +} diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/status/StatusServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/status/StatusServiceTest.java index 17e4694ec99c33cd92c2a8f61a07e36d3d26045e..eb96cbe9536c70f7a2a5ced05407b3c311d4d840 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/status/StatusServiceTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/status/StatusServiceTest.java @@ -39,6 +39,8 @@ import org.mockito.Mock; import org.mockito.Spy; import org.springframework.context.ApplicationEventPublisher; +import com.thedeanda.lorem.LoremIpsum; + import de.ozgcloud.command.Command; import de.ozgcloud.command.CommandFailedEvent; import de.ozgcloud.command.CommandRevokedEvent; @@ -52,18 +54,17 @@ import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; class StatusServiceTest { + @Spy + @InjectMocks + private StatusService service; + @Mock + private StatusRepository repository; + @Mock + private ApplicationEventPublisher publisher; + @DisplayName("Test handling vorgang status changes") @Nested class TestVorgangStatusChanges { - @Spy - @InjectMocks - private StatusService service; - - @Mock - private StatusRepository repository; - - @Mock - private ApplicationEventPublisher publisher; private PersistedCommand command = CommandTestFactory.create(); @@ -261,15 +262,6 @@ class StatusServiceTest { @DisplayName("Test status changes when a Vorgang is forwarded") @Nested class TestVorgangWeiterleiten { - @Spy - @InjectMocks - private StatusService service; - - @Mock - private StatusRepository repository; - - @Mock - private ApplicationEventPublisher publisher; @Mock private PersistedCommand command; @@ -299,15 +291,6 @@ class StatusServiceTest { @DisplayName("Test handling revoke command status changes") @Nested class TestRevokeStatusChanges { - @Spy - @InjectMocks - private StatusService service; - - @Mock - private StatusRepository repository; - - @Mock - private ApplicationEventPublisher publisher; private static final String PREVIOUS_STATUS = Vorgang.Status.BESCHIEDEN.name(); private PersistedCommand command = CommandTestFactory.createBuilder() @@ -381,4 +364,28 @@ class StatusServiceTest { } } } + + @Nested + class TestSetStatusToWeitergeleitet { + + @Test + void shouldUpdateStatus() { + service.setStatusToWeitergeleitet(VorgangTestFactory.ID, VorgangTestFactory.VERSION); + + verify(service).doUpdateStatus(VorgangTestFactory.ID, VorgangTestFactory.VERSION, Status.WEITERGELEITET.name()); + } + } + + @Nested + class TestDoUpdateStatus { + + @Test + void shouldPatchVorgangWithNewStatus() { + var statusName = LoremIpsum.getInstance().getWords(1); + + service.doUpdateStatus(VorgangTestFactory.ID, VorgangTestFactory.VERSION, statusName); + + verify(repository).patch(VorgangTestFactory.ID, VorgangTestFactory.VERSION, Map.of(Vorgang.MONGODB_FIELDNAME_STATUS, statusName)); + } + } } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/LockMapperTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/LockMapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2d0d9f17fa4c9435bc8b58674a41271867c9ae3b --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/LockMapperTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.vorgang; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mapstruct.factory.Mappers; +import org.mockito.Spy; + +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.command.Command; +import de.ozgcloud.vorgang.command.CommandTestFactory; + +class LockMapperTest { + + @Spy + private LockMapper mapper = Mappers.getMapper(LockMapper.class); + + @Nested + class TestFromCommand { + + private final Command command = CommandTestFactory.createBuilder().bodyObject(Map.of(Lock.FIELD_REASON, LockTestFactory.REASON)).build(); + + @BeforeEach + void init() { + doReturn(LockTestFactory.REASON).when(mapper).getReason(any()); + doReturn(LockTestFactory.LOCKED_SINCE).when(mapper).getLockedSince(); + } + + @Test + void shouldGetLockedSince() { + map(); + + verify(mapper).getLockedSince(); + } + + @Test + void shouldGetReason() { + map(); + + verify(mapper).getReason(command.getBodyObject()); + } + + @Test + void shouldMap() { + var mapped = map(); + + assertThat(mapped).usingRecursiveComparison().isEqualTo(LockTestFactory.create()); + } + + private Lock map() { + return mapper.fromCommand(command); + } + } + + @Nested + class TestGetReason { + + @Test + void shouldReturnNullIfKeyNotFound() { + var mapped = mapper.getReason(Map.of("dummyKey", "dummyValue")); + + assertThat(mapped).isNull(); + } + + @Test + void shouldReturnNullIfValueIsNull() { + Map<String, Object> bodyObject = new HashMap<>(); + bodyObject.put(Lock.FIELD_REASON, null); // need to put, because Map.of() does not accept null values + + var mapped = mapper.getReason(bodyObject); + + assertThat(mapped).isNull(); + } + + @Test + void shouldReturnReason() { + var reason = LoremIpsum.getInstance().getWords(2); + Map<String, Object> bodyObject = Map.of(Lock.FIELD_REASON, reason); + + var mapped = mapper.getReason(bodyObject); + + assertThat(mapped).isEqualTo(reason); + } + + @ParameterizedTest + @ValueSource(strings = { " " }) + @NullAndEmptySource + void shouldTrimToNull(String reason) { + Map<String, Object> bodyObject = new HashMap<>(); + bodyObject.put(Lock.FIELD_REASON, reason); // need to put, because Map.of() does not accept null values + + var mapped = mapper.getReason(bodyObject); + + assertThat(mapped).isNull(); + } + } + + @Nested + class TestLockedSince { + + @Test + void shouldReturnCurrentTime() { + var lockedSince = mapper.getLockedSince(); + + assertThat(lockedSince).isCloseTo(ZonedDateTime.now(ZoneOffset.UTC), within(2, ChronoUnit.SECONDS)); + } + } +} diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangEventListenerTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangEventListenerTest.java index d8bb400c4d00f2573bbc4a922e3991d1155ada24..68c76516d74789d6b7dd2a287e39f6fd6a89767b 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangEventListenerTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangEventListenerTest.java @@ -50,6 +50,7 @@ import de.ozgcloud.command.CommandFailedEvent; import de.ozgcloud.command.CommandRevokeFailedEvent; import de.ozgcloud.command.CommandRevokedEvent; import de.ozgcloud.command.RevokeCommandEvent; +import de.ozgcloud.command.VorgangLockedEvent; import de.ozgcloud.command.VorgangUnlockedEvent; import de.ozgcloud.common.errorhandling.TechnicalException; import de.ozgcloud.vorgang.command.CommandCreatedEventTestFactory; @@ -77,6 +78,8 @@ class VorgangEventListenerTest { private FileService fileService; @Mock private CommandService commandService; + @Mock + private LockMapper lockMapper; @Mock private ApplicationEventPublisher publisher; @@ -239,32 +242,66 @@ class VorgangEventListenerTest { @Nested class TestOnLockVorgang { + private final Command command = CommandTestFactory.create(); + private final Lock lock = LockTestFactory.create(); @Captor - private ArgumentCaptor<CommandFailedEvent> eventCaptor; + private ArgumentCaptor<VorgangLockedEvent> lockedEventCaptor; + @Captor + private ArgumentCaptor<CommandFailedEvent> failedEventCaptor; + + @BeforeEach + void init() { + when(lockMapper.fromCommand(any())).thenReturn(lock); + } + + @Test + void shouldMapToLock() { + onLockVorgang(); + + verify(lockMapper).fromCommand(command); + } @Test void shouldCallLockVorgang() { - var command = CommandTestFactory.create(); + onLockVorgang(); - listener.onLockVorgang(CommandCreatedEventTestFactory.create(command)); + verify(service).lockVorgang(lock, VorgangTestFactory.ID, CommandTestFactory.RELATION_VERSION); + } + + @Test + void shouldPublishVorgangLockedEvent() { + onLockVorgang(); - verify(service).lockVorgang(command); + verify(publisher).publishEvent(lockedEventCaptor.capture()); + assertThat(lockedEventCaptor.getValue().getCommand()).isSameAs(command); } @Test void shouldPublishCommandFailedEvent() { - var command = CommandTestFactory.create(); var errorMessage = "error message"; - doThrow(new RuntimeException(errorMessage)).when(service).lockVorgang(command); + doThrow(new RuntimeException(errorMessage)).when(service).lockVorgang(any(), any(), anyLong()); - listener.onLockVorgang(CommandCreatedEventTestFactory.create(command)); + onLockVorgang(); - verify(publisher).publishEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue()).satisfies(event -> { + verify(publisher).publishEvent(failedEventCaptor.capture()); + assertThat(failedEventCaptor.getValue()).satisfies(event -> { assertThat(event.getSource()).isEqualTo(CommandTestFactory.ID); assertThat(event.getErrorMessage()).isEqualTo(errorMessage); }); } + + @Test + void shouldNotPublishVorgangLockedEventOnFailure() { + doThrow(new RuntimeException("error message")).when(service).lockVorgang(any(), any(), anyLong()); + + onLockVorgang(); + + verify(publisher, never()).publishEvent(any(VorgangLockedEvent.class)); + } + + private void onLockVorgang() { + listener.onLockVorgang(CommandCreatedEventTestFactory.create(command)); + } } @Nested diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangServiceITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangServiceITCase.java index 1ddb4dee4a51eee8accad0ed3cf06c913228bddf..96de483c50b06c808794631fe5de47ec0fcd00f6 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangServiceITCase.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangServiceITCase.java @@ -29,20 +29,19 @@ import static org.mockito.Mockito.*; import java.util.List; -import jakarta.validation.ConstraintViolationException; - import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import de.ozgcloud.common.test.DataITCase; import de.ozgcloud.notification.antragsteller.AntragstellerNotificationEventListener; import de.ozgcloud.notification.user.UserNotificationEventListener; import de.ozgcloud.vorgang.command.CommandService; import de.ozgcloud.vorgang.servicekonto.ServiceKontoTestFactory; +import jakarta.validation.ConstraintViolationException; @DataITCase @WithMockUser @@ -51,13 +50,11 @@ class VorgangServiceITCase { @Autowired private VorgangService vorgangService; - @MockBean + @MockitoBean private CommandService commandService; - - @MockBean + @MockitoBean private AntragstellerNotificationEventListener antragstellerNotificationEventListener; - - @MockBean + @MockitoBean private UserNotificationEventListener userNotificationEventListener; @DisplayName("Test creating a new Vorgang") @@ -92,11 +89,49 @@ class VorgangServiceITCase { @Test void shouldBeInvalidOnMissingPostfachAddress() { var eingang = EingangTestFactory.createBuilder().header(EingangHeaderTestFactory.createBuilder() - .serviceKonto(ServiceKontoTestFactory.createBuilder().clearPostfachAddresses().build()).build()) + .serviceKonto(ServiceKontoTestFactory.createBuilder().clearPostfachAddresses().build()).build()) .build(); assertThatThrownBy(() -> vorgangService.startCreation(eingang)).isInstanceOf(ConstraintViolationException.class); } } } + + @Nested + class TestLockVorgang { + + @Nested + class TestValidation { + + @Test + void shouldBeInvalidOnMissingClientName() { + var lock = LockTestFactory.createBuilder().clientName(null).build(); + + assertThatThrownBy(() -> vorgangService.lockVorgang(lock, VorgangTestFactory.ID, VorgangTestFactory.VERSION)).isInstanceOf( + ConstraintViolationException.class); + } + + @Test + void shouldBeInvalidOnMissingLockedSince() { + var lock = LockTestFactory.createBuilder().lockedSince(null).build(); + + assertThatThrownBy(() -> vorgangService.lockVorgang(lock, VorgangTestFactory.ID, VorgangTestFactory.VERSION)).isInstanceOf( + ConstraintViolationException.class); + } + + @Test + void shouldBeInvalidOnMissingReason() { + var lock = LockTestFactory.createBuilder().reason(null).build(); + + assertThatThrownBy(() -> vorgangService.lockVorgang(lock, VorgangTestFactory.ID, VorgangTestFactory.VERSION)).isInstanceOf( + ConstraintViolationException.class); + } + + @Test + void shouldBeValidWhenRequiredFieldsAreNotNull() { + assertThatNoException().isThrownBy( + () -> vorgangService.lockVorgang(LockTestFactory.create(), VorgangTestFactory.ID, VorgangTestFactory.VERSION)); + } + } + } } \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangServiceTest.java index 3449659b130bca698edbf9b3b88daf73fe867efc..dd60f6033b802cfcf4909ad46d8420ee73119dad 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangServiceTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/VorgangServiceTest.java @@ -28,13 +28,10 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Map; import java.util.Optional; -import org.assertj.core.api.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -53,7 +50,6 @@ import de.ozgcloud.command.Command; import de.ozgcloud.command.VorgangAssignedEvent; import de.ozgcloud.command.VorgangCreatedEvent; import de.ozgcloud.command.VorgangLockedEvent; -import de.ozgcloud.common.errorhandling.TechnicalException; import de.ozgcloud.vorgang.clientattribute.ClientAttributeReadPermitted; import de.ozgcloud.vorgang.command.CommandTestFactory; import de.ozgcloud.vorgang.common.errorhandling.NotFoundException; @@ -352,7 +348,8 @@ class VorgangServiceTest { void shouldThrowAccessDeniedException() { when(vorgangAuthorizationService.authorizeByOrganisationseinheitenId(any(), any())).thenReturn(false); - assertThrows(AccessDeniedException.class, () -> service.getById(VorgangTestFactory.ID, FilterCriteriaTestFactory.create())); // NOSONAR + assertThrows(AccessDeniedException.class, + () -> service.getById(VorgangTestFactory.ID, FilterCriteriaTestFactory.create())); // NOSONAR } } @@ -516,7 +513,7 @@ class VorgangServiceTest { @Nested class TestLockVorgang { - private static final Command LOCK_COMMAND = CommandTestFactory.create(); + private final Lock lock = LockTestFactory.create(); private static final Map<String, Object> LOCK_PATCH = Map.of("key", "value"); @Captor @@ -524,92 +521,53 @@ class VorgangServiceTest { @BeforeEach void init() { - doNothing().when(service).validateLockCommand(any()); doReturn(LOCK_PATCH).when(service).buildLockPatch(any()); } - @Test - void shouldCallValidateLockCommand() { - service.lockVorgang(LOCK_COMMAND); - - verify(service).validateLockCommand(LOCK_COMMAND); - } - @Test void shouldCallBuildLockPatch() { - service.lockVorgang(LOCK_COMMAND); + lockVorgang(); - verify(service).buildLockPatch(LOCK_COMMAND); + verify(service).buildLockPatch(lock); } @Test void shouldCallRepository() { - service.lockVorgang(LOCK_COMMAND); + lockVorgang(); verify(repository).patch(VorgangTestFactory.ID, CommandTestFactory.RELATION_VERSION, LOCK_PATCH); } - @Test - void shouldCallPublishEvent() { - service.lockVorgang(LOCK_COMMAND); - - verify(publisher).publishEvent(eventCaptor.capture()); - assertThat(eventCaptor.getValue().getCommand()).isSameAs(LOCK_COMMAND); - } - } - - @Nested - class TestValidateLockCommand { - - @Test - void shouldThrowExceptionOnMissingClient() { - var command = CommandTestFactory.createBuilder().createdByClientName(null).build(); - - assertThrows(TechnicalException.class, () -> service.validateLockCommand(command)); - } - - @Test - void shouldThrowExceptionOnMissingReason() { - var command = CommandTestFactory.createBuilder().bodyObject(Map.of()).build(); - - assertThrows(TechnicalException.class, () -> service.validateLockCommand(command)); - } - - @Test - void shouldAcceptValidCommand() { - var command = CommandTestFactory.createBuilder().bodyObject(Map.of(Lock.FIELD_REASON, LockTestFactory.REASON)).build(); - - assertDoesNotThrow(() -> service.validateLockCommand(command)); + private void lockVorgang() { + service.lockVorgang(lock, VorgangTestFactory.ID, VorgangTestFactory.VERSION); } } @Nested class TestBuildLockPatch { - private final Command command = CommandTestFactory.createBuilder().bodyObject(Map.of(Lock.FIELD_REASON, LockTestFactory.REASON)).build(); - @Test void shouldSetClientName() { - var result = service.buildLockPatch(command); + var result = service.buildLockPatch(LockTestFactory.create()); assertThat(result).extractingByKey(VorgangService.KEY_HEADER_LOCK, MAP) - .containsEntry(Lock.FIELD_CLIENT_NAME, CommandTestFactory.CREATED_BY_CLIENT); + .containsEntry(Lock.FIELD_CLIENT_NAME, LockTestFactory.CLIENT_NAME); } @Test void shouldSetLockedSince() { - var result = service.buildLockPatch(command); + var result = service.buildLockPatch(LockTestFactory.create()); assertThat(result).extractingByKey(VorgangService.KEY_HEADER_LOCK, MAP) - .extractingByKey(Lock.FIELD_LOCKED_SINCE, InstanceOfAssertFactories.ZONED_DATE_TIME) - .isCloseTo(ZonedDateTime.now(), within(1, ChronoUnit.SECONDS)); + .containsEntry(Lock.FIELD_LOCKED_SINCE, LockTestFactory.LOCKED_SINCE); } @Test void shouldSetReason() { - var result = service.buildLockPatch(command); + var result = service.buildLockPatch(LockTestFactory.create()); - assertThat(result).extractingByKey(VorgangService.KEY_HEADER_LOCK, MAP).containsEntry(Lock.FIELD_REASON, LockTestFactory.REASON); + assertThat(result).extractingByKey(VorgangService.KEY_HEADER_LOCK, MAP) + .containsEntry(Lock.FIELD_REASON, LockTestFactory.REASON); } } @@ -760,7 +718,6 @@ class VorgangServiceTest { } } - @Nested class TestFindDeleted { diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/AddressTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/AddressTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..69d3d4e215f8aac71fd2b967d98b8212aa3d3b61 --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/AddressTestFactory.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.vorgang.redirect; + +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.vorgang.vorgang.redirect.Address.AddressBuilder; + +class AddressTestFactory { + + public static final String STREET = LoremIpsum.getInstance().getName(); + public static final String HOUSE_NUMBER = "42a"; + public static final String ZIP_CODE = LoremIpsum.getInstance().getZipCode(); + public static final String CITY = LoremIpsum.getInstance().getCity(); + + public static Address create() { + return createBuilder().build(); + } + + public static AddressBuilder createBuilder() { + return new AddressBuilder() + .street(STREET) + .houseNumber(HOUSE_NUMBER) + .zipCode(ZIP_CODE) + .city(CITY); + } +} diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingEventListenerITCase.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingEventListenerITCase.java index d196e31bcf0830fdbb0f670a5c7c6f574bd3ab96..52046ba411a9129129ef0a9f29a63306ddeb7c47 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingEventListenerITCase.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingEventListenerITCase.java @@ -23,29 +23,35 @@ */ package de.ozgcloud.vorgang.vorgang.redirect; +import static org.assertj.core.api.Assertions.*; import static org.awaitility.Awaitility.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; 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.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.ApplicationEventPublisher; import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import de.ozgcloud.command.Command; import de.ozgcloud.common.test.ITCase; import de.ozgcloud.nachrichten.email.MailSentEventTestFactory; import de.ozgcloud.vorgang.command.CommandCreatedEventTestFactory; +import de.ozgcloud.vorgang.command.CommandService; import de.ozgcloud.vorgang.command.CommandTestFactory; import de.ozgcloud.vorgang.command.Order; import de.ozgcloud.vorgang.vorgang.VorgangService; +import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; @ITCase class ForwardingEventListenerITCase { @@ -53,15 +59,17 @@ class ForwardingEventListenerITCase { @Autowired private ApplicationEventPublisher publisher; - @MockBean + @MockitoBean private ForwardingService service; - @MockBean + @MockitoBean private JavaMailSender mailSender; - @MockBean + @MockitoBean private VorgangService vorgangService; + @MockitoBean + private CommandService commandService; @Nested - class TestForwardVorgang { + class TestRedirectdVorgang { @Test void shouldCallForwardingService() { @@ -109,4 +117,45 @@ class ForwardingEventListenerITCase { } } + @Nested + class TestForwardVorgang { + + private Command command = CommandTestFactory.createBuilder().order(Order.FORWARD_VORGANG.name()).build(); + @Captor + private ArgumentCaptor<ForwardingRequest> requestCaptor; + + @BeforeEach + void init() { + when(vorgangService.getById(any())).thenReturn(VorgangTestFactory.create()); + } + + @Test + void shouldCallForwardingService() { + publishEvent(); + + await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> verify(service).forward(requestCaptor.capture())); + assertThat(requestCaptor.getValue().getVorgangId()).isEqualTo(command.getVorgangId()); + } + + @Test + void shouldSetCommandFinished() { + publishEvent(); + + await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> verify(commandService).setCommandFinished(command.getId(), null)); + } + + @Test + void shouldSetCommandFailed() { + doThrow(RuntimeException.class).when(service).forward(any()); + + publishEvent(); + + await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> verify(commandService).setCommandError(eq(command.getId()), any())); + } + + private void publishEvent() { + publisher.publishEvent(CommandCreatedEventTestFactory.create(command)); + } + } + } diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingEventListenerTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingEventListenerTest.java index 1c436314978374894da998e3a8dd766fc03801c8..529d4a09a7761f8e182b80fb6f2a5774cad2fdf9 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingEventListenerTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingEventListenerTest.java @@ -23,26 +23,43 @@ */ package de.ozgcloud.vorgang.vorgang.redirect; +import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.ConcurrentModificationException; +import java.util.Map; +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 org.springframework.context.ApplicationEventPublisher; +import com.thedeanda.lorem.LoremIpsum; + import de.ozgcloud.command.Command; +import de.ozgcloud.command.CommandCreatedEvent; import de.ozgcloud.command.CommandFailedEvent; +import de.ozgcloud.command.VorgangLockedEvent; +import de.ozgcloud.common.errorhandling.TechnicalException; import de.ozgcloud.nachrichten.email.MailSentEventTestFactory; import de.ozgcloud.vorgang.command.CommandCreatedEventTestFactory; import de.ozgcloud.vorgang.command.CommandTestFactory; import de.ozgcloud.vorgang.command.Order; +import de.ozgcloud.vorgang.vorgang.Lock; +import de.ozgcloud.vorgang.vorgang.LockMapper; +import de.ozgcloud.vorgang.vorgang.LockTestFactory; +import de.ozgcloud.vorgang.vorgang.VorgangService; +import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; class ForwardingEventListenerTest { + @Spy @InjectMocks // NOSONAR private ForwardingEventListener listener; @@ -50,13 +67,19 @@ class ForwardingEventListenerTest { private ForwardingService forwardingService; @Mock private ApplicationEventPublisher publisher; + @Mock + private ForwardingRequestMapper forwardingRequestMapper; + @Mock + private VorgangService vorgangService; + @Mock + private LockMapper lockMapper; @Nested - class onForwardOrderCommand { + class OnRedirectVorgangCommand { @Test void shouldCallService() { - listener.onForwardOrder(CommandCreatedEventTestFactory.create()); + listener.onRedirectVorgangOrder(CommandCreatedEventTestFactory.create()); verify(forwardingService).forwardByCommand(notNull()); } @@ -64,17 +87,17 @@ class ForwardingEventListenerTest { @Test void shouldPublishFailedEventOnError() { when(forwardingService.forwardByCommand(any())).thenThrow(ConcurrentModificationException.class); - - listener.onForwardOrder(CommandCreatedEventTestFactory.create()); - + + listener.onRedirectVorgangOrder(CommandCreatedEventTestFactory.create()); + verify(publisher).publishEvent(any(CommandFailedEvent.class)); } } @Nested - class onMailSentEvent { + class OnMailSentEvent { - private Forwarding forwarding = ForwardingTestFactory.create(); + private final Forwarding forwarding = ForwardingTestFactory.create(); @Test void shouldCallRedirectService() { @@ -85,6 +108,113 @@ class ForwardingEventListenerTest { } + @Nested + class TestOnForwardVorgangOrder { + + private final CommandCreatedEvent event = CommandCreatedEventTestFactory.create(); + + @Captor + private ArgumentCaptor<CommandFailedEvent> failedEventCaptor; + + @Test + void shouldHandleForwardVorgangCommand() { + doNothing().when(listener).handleForwardVorgangCommand(any()); + + onForwardVorgangOrder(); + + verify(listener).handleForwardVorgangCommand(event.getSource()); + } + + @Test + void shouldPublishFailedEventOnError() { + doThrow(TechnicalException.class).when(listener).handleForwardVorgangCommand(any()); + + onForwardVorgangOrder(); + + verify(publisher).publishEvent(failedEventCaptor.capture()); + assertThat(failedEventCaptor.getValue().getSource()).isEqualTo(event.getSource().getId()); + } + + private void onForwardVorgangOrder() { + listener.onForwardVorgangOrder(event); + } + } + + @Nested + class TestHandleForwardVorgangCommand { + + private final Command command = CommandTestFactory.createBuilder().bodyObject(commandBody()).build(); + private final Lock lock = LockTestFactory.create(); + @Captor + private ArgumentCaptor<Lock> lockCaptor; + @Captor + private ArgumentCaptor<VorgangLockedEvent> lockedEventCaptor; + @Captor + private ArgumentCaptor<ForwardingRequest> requestCaptor; + + @BeforeEach + void init() { + when(forwardingRequestMapper.fromCommand(any())).thenReturn(ForwardingRequestTestFactory.create()); + when(lockMapper.fromCommand(any())).thenReturn(lock); + } + + @Test + void shouldMapToLock() { + handleForwardVorgangCommand(); + + verify(lockMapper).fromCommand(command); + } + + @Test + void shouldLockVorgang() { + handleForwardVorgangCommand(); + + verify(vorgangService).lockVorgang(lockCaptor.capture(), eq(VorgangTestFactory.ID), eq(CommandTestFactory.RELATION_VERSION)); + assertThat(lockCaptor.getValue()).usingRecursiveComparison() + .isEqualTo(LockTestFactory.createBuilder().reason("Vorgang was forwarded").build()); + } + + @Test + void shouldMapCommandToForwardingRequest() { + handleForwardVorgangCommand(); + + verify(forwardingRequestMapper).fromCommand(command); + } + + @Test + void shouldCallForwardingService() { + handleForwardVorgangCommand(); + + verify(forwardingService).forward(requestCaptor.capture()); + assertThat(requestCaptor.getValue()).usingRecursiveComparison().ignoringFields("version") + .isEqualTo(ForwardingRequestTestFactory.create()); + } + + @Test + void shouldIncrementVersionInRequest() { + handleForwardVorgangCommand(); + + verify(forwardingService).forward(requestCaptor.capture()); + assertThat(requestCaptor.getValue().getVersion()).isEqualTo(VorgangTestFactory.VERSION + 1); + } + + @Test + void shouldPublishVorgangLockedEvent() { + handleForwardVorgangCommand(); + + verify(publisher).publishEvent(lockedEventCaptor.capture()); + assertThat(lockedEventCaptor.getValue().getCommand()).isSameAs(command); + } + + private void handleForwardVorgangCommand() { + listener.handleForwardVorgangCommand(command); + } + + private static Map<String, Object> commandBody() { + return Map.of(LoremIpsum.getInstance().getWords(1), LoremIpsum.getInstance().getWords(1)); + } + } + @Nested class TestMarkAsSuccessfull { diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRequestMapperTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRequestMapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..715529e268ded4133f6e9962fada6f8ce2c8ca35 --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRequestMapperTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.vorgang.redirect; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; +import org.mockito.Spy; + +import de.ozgcloud.vorgang.command.CommandTestFactory; +import de.ozgcloud.vorgang.vorgang.redirect.ForwardingRequestMapper.CommandBodyFields; + +class ForwardingRequestMapperTest { + + @Spy + private ForwardingRequestMapper mapper = Mappers.getMapper(ForwardingRequestMapper.class); + private final Map<String, Object> bodyObject = Map.of( + CommandBodyFields.ORGANISATION_EINHEIT_ID, OrganisationEinheitTestFactory.ID, + CommandBodyFields.NAME, OrganisationEinheitTestFactory.NAME, + CommandBodyFields.STRASSE, AddressTestFactory.STREET, + CommandBodyFields.HAUSNUMMER, AddressTestFactory.HOUSE_NUMBER, + CommandBodyFields.PLZ, AddressTestFactory.ZIP_CODE, + CommandBodyFields.ORT, AddressTestFactory.CITY + ); + + @Nested + class TestFromCommand { + + @BeforeEach + void init() { + doReturn(OrganisationEinheitTestFactory.create()).when(mapper).toOrganisationEinheit(any()); + } + + @Test + void shouldMapToOrganisationEinheit() { + map(); + + verify(mapper).toOrganisationEinheit(bodyObject); + } + + @Test + void shouldMapForwardingRequestFields() { + var mapped = map(); + + assertThat(mapped).usingRecursiveComparison().isEqualTo(ForwardingRequestTestFactory.create()); + } + + private ForwardingRequest map() { + return mapper.fromCommand(CommandTestFactory.createBuilder().bodyObject(bodyObject).build()); + } + } + + @Nested + class TestToOrganisationEinheit { + + @BeforeEach + void init() { + doReturn(AddressTestFactory.create()).when(mapper).toAddress(any()); + } + + @Test + void shouldMapToAddress() { + map(); + + verify(mapper).toAddress(bodyObject); + } + + @Test + void shouldMapOrganisationEinheitFields() { + var mapped = map(); + + assertThat(mapped).usingRecursiveComparison().isEqualTo(OrganisationEinheitTestFactory.create()); + } + + private OrganisationEinheit map() { + return mapper.toOrganisationEinheit(bodyObject); + } + } + + @Nested + class TestToAddress { + + @Test + void shouldMapAddressFields() { + var mapped = map(); + + assertThat(mapped).usingRecursiveComparison().isEqualTo(AddressTestFactory.create()); + } + + private Address map() { + return mapper.toAddress(bodyObject); + } + } +} diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRequestTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRequestTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..dbe06e0b8b4c9676408e944e7afb5b5028f507d3 --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingRequestTestFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.vorgang.redirect; + +import de.ozgcloud.vorgang.callcontext.UserTestFactory; +import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; +import de.ozgcloud.vorgang.vorgang.redirect.ForwardingRequest.ForwardingRequestBuilder; + +class ForwardingRequestTestFactory { + + public static ForwardingRequest create() { + return createBuilder().build(); + } + + public static ForwardingRequestBuilder createBuilder() { + return new ForwardingRequestBuilder() + .vorgangId(VorgangTestFactory.ID) + .version(VorgangTestFactory.VERSION) + .createdBy(UserTestFactory.ID) + .createdByName(UserTestFactory.NAME) + .organisationEinheit(OrganisationEinheitTestFactory.create()); + } +} diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingServiceTest.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingServiceTest.java index ac84ad8b780ed2655318ee1c05a5129b5e24b4a4..1b864c7a9ab50c247178efd8d1346e87aff1e4f5 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingServiceTest.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/ForwardingServiceTest.java @@ -622,4 +622,15 @@ class ForwardingServiceTest { } } + + @Nested + class TestForward { + + @Test + void shouldSetStatusToWeitergeleitet() { + service.forward(ForwardingRequestTestFactory.create()); + + verify(statusService).setStatusToWeitergeleitet(VorgangTestFactory.ID, VorgangTestFactory.VERSION); + } + } } \ No newline at end of file diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/OrganisationEinheitTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/OrganisationEinheitTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..27e64b0db89125d53cda528a838c9610f24dc34e --- /dev/null +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/vorgang/redirect/OrganisationEinheitTestFactory.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.vorgang.vorgang.redirect; + +import java.util.UUID; + +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.vorgang.vorgang.redirect.OrganisationEinheit.OrganisationEinheitBuilder; + +class OrganisationEinheitTestFactory { + + public static final String ID = UUID.randomUUID().toString(); + public static final String NAME = LoremIpsum.getInstance().getName(); + + public static OrganisationEinheit create() { + return createBuilder().build(); + } + + public static OrganisationEinheitBuilder createBuilder() { + return new OrganisationEinheitBuilder() + .id(ID) + .name(NAME) + .address(AddressTestFactory.create()); + } +}