From cfd94760a79d77e4723c9ed1379c9deede20d925 Mon Sep 17 00:00:00 2001 From: OZGCloud <ozgcloud@mgm-tp.com> Date: Sat, 17 Jun 2023 09:32:13 +0200 Subject: [PATCH] OZG-3961 - add user to keycloak, cleanup --- pom.xml | 56 ++++---------- .../operator/keycloak/KeycloakClient.java | 40 +++++++--- .../operator/keycloak/KeycloakException.java | 9 +++ .../user/KeycloakClientUserRemoteService.java | 19 ----- .../user/KeycloakClientUserService.java | 15 ---- .../keycloak/user/KeycloakUserMapper.java | 37 ++++++++++ .../keycloak/user/KeycloakUserReconciler.java | 58 +++++---------- .../user/KeycloakUserRemoteService.java | 28 +++++++ .../keycloak/user/KeycloakUserService.java | 22 ++++++ .../keycloak/user/KeycloakUserStatus.java | 2 +- .../KeycloakClientUserRemoteServiceTest.java | 31 -------- .../keycloak/user/KeycloakUserMapperTest.java | 39 ++++++++++ .../user/KeycloakUserReconcilerTest.java | 55 ++++++++------ .../user/KeycloakUserRemoteServiceTest.java | 74 +++++++++++++++++++ .../user/KeycloakUserServiceTest.java | 46 ++++++++++++ .../user/KeycloakUserSpecUserTestFactory.java | 7 +- .../user/KeycloakUserTestFactory.java | 9 ++- .../user/UserRepresentationTestFactory.java | 18 +++++ .../org.junit.jupiter.api.extension.Extension | 1 + src/test/resources/junit-platform.properties | 1 + 20 files changed, 378 insertions(+), 189 deletions(-) create mode 100644 src/main/java/de/ozgcloud/operator/keycloak/KeycloakException.java delete mode 100644 src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakClientUserRemoteService.java delete mode 100644 src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakClientUserService.java create mode 100644 src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakUserMapper.java create mode 100644 src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakUserRemoteService.java create mode 100644 src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakUserService.java delete mode 100644 src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakClientUserRemoteServiceTest.java create mode 100644 src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserMapperTest.java create mode 100644 src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserRemoteServiceTest.java create mode 100644 src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserServiceTest.java create mode 100644 src/test/java/de/ozgcloud/operator/keycloak/user/UserRepresentationTestFactory.java create mode 100644 src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension create mode 100644 src/test/resources/junit-platform.properties diff --git a/pom.xml b/pom.xml index 04d90dc..35173b6 100644 --- a/pom.xml +++ b/pom.xml @@ -2,21 +2,23 @@ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> + <parent> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-parent</artifactId> - <version>3.1.0</version> + <groupId>de.itvsh.kop.common</groupId> + <artifactId>kop-common-parent</artifactId> + <version>2.1.0</version> <relativePath/> <!-- lookup parent from repository --> </parent> + + + <groupId>de.ozgcloud</groupId> <artifactId>ozg-operator</artifactId> <version>1.0.0-SNAPSHOT</version> <name>OZG Cloud Operator</name> <description>OZG Cloud Operator</description> <properties> - <java.version>17</java.version> <operator-sdk.version>5.0.0</operator-sdk.version> -<!-- <operator-sdk.version>4.2.8</operator-sdk.version>--> <spring-boot.build-image.imageName>docker.ozg-sh.de/ozg-operator:build-latest</spring-boot.build-image.imageName> </properties> <dependencies> @@ -24,17 +26,14 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> -<!-- <dependency>--> -<!-- <groupId>org.springframework.boot</groupId>--> -<!-- <artifactId>spring-boot-starter-web-services</artifactId>--> -<!-- </dependency>--> - -<!-- https://mvnrepository.com/artifact/org.keycloak/keycloak-admin-client --> -<dependency> - <groupId>org.keycloak</groupId> - <artifactId>keycloak-admin-client</artifactId> - <version>21.1.1</version> -</dependency> + <dependency> + <groupId>org.keycloak</groupId> + <artifactId>keycloak-admin-client</artifactId> + </dependency> + <dependency> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct</artifactId> + </dependency> <dependency> @@ -46,41 +45,16 @@ <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> -<!-- <dependency>--> -<!-- <groupId>org.apache.logging.log4j</groupId>--> -<!-- <artifactId>log4j-core</artifactId>--> -<!-- </dependency>--> -<!-- https://mvnrepository.com/artifact/org.reflections/reflections --> <dependency> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> <version>0.10.2</version> </dependency> -<!-- <dependency>--> -<!-- <groupId>org.springframework.boot</groupId>--> -<!-- <artifactId>spring-boot-starter-actuator</artifactId>--> -<!-- </dependency>--> - - -<!-- <dependency>--> -<!-- <groupId>io.fabric8</groupId>--> -<!-- <artifactId>crd-generator-apt</artifactId>--> -<!-- <scope>provided</scope>--> -<!-- <version>6.7.1</version>--> -<!-- </dependency>--> - - <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> -<!-- <dependency>--> -<!-- <groupId>io.javaoperatorsdk</groupId>--> -<!-- <artifactId>operator-framework-spring-boot-starter-test</artifactId>--> -<!-- <version>${operator-sdk.version}</version>--> -<!-- <scope>test</scope>--> -<!-- </dependency>--> </dependencies> <build> diff --git a/src/main/java/de/ozgcloud/operator/keycloak/KeycloakClient.java b/src/main/java/de/ozgcloud/operator/keycloak/KeycloakClient.java index cc9f184..1797ee6 100644 --- a/src/main/java/de/ozgcloud/operator/keycloak/KeycloakClient.java +++ b/src/main/java/de/ozgcloud/operator/keycloak/KeycloakClient.java @@ -1,33 +1,49 @@ package de.ozgcloud.operator.keycloak; +import java.util.Base64; + import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.KeycloakBuilder; -import org.keycloak.representations.idm.UserRepresentation; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.dsl.Resource; import lombok.extern.java.Log; @Log @Component public class KeycloakClient { + @Autowired + private KubernetesClient kubernetesClient; + public Keycloak getKeycloak() { - Keycloak keycloak = KeycloakBuilder.builder() // - .serverUrl("https://sso.dev.by.ozg-cloud.de/") // - .realm("master") // -// .grantType(OAuth2Constants.PASSWORD) // - .username("admin") // - .password(" ") // + return KeycloakBuilder.builder() + .serverUrl("http://keycloak-keycloakx-http.keycloak") + .realm("master") + .username("admin") + .password(getKeycloakAdminPassword()) .clientId("admin-cli") .build(); + } - log.info(keycloak.realms().findAll().toString()); + String getKeycloakAdminPassword() { + return decodeBase64(getKeycloakRealmAdminSecret() + .get() + .getData() + .get("password")); + } - UserRepresentation user = new UserRepresentation(); - user.setUsername("helge"); - keycloak.realm("by-torsten-ozg-operator-dev").users().create(user); + Resource<Secret> getKeycloakRealmAdminSecret() { + return kubernetesClient.secrets() + .inNamespace("keycloak") + .withName("keycloak-admin-secret"); + } - return keycloak; + String decodeBase64(String base64String) { + return new String(Base64.getDecoder().decode(base64String)); } } diff --git a/src/main/java/de/ozgcloud/operator/keycloak/KeycloakException.java b/src/main/java/de/ozgcloud/operator/keycloak/KeycloakException.java new file mode 100644 index 0000000..d95cccb --- /dev/null +++ b/src/main/java/de/ozgcloud/operator/keycloak/KeycloakException.java @@ -0,0 +1,9 @@ +package de.ozgcloud.operator.keycloak; + +@SuppressWarnings("serial") +public class KeycloakException extends RuntimeException { + + public KeycloakException(String string) { + super(string); + } +} diff --git a/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakClientUserRemoteService.java b/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakClientUserRemoteService.java deleted file mode 100644 index 63ce46f..0000000 --- a/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakClientUserRemoteService.java +++ /dev/null @@ -1,19 +0,0 @@ -package de.ozgcloud.operator.keycloak.user; - -import org.keycloak.representations.account.UserRepresentation; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import de.ozgcloud.operator.keycloak.KeycloakClient; - -@Component -public class KeycloakClientUserRemoteService { - - @Autowired - private KeycloakClient keycloakClient; - - void createUser(UserRepresentation user) { - - keycloakClient.getKeycloak(); - } -} diff --git a/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakClientUserService.java b/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakClientUserService.java deleted file mode 100644 index a87f0ed..0000000 --- a/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakClientUserService.java +++ /dev/null @@ -1,15 +0,0 @@ -package de.ozgcloud.operator.keycloak.user; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -@Component -public class KeycloakClientUserService { - - @Autowired - KeycloakClientUserRemoteService remoteService; - - void addUser(KeycloakUserSpec userSpec) { - - } -} diff --git a/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakUserMapper.java b/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakUserMapper.java new file mode 100644 index 0000000..3d855fa --- /dev/null +++ b/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakUserMapper.java @@ -0,0 +1,37 @@ +package de.ozgcloud.operator.keycloak.user; + +import org.keycloak.representations.idm.UserRepresentation; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper +interface KeycloakUserMapper { + + @Mapping(target = "access", ignore = true) + @Mapping(target = "attributes", ignore = true) + @Mapping(target = "clientConsents", ignore = true) + @Mapping(target = "clientRoles", ignore = true) + @Mapping(target = "createdTimestamp", ignore = true) + @Mapping(target = "credentials", ignore = true) + @Mapping(target = "disableableCredentialTypes", ignore = true) + @Mapping(target = "email", ignore = true) + @Mapping(target = "emailVerified", ignore = true) + @Mapping(target = "enabled", ignore = true) + @Mapping(target = "federatedIdentities", ignore = true) + @Mapping(target = "federationLink", ignore = true) + @Mapping(target = "groups", ignore = true) + @Mapping(target = "id", ignore = true) + @Mapping(target = "notBefore", ignore = true) + @Mapping(target = "origin", ignore = true) + @Mapping(target = "realmRoles", ignore = true) + @Mapping(target = "requiredActions", ignore = true) + @Mapping(target = "self", ignore = true) + @Mapping(target = "serviceAccountClientId", ignore = true) + @Mapping(target = "socialLinks", ignore = true) + @Mapping(target = "totp", ignore = true) + @Mapping(target = "applicationRoles", ignore = true) + @Mapping(target = "username", source = "keycloakUser.username") + @Mapping(target = "firstName", source = "keycloakUser.firstName") + @Mapping(target = "lastName", source = "keycloakUser.lastName") + UserRepresentation toUserRepresentation(KeycloakUserSpec user); +} diff --git a/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakUserReconciler.java b/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakUserReconciler.java index 3aff0f6..a9a8672 100644 --- a/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakUserReconciler.java +++ b/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakUserReconciler.java @@ -1,11 +1,9 @@ package de.ozgcloud.operator.keycloak.user; -import java.time.LocalDate; -import java.util.Base64; +import java.util.logging.Level; + +import org.springframework.beans.factory.annotation.Autowired; -import io.fabric8.kubernetes.api.model.Secret; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.dsl.Resource; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; @@ -13,50 +11,30 @@ import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import lombok.extern.java.Log; @ControllerConfiguration -//@Log4j2 @Log public class KeycloakUserReconciler implements Reconciler<KeycloakUser> { - private final KubernetesClient kubernetesClient; + public static final String STATUS_OK = "OK"; + public static final String STATUS_ERROR = "ERROR"; - public KeycloakUserReconciler(KubernetesClient kubernetesClient) { - this.kubernetesClient = kubernetesClient; - } + @Autowired + private KeycloakUserService keycloakUserService; @Override - public UpdateControl<KeycloakUser> reconcile(KeycloakUser crd, Context<KeycloakUser> context) throws Exception { - -// String name = crd.getMetadata().getName(); - String name = crd.getSpec().getKeycloakUser().getUsername(); - String namespace = crd.getMetadata().getNamespace(); - log.warning("Reconciling: " + name + "/" + namespace); - log.warning("SecretTest:" + getKeycloakRealmAdminPassword(namespace)); - crd.setStatus(KeycloakUserStatus.builder().status("Updated status for " + name + LocalDate.now().toString()).phase("Pending").build()); - return UpdateControl.updateStatus(crd); - } - - String getKeycloakRealmAdminPassword(String namespace) { + public UpdateControl<KeycloakUser> reconcile(KeycloakUser crd, Context<KeycloakUser> context) { - Resource<Secret> secret = getKeycloakRealmAdminSecret(namespace); + try { + String namespace = crd.getMetadata().getNamespace(); -// if (!secret.isReady()) { -// throw new RuntimeException("Secret not exists " + namespace + "-admin-credentials"); -// } + keycloakUserService.addUser(crd.getSpec(), namespace); - return decodeBase64(secret - .get() - .getData() - .get("username")); -// .toString(); - } - - Resource<Secret> getKeycloakRealmAdminSecret(String namespace) { - return kubernetesClient.secrets() - .inNamespace("keycloak") - .withName("keycloak-admin-secret"); - } + crd.setStatus(KeycloakUserStatus.builder().status(STATUS_OK).errorMessage(null).build()); + return UpdateControl.updateStatus(crd); - String decodeBase64(String base64String) { - return new String(Base64.getDecoder().decode(base64String)); + } catch (Exception e) { + log.log(Level.SEVERE, "Could not reconcile user", e); + crd.setStatus(KeycloakUserStatus.builder().status(STATUS_ERROR).errorMessage(e.getMessage()).build()); + return UpdateControl.updateStatus(crd); + } } } diff --git a/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakUserRemoteService.java b/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakUserRemoteService.java new file mode 100644 index 0000000..ac5a68c --- /dev/null +++ b/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakUserRemoteService.java @@ -0,0 +1,28 @@ +package de.ozgcloud.operator.keycloak.user; + +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status.Family; + +import org.keycloak.representations.idm.UserRepresentation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import de.ozgcloud.operator.keycloak.KeycloakClient; +import de.ozgcloud.operator.keycloak.KeycloakException; + +@Component +class KeycloakUserRemoteService { + + @Autowired + private KeycloakClient keycloakClient; + + void createUser(UserRepresentation user, String realm) { + + try (Response response = keycloakClient.getKeycloak().realm(realm).users().create(user)) { + + if (!response.getStatusInfo().getFamily().equals(Family.SUCCESSFUL)) { + throw new KeycloakException("Could not update user " + user.getUsername() + ";" + response.getStatusInfo()); + } + } + } +} diff --git a/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakUserService.java b/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakUserService.java new file mode 100644 index 0000000..717a1be --- /dev/null +++ b/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakUserService.java @@ -0,0 +1,22 @@ +package de.ozgcloud.operator.keycloak.user; + +import org.keycloak.representations.idm.UserRepresentation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class KeycloakUserService { + + @Autowired + private KeycloakUserRemoteService remoteService; + + @Autowired + private KeycloakUserMapper userMapper; + + void addUser(KeycloakUserSpec userSpec, String namespace) { + + UserRepresentation keycloakUser = userMapper.toUserRepresentation(userSpec); + + remoteService.createUser(keycloakUser, namespace); + } +} diff --git a/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakUserStatus.java b/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakUserStatus.java index 7e83e84..43a5e5f 100644 --- a/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakUserStatus.java +++ b/src/main/java/de/ozgcloud/operator/keycloak/user/KeycloakUserStatus.java @@ -16,5 +16,5 @@ public class KeycloakUserStatus extends ObservedGenerationAwareStatus { private String status; - private String phase; + private String errorMessage; } diff --git a/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakClientUserRemoteServiceTest.java b/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakClientUserRemoteServiceTest.java deleted file mode 100644 index 86cd6ff..0000000 --- a/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakClientUserRemoteServiceTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package de.ozgcloud.operator.keycloak.user; - -import static org.mockito.Mockito.*; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Spy; -import org.mockito.junit.jupiter.MockitoExtension; - -import de.ozgcloud.operator.keycloak.KeycloakClient; - -@ExtendWith(MockitoExtension.class) -class KeycloakClientUserRemoteServiceTest { - - @Spy - @InjectMocks - KeycloakClientUserRemoteService userRemoteService; - - @Mock - KeycloakClient keycloakClient; - - @Test - void test() { - - userRemoteService.createUser(null); - - verify(keycloakClient).getKeycloak(); - } -} diff --git a/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserMapperTest.java b/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserMapperTest.java new file mode 100644 index 0000000..6b84fdf --- /dev/null +++ b/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserMapperTest.java @@ -0,0 +1,39 @@ +package de.ozgcloud.operator.keycloak.user; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; +import org.mockito.Spy; + +class KeycloakUserMapperTest { + + @Spy + private KeycloakUserMapper mapper = Mappers.getMapper(KeycloakUserMapper.class); + + @Nested + class TesttoUserRepresentation { + + @Test + void shouldMapUsername() { + var keycloakUser = mapper.toUserRepresentation(KeycloakUserSpecTestFactory.create()); + + assertThat(keycloakUser.getUsername()).isEqualTo(KeycloakUserSpecUserTestFactory.USERNAME); + } + + @Test + void shouldMapFirstname() { + var keycloakUser = mapper.toUserRepresentation(KeycloakUserSpecTestFactory.create()); + + assertThat(keycloakUser.getFirstName()).isEqualTo(KeycloakUserSpecUserTestFactory.FIRSTNAME); + } + + @Test + void shouldMapLastname() { + var keycloakUser = mapper.toUserRepresentation(KeycloakUserSpecTestFactory.create()); + + assertThat(keycloakUser.getLastName()).isEqualTo(KeycloakUserSpecUserTestFactory.LASTNAME); + } + } +} diff --git a/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserReconcilerTest.java b/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserReconcilerTest.java index 9c66f82..d8df746 100644 --- a/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserReconcilerTest.java +++ b/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserReconcilerTest.java @@ -1,23 +1,14 @@ package de.ozgcloud.operator.keycloak.user; 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.Disabled; -import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; -import de.ozgcloud.operator.keycloak.user.KeycloakUser; -import de.ozgcloud.operator.keycloak.user.KeycloakUserReconciler; -import io.fabric8.kubernetes.client.KubernetesClient; -import lombok.SneakyThrows; - -@Disabled class KeycloakUserReconcilerTest { @Spy @@ -25,26 +16,42 @@ class KeycloakUserReconcilerTest { private KeycloakUserReconciler conciler; @Mock - private KubernetesClient kubernetesClient; - - @Nested - class TestKeycloalUserStatus { + private KeycloakUserService userService; - @BeforeEach - void init() { - doReturn("hase").when(conciler).getKeycloakRealmAdminPassword(anyString()); + @BeforeEach + void init() { +// doReturn("hase").when(conciler).getKeycloakRealmAdminPassword(anyString()); // when(conciler.getKeycloakRealmAdminPassword(anyString())).thenReturn("hase"); - } + } + + @Test + void shouldCallServiceAddUser() { + + KeycloakUser user = KeycloakUserTestFactory.create(); + + conciler.reconcile(user, null); + + verify(userService).addUser(user.getSpec(), KeycloakUserTestFactory.METADATA_NAMESPACE); + } + + @Test + void shouldUpdateStatus() { + + KeycloakUser user = KeycloakUserTestFactory.create(); + + conciler.reconcile(user, null); + + assertThat(user.getStatus().getStatus()).isEqualTo(KeycloakUserReconciler.STATUS_OK); + } - @Test - @SneakyThrows - void shouldSetStatus() { + @Test + void shouldSetErrorStatusOnException() { - KeycloakUser user = KeycloakUserTestFactory.create(); + KeycloakUser user = KeycloakUserTestFactory.create(); + user.setMetadata(null); - conciler.reconcile(user, null); + conciler.reconcile(user, null); - assertThat(user.getStatus().getStatus()).contains("Updated"); - } + assertThat(user.getStatus().getStatus()).isEqualTo(KeycloakUserReconciler.STATUS_ERROR); } } diff --git a/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserRemoteServiceTest.java b/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserRemoteServiceTest.java new file mode 100644 index 0000000..a9491af --- /dev/null +++ b/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserRemoteServiceTest.java @@ -0,0 +1,74 @@ +package de.ozgcloud.operator.keycloak.user; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status.Family; +import javax.ws.rs.core.Response.StatusType; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.RealmResource; +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.ozgcloud.operator.keycloak.KeycloakClient; +import de.ozgcloud.operator.keycloak.KeycloakException; + +class KeycloakUserRemoteServiceTest { + + @Spy + @InjectMocks + private KeycloakUserRemoteService userRemoteService; + + @Mock + private KeycloakClient keycloakClient; + @Mock + private Keycloak keycloak; + @Mock + private RealmResource realmResource; + @Mock + private UsersResource usersResource; + @Mock + private Response response; + @Mock + private StatusType statusType; + + private final static UserRepresentation USER_REPRESENTATION = UserRepresentationTestFactory.create(); + private final static String REALM = "TestRealm"; + + @BeforeEach + void init() { + when(keycloakClient.getKeycloak()).thenReturn(keycloak); + when(keycloak.realm(REALM)).thenReturn(realmResource); + when(realmResource.users()).thenReturn(usersResource); + when(usersResource.create(USER_REPRESENTATION)).thenReturn(response); + when(response.getStatusInfo()).thenReturn(statusType); + when(statusType.getFamily()).thenReturn(Family.SUCCESSFUL); + } + + @Test + void shouldCallKeycloakClient() { + + when(statusType.getFamily()).thenReturn(Family.SUCCESSFUL); + + userRemoteService.createUser(USER_REPRESENTATION, REALM); + + verify(keycloakClient).getKeycloak(); + } + + @Test + void shouldThrowOnResponseError() { + + when(statusType.getFamily()).thenReturn(Family.SERVER_ERROR); + + assertThrows(KeycloakException.class, () -> + userRemoteService.createUser(USER_REPRESENTATION, REALM) + ); + } +} diff --git a/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserServiceTest.java b/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserServiceTest.java new file mode 100644 index 0000000..1587a8f --- /dev/null +++ b/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserServiceTest.java @@ -0,0 +1,46 @@ +package de.ozgcloud.operator.keycloak.user; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; + +class KeycloakUserServiceTest { + + private static final String TEST_NAMESPACE = "TestNamespace"; + + @Spy + @InjectMocks + private KeycloakUserService userService; + + @Mock + private KeycloakUserRemoteService userRemoteService; + + @Mock + private KeycloakUserMapper userMapper; + + @Test + void shouldCallUserMapper() { + + var testUser = KeycloakUserSpecTestFactory.create(); + + userService.addUser(testUser, TEST_NAMESPACE); + + verify(userMapper).toUserRepresentation(testUser); + } + + @Test + void shouldCallUserRemoteService() { + + var userRepresentation = UserRepresentationTestFactory.create(); + when(userMapper.toUserRepresentation(any())).thenReturn(userRepresentation); + + userService.addUser(KeycloakUserSpecTestFactory.create(), TEST_NAMESPACE); + + verify(userRemoteService).createUser(eq(userRepresentation), eq(TEST_NAMESPACE)); + } + +} diff --git a/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserSpecUserTestFactory.java b/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserSpecUserTestFactory.java index d388671..6ed256d 100644 --- a/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserSpecUserTestFactory.java +++ b/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserSpecUserTestFactory.java @@ -5,6 +5,8 @@ import de.ozgcloud.operator.keycloak.user.KeycloakUserSpec.KeycloakUserSpecUser; class KeycloakUserSpecUserTestFactory { public static final String USERNAME = "dorothea"; + public static final String FIRSTNAME = "Dorothea"; + public static final String LASTNAME = "Doe"; public static KeycloakUserSpecUser create() { return createBuiler().build(); @@ -12,7 +14,8 @@ class KeycloakUserSpecUserTestFactory { public static KeycloakUserSpecUser.KeycloakUserSpecUserBuilder createBuiler() { return KeycloakUserSpecUser.builder() - .username(USERNAME); + .username(USERNAME) + .firstName(FIRSTNAME) + .lastName(LASTNAME); } - } diff --git a/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserTestFactory.java b/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserTestFactory.java index fd3a9fb..4c71c40 100644 --- a/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserTestFactory.java +++ b/src/test/java/de/ozgcloud/operator/keycloak/user/KeycloakUserTestFactory.java @@ -1,19 +1,20 @@ package de.ozgcloud.operator.keycloak.user; -import de.ozgcloud.operator.keycloak.user.KeycloakUser; -import de.ozgcloud.operator.keycloak.user.KeycloakUserSpec; -import de.ozgcloud.operator.keycloak.user.KeycloakUserStatus; - class KeycloakUserTestFactory { public static final KeycloakUserStatus KEYCLOAK_USER_STATUS = KeycloakUserStatusTestFactory.create(); public static final KeycloakUserSpec KEYCLOAK_USER_SPEC = KeycloakUserSpecTestFactory.create(); + public static final String METADATA_NAMESPACE = "TestNamespace"; + public static KeycloakUser create() { KeycloakUser keycloakUser = new KeycloakUser(); keycloakUser.setStatus(KEYCLOAK_USER_STATUS); keycloakUser.setSpec(KEYCLOAK_USER_SPEC); + + keycloakUser.getMetadata().setNamespace(METADATA_NAMESPACE); + return keycloakUser; } diff --git a/src/test/java/de/ozgcloud/operator/keycloak/user/UserRepresentationTestFactory.java b/src/test/java/de/ozgcloud/operator/keycloak/user/UserRepresentationTestFactory.java new file mode 100644 index 0000000..022c856 --- /dev/null +++ b/src/test/java/de/ozgcloud/operator/keycloak/user/UserRepresentationTestFactory.java @@ -0,0 +1,18 @@ +package de.ozgcloud.operator.keycloak.user; + +import org.keycloak.representations.idm.UserRepresentation; + +public class UserRepresentationTestFactory { + + public static String USERNAME = KeycloakUserSpecUserTestFactory.USERNAME; + public static String FIRSTNAME = KeycloakUserSpecUserTestFactory.FIRSTNAME; + public static String LASTNAME = KeycloakUserSpecUserTestFactory.LASTNAME; + + public static UserRepresentation create() { + UserRepresentation user = new UserRepresentation(); + user.setUsername(USERNAME); + user.setFirstName(FIRSTNAME); + user.setLastName(LASTNAME); + return user; + } +} diff --git a/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 0000000..79b126e --- /dev/null +++ b/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1 @@ +org.mockito.junit.jupiter.MockitoExtension \ No newline at end of file diff --git a/src/test/resources/junit-platform.properties b/src/test/resources/junit-platform.properties new file mode 100644 index 0000000..b059a65 --- /dev/null +++ b/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.extensions.autodetection.enabled=true \ No newline at end of file -- GitLab