TestSuite PoC - Add support to configure lifecycle for realms and clients (#31145)

Closes #30610

Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
Stian Thorgersen 2024-07-09 13:07:56 +02:00 committed by GitHub
parent ffbfb7450f
commit 2dc37d2513
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 131 additions and 58 deletions

View file

@ -0,0 +1,37 @@
package org.keycloak.test.base;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.test.framework.KeycloakIntegrationTest;
import org.keycloak.test.framework.TestClient;
import org.keycloak.test.framework.TestRealm;
import org.keycloak.test.framework.injection.LifeCycle;
import java.util.List;
@KeycloakIntegrationTest
public class GlobalManagedResourcesTest {
@TestRealm(lifecycle = LifeCycle.GLOBAL)
RealmResource realmResource;
@TestClient
ClientResource clientResource;
@Test
public void testCreatedRealm() {
Assertions.assertEquals("DefaultRealmConfig", realmResource.toRepresentation().getRealm());
}
@Test
public void testCreatedClient() {
Assertions.assertEquals("GlobalManagedResourcesTest", clientResource.toRepresentation().getClientId());
List<ClientRepresentation> clients = realmResource.clients().findByClientId("GlobalManagedResourcesTest");
Assertions.assertEquals(1, clients.size());
}
}

View file

@ -0,0 +1,37 @@
package org.keycloak.test.base;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.test.framework.KeycloakIntegrationTest;
import org.keycloak.test.framework.TestClient;
import org.keycloak.test.framework.TestRealm;
import org.keycloak.test.framework.injection.LifeCycle;
import java.util.List;
@KeycloakIntegrationTest
public class ManagedResources2Test {
@TestRealm(lifecycle = LifeCycle.CLASS)
RealmResource realmResource;
@TestClient
ClientResource clientResource;
@Test
public void testCreatedRealm() {
Assertions.assertEquals("ManagedResources2Test", realmResource.toRepresentation().getRealm());
}
@Test
public void testCreatedClient() {
Assertions.assertEquals("ManagedResources2Test", clientResource.toRepresentation().getClientId());
List<ClientRepresentation> clients = realmResource.clients().findByClientId("ManagedResources2Test");
Assertions.assertEquals(1, clients.size());
}
}

View file

@ -8,13 +8,14 @@ import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.test.framework.KeycloakIntegrationTest;
import org.keycloak.test.framework.TestClient;
import org.keycloak.test.framework.TestRealm;
import org.keycloak.test.framework.injection.LifeCycle;
import java.util.List;
@KeycloakIntegrationTest
public class ManagedResourcesTest {
@TestRealm
@TestRealm(lifecycle = LifeCycle.CLASS)
RealmResource realmResource;
@TestClient

View file

@ -1,19 +1,12 @@
package org.keycloak.test.framework;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.keycloak.test.framework.injection.Registry;
public class KeycloakIntegrationTestExtension implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback {
@Override
public void beforeAll(ExtensionContext context) {
if (isExtensionEnabled(context)) {
getRegistry(context).beforeAll(context.getRequiredTestClass());
}
}
public class KeycloakIntegrationTestExtension implements BeforeEachCallback, AfterEachCallback, AfterAllCallback {
@Override
public void beforeEach(ExtensionContext context) {
@ -22,6 +15,13 @@ public class KeycloakIntegrationTestExtension implements BeforeAllCallback, Afte
}
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
if (isExtensionEnabled(context)) {
getRegistry(context).afterEach();
}
}
@Override
public void afterAll(ExtensionContext context) {
if (isExtensionEnabled(context)) {

View file

@ -1,5 +1,6 @@
package org.keycloak.test.framework;
import org.keycloak.test.framework.injection.LifeCycle;
import org.keycloak.test.framework.realm.ClientConfig;
import org.keycloak.test.framework.realm.DefaultClientConfig;
@ -14,4 +15,6 @@ public @interface TestClient {
Class<? extends ClientConfig> config() default DefaultClientConfig.class;
LifeCycle lifecycle() default LifeCycle.CLASS;
}

View file

@ -1,5 +1,6 @@
package org.keycloak.test.framework;
import org.keycloak.test.framework.injection.LifeCycle;
import org.keycloak.test.framework.realm.DefaultRealmConfig;
import org.keycloak.test.framework.realm.RealmConfig;
@ -14,4 +15,6 @@ public @interface TestRealm {
Class<? extends RealmConfig> config() default DefaultRealmConfig.class;
LifeCycle lifecycle() default LifeCycle.CLASS;
}

View file

@ -27,16 +27,11 @@ public class KeycloakAdminClientSupplier implements Supplier<Keycloak, TestAdmin
KeycloakTestServer testServer = registry.getDependency(KeycloakTestServer.class, wrapper);
Keycloak keycloak = Keycloak.getInstance(testServer.getBaseUrl(), "master", "admin", "admin", "admin-cli");
wrapper.setValue(keycloak);
wrapper.setValue(keycloak, LifeCycle.GLOBAL);
return wrapper;
}
@Override
public LifeCycle getLifeCycle() {
return LifeCycle.GLOBAL;
}
@Override
public boolean compatible(InstanceWrapper<Keycloak, TestAdminClient> a, InstanceWrapper<Keycloak, TestAdminClient> b) {
return true;

View file

@ -12,6 +12,7 @@ public class InstanceWrapper<T, A extends Annotation> {
private final A annotation;
private final Set<InstanceWrapper<T, A>> dependencies = new HashSet<>();
private T value;
private LifeCycle lifeCycle;
private final Map<String, Object> notes = new HashMap<>();
public InstanceWrapper(Supplier<T, A> supplier, A annotation) {
@ -19,14 +20,16 @@ public class InstanceWrapper<T, A extends Annotation> {
this.annotation = annotation;
}
public InstanceWrapper(Supplier<T, A> supplier, A annotation, T value) {
public InstanceWrapper(Supplier<T, A> supplier, A annotation, T value, LifeCycle lifeCycle) {
this.supplier = supplier;
this.annotation = annotation;
this.value = value;
this.lifeCycle = lifeCycle;
}
public void setValue(T value) {
public void setValue(T value, LifeCycle lifeCycle) {
this.value = value;
this.lifeCycle = lifeCycle;
}
public Supplier<T, A> getSupplier() {
@ -37,6 +40,10 @@ public class InstanceWrapper<T, A extends Annotation> {
return value;
}
public LifeCycle getLifeCycle() {
return lifeCycle;
}
public A getAnnotation() {
return annotation;
}

View file

@ -3,6 +3,7 @@ package org.keycloak.test.framework.injection;
public enum LifeCycle {
GLOBAL,
CLASS
CLASS,
METHOD
}

View file

@ -25,6 +25,7 @@ public class Registry {
public Registry() {
loadSuppliers();
Runtime.getRuntime().addShutdownHook(new Thread(this::onShutdown));
}
public ExtensionContext getCurrentContext() {
@ -82,7 +83,8 @@ public class Registry {
throw new RuntimeException("Dependency not found: " + typeClass);
}
public void beforeAll(Class testClass) {
public void beforeEach(Object testInstance) {
Class testClass = testInstance.getClass();
InstanceWrapper requestedServerInstance = createInstanceWrapper(testClass.getAnnotations());
requestedInstances.add(requestedServerInstance);
@ -137,9 +139,6 @@ public class Registry {
itr.remove();
}
}
public void beforeEach(Object testInstance) {
for (Field f : testInstance.getClass().getDeclaredFields()) {
InstanceWrapper<?, ?> instance = getDeployedInstance(f.getAnnotations());
try {
@ -152,7 +151,20 @@ public class Registry {
}
public void afterAll() {
List<InstanceWrapper<?, ?>> destroy = deployedInstances.stream().filter(i -> i.getSupplier().getLifeCycle().equals(LifeCycle.CLASS)).toList();
LOGGER.trace("Closing instances with class lifecycle");
List<InstanceWrapper<?, ?>> destroy = deployedInstances.stream().filter(i -> i.getLifeCycle().equals(LifeCycle.CLASS)).toList();
destroy.forEach(this::destroy);
}
public void afterEach() {
LOGGER.trace("Closing instances with method lifecycle");
List<InstanceWrapper<?, ?>> destroy = deployedInstances.stream().filter(i -> i.getLifeCycle().equals(LifeCycle.METHOD)).toList();
destroy.forEach(this::destroy);
}
public void onShutdown() {
LOGGER.trace("Closing instances with global lifecycle");
List<InstanceWrapper<?, ?>> destroy = deployedInstances.stream().filter(i -> i.getLifeCycle().equals(LifeCycle.GLOBAL)).toList();
destroy.forEach(this::destroy);
}

View file

@ -10,8 +10,6 @@ public interface Supplier<T, S extends Annotation> {
InstanceWrapper<T, S> getValue(Registry registry, S annotation);
LifeCycle getLifeCycle();
boolean compatible(InstanceWrapper<T, S> a, InstanceWrapper<T, S> b);
default void close(T instance) {}

View file

@ -28,6 +28,7 @@ public class ClientSupplier implements Supplier<ClientResource, TestClient> {
@Override
public InstanceWrapper<ClientResource, TestClient> getValue(Registry registry, TestClient annotation) {
InstanceWrapper<ClientResource, TestClient> wrapper = new InstanceWrapper<>(this, annotation);
LifeCycle lifecycle = annotation.lifecycle();
RealmResource realm = registry.getDependency(RealmResource.class, wrapper);
@ -35,7 +36,8 @@ public class ClientSupplier implements Supplier<ClientResource, TestClient> {
ClientRepresentation clientRepresentation = config.getRepresentation();
if (clientRepresentation.getClientId() == null) {
clientRepresentation.setClientId(registry.getCurrentContext().getRequiredTestClass().getSimpleName());
String clientId = lifecycle.equals(LifeCycle.GLOBAL) ? config.getClass().getSimpleName() : registry.getCurrentContext().getRequiredTestClass().getSimpleName();
clientRepresentation.setClientId(clientId);
}
Response response = realm.clients().create(clientRepresentation);
@ -48,16 +50,11 @@ public class ClientSupplier implements Supplier<ClientResource, TestClient> {
wrapper.addNote(CLIENT_UUID_KEY, clientId);
ClientResource clientResource = realm.clients().get(clientId);
wrapper.setValue(clientResource);
wrapper.setValue(clientResource, lifecycle);
return wrapper;
}
@Override
public LifeCycle getLifeCycle() {
return LifeCycle.CLASS;
}
@Override
public boolean compatible(InstanceWrapper<ClientResource, TestClient> a, InstanceWrapper<ClientResource, TestClient> b) {
return a.getAnnotation().config().equals(b.getAnnotation().config()) &&

View file

@ -27,6 +27,7 @@ public class RealmSupplier implements Supplier<RealmResource, TestRealm> {
@Override
public InstanceWrapper<RealmResource, TestRealm> getValue(Registry registry, TestRealm annotation) {
InstanceWrapper<RealmResource, TestRealm> wrapper = new InstanceWrapper<>(this, annotation);
LifeCycle lifecycle = annotation.lifecycle();
Keycloak adminClient = registry.getDependency(Keycloak.class, wrapper);
@ -34,7 +35,8 @@ public class RealmSupplier implements Supplier<RealmResource, TestRealm> {
RealmRepresentation realmRepresentation = config.getRepresentation();
if (realmRepresentation.getRealm() == null) {
realmRepresentation.setRealm(registry.getCurrentContext().getRequiredTestClass().getSimpleName());
String realmName = lifecycle.equals(LifeCycle.GLOBAL) ? config.getClass().getSimpleName() : registry.getCurrentContext().getRequiredTestClass().getSimpleName();
realmRepresentation.setRealm(realmName);
}
String realmName = realmRepresentation.getRealm();
@ -43,16 +45,11 @@ public class RealmSupplier implements Supplier<RealmResource, TestRealm> {
adminClient.realms().create(realmRepresentation);
RealmResource realmResource = adminClient.realm(realmRepresentation.getRealm());
wrapper.setValue(realmResource);
wrapper.setValue(realmResource, lifecycle);
return wrapper;
}
@Override
public LifeCycle getLifeCycle() {
return LifeCycle.CLASS;
}
@Override
public boolean compatible(InstanceWrapper<RealmResource, TestRealm> a, InstanceWrapper<RealmResource, TestRealm> b) {
return a.getAnnotation().config().equals(b.getAnnotation().config()) &&

View file

@ -28,12 +28,7 @@ public class KeycloakTestServerSupplier implements Supplier<KeycloakTestServer,
keycloakTestServer.start(serverConfig);
return new InstanceWrapper<>(this, annotation, keycloakTestServer);
}
@Override
public LifeCycle getLifeCycle() {
return LifeCycle.GLOBAL;
return new InstanceWrapper<>(this, annotation, keycloakTestServer, LifeCycle.GLOBAL);
}
@Override

View file

@ -22,12 +22,7 @@ public class ChromeWebDriverSupplier implements Supplier<WebDriver, TestWebDrive
@Override
public InstanceWrapper<WebDriver, TestWebDriver> getValue(Registry registry, TestWebDriver annotation) {
final var driver = new ChromeDriver();
return new InstanceWrapper<>(this, annotation, driver);
}
@Override
public LifeCycle getLifeCycle() {
return LifeCycle.GLOBAL;
return new InstanceWrapper<>(this, annotation, driver, LifeCycle.GLOBAL);
}
@Override

View file

@ -22,12 +22,7 @@ public class FirefoxWebDriverSupplier implements Supplier<WebDriver, TestWebDriv
@Override
public InstanceWrapper<WebDriver, TestWebDriver> getValue(Registry registry, TestWebDriver annotation) {
final var driver = new FirefoxDriver();
return new InstanceWrapper<>(this, annotation, driver);
}
@Override
public LifeCycle getLifeCycle() {
return LifeCycle.GLOBAL;
return new InstanceWrapper<>(this, annotation, driver, LifeCycle.GLOBAL);
}
@Override