From 9b6c9a565bdee02513b10c341eb7e56fe9cf8636 Mon Sep 17 00:00:00 2001
From: Martin <git@mail.de>
Date: Mon, 31 Mar 2025 10:31:58 +0200
Subject: [PATCH 1/4] OZG-7872 OZG-8039 add related resource link on postfach

---
 .../common/command/CommandModelAssembler.java | 65 ++++++++++----
 .../alfa/common/command/CommandProcessor.java | 37 ++++++++
 .../PostfachNachrichtCommandProcessor.java    | 47 ++++++++++
 .../command/CommandModelAssemblerTest.java    | 51 +++++++----
 .../common/command/CommandProcessorTest.java  | 77 ++++++++++++++++
 ...PostfachNachrichtCommandProcessorTest.java | 88 +++++++++++++++++++
 6 files changed, 330 insertions(+), 35 deletions(-)
 create mode 100644 alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandProcessor.java
 create mode 100644 alfa-service/src/main/java/de/ozgcloud/alfa/postfach/PostfachNachrichtCommandProcessor.java
 create mode 100644 alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandProcessorTest.java
 create mode 100644 alfa-service/src/test/java/de/ozgcloud/alfa/postfach/PostfachNachrichtCommandProcessorTest.java

diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandModelAssembler.java b/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandModelAssembler.java
index 94f0209dfd..467466ef0b 100644
--- a/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandModelAssembler.java
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandModelAssembler.java
@@ -25,6 +25,7 @@ package de.ozgcloud.alfa.common.command;
 
 import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
 
+import java.util.List;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 
@@ -33,7 +34,6 @@ import org.springframework.hateoas.EntityModel;
 import org.springframework.hateoas.Link;
 import org.springframework.hateoas.LinkRelation;
 import org.springframework.hateoas.server.RepresentationModelAssembler;
-import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
 import org.springframework.stereotype.Component;
 
 import de.ozgcloud.alfa.bescheid.BescheidController;
@@ -41,10 +41,11 @@ import de.ozgcloud.alfa.bescheid.DocumentController;
 import de.ozgcloud.alfa.collaboration.CollaborationController.CollaborationByVorgangController;
 import de.ozgcloud.alfa.forwarding.ForwardingController;
 import de.ozgcloud.alfa.kommentar.KommentarController;
-import de.ozgcloud.alfa.postfach.PostfachMailController;
 import de.ozgcloud.alfa.vorgang.VorgangController;
 import de.ozgcloud.alfa.wiedervorlage.WiedervorlageController;
+import lombok.RequiredArgsConstructor;
 
+@RequiredArgsConstructor
 @Component
 class CommandModelAssembler implements RepresentationModelAssembler<Command, EntityModel<Command>> {
 
@@ -57,33 +58,61 @@ class CommandModelAssembler implements RepresentationModelAssembler<Command, Ent
 
 	static final Predicate<Command> HAS_KNOWN_COMMAND_ORDER = command -> command.getCommandOrder() != CommandOrder.UNBEKANNT;
 
+	private static final List<String> NOT_PROCESSABLE_ORDER = List.of(
+			CommandOrder.RECEIVE_POSTFACH_NACHRICHT.name(),
+			CommandOrder.RESEND_POSTFACH_MAIL.name(),
+			CommandOrder.SEND_POSTFACH_MAIL.name(),
+			CommandOrder.SEND_POSTFACH_NACHRICHT.name(),
+			CommandOrder.RESEND_POSTFACH_MAIL.name(),
+			CommandOrder.RECEIVE_POSTFACH_NACHRICHT.name());
+
+	static final Predicate<Command> IS_PROCESSABLE_ORDER = command -> !NOT_PROCESSABLE_ORDER.contains(command.getOrder());
+
+	private final List<CommandProcessor> processors;
+
 	@Override
 	public EntityModel<Command> toModel(Command command) {
-		return EntityModel.of(command)
+		var entityModel = EntityModel.of(command)
 				.add(linkTo(CommandController.class).slash(command.getId()).withSelfRel())
-				.addIf(CommandHelper.IS_DONE.and(HAS_KNOWN_COMMAND_ORDER).test(command), () -> effectedResourceLinkByOrderType(command))
+				.addIf(CommandHelper.IS_DONE.and(HAS_KNOWN_COMMAND_ORDER).and(IS_PROCESSABLE_ORDER).test(command),
+						() -> effectedResourceLinkByOrderType(command))
 				.addIf(CommandHelper.IS_PENDING.test(command), () -> linkTo(CommandController.class).slash(command.getId()).withRel(REL_UPDATE))
 				.addIf(IS_NOT_LOESCH_ANFORDERUNG_AND_REVOKEABLE.test(command),
 						() -> linkTo(methodOn(CommandController.class).revoke(command.getId(), null)).withRel(REL_REVOKE));
+
+		processors.forEach(processor -> processor.process(entityModel));
+
+		return entityModel;
 	}
 
 	Link effectedResourceLinkByOrderType(Command entity) {
 		var type = entity.getCommandOrder().getType();
 
-		WebMvcLinkBuilder linkBuilder = switch (type) {
-			case FORWARDING -> linkTo(methodOn(ForwardingController.class).findByVorgangId(entity.getVorgangId()));
-			case KOMMENTAR -> linkTo(KommentarController.class).slash(entity.getRelationId());
-			case POSTFACH -> linkTo(methodOn(PostfachMailController.class).getAll(entity.getVorgangId()));
-			case VORGANG -> linkTo(VorgangController.class).slash(entity.getRelationId());
-			case VORGANG_LIST -> linkTo(VorgangController.class);
-			case WIEDERVORLAGE -> linkTo(WiedervorlageController.class).slash(entity.getRelationId());
-			case BESCHEID -> linkTo(methodOn(BescheidController.class).getDraft(entity.getVorgangId()));
-			case DOCUMENT -> linkTo(DocumentController.class).slash(entity.getCreatedResource());
-			case COLLABORATION -> linkTo(methodOn(CollaborationByVorgangController.class).getAllByVorgangId(entity.getVorgangId()));
-			case NONE -> throw new IllegalArgumentException("Unknown CommandOrder: " + entity.getOrder());
-		};
-
-		return linkBuilder.withRel(REL_EFFECTED_RESOURCE);
+		if (type == CommandOrder.Type.FORWARDING) {
+			return linkTo(methodOn(ForwardingController.class).findByVorgangId(entity.getVorgangId())).withRel(REL_EFFECTED_RESOURCE);
+		}
+		if (type == CommandOrder.Type.KOMMENTAR) {
+			return linkTo(KommentarController.class).slash(entity.getRelationId()).withRel(REL_EFFECTED_RESOURCE);
+		}
+		if (type == CommandOrder.Type.VORGANG) {
+			return linkTo(VorgangController.class).slash(entity.getRelationId()).withRel(REL_EFFECTED_RESOURCE);
+		}
+		if (type == CommandOrder.Type.VORGANG_LIST) {
+			return linkTo(VorgangController.class).withRel(REL_EFFECTED_RESOURCE);
+		}
+		if (type == CommandOrder.Type.WIEDERVORLAGE) {
+			return linkTo(WiedervorlageController.class).slash(entity.getRelationId()).withRel(REL_EFFECTED_RESOURCE);
+		}
+		if (type == CommandOrder.Type.BESCHEID) {
+			return linkTo(methodOn(BescheidController.class).getDraft(entity.getVorgangId())).withRel(REL_EFFECTED_RESOURCE);
+		}
+		if (type == CommandOrder.Type.DOCUMENT) {
+			return linkTo(DocumentController.class).slash(entity.getCreatedResource()).withRel(REL_EFFECTED_RESOURCE);
+		}
+		if (type == CommandOrder.Type.COLLABORATION) {
+			return linkTo(methodOn(CollaborationByVorgangController.class).getAllByVorgangId(entity.getVorgangId())).withRel(REL_EFFECTED_RESOURCE);
+		}
+		throw new IllegalArgumentException("Unknown CommandOrder: " + entity.getOrder());
 	}
 
 	public CollectionModel<EntityModel<Command>> toCollectionModel(Stream<Command> entities) {
diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandProcessor.java b/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandProcessor.java
new file mode 100644
index 0000000000..a7343b9a3f
--- /dev/null
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandProcessor.java
@@ -0,0 +1,37 @@
+package de.ozgcloud.alfa.common.command;
+
+import java.util.Objects;
+import java.util.function.Predicate;
+
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.LinkRelation;
+import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
+
+public abstract class CommandProcessor {
+
+	static final LinkRelation REL_EFFECTED_RESOURCE = LinkRelation.of("effected_resource");
+	static final LinkRelation REL_RELATED_RESOURCE = LinkRelation.of("related_resource");
+
+	static final Predicate<Command> HAS_KNOWN_COMMAND_ORDER = command -> command.getCommandOrder() != CommandOrder.UNBEKANNT;
+
+	public void process(EntityModel<Command> model) {
+		var command = model.getContent();
+
+		if (Objects.isNull(command)) {
+			return;
+		}
+
+		model.addIf(CommandHelper.IS_DONE.test(command) && isResponsibleForEffectedResource(command.getOrder()),
+				() -> createEffectedResourceLinkBuilder(command).withRel(REL_EFFECTED_RESOURCE));
+		model.addIf(isResponsible(command.getOrder()),
+				() -> createRelatedResourceLinkBuilder(command).withRel(REL_RELATED_RESOURCE));
+	}
+
+	public abstract boolean isResponsibleForEffectedResource(String order);
+
+	public abstract WebMvcLinkBuilder createEffectedResourceLinkBuilder(Command command);
+
+	public abstract boolean isResponsible(String order);
+
+	public abstract WebMvcLinkBuilder createRelatedResourceLinkBuilder(Command command);
+}
\ No newline at end of file
diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/postfach/PostfachNachrichtCommandProcessor.java b/alfa-service/src/main/java/de/ozgcloud/alfa/postfach/PostfachNachrichtCommandProcessor.java
new file mode 100644
index 0000000000..72f871b053
--- /dev/null
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/postfach/PostfachNachrichtCommandProcessor.java
@@ -0,0 +1,47 @@
+package de.ozgcloud.alfa.postfach;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
+import org.springframework.stereotype.Component;
+
+import de.ozgcloud.alfa.common.command.Command;
+import de.ozgcloud.alfa.common.command.CommandOrder;
+import de.ozgcloud.alfa.common.command.CommandProcessor;
+
+@Component
+public class PostfachNachrichtCommandProcessor extends CommandProcessor {
+
+	private static final List<String> RESPONSIBLE_ORDER = List.of(
+			CommandOrder.RECEIVE_POSTFACH_NACHRICHT.name(),
+			CommandOrder.RESEND_POSTFACH_MAIL.name());
+
+	private static final List<String> RESPONSIBLE_EFFECTED_RESOURCE_ORDER = List.of(
+			CommandOrder.SEND_POSTFACH_MAIL.name(),
+			CommandOrder.SEND_POSTFACH_NACHRICHT.name(),
+			CommandOrder.RESEND_POSTFACH_MAIL.name(),
+			CommandOrder.RECEIVE_POSTFACH_NACHRICHT.name());
+
+	@Override
+	public boolean isResponsible(String order) {
+		return Optional.ofNullable(order).map(RESPONSIBLE_ORDER::contains).orElse(false);
+	}
+
+	@Override
+	public WebMvcLinkBuilder createRelatedResourceLinkBuilder(Command command) {
+		return linkTo(PostfachMailController.class).slash(command.getRelationId());
+	}
+
+	@Override
+	public boolean isResponsibleForEffectedResource(String order) {
+		return Optional.ofNullable(order).map(RESPONSIBLE_EFFECTED_RESOURCE_ORDER::contains).orElse(false);
+	}
+
+	@Override
+	public WebMvcLinkBuilder createEffectedResourceLinkBuilder(Command command) {
+		return linkTo(methodOn(PostfachMailController.class).getAll(command.getVorgangId()));
+	}
+}
\ No newline at end of file
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandModelAssemblerTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandModelAssemblerTest.java
index bd98a6b027..cfaba07c47 100644
--- a/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandModelAssemblerTest.java
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandModelAssemblerTest.java
@@ -27,7 +27,12 @@ import static de.ozgcloud.alfa.common.command.CommandModelAssembler.*;
 import static de.ozgcloud.alfa.common.command.CommandTestFactory.*;
 import static org.assertj.core.api.Assertions.*;
 import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
 
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
@@ -35,7 +40,11 @@ import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.EnumSource;
 import org.junit.jupiter.params.provider.EnumSource.Mode;
 import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
 import org.springframework.hateoas.EntityModel;
 import org.springframework.hateoas.IanaLinkRelations;
 import org.springframework.hateoas.Link;
@@ -49,6 +58,19 @@ class CommandModelAssemblerTest {
 	@InjectMocks
 	private CommandModelAssembler modelAssembler;
 
+	@Spy
+	private List<CommandProcessor> processors = new ArrayList<>();
+	@Mock
+	private CommandProcessor processor;
+
+	@Captor
+	private ArgumentCaptor<EntityModel<Command>> commandEntityModelCaptor;
+
+	@BeforeEach
+	void setProcessorMocks() {
+		processors.add(processor);
+	}
+
 	@Test
 	void shouldHaveSelfLink() {
 		var model = modelAssembler.toModel(CommandTestFactory.create());
@@ -56,6 +78,16 @@ class CommandModelAssemblerTest {
 		assertThat(model.getLink(IanaLinkRelations.SELF)).isPresent().get().extracting(Link::getHref).isEqualTo(COMMAND_SINGLE_PATH);
 	}
 
+	@Test
+	void shouldCallProcessor() {
+		var command = CommandTestFactory.create();
+
+		modelAssembler.toModel(command);
+
+		verify(processor).process(commandEntityModelCaptor.capture());
+		assertThat(commandEntityModelCaptor.getValue().getContent()).isEqualTo(command);
+	}
+
 	@Nested
 	@DisplayName("Link to effected Resource")
 	class TestEffectedResourceLink {
@@ -103,22 +135,6 @@ class CommandModelAssemblerTest {
 			}
 		}
 
-		@DisplayName("on postfach")
-		@Nested
-		class TestOnPostfach {
-
-			private static final String POSTFACH_URL = "/api/postfachMails?vorgangId=" + VorgangHeaderTestFactory.ID;
-
-			@ParameterizedTest
-			@ValueSource(strings = { "SEND_POSTFACH_MAIL" })
-			void shouldHaveLinkToPostfach(String order) {
-				var model = toModelWithOrder(order);
-
-				assertThat(model.getLink(CommandModelAssembler.REL_EFFECTED_RESOURCE)).isPresent().get()
-						.extracting(Link::getHref).isEqualTo(POSTFACH_URL);
-			}
-		}
-
 		@DisplayName("on bescheid")
 		@Nested
 		class TestOnBescheid {
@@ -196,7 +212,8 @@ class CommandModelAssemblerTest {
 		}
 
 		@ParameterizedTest
-		@EnumSource(mode = Mode.EXCLUDE, names = { "UNBEKANNT" })
+		@EnumSource(mode = Mode.EXCLUDE, names = { "UNBEKANNT", "SEND_POSTFACH_MAIL", "SEND_POSTFACH_NACHRICHT", "RESEND_POSTFACH_MAIL",
+				"RECEIVE_POSTFACH_NACHRICHT" })
 		void shouldBeAbleToBuildLinkForEveryOrder(CommandOrder order) {
 			var link = modelAssembler.effectedResourceLinkByOrderType(CommandTestFactory.createBuilder().order(order.name()).build());
 
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandProcessorTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandProcessorTest.java
new file mode 100644
index 0000000000..224152f96e
--- /dev/null
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandProcessorTest.java
@@ -0,0 +1,77 @@
+package de.ozgcloud.alfa.common.command;
+
+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 org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.Spy;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
+
+class CommandProcessorTest {
+
+	@Spy
+	private CommandProcessor processor;
+
+	@DisplayName("Process")
+	@Nested
+	class TestProcess {
+
+		@Test
+		void shouldNotThrowExceptionsOnNullContent() {
+			EntityModel<Command> entityModel = when(mock(EntityModel.class).getContent()).thenReturn(null).getMock();
+
+			assertDoesNotThrow(() -> processor.process(entityModel));
+		}
+
+		@DisplayName("effected resource link")
+		@Nested
+		class TestEffectedResource {
+
+			@Test
+			void shouldBeAddedIfResponsible() {
+				var linkBuilder = WebMvcLinkBuilder.linkTo(CommandController.class).slash("id");
+				doReturn(true).when(processor).isResponsibleForEffectedResource(any());
+				doReturn(linkBuilder).when(processor).createEffectedResourceLinkBuilder(any());
+				var command = CommandTestFactory.createBuilder().status(CommandStatus.FINISHED).build();
+				var entityModel = EntityModel.of(command);
+
+				processor.process(entityModel);
+
+				assertThat(entityModel.getLink(CommandProcessor.REL_EFFECTED_RESOURCE).get().getHref()).isEqualTo("/api/commands/id");
+			}
+
+			@Test
+			void shouldNotBeAddedIfCommandNotDone() {
+				var command = CommandTestFactory.createBuilder().status(CommandStatus.PENDING).build();
+				var entityModel = EntityModel.of(command);
+
+				processor.process(entityModel);
+
+				assertThat(entityModel.getLink(CommandProcessor.REL_EFFECTED_RESOURCE)).isNotPresent();
+			}
+		}
+
+		@DisplayName("related resource link")
+		@Nested
+		class TestRelatedResource {
+
+			@Test
+			void shouldBeAddedIfResponsible() {
+				var linkBuilder = WebMvcLinkBuilder.linkTo(CommandController.class).slash("id");
+				doReturn(true).when(processor).isResponsible(any());
+				doReturn(linkBuilder).when(processor).createRelatedResourceLinkBuilder(any());
+				var command = CommandTestFactory.create();
+				var entityModel = EntityModel.of(command);
+
+				processor.process(entityModel);
+
+				assertThat(entityModel.getLink(CommandProcessor.REL_RELATED_RESOURCE).get().getHref()).isEqualTo("/api/commands/id");
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/postfach/PostfachNachrichtCommandProcessorTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/postfach/PostfachNachrichtCommandProcessorTest.java
new file mode 100644
index 0000000000..a4d6b2272e
--- /dev/null
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/postfach/PostfachNachrichtCommandProcessorTest.java
@@ -0,0 +1,88 @@
+package de.ozgcloud.alfa.postfach;
+
+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.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.EnumSource.Mode;
+import org.mockito.Spy;
+
+import de.ozgcloud.alfa.common.command.CommandOrder;
+import de.ozgcloud.alfa.common.command.CommandTestFactory;
+
+class PostfachNachrichtCommandProcessorTest {
+
+	@Spy
+	private PostfachNachrichtCommandProcessor processor;
+
+	@DisplayName("Related resource")
+	@Nested
+	class TestRelatedResource {
+
+		@DisplayName("is responsible")
+		@Nested
+		class TestIsResponsible {
+
+			@EnumSource(mode = Mode.INCLUDE, names = { "RECEIVE_POSTFACH_NACHRICHT", "RESEND_POSTFACH_MAIL" })
+			@ParameterizedTest(name = "{0}")
+			void shouldReturnTrueOnOrder(CommandOrder order) {
+				var isResponsible = processor.isResponsible(order.name());
+
+				assertThat(isResponsible).isTrue();
+			}
+
+			@EnumSource(mode = Mode.EXCLUDE, names = { "RECEIVE_POSTFACH_NACHRICHT", "RESEND_POSTFACH_MAIL" })
+			@ParameterizedTest(name = "{0}")
+			void shouldReturnFalseOnOrder(CommandOrder order) {
+				var isResponsible = processor.isResponsible(order.name());
+
+				assertThat(isResponsible).isFalse();
+			}
+		}
+
+		@Test
+		void shouldCreateLinkBuilder() {
+			var linkBuilder = processor.createRelatedResourceLinkBuilder(CommandTestFactory.create());
+
+			assertThat(linkBuilder).hasToString(PostfachMailController.PATH + "/" + CommandTestFactory.RELATION_ID);
+		}
+	}
+
+	@DisplayName("Effected resource")
+	@Nested
+	class TestEffectedResource {
+
+		@DisplayName("is responsible")
+		@Nested
+		class TestIsResponsible {
+
+			@EnumSource(mode = Mode.INCLUDE, names = { "SEND_POSTFACH_MAIL", "SEND_POSTFACH_NACHRICHT", "RESEND_POSTFACH_MAIL",
+					"RECEIVE_POSTFACH_NACHRICHT" })
+			@ParameterizedTest(name = "{0}")
+			void shouldReturnTrueOnOrder(CommandOrder order) {
+				var isResponsible = processor.isResponsibleForEffectedResource(order.name());
+
+				assertThat(isResponsible).isTrue();
+			}
+
+			@EnumSource(mode = Mode.EXCLUDE, names = { "SEND_POSTFACH_MAIL", "SEND_POSTFACH_NACHRICHT", "RESEND_POSTFACH_MAIL",
+					"RECEIVE_POSTFACH_NACHRICHT" })
+			@ParameterizedTest(name = "{0}")
+			void shouldReturnFalseOnOrder(CommandOrder order) {
+				var isResponsible = processor.isResponsibleForEffectedResource(order.name());
+
+				assertThat(isResponsible).isFalse();
+			}
+		}
+
+		@Test
+		void shouldCreateLinkBuilder() {
+			var linkBuilder = processor.createEffectedResourceLinkBuilder(CommandTestFactory.create());
+
+			assertThat(linkBuilder).hasToString(PostfachMailController.PATH + "?vorgangId=" + CommandTestFactory.VORGANG_ID);
+		}
+	}
+}
\ No newline at end of file
-- 
GitLab


From b84b32e08b6f89fbfed53e334f870298a43e6d2a Mon Sep 17 00:00:00 2001
From: Martin <git@mail.de>
Date: Thu, 3 Apr 2025 15:29:00 +0200
Subject: [PATCH 2/4] OZG-7872 use enum instead of string; adjust naming;
 remove redundant order

---
 .../common/command/CommandModelAssembler.java | 16 ++--
 .../alfa/common/command/CommandProcessor.java | 11 +--
 .../PostfachNachrichtCommandProcessor.java    | 25 +++--
 .../common/command/CommandProcessorTest.java  | 93 ++++++++++++++-----
 ...PostfachNachrichtCommandProcessorTest.java |  8 +-
 5 files changed, 98 insertions(+), 55 deletions(-)

diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandModelAssembler.java b/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandModelAssembler.java
index 467466ef0b..eb69a13aeb 100644
--- a/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandModelAssembler.java
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandModelAssembler.java
@@ -58,15 +58,13 @@ class CommandModelAssembler implements RepresentationModelAssembler<Command, Ent
 
 	static final Predicate<Command> HAS_KNOWN_COMMAND_ORDER = command -> command.getCommandOrder() != CommandOrder.UNBEKANNT;
 
-	private static final List<String> NOT_PROCESSABLE_ORDER = List.of(
-			CommandOrder.RECEIVE_POSTFACH_NACHRICHT.name(),
-			CommandOrder.RESEND_POSTFACH_MAIL.name(),
-			CommandOrder.SEND_POSTFACH_MAIL.name(),
-			CommandOrder.SEND_POSTFACH_NACHRICHT.name(),
-			CommandOrder.RESEND_POSTFACH_MAIL.name(),
-			CommandOrder.RECEIVE_POSTFACH_NACHRICHT.name());
-
-	static final Predicate<Command> IS_PROCESSABLE_ORDER = command -> !NOT_PROCESSABLE_ORDER.contains(command.getOrder());
+	private static final List<CommandOrder> ORDER_PROCESSED_SEPERATELY = List.of(
+			CommandOrder.RECEIVE_POSTFACH_NACHRICHT,
+			CommandOrder.RESEND_POSTFACH_MAIL,
+			CommandOrder.SEND_POSTFACH_MAIL,
+			CommandOrder.SEND_POSTFACH_NACHRICHT);
+
+	static final Predicate<Command> IS_PROCESSABLE_ORDER = command -> !ORDER_PROCESSED_SEPERATELY.contains(command.getCommandOrder());
 
 	private final List<CommandProcessor> processors;
 
diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandProcessor.java b/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandProcessor.java
index a7343b9a3f..77d8d40bf0 100644
--- a/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandProcessor.java
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandProcessor.java
@@ -1,7 +1,6 @@
 package de.ozgcloud.alfa.common.command;
 
 import java.util.Objects;
-import java.util.function.Predicate;
 
 import org.springframework.hateoas.EntityModel;
 import org.springframework.hateoas.LinkRelation;
@@ -12,8 +11,6 @@ public abstract class CommandProcessor {
 	static final LinkRelation REL_EFFECTED_RESOURCE = LinkRelation.of("effected_resource");
 	static final LinkRelation REL_RELATED_RESOURCE = LinkRelation.of("related_resource");
 
-	static final Predicate<Command> HAS_KNOWN_COMMAND_ORDER = command -> command.getCommandOrder() != CommandOrder.UNBEKANNT;
-
 	public void process(EntityModel<Command> model) {
 		var command = model.getContent();
 
@@ -21,17 +18,17 @@ public abstract class CommandProcessor {
 			return;
 		}
 
-		model.addIf(CommandHelper.IS_DONE.test(command) && isResponsibleForEffectedResource(command.getOrder()),
+		model.addIf(CommandHelper.IS_DONE.test(command) && isResponsibleForEffectedResource(command.getCommandOrder()),
 				() -> createEffectedResourceLinkBuilder(command).withRel(REL_EFFECTED_RESOURCE));
-		model.addIf(isResponsible(command.getOrder()),
+		model.addIf(isResponsibleForRelatedResource(command.getCommandOrder()),
 				() -> createRelatedResourceLinkBuilder(command).withRel(REL_RELATED_RESOURCE));
 	}
 
-	public abstract boolean isResponsibleForEffectedResource(String order);
+	public abstract boolean isResponsibleForEffectedResource(CommandOrder order);
 
 	public abstract WebMvcLinkBuilder createEffectedResourceLinkBuilder(Command command);
 
-	public abstract boolean isResponsible(String order);
+	public abstract boolean isResponsibleForRelatedResource(CommandOrder order);
 
 	public abstract WebMvcLinkBuilder createRelatedResourceLinkBuilder(Command command);
 }
\ No newline at end of file
diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/postfach/PostfachNachrichtCommandProcessor.java b/alfa-service/src/main/java/de/ozgcloud/alfa/postfach/PostfachNachrichtCommandProcessor.java
index 72f871b053..358f358b47 100644
--- a/alfa-service/src/main/java/de/ozgcloud/alfa/postfach/PostfachNachrichtCommandProcessor.java
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/postfach/PostfachNachrichtCommandProcessor.java
@@ -3,7 +3,6 @@ package de.ozgcloud.alfa.postfach;
 import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
 
 import java.util.List;
-import java.util.Optional;
 
 import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
 import org.springframework.stereotype.Component;
@@ -15,19 +14,19 @@ import de.ozgcloud.alfa.common.command.CommandProcessor;
 @Component
 public class PostfachNachrichtCommandProcessor extends CommandProcessor {
 
-	private static final List<String> RESPONSIBLE_ORDER = List.of(
-			CommandOrder.RECEIVE_POSTFACH_NACHRICHT.name(),
-			CommandOrder.RESEND_POSTFACH_MAIL.name());
+	private static final List<CommandOrder> RESPONSIBLE_ORDER = List.of(
+			CommandOrder.RECEIVE_POSTFACH_NACHRICHT,
+			CommandOrder.RESEND_POSTFACH_MAIL);
 
-	private static final List<String> RESPONSIBLE_EFFECTED_RESOURCE_ORDER = List.of(
-			CommandOrder.SEND_POSTFACH_MAIL.name(),
-			CommandOrder.SEND_POSTFACH_NACHRICHT.name(),
-			CommandOrder.RESEND_POSTFACH_MAIL.name(),
-			CommandOrder.RECEIVE_POSTFACH_NACHRICHT.name());
+	private static final List<CommandOrder> RESPONSIBLE_EFFECTED_RESOURCE_ORDER = List.of(
+			CommandOrder.SEND_POSTFACH_MAIL,
+			CommandOrder.SEND_POSTFACH_NACHRICHT,
+			CommandOrder.RESEND_POSTFACH_MAIL,
+			CommandOrder.RECEIVE_POSTFACH_NACHRICHT);
 
 	@Override
-	public boolean isResponsible(String order) {
-		return Optional.ofNullable(order).map(RESPONSIBLE_ORDER::contains).orElse(false);
+	public boolean isResponsibleForRelatedResource(CommandOrder order) {
+		return RESPONSIBLE_ORDER.contains(order);
 	}
 
 	@Override
@@ -36,8 +35,8 @@ public class PostfachNachrichtCommandProcessor extends CommandProcessor {
 	}
 
 	@Override
-	public boolean isResponsibleForEffectedResource(String order) {
-		return Optional.ofNullable(order).map(RESPONSIBLE_EFFECTED_RESOURCE_ORDER::contains).orElse(false);
+	public boolean isResponsibleForEffectedResource(CommandOrder order) {
+		return RESPONSIBLE_EFFECTED_RESOURCE_ORDER.contains(order);
 	}
 
 	@Override
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandProcessorTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandProcessorTest.java
index 224152f96e..d62ad2e600 100644
--- a/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandProcessorTest.java
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandProcessorTest.java
@@ -21,6 +21,11 @@ class CommandProcessorTest {
 	@Nested
 	class TestProcess {
 
+		private final Command command = CommandTestFactory.create();
+		private final EntityModel<Command> model = EntityModel.of(command);
+
+		private final WebMvcLinkBuilder linkBuilder = WebMvcLinkBuilder.linkTo(CommandController.class).slash("id");
+
 		@Test
 		void shouldNotThrowExceptionsOnNullContent() {
 			EntityModel<Command> entityModel = when(mock(EntityModel.class).getContent()).thenReturn(null).getMock();
@@ -28,49 +33,93 @@ class CommandProcessorTest {
 			assertDoesNotThrow(() -> processor.process(entityModel));
 		}
 
-		@DisplayName("effected resource link")
+		@DisplayName("effected resource")
 		@Nested
 		class TestEffectedResource {
 
+			@DisplayName("should verify if responsibility is related to the order")
+			@Test
+			void shouldCallIsResponsibleForEffectedResource() {
+				processor.process(model);
+
+				verify(processor).isResponsibleForEffectedResource(CommandOrder.fromOrder(CommandTestFactory.ORDER));
+			}
+
+			@DisplayName("should create link builder for effected resource if the responsibility matches")
 			@Test
-			void shouldBeAddedIfResponsible() {
-				var linkBuilder = WebMvcLinkBuilder.linkTo(CommandController.class).slash("id");
-				doReturn(true).when(processor).isResponsibleForEffectedResource(any());
+			void shouldCallCreateEffectedResourceLinkBuilder() {
+				when(processor.isResponsibleForEffectedResource(any())).thenReturn(true);
 				doReturn(linkBuilder).when(processor).createEffectedResourceLinkBuilder(any());
-				var command = CommandTestFactory.createBuilder().status(CommandStatus.FINISHED).build();
-				var entityModel = EntityModel.of(command);
 
-				processor.process(entityModel);
+				processor.process(model);
 
-				assertThat(entityModel.getLink(CommandProcessor.REL_EFFECTED_RESOURCE).get().getHref()).isEqualTo("/api/commands/id");
+				verify(processor).createEffectedResourceLinkBuilder(command);
 			}
 
-			@Test
-			void shouldNotBeAddedIfCommandNotDone() {
-				var command = CommandTestFactory.createBuilder().status(CommandStatus.PENDING).build();
-				var entityModel = EntityModel.of(command);
+			@DisplayName("link")
+			@Nested
+			class TestEffectedResourceLink {
+
+				@Test
+				void shouldBeAddedIfResponsible() {
+					doReturn(true).when(processor).isResponsibleForEffectedResource(any());
+					doReturn(linkBuilder).when(processor).createEffectedResourceLinkBuilder(any());
+					var finishedCommand = CommandTestFactory.createBuilder().status(CommandStatus.FINISHED).build();
+					var entityModel = EntityModel.of(finishedCommand);
+
+					processor.process(entityModel);
+
+					assertThat(entityModel.getLink(CommandProcessor.REL_EFFECTED_RESOURCE).get().getHref()).isEqualTo("/api/commands/id");
+				}
 
-				processor.process(entityModel);
+				@Test
+				void shouldNotBeAddedIfCommandNotDone() {
+					var pendingCommand = CommandTestFactory.createBuilder().status(CommandStatus.PENDING).build();
+					var entityModel = EntityModel.of(pendingCommand);
 
-				assertThat(entityModel.getLink(CommandProcessor.REL_EFFECTED_RESOURCE)).isNotPresent();
+					processor.process(entityModel);
+
+					assertThat(entityModel.getLink(CommandProcessor.REL_EFFECTED_RESOURCE)).isNotPresent();
+				}
 			}
 		}
 
-		@DisplayName("related resource link")
+		@DisplayName("related resource")
 		@Nested
 		class TestRelatedResource {
 
+			@DisplayName("should verify if responsibility is related to the order")
+			@Test
+			void shouldCallIsResponsibleForRelatedResource() {
+				processor.process(model);
+
+				verify(processor).isResponsibleForRelatedResource(CommandOrder.fromOrder(CommandTestFactory.ORDER));
+			}
+
+			@DisplayName("should create link builder for related resource if the responsibility matches")
 			@Test
-			void shouldBeAddedIfResponsible() {
-				var linkBuilder = WebMvcLinkBuilder.linkTo(CommandController.class).slash("id");
-				doReturn(true).when(processor).isResponsible(any());
+			void shouldCallCreateRelatedResourceLinkBuilder() {
+				when(processor.isResponsibleForRelatedResource(any())).thenReturn(true);
 				doReturn(linkBuilder).when(processor).createRelatedResourceLinkBuilder(any());
-				var command = CommandTestFactory.create();
-				var entityModel = EntityModel.of(command);
 
-				processor.process(entityModel);
+				processor.process(model);
+
+				verify(processor).createRelatedResourceLinkBuilder(command);
+			}
+
+			@DisplayName("link")
+			@Nested
+			class TestRelatedResourceLink {
+
+				@Test
+				void shouldBeAddedIfResponsible() {
+					doReturn(true).when(processor).isResponsibleForRelatedResource(any());
+					doReturn(linkBuilder).when(processor).createRelatedResourceLinkBuilder(any());
+
+					processor.process(model);
 
-				assertThat(entityModel.getLink(CommandProcessor.REL_RELATED_RESOURCE).get().getHref()).isEqualTo("/api/commands/id");
+					assertThat(model.getLink(CommandProcessor.REL_RELATED_RESOURCE).get().getHref()).isEqualTo("/api/commands/id");
+				}
 			}
 		}
 	}
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/postfach/PostfachNachrichtCommandProcessorTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/postfach/PostfachNachrichtCommandProcessorTest.java
index a4d6b2272e..5b2cbca51b 100644
--- a/alfa-service/src/test/java/de/ozgcloud/alfa/postfach/PostfachNachrichtCommandProcessorTest.java
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/postfach/PostfachNachrichtCommandProcessorTest.java
@@ -29,7 +29,7 @@ class PostfachNachrichtCommandProcessorTest {
 			@EnumSource(mode = Mode.INCLUDE, names = { "RECEIVE_POSTFACH_NACHRICHT", "RESEND_POSTFACH_MAIL" })
 			@ParameterizedTest(name = "{0}")
 			void shouldReturnTrueOnOrder(CommandOrder order) {
-				var isResponsible = processor.isResponsible(order.name());
+				var isResponsible = processor.isResponsibleForRelatedResource(order);
 
 				assertThat(isResponsible).isTrue();
 			}
@@ -37,7 +37,7 @@ class PostfachNachrichtCommandProcessorTest {
 			@EnumSource(mode = Mode.EXCLUDE, names = { "RECEIVE_POSTFACH_NACHRICHT", "RESEND_POSTFACH_MAIL" })
 			@ParameterizedTest(name = "{0}")
 			void shouldReturnFalseOnOrder(CommandOrder order) {
-				var isResponsible = processor.isResponsible(order.name());
+				var isResponsible = processor.isResponsibleForRelatedResource(order);
 
 				assertThat(isResponsible).isFalse();
 			}
@@ -63,7 +63,7 @@ class PostfachNachrichtCommandProcessorTest {
 					"RECEIVE_POSTFACH_NACHRICHT" })
 			@ParameterizedTest(name = "{0}")
 			void shouldReturnTrueOnOrder(CommandOrder order) {
-				var isResponsible = processor.isResponsibleForEffectedResource(order.name());
+				var isResponsible = processor.isResponsibleForEffectedResource(order);
 
 				assertThat(isResponsible).isTrue();
 			}
@@ -72,7 +72,7 @@ class PostfachNachrichtCommandProcessorTest {
 					"RECEIVE_POSTFACH_NACHRICHT" })
 			@ParameterizedTest(name = "{0}")
 			void shouldReturnFalseOnOrder(CommandOrder order) {
-				var isResponsible = processor.isResponsibleForEffectedResource(order.name());
+				var isResponsible = processor.isResponsibleForEffectedResource(order);
 
 				assertThat(isResponsible).isFalse();
 			}
-- 
GitLab


From 28a05fb394e51a37bf1d7f78e84fc1dfe6c03b9c Mon Sep 17 00:00:00 2001
From: Martin <git@mail.de>
Date: Thu, 3 Apr 2025 16:28:39 +0200
Subject: [PATCH 3/4] OZG-7872 add safety tests

---
 .../common/command/CommandProcessorTest.java  | 20 +++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandProcessorTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandProcessorTest.java
index d62ad2e600..edc832bafc 100644
--- a/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandProcessorTest.java
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandProcessorTest.java
@@ -45,6 +45,16 @@ class CommandProcessorTest {
 				verify(processor).isResponsibleForEffectedResource(CommandOrder.fromOrder(CommandTestFactory.ORDER));
 			}
 
+			@DisplayName("should NOT create link builder for related resource if the responsibility matches")
+			@Test
+			void shouldNOTCallCreateEffectedResourceLinkBuilder() {
+				when(processor.isResponsibleForEffectedResource(any())).thenReturn(false);
+
+				processor.process(model);
+
+				verify(processor, never()).createEffectedResourceLinkBuilder(command);
+			}
+
 			@DisplayName("should create link builder for effected resource if the responsibility matches")
 			@Test
 			void shouldCallCreateEffectedResourceLinkBuilder() {
@@ -107,6 +117,16 @@ class CommandProcessorTest {
 				verify(processor).createRelatedResourceLinkBuilder(command);
 			}
 
+			@DisplayName("should NOT create link builder for related resource if the responsibility matches")
+			@Test
+			void shouldNOTCallCreateRelatedResourceLinkBuilder() {
+				when(processor.isResponsibleForRelatedResource(any())).thenReturn(false);
+
+				processor.process(model);
+
+				verify(processor, never()).createRelatedResourceLinkBuilder(command);
+			}
+
 			@DisplayName("link")
 			@Nested
 			class TestRelatedResourceLink {
-- 
GitLab


From 17669612e79f16181180335eb460fbd03b4b06bb Mon Sep 17 00:00:00 2001
From: Martin <git@mail.de>
Date: Thu, 3 Apr 2025 16:28:48 +0200
Subject: [PATCH 4/4] OZG-7872 revert switch case

---
 .../common/command/CommandModelAssembler.java | 39 +++++++------------
 1 file changed, 14 insertions(+), 25 deletions(-)

diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandModelAssembler.java b/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandModelAssembler.java
index eb69a13aeb..74c0bcb2df 100644
--- a/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandModelAssembler.java
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandModelAssembler.java
@@ -34,6 +34,7 @@ import org.springframework.hateoas.EntityModel;
 import org.springframework.hateoas.Link;
 import org.springframework.hateoas.LinkRelation;
 import org.springframework.hateoas.server.RepresentationModelAssembler;
+import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
 import org.springframework.stereotype.Component;
 
 import de.ozgcloud.alfa.bescheid.BescheidController;
@@ -86,31 +87,19 @@ class CommandModelAssembler implements RepresentationModelAssembler<Command, Ent
 	Link effectedResourceLinkByOrderType(Command entity) {
 		var type = entity.getCommandOrder().getType();
 
-		if (type == CommandOrder.Type.FORWARDING) {
-			return linkTo(methodOn(ForwardingController.class).findByVorgangId(entity.getVorgangId())).withRel(REL_EFFECTED_RESOURCE);
-		}
-		if (type == CommandOrder.Type.KOMMENTAR) {
-			return linkTo(KommentarController.class).slash(entity.getRelationId()).withRel(REL_EFFECTED_RESOURCE);
-		}
-		if (type == CommandOrder.Type.VORGANG) {
-			return linkTo(VorgangController.class).slash(entity.getRelationId()).withRel(REL_EFFECTED_RESOURCE);
-		}
-		if (type == CommandOrder.Type.VORGANG_LIST) {
-			return linkTo(VorgangController.class).withRel(REL_EFFECTED_RESOURCE);
-		}
-		if (type == CommandOrder.Type.WIEDERVORLAGE) {
-			return linkTo(WiedervorlageController.class).slash(entity.getRelationId()).withRel(REL_EFFECTED_RESOURCE);
-		}
-		if (type == CommandOrder.Type.BESCHEID) {
-			return linkTo(methodOn(BescheidController.class).getDraft(entity.getVorgangId())).withRel(REL_EFFECTED_RESOURCE);
-		}
-		if (type == CommandOrder.Type.DOCUMENT) {
-			return linkTo(DocumentController.class).slash(entity.getCreatedResource()).withRel(REL_EFFECTED_RESOURCE);
-		}
-		if (type == CommandOrder.Type.COLLABORATION) {
-			return linkTo(methodOn(CollaborationByVorgangController.class).getAllByVorgangId(entity.getVorgangId())).withRel(REL_EFFECTED_RESOURCE);
-		}
-		throw new IllegalArgumentException("Unknown CommandOrder: " + entity.getOrder());
+		WebMvcLinkBuilder linkBuilder = switch (type) {
+			case FORWARDING -> linkTo(methodOn(ForwardingController.class).findByVorgangId(entity.getVorgangId()));
+			case KOMMENTAR -> linkTo(KommentarController.class).slash(entity.getRelationId());
+			case VORGANG -> linkTo(VorgangController.class).slash(entity.getRelationId());
+			case VORGANG_LIST -> linkTo(VorgangController.class);
+			case WIEDERVORLAGE -> linkTo(WiedervorlageController.class).slash(entity.getRelationId());
+			case BESCHEID -> linkTo(methodOn(BescheidController.class).getDraft(entity.getVorgangId()));
+			case DOCUMENT -> linkTo(DocumentController.class).slash(entity.getCreatedResource());
+			case COLLABORATION -> linkTo(methodOn(CollaborationByVorgangController.class).getAllByVorgangId(entity.getVorgangId()));
+			default -> throw new IllegalArgumentException("Unknown CommandOrder: " + entity.getOrder());
+		};
+
+		return linkBuilder.withRel(REL_EFFECTED_RESOURCE);
 	}
 
 	public CollectionModel<EntityModel<Command>> toCollectionModel(Stream<Command> entities) {
-- 
GitLab