From 67794159fc53cac106a47d1ea0c6c057ae742c75 Mon Sep 17 00:00:00 2001
From: OZGCloud <ozgcloud@mgm-tp.com>
Date: Mon, 23 Sep 2024 17:43:38 +0200
Subject: [PATCH] OZG-5031 OZG-6794 Implement grpc interface to provide list of
 user profiles including user settings belonging to Organisationseinheit

---
 .vscode/settings.json                         |  4 +
 .../src/main/protobuf/recipient.model.proto   |  1 +
 .../src/main/protobuf/recipient.proto         |  6 +-
 .../src/main/protobuf/userprofile.model.proto | 17 ++++
 .../src/main/protobuf/userprofile.proto       |  2 +
 .../java/de/ozgcloud/user/UserRepository.java |  7 +-
 .../java/de/ozgcloud/user/UserService.java    | 19 +++--
 .../user/recipient/RecipientGrpcService.java  |  1 +
 .../user/recipient/RecipientMapper.java       |  1 +
 .../user/recipient/RecipientRepository.java   |  1 +
 .../user/recipient/RecipientService.java      |  3 +-
 .../user/settings/UserSettingsMapper.java     | 25 ++++++
 .../userprofile/UserProfileGrpcService.java   | 26 ++++--
 .../user/userprofile/UserProfileMapper.java   |  5 +-
 .../ozgcloud/user/UserRepositoryITCase.java   | 53 +++++++++---
 .../de/ozgcloud/user/UserRepositoryTest.java  | 40 ++++++++--
 .../de/ozgcloud/user/UserServiceTest.java     | 41 ++++++++++
 .../de/ozgcloud/user/UserTestFactory.java     |  4 +-
 .../settings/GrpcUserSettingsTestFactory.java | 48 +++++++++++
 .../user/settings/UserSettingsMapperTest.java | 23 ++++++
 ...anisationsEinheitIdRequestTestFactory.java | 19 +++++
 ...nisationsEinheitIdResponseTestFactory.java | 17 ++++
 .../GrpcUserProfileTestFactory.java           |  4 +-
 .../UserProfileGrpcServiceTest.java           | 80 +++++++++++++++++++
 .../userprofile/UserProfileMapperTest.java    |  8 ++
 25 files changed, 422 insertions(+), 33 deletions(-)
 create mode 100644 .vscode/settings.json
 create mode 100644 user-manager-server/src/main/java/de/ozgcloud/user/settings/UserSettingsMapper.java
 create mode 100644 user-manager-server/src/test/java/de/ozgcloud/user/settings/GrpcUserSettingsTestFactory.java
 create mode 100644 user-manager-server/src/test/java/de/ozgcloud/user/settings/UserSettingsMapperTest.java
 create mode 100644 user-manager-server/src/test/java/de/ozgcloud/user/userprofile/GrpcGetAllByOrganisationsEinheitIdRequestTestFactory.java
 create mode 100644 user-manager-server/src/test/java/de/ozgcloud/user/userprofile/GrpcGetAllByOrganisationsEinheitIdResponseTestFactory.java

diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..9bd06c2e
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+    "java.configuration.updateBuildConfiguration": "automatic",
+    "java.debug.settings.onBuildFailureProceed": true
+}
\ No newline at end of file
diff --git a/user-manager-interface/src/main/protobuf/recipient.model.proto b/user-manager-interface/src/main/protobuf/recipient.model.proto
index 5c286982..5f07b59f 100644
--- a/user-manager-interface/src/main/protobuf/recipient.model.proto
+++ b/user-manager-interface/src/main/protobuf/recipient.model.proto
@@ -30,6 +30,7 @@ option java_package = "de.ozgcloud.user.recipient";
 option java_outer_classname = "RecipientModelProto";
 
 message GrpcRecipient {
+  option deprecated = true;
   string firstName = 1;
   string lastName = 2;
   string email = 3;
diff --git a/user-manager-interface/src/main/protobuf/recipient.proto b/user-manager-interface/src/main/protobuf/recipient.proto
index bbaae80c..8c3d9b27 100644
--- a/user-manager-interface/src/main/protobuf/recipient.proto
+++ b/user-manager-interface/src/main/protobuf/recipient.proto
@@ -32,13 +32,17 @@ option java_package = "de.ozgcloud.user.grpc.recipient";
 option java_outer_classname = "RecipientProto";
 
 service RecipientService {
-  rpc findRecipientByOrganisationsEinheitId(GrpcFindRecipientRequest) returns (GrpcFindRecipientResponse);
+  rpc findRecipientByOrganisationsEinheitId(GrpcFindRecipientRequest) returns (GrpcFindRecipientResponse){
+    option deprecated = true;
+  };
 }
 
 message GrpcFindRecipientRequest {
+  option deprecated = true;
   string organisationsEinheitId = 1;
 }
 
 message GrpcFindRecipientResponse {
+  option deprecated = true;
   repeated GrpcRecipient recipient = 1;
 }
\ No newline at end of file
diff --git a/user-manager-interface/src/main/protobuf/userprofile.model.proto b/user-manager-interface/src/main/protobuf/userprofile.model.proto
index 0a5db433..910054b1 100644
--- a/user-manager-interface/src/main/protobuf/userprofile.model.proto
+++ b/user-manager-interface/src/main/protobuf/userprofile.model.proto
@@ -34,6 +34,15 @@ message GrpcUserProfile {
   string firstName = 2;
   string lastName = 3;
   string email = 4;
+  GrpcUserSettings userSettings = 5;
+}
+
+message GrpcUserSettings {
+  bool notificationsSendForAll = 1;
+	bool vorgangCreated = 2;
+	bool vorgangAssignedToUser = 3;
+	bool postfachNachrichtFromAntragsteller = 4;
+	bool wiedervorlageOverdue = 5;
 }
 
 
@@ -45,6 +54,14 @@ message GrpcGetUserProfileResponse {
   GrpcUserProfile userProfile = 1;
 }
 
+message GrpcGetAllByOrganisationsEinheitIdRequest{
+  string organisationsEinheitId = 1;
+}
+
+message GrpcGetAllByOrganisationsEinheitIdResponse{
+  repeated GrpcUserProfile userProfile = 1;
+}
+
 message GrpcFindInactiveUserIdsResponse {
   repeated GrpcUserProfileId userProfileIds = 1;
 }
diff --git a/user-manager-interface/src/main/protobuf/userprofile.proto b/user-manager-interface/src/main/protobuf/userprofile.proto
index ab445da5..0b22ea51 100644
--- a/user-manager-interface/src/main/protobuf/userprofile.proto
+++ b/user-manager-interface/src/main/protobuf/userprofile.proto
@@ -37,6 +37,8 @@ service UserProfileService {
 
   rpc GetByExternalId(GrpcGetUserProfileRequest) returns (GrpcGetUserProfileResponse);
 
+  rpc GetAllByOrganisationsEinheitId(GrpcGetAllByOrganisationsEinheitIdRequest) returns (GrpcGetAllByOrganisationsEinheitIdResponse);
+
   rpc FindInactiveUserIds(GrpcFindInactiveUserIdsRequest) returns (GrpcFindInactiveUserIdsResponse);
 
   rpc DeleteInactiveUser(GrpcDeleteInactiveUserRequest) returns (GrpcDeleteInactiveUserResponse);
diff --git a/user-manager-server/src/main/java/de/ozgcloud/user/UserRepository.java b/user-manager-server/src/main/java/de/ozgcloud/user/UserRepository.java
index 56f1b35b..df9598eb 100644
--- a/user-manager-server/src/main/java/de/ozgcloud/user/UserRepository.java
+++ b/user-manager-server/src/main/java/de/ozgcloud/user/UserRepository.java
@@ -28,13 +28,14 @@ import static de.ozgcloud.user.User.*;
 import java.util.Optional;
 import java.util.stream.Stream;
 
+import jakarta.enterprise.context.ApplicationScoped;
+
 import org.bson.types.ObjectId;
 
 import de.ozgcloud.common.logging.OzgCloudLogging;
 import de.ozgcloud.user.common.errorhandling.ResourceNotFoundException;
 import io.quarkus.mongodb.panache.PanacheMongoRepository;
 import io.quarkus.panache.common.Parameters;
-import jakarta.enterprise.context.ApplicationScoped;
 
 @ApplicationScoped
 @OzgCloudLogging
@@ -127,6 +128,10 @@ class UserRepository implements PanacheMongoRepository<User> {
 		return find(DELETED_FIELD, true).project(UserIdProjection.class).stream().map(UserIdProjection::getAsString);
 	}
 
+	public Stream<User> findAllUsersByOrganisationsEinheitId(String organisationsEinheitId) {
+		return stream(ORGANISATIONS_EINHEIT_IDS_FIELD, organisationsEinheitId);
+	}
+
 	public void deleteById(String id) {
 		deleteById(new ObjectId(id));
 	}
diff --git a/user-manager-server/src/main/java/de/ozgcloud/user/UserService.java b/user-manager-server/src/main/java/de/ozgcloud/user/UserService.java
index 27edabcc..2fd9d63a 100644
--- a/user-manager-server/src/main/java/de/ozgcloud/user/UserService.java
+++ b/user-manager-server/src/main/java/de/ozgcloud/user/UserService.java
@@ -28,26 +28,28 @@ import java.util.Optional;
 import java.util.stream.Stream;
 
 import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.inject.Inject;
 
 import org.apache.commons.lang3.StringUtils;
 
 import de.ozgcloud.common.logging.OzgCloudLogging;
+import de.ozgcloud.user.common.errorhandling.FunctionalException;
 import de.ozgcloud.user.common.errorhandling.ResourceNotFoundException;
 import de.ozgcloud.user.common.errorhandling.TechnicalException;
 import de.ozgcloud.user.keycloak.KeycloakUserRemoteService;
 import de.ozgcloud.user.settings.UserSettings;
 import io.smallrye.mutiny.Uni;
 import io.smallrye.mutiny.infrastructure.Infrastructure;
+import lombok.RequiredArgsConstructor;
 
 @ApplicationScoped
 @OzgCloudLogging
+@RequiredArgsConstructor
 public class UserService {
 
-	@Inject
-	UserRepository repository;
-	@Inject
-	KeycloakUserRemoteService keycloakUserRemoteService;
+	static final String MISSING_ORGANISATIONS_EINHEIT_ID_MESSAGE_TEMPLATE = "OrganisationsEinheitId ('%s') can not be null or empty";
+
+	private final UserRepository repository;
+	private final KeycloakUserRemoteService keycloakUserRemoteService;
 
 	public User save(User user) {
 		findUser(user).ifPresentOrElse(persistedUser -> repository.updateUser(addIdUser(user, persistedUser)),
@@ -146,4 +148,11 @@ public class UserService {
 			throw new TechnicalException(String.format("Deleting active users is not allowed [user id: %s].", user.getId().toHexString()));
 		}
 	}
+
+	public Stream<User> findAllUsersOfOrganisationsEinheit(String organisationsEinheitId) {
+		if (StringUtils.isEmpty(organisationsEinheitId)) {
+			throw new FunctionalException(() -> String.format(MISSING_ORGANISATIONS_EINHEIT_ID_MESSAGE_TEMPLATE, organisationsEinheitId));
+		}
+		return repository.findAllUsersByOrganisationsEinheitId(organisationsEinheitId);
+	}
 }
\ No newline at end of file
diff --git a/user-manager-server/src/main/java/de/ozgcloud/user/recipient/RecipientGrpcService.java b/user-manager-server/src/main/java/de/ozgcloud/user/recipient/RecipientGrpcService.java
index fa9dc41d..ab0ebdd2 100644
--- a/user-manager-server/src/main/java/de/ozgcloud/user/recipient/RecipientGrpcService.java
+++ b/user-manager-server/src/main/java/de/ozgcloud/user/recipient/RecipientGrpcService.java
@@ -35,6 +35,7 @@ import io.grpc.stub.StreamObserver;
 import io.quarkus.grpc.GrpcService;
 
 @GrpcService
+@Deprecated
 public class RecipientGrpcService extends RecipientServiceImplBase {
 
 	@Inject
diff --git a/user-manager-server/src/main/java/de/ozgcloud/user/recipient/RecipientMapper.java b/user-manager-server/src/main/java/de/ozgcloud/user/recipient/RecipientMapper.java
index bea3d985..334c81ea 100644
--- a/user-manager-server/src/main/java/de/ozgcloud/user/recipient/RecipientMapper.java
+++ b/user-manager-server/src/main/java/de/ozgcloud/user/recipient/RecipientMapper.java
@@ -34,6 +34,7 @@ import de.ozgcloud.user.User;
 		unmappedSourcePolicy = ReportingPolicy.WARN, //
 		nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, //
 		collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
+@Deprecated
 interface RecipientMapper {
 
 	GrpcRecipient toRecipient(User user);
diff --git a/user-manager-server/src/main/java/de/ozgcloud/user/recipient/RecipientRepository.java b/user-manager-server/src/main/java/de/ozgcloud/user/recipient/RecipientRepository.java
index 17482b93..f076298b 100644
--- a/user-manager-server/src/main/java/de/ozgcloud/user/recipient/RecipientRepository.java
+++ b/user-manager-server/src/main/java/de/ozgcloud/user/recipient/RecipientRepository.java
@@ -15,6 +15,7 @@ import io.quarkus.mongodb.panache.PanacheMongoRepository;
 
 @ApplicationScoped
 @OzgCloudLogging
+@Deprecated
 class RecipientRepository implements PanacheMongoRepository<User> {
 
 	private static final String SEARCH_RECIPIENT_QUERY = ORGANISATIONS_EINHEIT_IDS_FIELD + " = ?1 and "
diff --git a/user-manager-server/src/main/java/de/ozgcloud/user/recipient/RecipientService.java b/user-manager-server/src/main/java/de/ozgcloud/user/recipient/RecipientService.java
index b383eceb..c6444739 100644
--- a/user-manager-server/src/main/java/de/ozgcloud/user/recipient/RecipientService.java
+++ b/user-manager-server/src/main/java/de/ozgcloud/user/recipient/RecipientService.java
@@ -25,15 +25,16 @@ package de.ozgcloud.user.recipient;
 
 import java.util.List;
 
-import de.ozgcloud.user.common.errorhandling.FunctionalException;
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
 
 import com.cronutils.utils.StringUtils;
 
 import de.ozgcloud.user.User;
+import de.ozgcloud.user.common.errorhandling.FunctionalException;
 
 @ApplicationScoped
+@Deprecated
 class RecipientService {
 
 	static final String MISSING_ORGANISATIONS_EINHEIT_ID_MESSAGE_TEMPLATE = "OrganisationsEinheitId ('%s') can not be null or empty";
diff --git a/user-manager-server/src/main/java/de/ozgcloud/user/settings/UserSettingsMapper.java b/user-manager-server/src/main/java/de/ozgcloud/user/settings/UserSettingsMapper.java
new file mode 100644
index 00000000..329078be
--- /dev/null
+++ b/user-manager-server/src/main/java/de/ozgcloud/user/settings/UserSettingsMapper.java
@@ -0,0 +1,25 @@
+package de.ozgcloud.user.settings;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.NullValueCheckStrategy;
+import org.mapstruct.ReportingPolicy;
+
+import de.ozgcloud.user.userprofile.GrpcUserSettings;
+
+@Mapper(unmappedTargetPolicy = ReportingPolicy.WARN, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
+public interface UserSettingsMapper {
+
+	@Mapping(target = "mergeFrom", ignore = true)
+	@Mapping(target = "clearField", ignore = true)
+	@Mapping(target = "clearOneof", ignore = true)
+	@Mapping(target = "mergeUnknownFields", ignore = true)
+	@Mapping(target = "unknownFields", ignore = true)
+	@Mapping(target = "allFields", ignore = true)
+	@Mapping(target = "notificationsSendForAll", source = "notificationsSendFor")
+	GrpcUserSettings toGrpc(UserSettings userSettings);
+
+	default boolean map(NotificationsSendFor sendFor) {
+		return NotificationsSendFor.ALL.equals(sendFor);
+	}
+}
diff --git a/user-manager-server/src/main/java/de/ozgcloud/user/userprofile/UserProfileGrpcService.java b/user-manager-server/src/main/java/de/ozgcloud/user/userprofile/UserProfileGrpcService.java
index c9d773be..b41ec61d 100644
--- a/user-manager-server/src/main/java/de/ozgcloud/user/userprofile/UserProfileGrpcService.java
+++ b/user-manager-server/src/main/java/de/ozgcloud/user/userprofile/UserProfileGrpcService.java
@@ -25,22 +25,20 @@ package de.ozgcloud.user.userprofile;
 
 import java.util.stream.Stream;
 
-import jakarta.inject.Inject;
-
 import de.ozgcloud.user.User;
 import de.ozgcloud.user.UserService;
 import de.ozgcloud.user.grpc.userprofile.UserProfileServiceGrpc.UserProfileServiceImplBase;
 import io.grpc.stub.StreamObserver;
 import io.quarkus.grpc.GrpcService;
+import lombok.RequiredArgsConstructor;
 
 @GrpcService
+@RequiredArgsConstructor
 public class UserProfileGrpcService extends UserProfileServiceImplBase {
 
-	@Inject
-	UserService service;
+	private final UserService service;
 
-	@Inject
-	UserProfileMapper mapper;
+	private final UserProfileMapper mapper;
 
 	@Override
 	public void getById(GrpcGetUserProfileRequest request, StreamObserver<GrpcGetUserProfileResponse> responseObserver) {
@@ -62,6 +60,22 @@ public class UserProfileGrpcService extends UserProfileServiceImplBase {
 		responseObserver.onCompleted();
 	}
 
+	@Override
+	public void getAllByOrganisationsEinheitId(GrpcGetAllByOrganisationsEinheitIdRequest request,
+			StreamObserver<GrpcGetAllByOrganisationsEinheitIdResponse> responseObserver) {
+
+		var userProfiles = service.findAllUsersOfOrganisationsEinheit(request.getOrganisationsEinheitId());
+
+		responseObserver.onNext(buildGrpcGetAllByOrganisationsEinheitIdResponse(userProfiles));
+		responseObserver.onCompleted();
+	}
+
+	GrpcGetAllByOrganisationsEinheitIdResponse buildGrpcGetAllByOrganisationsEinheitIdResponse(Stream<User> users) {
+		return GrpcGetAllByOrganisationsEinheitIdResponse.newBuilder()
+				.addAllUserProfile(users.map(mapper::mapTo).toList())
+				.build();
+	}
+
 	@Override
 	public void findInactiveUserIds(GrpcFindInactiveUserIdsRequest request, StreamObserver<GrpcFindInactiveUserIdsResponse> responseObserver) {
 		var userIds = service.findAllInactiveUserIds();
diff --git a/user-manager-server/src/main/java/de/ozgcloud/user/userprofile/UserProfileMapper.java b/user-manager-server/src/main/java/de/ozgcloud/user/userprofile/UserProfileMapper.java
index c29c5e8d..3807a4bc 100644
--- a/user-manager-server/src/main/java/de/ozgcloud/user/userprofile/UserProfileMapper.java
+++ b/user-manager-server/src/main/java/de/ozgcloud/user/userprofile/UserProfileMapper.java
@@ -30,9 +30,9 @@ import org.mapstruct.NullValueCheckStrategy;
 import org.mapstruct.ReportingPolicy;
 
 import de.ozgcloud.user.User;
+import de.ozgcloud.user.settings.UserSettingsMapper;
 
-@Mapper(unmappedTargetPolicy = ReportingPolicy.WARN, //
-		nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
+@Mapper(unmappedTargetPolicy = ReportingPolicy.WARN, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, uses = UserSettingsMapper.class)
 interface UserProfileMapper {
 
 	@Mapping(target = "emailBytes", ignore = true)
@@ -45,6 +45,7 @@ interface UserProfileMapper {
 	@Mapping(target = "lastNameBytes", ignore = true)
 	@Mapping(target = "unknownFields", ignore = true)
 	@Mapping(target = "allFields", ignore = true)
+	@Mapping(target = "mergeUserSettings", ignore = true)
 	GrpcUserProfile mapTo(User userProfile);
 
 	default String toString(ObjectId objectId) {
diff --git a/user-manager-server/src/test/java/de/ozgcloud/user/UserRepositoryITCase.java b/user-manager-server/src/test/java/de/ozgcloud/user/UserRepositoryITCase.java
index e8cf01e2..c8bec751 100644
--- a/user-manager-server/src/test/java/de/ozgcloud/user/UserRepositoryITCase.java
+++ b/user-manager-server/src/test/java/de/ozgcloud/user/UserRepositoryITCase.java
@@ -29,6 +29,7 @@ import static org.assertj.core.api.Assertions.*;
 import java.time.Instant;
 import java.util.Arrays;
 import java.util.List;
+import java.util.UUID;
 import java.util.stream.Stream;
 
 import jakarta.inject.Inject;
@@ -51,7 +52,7 @@ import io.quarkus.test.junit.TestProfile;
 class UserRepositoryITCase {
 
 	@Inject
-	UserRepository repository;
+	private UserRepository repository;
 
 	@DisplayName("Update unsynced users")
 	@Nested
@@ -249,8 +250,7 @@ class UserRepositoryITCase {
 			Arrays.asList(
 					UserTestFactory.createBuilder().id(null).username("user1").fullName("Franz Vogel").build(),
 					UserTestFactory.createBuilder().id(null).username("user2").fullName("Franz von Holzhausen").build(),
-					UserTestFactory.createBuilder().id(null).username("user3").fullName("Peter Lustig").build()
-			).forEach(repository::persist);
+					UserTestFactory.createBuilder().id(null).username("user3").fullName("Peter Lustig").build()).forEach(repository::persist);
 
 			var foundUsernames = repository.findUsers(query, 10).map(User::getUsername);
 
@@ -266,8 +266,7 @@ class UserRepositoryITCase {
 					Arguments.of("Franz von", new String[] { "user2" }),
 					Arguments.of("Franz von Holzhausen", new String[] { "user2" }),
 					Arguments.of("Franz L", new String[0]),
-					Arguments.of("Peter V", new String[0])
-			);
+					Arguments.of("Peter V", new String[0]));
 		}
 	}
 
@@ -285,8 +284,7 @@ class UserRepositoryITCase {
 			Arrays.asList(
 					UserTestFactory.createBuilder().id(null).username("user1").fullNameReversed("Langbein Franz").build(),
 					UserTestFactory.createBuilder().id(null).username("user2").fullNameReversed("Lustig Peter").build(),
-					UserTestFactory.createBuilder().id(null).username("user3").fullNameReversed("Ilona Nowak").build()
-			).forEach(repository::persist);
+					UserTestFactory.createBuilder().id(null).username("user3").fullNameReversed("Ilona Nowak").build()).forEach(repository::persist);
 
 			var foundUsernames = repository.findUsers(query, 10).map(User::getUsername);
 
@@ -297,8 +295,7 @@ class UserRepositoryITCase {
 			return Stream.of(
 					Arguments.of("L", new String[] { "user1", "user2", "user3" }),
 					Arguments.of("Lustig", new String[] { "user2" }),
-					Arguments.of("Lustig Peter", new String[] { "user2" })
-			);
+					Arguments.of("Lustig Peter", new String[] { "user2" }));
 		}
 	}
 
@@ -573,6 +570,44 @@ class UserRepositoryITCase {
 		}
 	}
 
+	@Nested
+	class TestFindAllUsersByOrganisationsEinheitId {
+
+		private static final String ORGANISATIONSEINHEIT_ID_1 = UUID.randomUUID().toString();
+		private static final String ORGANISATIONSEINHEIT_ID_2 = UUID.randomUUID().toString();
+
+		private final User user1 = UserTestFactory.createBuilder()
+				.id(null)
+				.clearOrganisationsEinheitIds()
+				.organisationsEinheitId(ORGANISATIONSEINHEIT_ID_1)
+				.build();
+		private final User user2 = UserTestFactory.createBuilder()
+				.id(null)
+				.clearOrganisationsEinheitIds()
+				.organisationsEinheitId(ORGANISATIONSEINHEIT_ID_1)
+				.build();
+		private final User user3 = UserTestFactory.createBuilder()
+				.id(null)
+				.clearOrganisationsEinheitIds()
+				.organisationsEinheitId(ORGANISATIONSEINHEIT_ID_2)
+				.build();
+
+		@BeforeEach
+		void init() {
+			repository.deleteAll();
+			repository.persist(user1);
+			repository.persist(user2);
+			repository.persist(user3);
+		}
+
+		@Test
+		void shouldFindUsersByOrganisationsEinheitId() {
+			var users = repository.findAllUsersByOrganisationsEinheitId(ORGANISATIONSEINHEIT_ID_1);
+
+			assertThat(users).usingRecursiveFieldByFieldElementComparator().containsExactlyInAnyOrder(user1, user2);
+		}
+	}
+
 	@Nested
 	class TestDeleteById {
 
diff --git a/user-manager-server/src/test/java/de/ozgcloud/user/UserRepositoryTest.java b/user-manager-server/src/test/java/de/ozgcloud/user/UserRepositoryTest.java
index 98c81190..3ca1e919 100644
--- a/user-manager-server/src/test/java/de/ozgcloud/user/UserRepositoryTest.java
+++ b/user-manager-server/src/test/java/de/ozgcloud/user/UserRepositoryTest.java
@@ -30,6 +30,7 @@ import static org.mockito.Mockito.*;
 import java.util.Date;
 import java.util.List;
 import java.util.Optional;
+import java.util.stream.Stream;
 
 import org.assertj.core.api.Condition;
 import org.bson.types.ObjectId;
@@ -49,12 +50,12 @@ import io.quarkus.panache.common.Parameters;
 
 class UserRepositoryTest {
 	@Spy
-	UserRepository userRepository;
+	private UserRepository userRepository;
 
 	@Mock
-	PanacheQuery<User> panacheQuery;
+	private PanacheQuery<User> panacheQuery;
 
-	User user = UserTestFactory.create();
+	private final User user = UserTestFactory.create();
 
 	@DisplayName("Test updating users")
 	@Nested
@@ -190,15 +191,42 @@ class UserRepositoryTest {
 			assertThatExceptionOfType(ResourceNotFoundException.class).isThrownBy(() -> userRepository.refresh(UserTestFactory.create())); // NOSONAR
 		}
 	}
-	
+
 	@Nested
 	class TestToRegex {
-		
+
 		@Test
-		void shouldReturnRegex(){
+		void shouldReturnRegex() {
 			var regex = userRepository.toRegex("abc");
 
 			assertThat(regex).isEqualTo("/.*abc.*/i");
 		}
 	}
+
+	@Nested
+	class TestFindAllUsersByOrganisationsEinheitId {
+
+		@BeforeEach
+		void mock() {
+			doReturn(Stream.of(user)).when(userRepository).stream(User.ORGANISATIONS_EINHEIT_IDS_FIELD, UserTestFactory.ORGANISTATIONSEINHEITEN_ID);
+		}
+
+		@Test
+		void shouldFindUsers() {
+			callRepository();
+
+			verify(userRepository).stream(User.ORGANISATIONS_EINHEIT_IDS_FIELD, UserTestFactory.ORGANISTATIONSEINHEITEN_ID);
+		}
+
+		@Test
+		void shouldReturnStreamOfUsers() {
+			var users = callRepository();
+
+			assertThat(users).containsExactly(user);
+		}
+
+		private Stream<User> callRepository() {
+			return userRepository.findAllUsersByOrganisationsEinheitId(UserTestFactory.ORGANISTATIONSEINHEITEN_ID);
+		}
+	}
 }
diff --git a/user-manager-server/src/test/java/de/ozgcloud/user/UserServiceTest.java b/user-manager-server/src/test/java/de/ozgcloud/user/UserServiceTest.java
index 434d3555..c9f783ae 100644
--- a/user-manager-server/src/test/java/de/ozgcloud/user/UserServiceTest.java
+++ b/user-manager-server/src/test/java/de/ozgcloud/user/UserServiceTest.java
@@ -38,10 +38,13 @@ import org.bson.types.ObjectId;
 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.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Spy;
 
+import de.ozgcloud.user.common.errorhandling.FunctionalException;
 import de.ozgcloud.user.common.errorhandling.ResourceNotFoundException;
 import de.ozgcloud.user.common.errorhandling.TechnicalException;
 import de.ozgcloud.user.keycloak.KeycloakUserRemoteService;
@@ -399,4 +402,42 @@ class UserServiceTest {
 			verify(repository).persist(user);
 		}
 	}
+
+	@Nested
+	class TestFindAllUsersOfOrganisationsEinheit {
+
+		private static final String EXPECTED_EXCEPTION_MESSAGE_TEMPLATE = "Functional error: "
+				+ UserService.MISSING_ORGANISATIONS_EINHEIT_ID_MESSAGE_TEMPLATE;
+
+		@Test
+		void shouldCallRepository() {
+			callService();
+
+			verify(repository).findAllUsersByOrganisationsEinheitId(UserTestFactory.ORGANISTATIONSEINHEITEN_ID);
+		}
+
+		@Test
+		void shouldReturnFoundUsers() {
+			var users = Stream.of(UserTestFactory.create());
+			when(repository.findAllUsersByOrganisationsEinheitId(UserTestFactory.ORGANISTATIONSEINHEITEN_ID)).thenReturn(users);
+
+			var returnedUsers = callService();
+
+			assertThat(returnedUsers).isEqualTo(users);
+		}
+
+		private Stream<User> callService() {
+			return service.findAllUsersOfOrganisationsEinheit(UserTestFactory.ORGANISTATIONSEINHEITEN_ID);
+		}
+
+		@ParameterizedTest
+		@NullAndEmptySource
+		void shouldThrowFunctionalExceptionOnInvalidOrganisationsId(String organisationsEinheitId) {
+			var expectedMessage = String.format(EXPECTED_EXCEPTION_MESSAGE_TEMPLATE, organisationsEinheitId);
+
+			assertThatExceptionOfType(FunctionalException.class)
+					.isThrownBy(() -> service.findAllUsersOfOrganisationsEinheit(organisationsEinheitId))
+					.withMessageStartingWith(expectedMessage);
+		}
+	}
 }
\ No newline at end of file
diff --git a/user-manager-server/src/test/java/de/ozgcloud/user/UserTestFactory.java b/user-manager-server/src/test/java/de/ozgcloud/user/UserTestFactory.java
index fc8e1524..42927031 100644
--- a/user-manager-server/src/test/java/de/ozgcloud/user/UserTestFactory.java
+++ b/user-manager-server/src/test/java/de/ozgcloud/user/UserTestFactory.java
@@ -27,6 +27,7 @@ import java.util.List;
 
 import org.bson.types.ObjectId;
 
+import de.ozgcloud.user.settings.UserSettings;
 import de.ozgcloud.user.settings.UserSettingsTestFactory;
 
 public class UserTestFactory {
@@ -45,6 +46,7 @@ public class UserTestFactory {
 	public static final String EXTERNAL_ID = "aec115b5-67be-42c9-a693-36d4d711a87a";
 	public static final String ORGANISTATIONSEINHEITEN_ID = "0815";
 	public static final List<String> ROLES = List.of("ROLE_1", "POSTSTELLE");
+	public static final UserSettings USER_SETTINGS = UserSettingsTestFactory.create();
 
 	public static User create() {
 		return createBuilder().build();
@@ -64,6 +66,6 @@ public class UserTestFactory {
 				.externalId(EXTERNAL_ID)
 				.organisationsEinheitId(ORGANISTATIONSEINHEITEN_ID)
 				.roles(ROLES)
-				.userSettings(UserSettingsTestFactory.create());
+				.userSettings(USER_SETTINGS);
 	}
 }
diff --git a/user-manager-server/src/test/java/de/ozgcloud/user/settings/GrpcUserSettingsTestFactory.java b/user-manager-server/src/test/java/de/ozgcloud/user/settings/GrpcUserSettingsTestFactory.java
new file mode 100644
index 00000000..39e625f6
--- /dev/null
+++ b/user-manager-server/src/test/java/de/ozgcloud/user/settings/GrpcUserSettingsTestFactory.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.user.settings;
+
+import de.ozgcloud.user.userprofile.GrpcUserSettings;
+
+public class GrpcUserSettingsTestFactory {
+
+	public static final boolean NOTIFICATIONS_SEND_FOR = NotificationsSendFor.ALL == UserSettingsTestFactory.NOTIFICATIONS_SEND_FOR;
+	public static final boolean VORGANG_CREATED = UserSettingsTestFactory.VORGANG_CREATED;
+	public static final boolean VORGANG_ASSIGNED_TO_USER = UserSettingsTestFactory.VORGANG_ASSIGNED_TO_USER;
+	public static final boolean POSTFACH_NACHRICHT_FROM_ANTRAGSTELLER = UserSettingsTestFactory.POSTFACH_NACHRICHT_FROM_ANTRAGSTELLER;
+	public static final boolean WIEDERVORLAGE_OVERDUE = UserSettingsTestFactory.WIEDERVORLAGE_OVERDUE;
+
+	public static GrpcUserSettings create() {
+		return createBuilder().build();
+	}
+
+	public static GrpcUserSettings.Builder createBuilder() {
+		return GrpcUserSettings.newBuilder()
+				.setNotificationsSendForAll(NOTIFICATIONS_SEND_FOR)
+				.setVorgangCreated(VORGANG_CREATED)
+				.setVorgangAssignedToUser(VORGANG_ASSIGNED_TO_USER)
+				.setPostfachNachrichtFromAntragsteller(POSTFACH_NACHRICHT_FROM_ANTRAGSTELLER)
+				.setWiedervorlageOverdue(WIEDERVORLAGE_OVERDUE);
+	}
+}
\ No newline at end of file
diff --git a/user-manager-server/src/test/java/de/ozgcloud/user/settings/UserSettingsMapperTest.java b/user-manager-server/src/test/java/de/ozgcloud/user/settings/UserSettingsMapperTest.java
new file mode 100644
index 00000000..41c63661
--- /dev/null
+++ b/user-manager-server/src/test/java/de/ozgcloud/user/settings/UserSettingsMapperTest.java
@@ -0,0 +1,23 @@
+package de.ozgcloud.user.settings;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mapstruct.factory.Mappers;
+
+class UserSettingsMapperTest {
+
+	private final UserSettingsMapper mapper = Mappers.getMapper(UserSettingsMapper.class);
+
+	@Nested
+	class TestToGrpc {
+
+		@Test
+		void shouldMapToGrpc() {
+			var userSettings = mapper.toGrpc(UserSettingsTestFactory.create());
+
+			assertThat(userSettings).usingRecursiveComparison().isEqualTo(GrpcUserSettingsTestFactory.create());
+		}
+	}
+}
diff --git a/user-manager-server/src/test/java/de/ozgcloud/user/userprofile/GrpcGetAllByOrganisationsEinheitIdRequestTestFactory.java b/user-manager-server/src/test/java/de/ozgcloud/user/userprofile/GrpcGetAllByOrganisationsEinheitIdRequestTestFactory.java
new file mode 100644
index 00000000..32bbabc5
--- /dev/null
+++ b/user-manager-server/src/test/java/de/ozgcloud/user/userprofile/GrpcGetAllByOrganisationsEinheitIdRequestTestFactory.java
@@ -0,0 +1,19 @@
+package de.ozgcloud.user.userprofile;
+
+import de.ozgcloud.user.UserTestFactory;
+import de.ozgcloud.user.userprofile.GrpcGetAllByOrganisationsEinheitIdRequest.Builder;
+
+public class GrpcGetAllByOrganisationsEinheitIdRequestTestFactory {
+
+	public static final String ORGANISTATIONSEINHEITEN_ID = UserTestFactory.ORGANISTATIONSEINHEITEN_ID;
+
+	public static GrpcGetAllByOrganisationsEinheitIdRequest create() {
+		return createBuilder()
+				.build();
+	}
+
+	public static Builder createBuilder() {
+		return GrpcGetAllByOrganisationsEinheitIdRequest.newBuilder()
+				.setOrganisationsEinheitId(ORGANISTATIONSEINHEITEN_ID);
+	}
+}
diff --git a/user-manager-server/src/test/java/de/ozgcloud/user/userprofile/GrpcGetAllByOrganisationsEinheitIdResponseTestFactory.java b/user-manager-server/src/test/java/de/ozgcloud/user/userprofile/GrpcGetAllByOrganisationsEinheitIdResponseTestFactory.java
new file mode 100644
index 00000000..de7b1c0d
--- /dev/null
+++ b/user-manager-server/src/test/java/de/ozgcloud/user/userprofile/GrpcGetAllByOrganisationsEinheitIdResponseTestFactory.java
@@ -0,0 +1,17 @@
+package de.ozgcloud.user.userprofile;
+
+import de.ozgcloud.user.userprofile.GrpcGetAllByOrganisationsEinheitIdResponse.Builder;
+
+public class GrpcGetAllByOrganisationsEinheitIdResponseTestFactory {
+
+	public static final GrpcUserProfile USER_PROFILE = GrpcUserProfileTestFactory.create();
+
+	public static GrpcGetAllByOrganisationsEinheitIdResponse create() {
+		return createBuilder().build();
+	}
+
+	public static Builder createBuilder() {
+		return GrpcGetAllByOrganisationsEinheitIdResponse.newBuilder()
+				.addUserProfile(USER_PROFILE);
+	}
+}
diff --git a/user-manager-server/src/test/java/de/ozgcloud/user/userprofile/GrpcUserProfileTestFactory.java b/user-manager-server/src/test/java/de/ozgcloud/user/userprofile/GrpcUserProfileTestFactory.java
index 16fd8ad1..5146952f 100644
--- a/user-manager-server/src/test/java/de/ozgcloud/user/userprofile/GrpcUserProfileTestFactory.java
+++ b/user-manager-server/src/test/java/de/ozgcloud/user/userprofile/GrpcUserProfileTestFactory.java
@@ -24,6 +24,7 @@
 package de.ozgcloud.user.userprofile;
 
 import de.ozgcloud.user.UserTestFactory;
+import de.ozgcloud.user.settings.GrpcUserSettingsTestFactory;
 
 public class GrpcUserProfileTestFactory {
 
@@ -34,6 +35,7 @@ public class GrpcUserProfileTestFactory {
 	public static GrpcUserProfile.Builder createBuilder() {
 		return GrpcUserProfile.newBuilder()
 				.setFirstName(UserTestFactory.FIRST_NAME)
-				.setLastName(UserTestFactory.LAST_NAME);
+				.setLastName(UserTestFactory.LAST_NAME)
+				.setUserSettings(GrpcUserSettingsTestFactory.create());
 	}
 }
\ No newline at end of file
diff --git a/user-manager-server/src/test/java/de/ozgcloud/user/userprofile/UserProfileGrpcServiceTest.java b/user-manager-server/src/test/java/de/ozgcloud/user/userprofile/UserProfileGrpcServiceTest.java
index 3b803854..1dbd5875 100644
--- a/user-manager-server/src/test/java/de/ozgcloud/user/userprofile/UserProfileGrpcServiceTest.java
+++ b/user-manager-server/src/test/java/de/ozgcloud/user/userprofile/UserProfileGrpcServiceTest.java
@@ -157,6 +157,86 @@ class UserProfileGrpcServiceTest {
 		}
 	}
 
+	@Nested
+	class TestGetAllByOrganisationsEinheitId {
+
+		@Mock
+		private StreamObserver<GrpcGetAllByOrganisationsEinheitIdResponse> streamObserver;
+
+		private final Stream<User> users = Stream.of(UserTestFactory.create());
+		private final GrpcGetAllByOrganisationsEinheitIdResponse response = GrpcGetAllByOrganisationsEinheitIdResponseTestFactory.create();
+
+		@BeforeEach
+		void mock() {
+			when(userService.findAllUsersOfOrganisationsEinheit(UserTestFactory.ORGANISTATIONSEINHEITEN_ID)).thenReturn(users);
+		}
+
+		@Test
+		void shouldCallUserService() {
+			callService();
+
+			verify(userService).findAllUsersOfOrganisationsEinheit(UserTestFactory.ORGANISTATIONSEINHEITEN_ID);
+		}
+
+		@Test
+		void shouldBuildGrpcGetAllByOrganisationsEinheitIdResponse() {
+			callService();
+
+			verify(grpcService).buildGrpcGetAllByOrganisationsEinheitIdResponse(users);
+		}
+
+		@Test
+		void shouldCallOnNext() {
+			doReturn(response).when(grpcService).buildGrpcGetAllByOrganisationsEinheitIdResponse(users);
+
+			callService();
+
+			verify(streamObserver).onNext(response);
+		}
+
+		@Test
+		void shouldCallOnCompleted() {
+			callService();
+
+			verify(streamObserver).onCompleted();
+		}
+
+		private void callService() {
+			grpcService.getAllByOrganisationsEinheitId(GrpcGetAllByOrganisationsEinheitIdRequestTestFactory.create(), streamObserver);
+		}
+	}
+
+	@Nested
+	class TestBuildGrpcGetAllByOrganisationsEinheitIdResponse {
+
+		private final User user = UserTestFactory.create();
+		private final Stream<User> users = Stream.of(user);
+		private GrpcUserProfile grpcUserProfile = GrpcUserProfileTestFactory.create();
+
+		@BeforeEach
+		void mock() {
+			when(mapper.mapTo(user)).thenReturn(grpcUserProfile);
+		}
+
+		@Test
+		void shouldMapUser() {
+			callService();
+
+			verify(mapper).mapTo(user);
+		}
+
+		@Test
+		void shouldMappedUsersInResponse() {
+			var response = callService();
+
+			assertThat(response.getUserProfileList()).containsExactly(grpcUserProfile);
+		}
+
+		private GrpcGetAllByOrganisationsEinheitIdResponse callService() {
+			return grpcService.buildGrpcGetAllByOrganisationsEinheitIdResponse(users);
+		}
+	}
+
 	@Nested
 	class TestBuildGrpcGetInactiveUserIdsResponse {
 
diff --git a/user-manager-server/src/test/java/de/ozgcloud/user/userprofile/UserProfileMapperTest.java b/user-manager-server/src/test/java/de/ozgcloud/user/userprofile/UserProfileMapperTest.java
index fa280033..83452481 100644
--- a/user-manager-server/src/test/java/de/ozgcloud/user/userprofile/UserProfileMapperTest.java
+++ b/user-manager-server/src/test/java/de/ozgcloud/user/userprofile/UserProfileMapperTest.java
@@ -33,6 +33,7 @@ import org.mapstruct.factory.Mappers;
 
 import de.ozgcloud.user.User;
 import de.ozgcloud.user.UserTestFactory;
+import de.ozgcloud.user.settings.GrpcUserSettingsTestFactory;
 
 class UserProfileMapperTest {
 
@@ -70,6 +71,13 @@ class UserProfileMapperTest {
 			assertThat(userProfile.getId()).isEqualTo(UserTestFactory.ID.toString());
 		}
 
+		@Test
+		void shouldMapUserSettings() {
+			var userProfile = map();
+
+			assertThat(userProfile.getUserSettings()).usingRecursiveComparison().isEqualTo(GrpcUserSettingsTestFactory.create());
+		}
+
 		private GrpcUserProfile map() {
 			return mapper.mapTo(UserTestFactory.create());
 		}
-- 
GitLab