diff --git a/vorgang-manager-command/src/main/java/de/ozgcloud/command/Command.java b/vorgang-manager-command/src/main/java/de/ozgcloud/command/Command.java index 61876bd402f13ce74003c41efe440aca9fa4165b..03beadfa1d78503796ab49ce66050226de991ee9 100644 --- a/vorgang-manager-command/src/main/java/de/ozgcloud/command/Command.java +++ b/vorgang-manager-command/src/main/java/de/ozgcloud/command/Command.java @@ -44,6 +44,7 @@ public interface Command { public String getCreatedBy(); public String getCreatedByName(); public CommandStatus getStatus(); + String getCreatedByClient(); public Map<String, Object> getBodyObject(); public Map<String, String> getBody(); diff --git a/vorgang-manager-command/src/test/java/de/ozgcloud/command/TestCommand.java b/vorgang-manager-command/src/test/java/de/ozgcloud/command/TestCommand.java index 3ebddf936e18f8c1576ca81400a5a93cd1f7ec07..3d2c5d8263bd630782c6b968d50c265e15c0597c 100644 --- a/vorgang-manager-command/src/test/java/de/ozgcloud/command/TestCommand.java +++ b/vorgang-manager-command/src/test/java/de/ozgcloud/command/TestCommand.java @@ -21,6 +21,7 @@ public class TestCommand implements Command { private ZonedDateTime finishedAt; private String createdBy; private String createdByName; + private String createdByClient; private CommandStatus status; diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/PersistedCommand.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/PersistedCommand.java index 71964af4d27968376adf98bb011157c544c41e37..0973a39797c8235f7d02f1ee9a3bb363ae235817 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/PersistedCommand.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/command/PersistedCommand.java @@ -63,6 +63,7 @@ public class PersistedCommand implements Command { private String createdBy; private String createdByName; + private String createdByClient; @Setter private CommandStatus status; diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/Vorgang.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/Vorgang.java index 88557e44c95e469d95f17dbd1f413c3e941b19fc..979de7347065ba922cf5b41c2eb4151bfc293013 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/Vorgang.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/Vorgang.java @@ -50,6 +50,7 @@ public class Vorgang { static final String MONGODB_FIELDNAME_CREATED_AT = "createdAt"; public static final String MONGODB_FIELDNAME_ASSIGNED_TO = "assignedTo"; public static final String MONGODB_FIELDNAME_IN_CREATION = "inCreation"; + public static final String MONGODB_FIELDNAME_HEADER = "header"; public static final String FIELD_EINGANGSKENNZ = "eingangs.header.requestId"; public static final String FIELD_AKTENZEICHEN = "aktenzeichen"; diff --git a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangHead.java b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangHead.java index ee5dc6b922cdaaf267c7c69d49234ac2628751b6..2f3cb7ddde140b25b2e624da26d1594735bddaf7 100644 --- a/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangHead.java +++ b/vorgang-manager-server/src/main/java/de/ozgcloud/vorgang/vorgang/VorgangHead.java @@ -11,6 +11,8 @@ import lombok.Getter; @TypeAlias("VorgangHeader") public class VorgangHead { + public static final String FIELD_LOCK = "lock"; + private ServiceKonto serviceKonto; private String organisationsEinheitId; 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 274e1945c40d174731604e4ab38dd291417d1e93..801685f1f65300819e724da8b4811399b75a6a49 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,10 +23,13 @@ */ 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; @@ -45,6 +48,7 @@ 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; @@ -58,6 +62,7 @@ public class VorgangService { private static final String BODY_ASSIGNED_TO_FIELD = "assignedTo"; public static final String BODY_OBJECT_AKTENZEICHEN = "aktenzeichen"; + static final String KEY_HEADER_LOCK = "%s.%s".formatted(Vorgang.MONGODB_FIELDNAME_HEADER, VorgangHead.FIELD_LOCK); @Autowired private VorgangAuthorizationService vorgangAuthenticationService; @@ -226,9 +231,31 @@ public class VorgangService { } public void lockVorgang(Command command) { + validateLockCommand(command); + repository.patch(command.getVorgangId(), command.getRelationVersion(), buildLockPatch(command)); publisher.publishEvent(new VorgangLockedEvent(command)); } + void validateLockCommand(Command command) { + if (Objects.isNull(command.getCreatedByClient())) { + 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) { + return Map.of(KEY_HEADER_LOCK, Map.of( + Lock.FIELD_CLIENT_NAME, command.getCreatedByClient(), + 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)); + } + public void unlockVorgang(Command command) { // TODO wird implementiert in OZG-6992 // publisher.publishEvent(new CommandRevokedEvent(command)); diff --git a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/CommandTestFactory.java b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/CommandTestFactory.java index ff3e080efe5ba9eaa41c46b74820bcda8427dd5c..47f9149c3e6ccb74edeb5add3842b81b02cd7646 100644 --- a/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/CommandTestFactory.java +++ b/vorgang-manager-server/src/test/java/de/ozgcloud/vorgang/command/CommandTestFactory.java @@ -29,6 +29,7 @@ import java.util.UUID; import de.ozgcloud.command.CommandStatus; import de.ozgcloud.vorgang.attached_item.VorgangAttachedItemTestFactory; +import de.ozgcloud.vorgang.callcontext.CallContextTestFactory; import de.ozgcloud.vorgang.callcontext.UserTestFactory; import de.ozgcloud.vorgang.vorgang.VorgangTestFactory; @@ -39,6 +40,7 @@ public class CommandTestFactory { public static final ZonedDateTime CREATED_AT = ZonedDateTime.parse(CREATED_AT_STR); public static final String CREATED_BY = UserTestFactory.ID; public static final String CREATED_BY_NAME = UserTestFactory.NAME; + public static final String CREATED_BY_CLIENT = CallContextTestFactory.CLIENT; public static final CommandStatus STATUS = CommandStatus.PENDING; public static final String RELATION_ID = VorgangAttachedItemTestFactory.ID; @@ -59,6 +61,7 @@ public class CommandTestFactory { .createdAt(CREATED_AT) .createdBy(CREATED_BY) .createdByName(CREATED_BY_NAME) + .createdByClient(CREATED_BY_CLIENT) .status(STATUS) .relationId(RELATION_ID) .relationVersion(RELATION_VERSION) 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 c6a8daf26e2d1696cad785c5cb1c66cf1d73f33a..db34a67ab574fbeb28cf458ef75766e20714943c 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,10 +28,13 @@ 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.InstanceOfAssertFactories; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -49,6 +52,8 @@ import org.springframework.security.access.AccessDeniedException; 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; @@ -507,4 +512,108 @@ class VorgangServiceTest { verify(repository).setAktenzeichen(VorgangTestFactory.ID, CommandTestFactory.RELATION_VERSION, null); } } + + @Nested + class TestLockVorgang { + + private static final Command LOCK_COMMAND = CommandTestFactory.create(); + private static final Map<String, Object> LOCK_PATCH = Map.of("key", "value"); + + @Captor + private ArgumentCaptor<VorgangLockedEvent> eventCaptor; + + @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); + + verify(service).buildLockPatch(LOCK_COMMAND); + } + + @Test + void shouldCallRepository() { + service.lockVorgang(LOCK_COMMAND); + + 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().createdByClient(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)); + } + } + + @Nested + class TestBuildLockPatch { + + @Test + void shouldSetClientName() { + var command = CommandTestFactory.createBuilder().bodyObject(Map.of(Lock.FIELD_REASON, LockTestFactory.REASON)).build(); + + var result = service.buildLockPatch(command); + + assertThat(result).extractingByKey(VorgangService.KEY_HEADER_LOCK, MAP) + .containsEntry(Lock.FIELD_CLIENT_NAME, CommandTestFactory.CREATED_BY_CLIENT); + } + + @Test + void shouldSetLockedSince() { + var command = CommandTestFactory.createBuilder().bodyObject(Map.of(Lock.FIELD_REASON, LockTestFactory.REASON)).build(); + + var result = service.buildLockPatch(command); + + assertThat(result).extractingByKey(VorgangService.KEY_HEADER_LOCK, MAP) + .extractingByKey(Lock.FIELD_LOCKED_SINCE, InstanceOfAssertFactories.ZONED_DATE_TIME) + .isCloseTo(ZonedDateTime.now(), within(1, ChronoUnit.SECONDS)); + } + + @Test + void shouldSetReason() { + var command = CommandTestFactory.createBuilder().bodyObject(Map.of(Lock.FIELD_REASON, LockTestFactory.REASON)).build(); + + var result = service.buildLockPatch(command); + + assertThat(result).extractingByKey(VorgangService.KEY_HEADER_LOCK, MAP).containsEntry(Lock.FIELD_REASON, LockTestFactory.REASON); + } + } } \ No newline at end of file