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