From c18a24e1844c17be985400a9b0029469e6b4369d Mon Sep 17 00:00:00 2001
From: OZGCloud <ozgcloud@mgm-tp.com>
Date: Thu, 7 Nov 2024 12:05:27 +0100
Subject: [PATCH] =?UTF-8?q?OZG-7062=20Logging=20Aspect=20gefixed.=20Sync?=
 =?UTF-8?q?=20ozgCloudUserId=20ITCase=20hinzugef=C3=BCgt?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../java/de/ozgcloud/user/UserService.java    |  38 +++----
 .../user/common/logging/OzgCloudLogging.java  |  37 ++++++
 .../logging/OzgCloudLoggingInterceptor.java   |  49 ++++++++
 .../user/keycloak/KeycloakApiService.java     |  21 ++--
 .../keycloak/KeycloakUserRemoteService.java   |  13 +--
 .../OrganisationsEinheitService.java          |   9 +-
 .../user/settings/UserSettingsService.java    |   8 +-
 .../de/ozgcloud/user/sync/SyncService.java    |  17 ++-
 .../user/sync/KeycloakMongoDbTestProfile.java |  38 +++++++
 .../user/sync/SyncOzgCloudIdITCase.java       | 105 ++++++++++++++++++
 10 files changed, 274 insertions(+), 61 deletions(-)
 create mode 100644 user-manager-server/src/main/java/de/ozgcloud/user/common/logging/OzgCloudLogging.java
 create mode 100644 user-manager-server/src/main/java/de/ozgcloud/user/common/logging/OzgCloudLoggingInterceptor.java
 create mode 100644 user-manager-server/src/test/java/de/ozgcloud/user/sync/KeycloakMongoDbTestProfile.java
 create mode 100644 user-manager-server/src/test/java/de/ozgcloud/user/sync/SyncOzgCloudIdITCase.java

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 b3ec71cd..b33b35f0 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
@@ -1,9 +1,5 @@
 /*
- * 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
- *
+ * Copyright (c) 2022-2024.
  * Lizenziert unter der EUPL, Version 1.2 oder - sobald
  * diese von der Europäischen Kommission genehmigt wurden -
  * Folgeversionen der EUPL ("Lizenz");
@@ -31,10 +27,10 @@ import jakarta.enterprise.context.ApplicationScoped;
 
 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.common.logging.OzgCloudLogging;
 import de.ozgcloud.user.keycloak.KeycloakUserRemoteService;
 import de.ozgcloud.user.settings.UserSettings;
 import io.smallrye.mutiny.Uni;
@@ -53,7 +49,7 @@ public class UserService {
 
 	public User save(User user) {
 		findUser(user).ifPresentOrElse(persistedUser -> repository.updateUser(addIdUser(user, persistedUser)),
-				() -> saveNewUser(user));
+		  () -> saveNewUser(user));
 		return repository.refresh(user);
 	}
 
@@ -71,10 +67,10 @@ public class UserService {
 	public Stream<User> findActiveUsers(FindUserProfilesQuery query) {
 		if (StringUtils.isNotBlank(query.getOrganisationsEinheitId())) {
 			return repository.findUsersByDeletedAndOrganisationsEinheitId(
-					query.getSearchBy(),
-					false,
-					query.getOrganisationsEinheitId(),
-					query.getLimit());
+			  query.getSearchBy(),
+			  false,
+			  query.getOrganisationsEinheitId(),
+			  query.getLimit());
 		}
 		return repository.findUsersByDeleted(query.getSearchBy(), false, query.getLimit());
 	}
@@ -82,10 +78,10 @@ public class UserService {
 	public Stream<User> findInactiveUsers(FindUserProfilesQuery query) {
 		if (StringUtils.isNotBlank(query.getOrganisationsEinheitId())) {
 			return repository.findUsersByDeletedAndOrganisationsEinheitId(
-					query.getSearchBy(),
-					true,
-					query.getOrganisationsEinheitId(),
-					query.getLimit());
+			  query.getSearchBy(),
+			  true,
+			  query.getOrganisationsEinheitId(),
+			  query.getLimit());
 		}
 		return repository.findUsersByDeleted(query.getSearchBy(), true, query.getLimit());
 	}
@@ -115,8 +111,8 @@ public class UserService {
 
 	public User findByExternalId(String externalId) {
 		return repository.findByKeycloakId(externalId)
-				.or(() -> findInKeycloakAndSave(externalId))
-				.orElseThrow(() -> new ResourceNotFoundException(User.class, externalId));
+		  .or(() -> findInKeycloakAndSave(externalId))
+		  .orElseThrow(() -> new ResourceNotFoundException(User.class, externalId));
 	}
 
 	private Optional<User> findInKeycloakAndSave(String externalId) {
@@ -126,10 +122,10 @@ public class UserService {
 	public User saveAndSync(User user) {
 		var saved = save(user);
 		Uni.createFrom()
-				.item(user)
-				.runSubscriptionOn(Infrastructure.getDefaultWorkerPool())
-				.subscribe()
-				.with(u -> keycloakUserRemoteService.updateOzgCloudUserId(saved));
+		  .item(user)
+		  .runSubscriptionOn(Infrastructure.getDefaultWorkerPool())
+		  .subscribe()
+		  .with(u -> keycloakUserRemoteService.updateOzgCloudUserId(saved));
 		return saved;
 	}
 
diff --git a/user-manager-server/src/main/java/de/ozgcloud/user/common/logging/OzgCloudLogging.java b/user-manager-server/src/main/java/de/ozgcloud/user/common/logging/OzgCloudLogging.java
new file mode 100644
index 00000000..2458859c
--- /dev/null
+++ b/user-manager-server/src/main/java/de/ozgcloud/user/common/logging/OzgCloudLogging.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2024.
+ * 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.common.logging;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import jakarta.interceptor.InterceptorBinding;
+
+@Target({ TYPE, METHOD })
+@Retention(RUNTIME)
+@Documented
+@InterceptorBinding
+public @interface OzgCloudLogging {
+
+}
diff --git a/user-manager-server/src/main/java/de/ozgcloud/user/common/logging/OzgCloudLoggingInterceptor.java b/user-manager-server/src/main/java/de/ozgcloud/user/common/logging/OzgCloudLoggingInterceptor.java
new file mode 100644
index 00000000..05e84a81
--- /dev/null
+++ b/user-manager-server/src/main/java/de/ozgcloud/user/common/logging/OzgCloudLoggingInterceptor.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2024.
+ * 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.common.logging;
+
+import jakarta.annotation.Priority;
+import jakarta.interceptor.AroundInvoke;
+import jakarta.interceptor.Interceptor;
+import jakarta.interceptor.InvocationContext;
+
+import de.ozgcloud.common.logging.AspectLoggingUtils;
+import de.ozgcloud.common.logging.OzgCloudLogging;
+
+@OzgCloudLogging
+@Priority(10)
+@Interceptor
+public class OzgCloudLoggingInterceptor {
+
+	@AroundInvoke
+	Object logging(InvocationContext context) throws Exception {
+		try {
+			AspectLoggingUtils.log(context);
+
+			var nextChainMethod = context.proceed();
+
+			AspectLoggingUtils.logReturnValue(context, nextChainMethod);
+			return nextChainMethod;
+		} catch (Exception e) {
+			AspectLoggingUtils.logException(context, e);
+			throw e;
+		}
+	}
+}
\ No newline at end of file
diff --git a/user-manager-server/src/main/java/de/ozgcloud/user/keycloak/KeycloakApiService.java b/user-manager-server/src/main/java/de/ozgcloud/user/keycloak/KeycloakApiService.java
index 5855bd35..de55da8e 100644
--- a/user-manager-server/src/main/java/de/ozgcloud/user/keycloak/KeycloakApiService.java
+++ b/user-manager-server/src/main/java/de/ozgcloud/user/keycloak/KeycloakApiService.java
@@ -1,9 +1,5 @@
 /*
- * 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
- *
+ * Copyright (c) 2022-2024.
  * Lizenziert unter der EUPL, Version 1.2 oder - sobald
  * diese von der Europäischen Kommission genehmigt wurden -
  * Folgeversionen der EUPL ("Lizenz");
@@ -29,23 +25,24 @@ import java.util.function.Supplier;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.BadRequestException;
+import jakarta.ws.rs.ClientErrorException;
+import jakarta.ws.rs.NotFoundException;
+import jakarta.ws.rs.ProcessingException;
+
 import org.apache.commons.lang3.ObjectUtils;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.keycloak.admin.client.resource.RealmResource;
 import org.keycloak.admin.client.resource.UserResource;
 import org.keycloak.representations.idm.UserRepresentation;
 
-import de.ozgcloud.common.logging.OzgCloudLogging;
 import de.ozgcloud.user.RemoteUserIterator;
 import de.ozgcloud.user.User;
 import de.ozgcloud.user.UserResourceMapper;
 import de.ozgcloud.user.common.errorhandling.KeycloakUnavailableException;
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.inject.Inject;
-import jakarta.ws.rs.BadRequestException;
-import jakarta.ws.rs.ClientErrorException;
-import jakarta.ws.rs.NotFoundException;
-import jakarta.ws.rs.ProcessingException;
+import de.ozgcloud.user.common.logging.OzgCloudLogging;
 import lombok.extern.log4j.Log4j2;
 
 @ApplicationScoped
diff --git a/user-manager-server/src/main/java/de/ozgcloud/user/keycloak/KeycloakUserRemoteService.java b/user-manager-server/src/main/java/de/ozgcloud/user/keycloak/KeycloakUserRemoteService.java
index 555b33c1..c8f959b1 100644
--- a/user-manager-server/src/main/java/de/ozgcloud/user/keycloak/KeycloakUserRemoteService.java
+++ b/user-manager-server/src/main/java/de/ozgcloud/user/keycloak/KeycloakUserRemoteService.java
@@ -1,9 +1,5 @@
 /*
- * 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
- *
+ * Copyright (c) 2024.
  * Lizenziert unter der EUPL, Version 1.2 oder - sobald
  * diese von der Europäischen Kommission genehmigt wurden -
  * Folgeversionen der EUPL ("Lizenz");
@@ -26,16 +22,17 @@ package de.ozgcloud.user.keycloak;
 import java.util.Optional;
 import java.util.stream.Stream;
 
-import de.ozgcloud.common.logging.OzgCloudLogging;
-import de.ozgcloud.user.User;
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
 
+import de.ozgcloud.user.User;
+import de.ozgcloud.user.common.logging.OzgCloudLogging;
+
 @ApplicationScoped
 @OzgCloudLogging
 public class KeycloakUserRemoteService {
 
-	static final String ATTRIBUTE_NAME_OZG_CLOUD_USER_ID = "ozgCloudUserId";
+	public static final String ATTRIBUTE_NAME_OZG_CLOUD_USER_ID = "ozgCloudUserId";
 
 	@Inject
 	KeycloakApiService apiService;
diff --git a/user-manager-server/src/main/java/de/ozgcloud/user/organisationseinheit/OrganisationsEinheitService.java b/user-manager-server/src/main/java/de/ozgcloud/user/organisationseinheit/OrganisationsEinheitService.java
index bf096522..5fd1885e 100644
--- a/user-manager-server/src/main/java/de/ozgcloud/user/organisationseinheit/OrganisationsEinheitService.java
+++ b/user-manager-server/src/main/java/de/ozgcloud/user/organisationseinheit/OrganisationsEinheitService.java
@@ -1,9 +1,5 @@
 /*
- * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den
- * Ministerpräsidenten des Landes Schleswig-Holstein
- * Staatskanzlei
- * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
- *
+ * Copyright (c) 2023-2024.
  * Lizenziert unter der EUPL, Version 1.2 oder - sobald
  * diese von der Europäischen Kommission genehmigt wurden -
  * Folgeversionen der EUPL ("Lizenz");
@@ -28,6 +24,9 @@ import java.util.Collection;
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
 
+import de.ozgcloud.user.common.logging.OzgCloudLogging;
+
+@OzgCloudLogging
 @ApplicationScoped
 class OrganisationsEinheitService {
 
diff --git a/user-manager-server/src/main/java/de/ozgcloud/user/settings/UserSettingsService.java b/user-manager-server/src/main/java/de/ozgcloud/user/settings/UserSettingsService.java
index d7aa65b6..c1243a62 100644
--- a/user-manager-server/src/main/java/de/ozgcloud/user/settings/UserSettingsService.java
+++ b/user-manager-server/src/main/java/de/ozgcloud/user/settings/UserSettingsService.java
@@ -1,9 +1,5 @@
 /*
- * 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
- *
+ * Copyright (c) 2022-2024.
  * Lizenziert unter der EUPL, Version 1.2 oder - sobald
  * diese von der Europäischen Kommission genehmigt wurden -
  * Folgeversionen der EUPL ("Lizenz");
@@ -30,7 +26,9 @@ import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
 
 import de.ozgcloud.user.UserService;
+import de.ozgcloud.user.common.logging.OzgCloudLogging;
 
+@OzgCloudLogging
 @ApplicationScoped
 class UserSettingsService {
 
diff --git a/user-manager-server/src/main/java/de/ozgcloud/user/sync/SyncService.java b/user-manager-server/src/main/java/de/ozgcloud/user/sync/SyncService.java
index 498cde45..c4a79d48 100644
--- a/user-manager-server/src/main/java/de/ozgcloud/user/sync/SyncService.java
+++ b/user-manager-server/src/main/java/de/ozgcloud/user/sync/SyncService.java
@@ -1,9 +1,5 @@
 /*
- * Copyright (C) 2022-2023 Das Land Schleswig-Holstein vertreten durch den
- * Ministerpräsidenten des Landes Schleswig-Holstein
- * Staatskanzlei
- * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
- *
+ * Copyright (c) 2022-2024.
  * Lizenziert unter der EUPL, Version 1.2 oder - sobald
  * diese von der Europäischen Kommission genehmigt wurden -
  * Folgeversionen der EUPL ("Lizenz");
@@ -25,14 +21,15 @@ package de.ozgcloud.user.sync;
 
 import java.util.function.Predicate;
 
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
 import org.apache.commons.collections4.CollectionUtils;
 
-import de.ozgcloud.common.logging.OzgCloudLogging;
 import de.ozgcloud.user.User;
 import de.ozgcloud.user.UserService;
+import de.ozgcloud.user.common.logging.OzgCloudLogging;
 import de.ozgcloud.user.keycloak.KeycloakUserRemoteService;
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.inject.Inject;
 
 @ApplicationScoped
 @OzgCloudLogging
@@ -47,8 +44,8 @@ class SyncService {
 
 	void sync(long syncTimestamp) {
 		keycloakService.getAllUsers()
-				.filter(HAS_ANY_ROLE)
-				.forEach(user -> userService.saveAndSync(addLastSyncTimestamp(user, syncTimestamp)));
+		  .filter(HAS_ANY_ROLE)
+		  .forEach(user -> userService.saveAndSync(addLastSyncTimestamp(user, syncTimestamp)));
 
 		userService.markUnsyncedUsersAsDeleted(syncTimestamp);
 	}
diff --git a/user-manager-server/src/test/java/de/ozgcloud/user/sync/KeycloakMongoDbTestProfile.java b/user-manager-server/src/test/java/de/ozgcloud/user/sync/KeycloakMongoDbTestProfile.java
new file mode 100644
index 00000000..63676d74
--- /dev/null
+++ b/user-manager-server/src/test/java/de/ozgcloud/user/sync/KeycloakMongoDbTestProfile.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2024.
+ * 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.sync;
+
+import java.util.Map;
+
+import io.quarkus.test.junit.QuarkusTestProfile;
+
+public class KeycloakMongoDbTestProfile implements QuarkusTestProfile {
+
+	@Override
+	public Map<String, String> getConfigOverrides() {
+		return Map.of("ozgcloud.keycloak.api.user", "userManagerApiUser",
+		  "ozgcloud.keycloak.api.password", "hlc_j1I1Ji0trC0",
+		  "ozgcloud.keycloak.api.realm", "by-kiel-dev",
+		  "keycloak.url", "https://sso.dev.by.ozg-cloud.de",
+		  "quarkus.mongodb.devservices.enabled", "true",
+		  "quarkus.oidc.auth-server-url", "https://sso.dev.by.ozg-cloud.de"
+		);
+	}
+}
\ No newline at end of file
diff --git a/user-manager-server/src/test/java/de/ozgcloud/user/sync/SyncOzgCloudIdITCase.java b/user-manager-server/src/test/java/de/ozgcloud/user/sync/SyncOzgCloudIdITCase.java
new file mode 100644
index 00000000..88220b14
--- /dev/null
+++ b/user-manager-server/src/test/java/de/ozgcloud/user/sync/SyncOzgCloudIdITCase.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2024.
+ * 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.sync;
+
+import static de.ozgcloud.user.keycloak.KeycloakUserRemoteService.*;
+import static org.assertj.core.api.Assertions.*;
+
+import java.time.Instant;
+import java.util.List;
+
+import jakarta.inject.Inject;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.keycloak.admin.client.resource.RealmResource;
+
+import com.mongodb.client.MongoClient;
+
+import de.ozgcloud.user.TestDatabaseUtils;
+import de.ozgcloud.user.User;
+import de.ozgcloud.user.UserService;
+import de.ozgcloud.user.UserTestFactory;
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.TestProfile;
+
+@QuarkusTest
+@TestProfile(KeycloakMongoDbTestProfile.class)
+public class SyncOzgCloudIdITCase {
+	public static final String KEYCLOAK_USER_ID = "90748555-7894-4854-8292-1f6106826962";
+	@Inject
+	SyncService service;
+
+	@Inject
+	MongoClient mongoClient;
+
+	@Inject
+	UserService userService;
+
+	@Inject
+	RealmResource realmResource;
+
+	private String userIdDb;
+
+	@BeforeEach
+	void init() {
+		TestDatabaseUtils.getDatabase(mongoClient).drop();
+
+		prepareData();
+	}
+
+	private void prepareData() {
+		User user1 = UserTestFactory.createBuilder()
+		  .externalId(KEYCLOAK_USER_ID)
+		  .keycloakUserId(KEYCLOAK_USER_ID)
+		  .email("testerozg+dorothea@gmail.com")
+		  .firstName("Dorothea")
+		  .lastName("Doe")
+		  .fullName("Dorothea Doe")
+		  .fullNameReversed("Doe Dorothea")
+		  .username("dorothea")
+		  .organisationsEinheitIds(List.of("248240886", "9030229", "10363455"))
+		  .build();
+
+		userIdDb = userService.save(user1).getId().toString();
+	}
+
+	@DisplayName("Update ozgCloudUserId attribute in keycloak")
+	@Test
+	void shouldUpdateOzgCloudUserId() {
+		syncUsers();
+
+		var ozgCloudUserId = getOzgCloudUserIdFromKeycloak();
+
+		assertThat(userIdDb).isEqualTo(ozgCloudUserId);
+	}
+
+	private void syncUsers() {
+		var timestamp = Instant.now().toEpochMilli();
+		service.sync(timestamp);
+	}
+
+	private String getOzgCloudUserIdFromKeycloak() {
+		var userResource = realmResource.users().get(KEYCLOAK_USER_ID);
+		return userResource.toRepresentation().getAttributes().get(ATTRIBUTE_NAME_OZG_CLOUD_USER_ID).getFirst();
+	}
+}
-- 
GitLab