From 55ba1b3230fa5d9708e85e969b7a3fd5bedeff13 Mon Sep 17 00:00:00 2001
From: Martin <git@mail.de>
Date: Tue, 25 Mar 2025 08:26:24 +0100
Subject: [PATCH] OZG-725 OZG-7990 move link for searching
 organisationseinheiten to seperate processor

---
 .../CollaborationVorgangProcessor.java        |  10 +-
 ...onsEinheitVorgangWithEingangProcessor.java |  57 +++
 .../CollaborationVorgangProcessorTest.java    |  34 --
 ...inheitVorgangWithEingangProcessorTest.java | 387 ++++++++++++++++++
 4 files changed, 445 insertions(+), 43 deletions(-)
 create mode 100644 alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitVorgangWithEingangProcessor.java
 create mode 100644 alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitVorgangWithEingangProcessorTest.java

diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessor.java b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessor.java
index 2f75114f05..919b64ed9a 100644
--- a/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessor.java
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessor.java
@@ -25,7 +25,6 @@ package de.ozgcloud.alfa.collaboration;
 
 import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
 
-import java.util.List;
 import java.util.Objects;
 
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -47,7 +46,6 @@ import lombok.RequiredArgsConstructor;
 class CollaborationVorgangProcessor implements RepresentationModelProcessor<EntityModel<VorgangWithEingang>> {
 
 	static final LinkRelation REL_COLLABORATIONS = LinkRelation.of("collaborations");
-	static final LinkRelation REL_SEARCH_ORGANISATIONS_EINHEIT = LinkRelation.of("searchOrganisationsEinheit");
 	static final LinkRelation REL_SEARCH_FACHSTELLE = LinkRelation.of("searchFachstelle");
 
 	private final CurrentUserService currentUserService;
@@ -60,17 +58,11 @@ class CollaborationVorgangProcessor implements RepresentationModelProcessor<Enti
 		if (Objects.isNull(vorgang) || !currentUserService.hasRole(UserRole.VERWALTUNG_USER)) {
 			return model;
 		}
-		model.addAllIf(!collaborationService.hasCollaboration(vorgang.getId()),
-				() -> List.of(buildSearchOrganisationsEinheitLink(), buildSearchFachstelleLink()))
+		model.addIf(!collaborationService.hasCollaboration(vorgang.getId()), this::buildSearchFachstelleLink)
 				.add(linkTo(methodOn(CollaborationByVorgangController.class).getAllByVorgangId(vorgang.getId())).withRel(REL_COLLABORATIONS));
 		return model;
 	}
 
-	private Link buildSearchOrganisationsEinheitLink() {
-		return linkTo(methodOn(OrganisationsEinheitController.class).search(null)).withRel(
-				REL_SEARCH_ORGANISATIONS_EINHEIT);
-	}
-
 	private Link buildSearchFachstelleLink() {
 		return linkTo(methodOn(FachstelleController.class).search(null)).withRel(REL_SEARCH_FACHSTELLE);
 	}
diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitVorgangWithEingangProcessor.java b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitVorgangWithEingangProcessor.java
new file mode 100644
index 0000000000..301f5947d0
--- /dev/null
+++ b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitVorgangWithEingangProcessor.java
@@ -0,0 +1,57 @@
+package de.ozgcloud.alfa.collaboration;
+
+import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
+
+import java.util.Optional;
+
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.Link;
+import org.springframework.hateoas.LinkRelation;
+import org.springframework.hateoas.server.RepresentationModelProcessor;
+import org.springframework.stereotype.Component;
+
+import de.ozgcloud.alfa.common.FeatureToggleProperties;
+import de.ozgcloud.alfa.common.user.CurrentUserService;
+import de.ozgcloud.alfa.common.user.UserRole;
+import de.ozgcloud.alfa.vorgang.Vorgang.VorgangStatus;
+import de.ozgcloud.alfa.vorgang.VorgangWithEingang;
+import lombok.RequiredArgsConstructor;
+
+@Component
+@RequiredArgsConstructor
+class OrganisationsEinheitVorgangWithEingangProcessor implements RepresentationModelProcessor<EntityModel<VorgangWithEingang>> {
+
+	static final LinkRelation REL_SEARCH_ORGANISATIONS_EINHEIT = LinkRelation.of("searchOrganisationsEinheit");
+
+	private final FeatureToggleProperties featureToggleProperties;
+	private final CurrentUserService userService;
+
+	@Override
+	public EntityModel<VorgangWithEingang> process(EntityModel<VorgangWithEingang> model) {
+		Optional.ofNullable(model.getContent())
+				.ifPresent(vorgang -> model.addIf(this.isSearchNeeded(vorgang),
+						this::buildSearchOrganisationsEinheitLink));
+		return model;
+	}
+
+	boolean isSearchNeeded(VorgangWithEingang vorgang) {
+		return this.isForwardable(vorgang) || this.isCollaborationCreateable();
+	}
+
+	boolean isForwardable(VorgangWithEingang vorgang) {
+		return featureToggleProperties.isForwardByOzgCloudEnabled() && isStatusNeu(vorgang);
+	}
+
+	boolean isCollaborationCreateable() {
+		return featureToggleProperties.isCollaborationEnabled() && this.userService.hasRole(UserRole.VERWALTUNG_USER);
+	}
+
+	private boolean isStatusNeu(VorgangWithEingang vorgang) {
+		return vorgang.getStatus() == VorgangStatus.NEU;
+	}
+
+	private Link buildSearchOrganisationsEinheitLink() {
+		return linkTo(methodOn(OrganisationsEinheitController.class).search(null)).withRel(
+				REL_SEARCH_ORGANISATIONS_EINHEIT);
+	}
+}
\ No newline at end of file
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessorTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessorTest.java
index 97079a883a..9f245c3c6a 100644
--- a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessorTest.java
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessorTest.java
@@ -103,20 +103,6 @@ class CollaborationVorgangProcessorTest {
 					when(collaborationService.hasCollaboration(VorgangHeaderTestFactory.ID)).thenReturn(true);
 				}
 
-				@Test
-				void shouldHaveOneLink() {
-					var model = callProcessor();
-
-					assertThat(model.getLinks()).hasSize(1);
-				}
-
-				@Test
-				void shouldNotAddSearchOrganisationsEinheitLink() {
-					var model = callProcessor();
-
-					assertThat(model.getLink(CollaborationVorgangProcessor.REL_SEARCH_ORGANISATIONS_EINHEIT)).isEmpty();
-				}
-
 				@Test
 				void shouldAddCollaborationsLink() {
 					var model = callProcessor();
@@ -135,26 +121,6 @@ class CollaborationVorgangProcessorTest {
 					when(collaborationService.hasCollaboration(VorgangHeaderTestFactory.ID)).thenReturn(false);
 				}
 
-				@Test
-				void shouldHaveThreeLinks() {
-					var model = callProcessor();
-
-					assertThat(model.getLinks()).hasSize(3);
-				}
-
-				@Test
-				void shouldAddSearchOrganisationsEinheitLink() {
-					var expectedHref = UriComponentsBuilder.fromUriString(OrganisationsEinheitController.PATH)
-							.queryParam(OrganisationsEinheitController.SEARCH_BY_PARAM, "{" + OrganisationsEinheitController.SEARCH_BY_PARAM + "}")
-							.build().toString();
-
-					var model = callProcessor();
-
-					assertThat(model.getLink(CollaborationVorgangProcessor.REL_SEARCH_ORGANISATIONS_EINHEIT)).get()
-							.extracting(Link::getHref)
-							.isEqualTo(expectedHref);
-				}
-
 				@Test
 				void shouldAddSearchFachstelleLink() {
 					var expectedHref = UriComponentsBuilder.fromUriString(FachstelleController.PATH)
diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitVorgangWithEingangProcessorTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitVorgangWithEingangProcessorTest.java
new file mode 100644
index 0000000000..b3832f8cbb
--- /dev/null
+++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitVorgangWithEingangProcessorTest.java
@@ -0,0 +1,387 @@
+package de.ozgcloud.alfa.collaboration;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.EnumSource.Mode;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.Link;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import de.ozgcloud.alfa.common.FeatureToggleProperties;
+import de.ozgcloud.alfa.common.user.CurrentUserService;
+import de.ozgcloud.alfa.common.user.UserRole;
+import de.ozgcloud.alfa.vorgang.Vorgang.VorgangStatus;
+import de.ozgcloud.alfa.vorgang.VorgangWithEingang;
+import de.ozgcloud.alfa.vorgang.VorgangWithEingangTestFactory;
+
+public class OrganisationsEinheitVorgangWithEingangProcessorTest {
+
+	@InjectMocks
+	@Spy
+	private OrganisationsEinheitVorgangWithEingangProcessor processor;
+
+	@Mock
+	private CurrentUserService userService;
+	@Mock
+	private FeatureToggleProperties featureToggleProperties;
+
+	@DisplayName("Process")
+	@Nested
+	class TestProcess {
+
+		private final VorgangWithEingang vorgang = VorgangWithEingangTestFactory.create();
+
+		private final EntityModel<VorgangWithEingang> model = EntityModel.of(vorgang);
+
+		@SuppressWarnings("unchecked")
+		@Test
+		void shouldReturnSameModelOnNullContent() {
+			var entityModel = mock(EntityModel.class);
+			when(entityModel.getContent()).thenReturn(null);
+
+			var processedModel = processor.process(entityModel);
+
+			assertThat(processedModel).isSameAs(entityModel);
+		}
+
+		@DisplayName("entity model links")
+		@Nested
+		class TestEntityModelLinks {
+
+			@Test
+			void shouldContainsSearchLinkIfNeeded() {
+				doReturn(true).when(processor).isSearchNeeded(any());
+
+				var processedModel = processor.process(model);
+
+				var expectedHref = UriComponentsBuilder.fromUriString(OrganisationsEinheitController.PATH).build().toString();
+
+				assertThat(processedModel.getLink(OrganisationsEinheitVorgangWithEingangProcessor.REL_SEARCH_ORGANISATIONS_EINHEIT)).get()
+						.extracting(Link::getHref)
+						.isEqualTo(OrganisationsEinheitController.PATH + "?searchBy={searchBy}");
+			}
+		}
+	}
+
+	@DisplayName("is search needed")
+	@Nested
+	class TestIsSearchNeeded {
+
+		private VorgangWithEingang vorgang = VorgangWithEingangTestFactory.create();
+
+		@Test
+		void shouldReturnTrueIfForwardable() {
+			doReturn(true).when(processor).isForwardable(any());
+
+			var isSearchNeeded = processor.isSearchNeeded(vorgang);
+
+			assertThat(isSearchNeeded).isTrue();
+		}
+
+		@Test
+		void shouldReturnFalseIfCollaborationCreateable() {
+			doReturn(true).when(processor).isCollaborationCreateable();
+
+			var isSearchNeeded = processor.isSearchNeeded(vorgang);
+
+			assertThat(isSearchNeeded).isTrue();
+		}
+	}
+
+	@DisplayName("is forwardable")
+	@Nested
+	class TestIsForwardable {
+
+		@DisplayName("on feature disabled")
+		@Nested
+		class TestOnFeatureDisabled {
+
+			@BeforeEach
+			void mockFeatureToggle() {
+				when(featureToggleProperties.isForwardByOzgCloudEnabled()).thenReturn(false);
+			}
+
+			@ParameterizedTest
+			@EnumSource
+			void shouldReturnFalse(VorgangStatus status) {
+				var vorgang = VorgangWithEingangTestFactory.createBuilder().status(status).build();
+
+				var forwardableByOzgCloud = processor.isForwardable(vorgang);
+
+				assertThat(forwardableByOzgCloud).isFalse();
+			}
+		}
+
+		@DisplayName("on feature enabled")
+		@Nested
+		class TestOnFeatureEnabled {
+
+			@BeforeEach
+			void mockFeatureToggle() {
+				when(featureToggleProperties.isForwardByOzgCloudEnabled()).thenReturn(true);
+			}
+
+			@ParameterizedTest(name = "{0}")
+			@EnumSource(mode = Mode.EXCLUDE, names = { "NEU" })
+			void shouldReturnFalseOnVorgangStatus(VorgangStatus status) {
+				var forwardableByOzgCloud = processor.isForwardable(VorgangWithEingangTestFactory.createBuilder().status(status).build());
+
+				assertThat(forwardableByOzgCloud).isFalse();
+			}
+
+			@Test
+			void shouldReturnTrueOnVorgangStatusNeu() {
+				var forwardable = processor.isForwardable(VorgangWithEingangTestFactory.createBuilder().status(VorgangStatus.NEU).build());
+
+				assertThat(forwardable).isTrue();
+			}
+		}
+	}
+
+	@DisplayName("is collaboration createable")
+	@Nested
+	class TestIsCollaborationCreateable {
+
+		@DisplayName("on feature disabled")
+		@Nested
+		class TestOnFeatureDisabled {
+
+			@BeforeEach
+			void mockFeatureToggle() {
+				when(featureToggleProperties.isCollaborationEnabled()).thenReturn(false);
+			}
+
+			void shouldReturnFalse() {
+				var isCollaborationEnabled = processor.isCollaborationCreateable();
+
+				assertThat(isCollaborationEnabled).isFalse();
+			}
+		}
+
+		@DisplayName("on feature enabled")
+		@Nested
+		class TestOnFeatureEnabled {
+
+			@BeforeEach
+			void mockFeatureToggle() {
+				when(featureToggleProperties.isCollaborationEnabled()).thenReturn(true);
+			}
+
+			@Test
+			void shouldVerifyRole() {
+				processor.isCollaborationCreateable();
+
+				verify(userService).hasRole(UserRole.VERWALTUNG_USER);
+			}
+
+			@Test
+			void shouldReturnFalseMissingRole() {
+				when(userService.hasRole(any())).thenReturn(false);
+
+				var isCollaborationEnabled = processor.isCollaborationCreateable();
+
+				assertThat(isCollaborationEnabled).isFalse();
+			}
+
+			@Test
+			void shouldReturnTrueOnAssignedRole() {
+				when(userService.hasRole(any())).thenReturn(true);
+
+				var isCollaborationEnabled = processor.isCollaborationCreateable();
+
+				assertThat(isCollaborationEnabled).isTrue();
+			}
+		}
+	}
+
+//		@Nested
+//		class TestForwardByOzgCloudLink {
+//
+//			@Test
+//			void shouldCallIsForwardableByOzgCloud() {
+//				doReturn(false).when(processor).isForwardableByOzgCloud(any());
+//
+//				processor.process(EntityModel.of(vorgang));
+//
+//				verify(processor).isForwardableByOzgCloud(vorgang);
+//			}
+//
+//			@Nested
+//			class TestOnIsNotForwardableByOzgCloud {
+//
+//				@BeforeEach
+//				void givenIsNotForwardableByOzgCloud() {
+//					doReturn(false).when(processor).isForwardableByOzgCloud(any());
+//				}
+//
+//				@Test
+//				void shouldNotAddLink() {
+//					var model = processor.process(EntityModel.of(vorgang));
+//
+//					assertThat(model.getLink(ForwardingVorgangWithEingangProcessor.REL_FORWARD_BY_OZGCLOUD)).isEmpty();
+//				}
+//			}
+//
+//			@Nested
+//			class TestOnIsForwardableByOzgCloud {
+//
+//				@BeforeEach
+//				void givenIsNotForwardableByOzgCloud() {
+//					doReturn(true).when(processor).isForwardableByOzgCloud(any());
+//				}
+//
+//				@Test
+//				void shouldAddForwardByOzgCloudLink() {
+//					var expectedHref = UriComponentsBuilder.fromUriString(ForwardByVorgangCommandController.PATH)
+//							.buildAndExpand(VorgangHeaderTestFactory.ID, VorgangHeaderTestFactory.VERSION).toString();
+//
+//					var model = processor.process(EntityModel.of(vorgang));
+//
+//					assertThat(model.getLink(ForwardingVorgangWithEingangProcessor.REL_FORWARD_BY_OZGCLOUD)).get()
+//							.extracting(Link::getHref)
+//							.isEqualTo(expectedHref);
+//				}
+//			}
+//	}
+//
+//		@Nested
+//		class TestForwardByEmailLink {
+//
+//			@Test
+//			void shouldCallIsForwardableByEmail() {
+//				doReturn(false).when(processor).isForwardableByEmail();
+//
+//				processor.process(EntityModel.of(vorgang));
+//
+//				verify(processor).isForwardableByEmail();
+//			}
+//
+//			@Nested
+//			class TestOnIsNotForwardableByEmail {
+//
+//				@BeforeEach
+//				void givenIsNotForwardableByEmail() {
+//					doReturn(false).when(processor).isForwardableByEmail();
+//				}
+//
+//				@Test
+//				void shouldNotAddLink() {
+//					var model = processor.process(EntityModel.of(vorgang));
+//
+//					assertThat(model.getLink(ForwardingVorgangWithEingangProcessor.REL_FORWARD_BY_EMAIL)).isEmpty();
+//				}
+//			}
+//
+//			@Nested
+//			class TestOnIsForwardableByEmail {
+//
+//				@BeforeEach
+//				void givenIsForwardableByEmail() {
+//					doReturn(true).when(processor).isForwardableByEmail();
+//				}
+//
+//				@Test
+//				void shouldAddLink() {
+//					var expectedHref = UriComponentsBuilder.fromUriString(ForwardingController.LIST_PATH)
+//							.queryParam(ForwardingController.PARAM_VORGANG_ID, VorgangHeaderTestFactory.ID).build().toString();
+//
+//					var model = processor.process(EntityModel.of(vorgang));
+//
+//					assertThat(model.getLink(ForwardingVorgangWithEingangProcessor.REL_FORWARD_BY_EMAIL)).get()
+//							.extracting(Link::getHref)
+//							.isEqualTo(expectedHref);
+//				}
+//			}
+//		}
+//	}
+//
+//	@Nested
+//	class TestIsForwardableByOzgCloud {
+//
+//		@Nested
+//		class TestOnFeatureDisabled {
+//
+//			@BeforeEach
+//			void givenFeatureDisabled() {
+//				when(featureToggleProperties.isForwardByOzgCloudEnabled()).thenReturn(false);
+//			}
+//
+//			@ParameterizedTest
+//			@EnumSource
+//			void shouldReturnFalse(VorgangStatus status) {
+//				var vorgang = VorgangWithEingangTestFactory.createBuilder().status(status).build();
+//
+//				var forwardableByOzgCloud = processor.isForwardableByOzgCloud(vorgang);
+//
+//				assertThat(forwardableByOzgCloud).isFalse();
+//			}
+//		}
+//
+//		@Nested
+//		class TestOnFeatureEnabled {
+//
+//			@BeforeEach
+//			void givenFeatureEnabled() {
+//				when(featureToggleProperties.isForwardByOzgCloudEnabled()).thenReturn(true);
+//			}
+//
+//			@Nested
+//			class TestOnVorgangNotNeu {
+//				@ParameterizedTest
+//				@EnumSource(mode = Mode.EXCLUDE, names = { "NEU" })
+//				void shouldReturnFalseOnVorgangStatusNotNeu(VorgangStatus status) {
+//					var vorgang = VorgangWithEingangTestFactory.createBuilder().status(status).build();
+//
+//					var forwardableByOzgCloud = processor.isForwardableByOzgCloud(vorgang);
+//
+//					assertThat(forwardableByOzgCloud).isFalse();
+//				}
+//			}
+//
+//			@Nested
+//			class TestOnVorgangIsNeu {
+//
+//				private final VorgangWithEingang newVorgang = VorgangWithEingangTestFactory.createBuilder().status(VorgangStatus.NEU).build();
+//
+//				@Test
+//				void shouldReturnTrue() {
+//					var forwardableByOzgCloud = processor.isForwardableByOzgCloud(newVorgang);
+//
+//					assertThat(forwardableByOzgCloud).isTrue();
+//				}
+//			}
+//		}
+//	}
+//
+//	@Nested
+//	class TestIsForwardableByEmail {
+//
+//		@Test
+//		void shouldCallUserServiceForCurrentRole() {
+//			processor.isForwardableByEmail();
+//
+//			verify(userService).hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER);
+//		}
+//
+//		@ParameterizedTest
+//		@ValueSource(booleans = { true, false })
+//		void shouldReturnIsEinheitlicherAnsprechpartner(boolean isEinheitlicherAnsprechpartner) {
+//			when(userService.hasRole(any())).thenReturn(isEinheitlicherAnsprechpartner);
+//
+//			var isForwardableByEmail = processor.isForwardableByEmail();
+//
+//			assertThat(isForwardableByEmail).isEqualTo(isEinheitlicherAnsprechpartner);
+//		}
+//	}
+}
-- 
GitLab