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.KeycloakIntegrationTest;
import org.keycloak.test.framework.TestClient; import org.keycloak.test.framework.TestClient;
import org.keycloak.test.framework.TestRealm; import org.keycloak.test.framework.TestRealm;
import org.keycloak.test.framework.injection.LifeCycle;
import java.util.List; import java.util.List;
@KeycloakIntegrationTest @KeycloakIntegrationTest
public class ManagedResourcesTest { public class ManagedResourcesTest {
@TestRealm @TestRealm(lifecycle = LifeCycle.CLASS)
RealmResource realmResource; RealmResource realmResource;
@TestClient @TestClient

View file

@ -1,19 +1,12 @@
package org.keycloak.test.framework; package org.keycloak.test.framework;
import org.junit.jupiter.api.extension.AfterAllCallback; 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.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext;
import org.keycloak.test.framework.injection.Registry; import org.keycloak.test.framework.injection.Registry;
public class KeycloakIntegrationTestExtension implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback { public class KeycloakIntegrationTestExtension implements BeforeEachCallback, AfterEachCallback, AfterAllCallback {
@Override
public void beforeAll(ExtensionContext context) {
if (isExtensionEnabled(context)) {
getRegistry(context).beforeAll(context.getRequiredTestClass());
}
}
@Override @Override
public void beforeEach(ExtensionContext context) { 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 @Override
public void afterAll(ExtensionContext context) { public void afterAll(ExtensionContext context) {
if (isExtensionEnabled(context)) { if (isExtensionEnabled(context)) {

View file

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

View file

@ -1,5 +1,6 @@
package org.keycloak.test.framework; 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.DefaultRealmConfig;
import org.keycloak.test.framework.realm.RealmConfig; import org.keycloak.test.framework.realm.RealmConfig;
@ -14,4 +15,6 @@ public @interface TestRealm {
Class<? extends RealmConfig> config() default DefaultRealmConfig.class; 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); KeycloakTestServer testServer = registry.getDependency(KeycloakTestServer.class, wrapper);
Keycloak keycloak = Keycloak.getInstance(testServer.getBaseUrl(), "master", "admin", "admin", "admin-cli"); Keycloak keycloak = Keycloak.getInstance(testServer.getBaseUrl(), "master", "admin", "admin", "admin-cli");
wrapper.setValue(keycloak); wrapper.setValue(keycloak, LifeCycle.GLOBAL);
return wrapper; return wrapper;
} }
@Override
public LifeCycle getLifeCycle() {
return LifeCycle.GLOBAL;
}
@Override @Override
public boolean compatible(InstanceWrapper<Keycloak, TestAdminClient> a, InstanceWrapper<Keycloak, TestAdminClient> b) { public boolean compatible(InstanceWrapper<Keycloak, TestAdminClient> a, InstanceWrapper<Keycloak, TestAdminClient> b) {
return true; return true;

View file

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

View file

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

View file

@ -25,6 +25,7 @@ public class Registry {
public Registry() { public Registry() {
loadSuppliers(); loadSuppliers();
Runtime.getRuntime().addShutdownHook(new Thread(this::onShutdown));
} }
public ExtensionContext getCurrentContext() { public ExtensionContext getCurrentContext() {
@ -82,7 +83,8 @@ public class Registry {
throw new RuntimeException("Dependency not found: " + typeClass); 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()); InstanceWrapper requestedServerInstance = createInstanceWrapper(testClass.getAnnotations());
requestedInstances.add(requestedServerInstance); requestedInstances.add(requestedServerInstance);
@ -137,9 +139,6 @@ public class Registry {
itr.remove(); itr.remove();
} }
}
public void beforeEach(Object testInstance) {
for (Field f : testInstance.getClass().getDeclaredFields()) { for (Field f : testInstance.getClass().getDeclaredFields()) {
InstanceWrapper<?, ?> instance = getDeployedInstance(f.getAnnotations()); InstanceWrapper<?, ?> instance = getDeployedInstance(f.getAnnotations());
try { try {
@ -152,7 +151,20 @@ public class Registry {
} }
public void afterAll() { 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); 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); InstanceWrapper<T, S> getValue(Registry registry, S annotation);
LifeCycle getLifeCycle();
boolean compatible(InstanceWrapper<T, S> a, InstanceWrapper<T, S> b); boolean compatible(InstanceWrapper<T, S> a, InstanceWrapper<T, S> b);
default void close(T instance) {} default void close(T instance) {}

View file

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

View file

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

View file

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

View file

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

View file

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