From df4c4ef119c4c8ebd9f88c9086a9eb00fa702e6e Mon Sep 17 00:00:00 2001
From: OZGCloud <ozgcloud@mgm-tp.com>
Date: Mon, 29 Aug 2022 14:04:04 +0200
Subject: [PATCH] OZG-2652 add userItertor for paging

---
 .../de/itvsh/kop/user/RemoteUserIterator.java | 47 +++++++++++
 .../kop/user/keycloak/KeycloakApiService.java | 22 +++++-
 .../user/UserRepresentationTestFactory.java   |  4 +-
 .../keycloak/KeycloakApiServiceITCase.java    |  2 +-
 .../user/keycloak/KeycloakApiServiceTest.java | 78 +++++++++++++++++++
 5 files changed, 146 insertions(+), 7 deletions(-)
 create mode 100644 src/main/java/de/itvsh/kop/user/RemoteUserIterator.java
 create mode 100644 src/test/java/de/itvsh/kop/user/keycloak/KeycloakApiServiceTest.java

diff --git a/src/main/java/de/itvsh/kop/user/RemoteUserIterator.java b/src/main/java/de/itvsh/kop/user/RemoteUserIterator.java
new file mode 100644
index 00000000..93958622
--- /dev/null
+++ b/src/main/java/de/itvsh/kop/user/RemoteUserIterator.java
@@ -0,0 +1,47 @@
+package de.itvsh.kop.user;
+
+import java.util.Iterator;
+
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.admin.client.resource.UsersResource;
+import org.keycloak.representations.idm.UserRepresentation;
+
+import com.cronutils.utils.StringUtils;
+
+public class RemoteUserIterator implements Iterator<UserRepresentation> {
+
+	private static final int PAGE_SIZE = 10;// TODO
+
+	private final UsersResource users;
+	private final int count;
+
+	private int step = 0;
+	private Iterator<UserRepresentation> loadedUserIterator;
+
+	public RemoteUserIterator(RealmResource resource) {
+		users = resource.users();
+		count = users.count();
+		loadNextStep();
+	}
+
+	@Override
+	public boolean hasNext() {
+		if (loadedUserIterator.hasNext()) {
+			return true;
+		}
+		if (count > step * PAGE_SIZE) {
+			loadNextStep();
+			return loadedUserIterator.hasNext();
+		}
+		return false;
+	}
+
+	private void loadNextStep() {
+		loadedUserIterator = users.search(StringUtils.EMPTY, step * PAGE_SIZE, ++step * PAGE_SIZE).iterator();
+	}
+
+	@Override
+	public UserRepresentation next() {
+		return loadedUserIterator.next();
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/de/itvsh/kop/user/keycloak/KeycloakApiService.java b/src/main/java/de/itvsh/kop/user/keycloak/KeycloakApiService.java
index ac6b1b8f..03dec638 100644
--- a/src/main/java/de/itvsh/kop/user/keycloak/KeycloakApiService.java
+++ b/src/main/java/de/itvsh/kop/user/keycloak/KeycloakApiService.java
@@ -1,7 +1,9 @@
 package de.itvsh.kop.user.keycloak;
 
+import java.util.Spliterators;
 import java.util.function.Supplier;
 import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
 
 import javax.enterprise.context.ApplicationScoped;
 import javax.inject.Inject;
@@ -10,7 +12,9 @@ import javax.ws.rs.ProcessingException;
 
 import org.eclipse.microprofile.config.inject.ConfigProperty;
 import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.representations.idm.UserRepresentation;
 
+import de.itvsh.kop.user.RemoteUserIterator;
 import de.itvsh.kop.user.User;
 import de.itvsh.kop.user.UserResourceMapper;
 import de.itvsh.kop.user.common.errorhandling.KeycloakClientException;
@@ -30,10 +34,20 @@ class KeycloakApiService {
 	private String keycloakUrl;
 
 	public Stream<User> findAllUser() {
-		return handlingKeycloakException(() -> {
-			var users = realmResource.users();
-			return users.list().stream().map(userRep -> mapper.toKopUser(users.get(userRep.getId())));
-		});
+		return handlingKeycloakException(() -> getAllUserRepresentation().map(this::toUser));
+	}
+
+	Stream<UserRepresentation> getAllUserRepresentation() {
+		var iterator = createRealmUserIterator();
+		return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
+	}
+
+	private RemoteUserIterator createRealmUserIterator() {
+		return new RemoteUserIterator(realmResource);
+	}
+
+	private User toUser(UserRepresentation userRepresentation) {
+		return mapper.toKopUser(realmResource.users().get(userRepresentation.getId()));
 	}
 
 	private <T> T handlingKeycloakException(Supplier<T> runnable) {
diff --git a/src/test/java/de/itvsh/kop/user/UserRepresentationTestFactory.java b/src/test/java/de/itvsh/kop/user/UserRepresentationTestFactory.java
index 55c73a34..9f95fd80 100644
--- a/src/test/java/de/itvsh/kop/user/UserRepresentationTestFactory.java
+++ b/src/test/java/de/itvsh/kop/user/UserRepresentationTestFactory.java
@@ -8,7 +8,7 @@ import org.keycloak.representations.idm.UserRepresentation;
 
 import com.github.javafaker.Faker;
 
-class UserRepresentationTestFactory {
+public class UserRepresentationTestFactory {
 	static final String EMAIL = Faker.instance().internet().emailAddress();
 	static final String FIRST_NAME = Faker.instance().name().firstName();
 	static final String LAST_NAME = Faker.instance().name().lastName();
@@ -21,7 +21,7 @@ class UserRepresentationTestFactory {
 	static final Map<String, List<String>> ATTRIBUTES = Map.of("LDAP_ID", List.of(EXTERNAL_ID), "organisationseinheitId",
 			List.of(ORGANSISATIONS_EINHEIT_ID));
 
-	static UserRepresentation create() {
+	public static UserRepresentation create() {
 		UserRepresentation user = new UserRepresentation();
 		user.setEmail(EMAIL);
 		user.setFirstName(FIRST_NAME);
diff --git a/src/test/java/de/itvsh/kop/user/keycloak/KeycloakApiServiceITCase.java b/src/test/java/de/itvsh/kop/user/keycloak/KeycloakApiServiceITCase.java
index 51a3436f..93e6cbb5 100644
--- a/src/test/java/de/itvsh/kop/user/keycloak/KeycloakApiServiceITCase.java
+++ b/src/test/java/de/itvsh/kop/user/keycloak/KeycloakApiServiceITCase.java
@@ -20,6 +20,6 @@ class KeycloakApiServiceITCase {
 	void shouldGetAllUsers() {
 		var usersStream = service.findAllUser();
 
-		assertThat(usersStream).isNotNull();
+		assertThat(usersStream).isNotEmpty();
 	}
 }
\ No newline at end of file
diff --git a/src/test/java/de/itvsh/kop/user/keycloak/KeycloakApiServiceTest.java b/src/test/java/de/itvsh/kop/user/keycloak/KeycloakApiServiceTest.java
new file mode 100644
index 00000000..967f3f74
--- /dev/null
+++ b/src/test/java/de/itvsh/kop/user/keycloak/KeycloakApiServiceTest.java
@@ -0,0 +1,78 @@
+package de.itvsh.kop.user.keycloak;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.stream.Stream;
+
+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.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.admin.client.resource.UsersResource;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import de.itvsh.kop.user.RemoteUserIterator;
+import de.itvsh.kop.user.User;
+import de.itvsh.kop.user.UserRepresentationTestFactory;
+import de.itvsh.kop.user.UserResourceMapper;
+import de.itvsh.kop.user.UserTestFactory;
+
+class KeycloakApiServiceTest {
+
+	@Spy
+	@InjectMocks
+	private KeycloakApiService service;
+	@Mock
+	private RealmResource realmResource;
+	@Mock
+	private UserResourceMapper userResourceMapper;
+	@Mock
+	private KeycloakApiProperties properties;
+
+	@Mock
+	private UserResource userResource;
+
+	@DisplayName("Find all user")
+	@Nested
+	class TestFindAllUser {
+
+		@Mock
+		private UsersResource usersResource;
+
+		@Mock
+		private RemoteUserIterator iterator;
+
+		private User user = UserTestFactory.create();
+		private UserRepresentation userRepresentation = UserRepresentationTestFactory.create();
+		private Stream<UserRepresentation> userStream = Stream.of(userRepresentation);
+
+		@BeforeEach
+		void init() {
+			doReturn(userStream).when(service).getAllUserRepresentation();
+		}
+
+		@Test
+		void shouldCreateIteratorWithRealmResource() {
+			service.findAllUser();
+
+			verify(service).getAllUserRepresentation();
+		}
+
+		@Test
+		void shouldCallMapper() {
+			when(userResourceMapper.toKopUser(any(UserResource.class))).thenReturn(user);
+			when(realmResource.users()).thenReturn(usersResource);
+			when(usersResource.get(anyString())).thenReturn(userResource);
+
+			service.findAllUser().toList();
+
+			verify(userResourceMapper).toKopUser(any(UserResource.class));
+		}
+	}
+}
\ No newline at end of file
-- 
GitLab