Update testframework registry to explicitly declare dependencies in all suppliers (#44974)

Closes #44947, Closes #40756

Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
Stian Thorgersen 2025-12-18 14:08:32 +01:00 committed by GitHub
parent 7512a0412b
commit 94dc60822b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 473 additions and 89 deletions

View File

@ -1,9 +1,12 @@
package org.keycloak.testframework.admin;
import java.util.List;
import javax.net.ssl.SSLContext;
import org.keycloak.testframework.annotations.InjectAdminClientFactory;
import org.keycloak.testframework.https.ManagedCertificates;
import org.keycloak.testframework.injection.DependenciesBuilder;
import org.keycloak.testframework.injection.Dependency;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.RequestedInstance;
import org.keycloak.testframework.injection.Supplier;
@ -11,6 +14,11 @@ import org.keycloak.testframework.server.KeycloakServer;
public class AdminClientFactorySupplier implements Supplier<AdminClientFactory, InjectAdminClientFactory> {
@Override
public List<Dependency> getDependencies(RequestedInstance<AdminClientFactory, InjectAdminClientFactory> instanceContext) {
return DependenciesBuilder.create(KeycloakServer.class).add(ManagedCertificates.class).build();
}
@Override
public AdminClientFactory getValue(InstanceContext<AdminClientFactory, InjectAdminClientFactory> instanceContext) {
KeycloakServer server = instanceContext.getDependency(KeycloakServer.class);

View File

@ -1,5 +1,7 @@
package org.keycloak.testframework.admin;
import java.util.List;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.representations.idm.ClientRepresentation;
@ -8,6 +10,8 @@ import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testframework.TestFrameworkException;
import org.keycloak.testframework.annotations.InjectAdminClient;
import org.keycloak.testframework.config.Config;
import org.keycloak.testframework.injection.DependenciesBuilder;
import org.keycloak.testframework.injection.Dependency;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.LifeCycle;
import org.keycloak.testframework.injection.RequestedInstance;
@ -17,6 +21,15 @@ import org.keycloak.testframework.realm.ManagedUser;
public class AdminClientSupplier implements Supplier<Keycloak, InjectAdminClient> {
@Override
public List<Dependency> getDependencies(RequestedInstance<Keycloak, InjectAdminClient> instanceContext) {
DependenciesBuilder builder = DependenciesBuilder.create(AdminClientFactory.class);
if (instanceContext.getAnnotation().mode().equals(InjectAdminClient.Mode.MANAGED_REALM)) {
builder.add(ManagedRealm.class);
}
return builder.build();
}
@Override
public Keycloak getValue(InstanceContext<Keycloak, InjectAdminClient> instanceContext) {
InjectAdminClient annotation = instanceContext.getAnnotation();

View File

@ -1,7 +1,10 @@
package org.keycloak.testframework.events;
import java.lang.annotation.Annotation;
import java.util.List;
import org.keycloak.testframework.injection.DependenciesBuilder;
import org.keycloak.testframework.injection.Dependency;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.LifeCycle;
import org.keycloak.testframework.injection.RequestedInstance;
@ -13,6 +16,11 @@ import org.keycloak.testframework.realm.RealmConfigInterceptor;
@SuppressWarnings("rawtypes")
public abstract class AbstractEventsSupplier<E extends AbstractEvents, A extends Annotation> implements Supplier<E, A>, RealmConfigInterceptor<E, A> {
@Override
public List<Dependency> getDependencies(RequestedInstance<E, A> instanceContext) {
return DependenciesBuilder.create(ManagedRealm.class, SupplierHelpers.getAnnotationField(instanceContext.getAnnotation(), "realmRef")).build();
}
@Override
public E getValue(InstanceContext<E, A> instanceContext) {
String realmRef = SupplierHelpers.getAnnotationField(instanceContext.getAnnotation(), "realmRef");

View File

@ -1,10 +1,13 @@
package org.keycloak.testframework.http;
import java.io.IOException;
import java.util.List;
import javax.net.ssl.SSLContext;
import org.keycloak.testframework.annotations.InjectHttpClient;
import org.keycloak.testframework.https.ManagedCertificates;
import org.keycloak.testframework.injection.DependenciesBuilder;
import org.keycloak.testframework.injection.Dependency;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.LifeCycle;
import org.keycloak.testframework.injection.RequestedInstance;
@ -17,6 +20,11 @@ import org.apache.http.impl.client.HttpClientBuilder;
public class HttpClientSupplier implements Supplier<HttpClient, InjectHttpClient> {
@Override
public List<Dependency> getDependencies(RequestedInstance<HttpClient, InjectHttpClient> instanceContext) {
return DependenciesBuilder.create(ManagedCertificates.class).build();
}
@Override
public HttpClient getValue(InstanceContext<HttpClient, InjectHttpClient> instanceContext) {
HttpClientBuilder builder = HttpClientBuilder.create();

View File

@ -1,7 +1,11 @@
package org.keycloak.testframework.http;
import java.util.List;
import org.keycloak.http.simple.SimpleHttp;
import org.keycloak.testframework.annotations.InjectSimpleHttp;
import org.keycloak.testframework.injection.DependenciesBuilder;
import org.keycloak.testframework.injection.Dependency;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.RequestedInstance;
import org.keycloak.testframework.injection.Supplier;
@ -10,6 +14,11 @@ import org.apache.http.client.HttpClient;
public class SimpleHttpSupplier implements Supplier<SimpleHttp, InjectSimpleHttp> {
@Override
public List<Dependency> getDependencies(RequestedInstance<SimpleHttp, InjectSimpleHttp> instanceContext) {
return DependenciesBuilder.create(HttpClient.class).build();
}
@Override
public SimpleHttp getValue(InstanceContext<SimpleHttp, InjectSimpleHttp> instanceContext) {
HttpClient httpClient = instanceContext.getDependency(HttpClient.class);

View File

@ -34,7 +34,7 @@ public class InfinispanExternalServerSupplier implements Supplier<InfinispanServ
@Override
public boolean compatible(InstanceContext<InfinispanServer, InjectInfinispanServer> a, RequestedInstance<InfinispanServer, InjectInfinispanServer> b) {
return a.getSupplier().getRef(a.getAnnotation()).equals(b.getSupplier().getRef(a.getAnnotation()));
return true;
}
@Override

View File

@ -22,9 +22,9 @@ public abstract class AbstractInterceptorHelper<I, V> {
value = intercept(value, interception.supplier, interception.existingInstance);
registry.getLogger().logIntercepted(value, interception.supplier);
if (interception.existingInstance != null) {
interception.existingInstance.registerDependency(instanceContext);
interception.existingInstance.registerDependent(instanceContext);
} else {
interception.requestedInstance.registerDependency(instanceContext);
interception.requestedInstance.registerDependent(instanceContext);
}
}
return value;

View File

@ -9,13 +9,17 @@ public class DefaultAnnotationProxy implements InvocationHandler {
private final Class<?> annotationClass;
private final String ref;
public <S> DefaultAnnotationProxy(Class<?> annotationClass, String ref) {
this.annotationClass = annotationClass;
this.ref = ref;
public static <S> S proxy(Class<S> annotationClass, String ref) {
// Annotations can't have a null default value, hence we use an empty string instead
if (ref == null) {
ref = "";
}
return (S) Proxy.newProxyInstance(DefaultAnnotationProxy.class.getClassLoader(), new Class<?>[]{annotationClass}, new DefaultAnnotationProxy(annotationClass, ref));
}
public static <S> S proxy(Class<S> annotationClass, String ref) {
return (S) Proxy.newProxyInstance(DefaultAnnotationProxy.class.getClassLoader(), new Class<?>[]{annotationClass}, new DefaultAnnotationProxy(annotationClass, ref));
private <S> DefaultAnnotationProxy(Class<?> annotationClass, String ref) {
this.annotationClass = annotationClass;
this.ref = ref;
}
@Override
@ -23,7 +27,7 @@ public class DefaultAnnotationProxy implements InvocationHandler {
if (method.getName().equals("annotationType")) {
return annotationClass;
} else if (method.getName().equals("ref")) {
return ref;
return ref != null ? ref : "";
} else {
return annotationClass.getMethod(method.getName()).getDefaultValue();
}

View File

@ -0,0 +1,36 @@
package org.keycloak.testframework.injection;
import java.util.LinkedList;
import java.util.List;
public class DependenciesBuilder {
public static DependenciesBuilder create(Class<?> valueType) {
return new DependenciesBuilder().add(valueType);
}
public static DependenciesBuilder create(Class<?> valueType, String ref) {
return new DependenciesBuilder().add(valueType, ref);
}
private final List<Dependency> dependencies;
public DependenciesBuilder add(Class<?> valueType) {
dependencies.add(new Dependency(valueType, null));
return this;
}
public DependenciesBuilder add(Class<?> valueType, String ref) {
dependencies.add(new Dependency(valueType, ref));
return this;
}
public DependenciesBuilder() {
this.dependencies = new LinkedList<>();
}
public List<Dependency> build() {
return dependencies;
}
}

View File

@ -0,0 +1,13 @@
package org.keycloak.testframework.injection;
public record Dependency(Class<?> valueType, String ref) {
public Dependency {
ref = StringUtil.convertEmptyToNull(ref);
}
@Override
public String toString() {
return valueType.getSimpleName() + ":" + ref;
}
}

View File

@ -0,0 +1,74 @@
package org.keycloak.testframework.injection;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.keycloak.testframework.injection.predicates.RequestedInstancePredicates;
import org.jboss.logging.Logger;
public class DependencyGraphResolver {
private static final Logger log = Logger.getLogger(DependencyGraphResolver.class);
private final Registry registry;
private final List<RequestedInstance<?, ?>> missingInstances;
private Set<Dependency> visited = new HashSet<>();
private Set<Dependency> visiting = new HashSet<>();
public DependencyGraphResolver(Registry registry) {
this.registry = registry;
this.missingInstances = new LinkedList<>();
for (RequestedInstance requestedInstance : registry.getRequestedInstances()) {
List<Dependency> dependencies = requestedInstance.getSupplier().getDependencies(requestedInstance);
requestedInstance.setDeclaredDependencies(dependencies);
for (Dependency dependency : dependencies) {
scan(dependency);
}
}
}
public List<RequestedInstance<?, ?>> getMissingInstances() {
return missingInstances;
}
private void scan(Dependency dependency) {
if (visited.contains(dependency)) {
log.tracev("Skipping {0} already scanned", dependency);
} else {
log.tracev("Scanning dependency {0}", dependency);
}
if (visiting.contains(dependency)) {
throw new RuntimeException("Dependency cycle detected in " + visiting.stream().map(Dependency::toString).collect(Collectors.joining(", ")));
}
visiting.add(dependency);
RequestedInstance matchingInstance = registry.getRequestedInstances().stream().filter(RequestedInstancePredicates.matches(dependency.valueType(), dependency.ref())).findFirst().orElse(null);
if (matchingInstance == null) {
matchingInstance = missingInstances.stream().filter(RequestedInstancePredicates.matches(dependency.valueType(), dependency.ref())).findFirst().orElse(null);
}
if (matchingInstance == null) {
Supplier<?, ?> supplier = registry.getExtensions().findSupplierByType(dependency.valueType());
Annotation defaultAnnotation = DefaultAnnotationProxy.proxy(supplier.getAnnotationClass(), dependency.ref());
matchingInstance = registry.createRequestedInstance(new Annotation[]{ defaultAnnotation }, dependency.valueType());
missingInstances.add(matchingInstance);
}
List<Dependency> dependencies = matchingInstance.getSupplier().getDependencies(matchingInstance);
matchingInstance.setDeclaredDependencies(dependencies);
dependencies.forEach(this::scan);
visiting.remove(dependency);
visited.add(dependency);
}
}

View File

@ -3,6 +3,7 @@ package org.keycloak.testframework.injection;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -12,14 +13,15 @@ public class InstanceContext<T, A extends Annotation> {
private final Registry registry;
private final Supplier<T, A> supplier;
private final A annotation;
private final Set<InstanceContext<?, ?>> dependencies = new HashSet<>();
private final Set<InstanceContext<?, ?>> dependents = new HashSet<>();
private T value;
private Class<? extends T> requestedValueType;
private LifeCycle lifeCycle;
private final String ref;
private final Map<String, Object> notes = new HashMap<>();
private final List<Dependency> declaredDependencies;
public InstanceContext(int instanceId, Registry registry, Supplier<T, A> supplier, A annotation, Class<? extends T> requestedValueType) {
public InstanceContext(int instanceId, Registry registry, Supplier<T, A> supplier, A annotation, Class<? extends T> requestedValueType, List<Dependency> declaredDependencies) {
this.instanceId = instanceId != -1 ? instanceId : hashCode();
this.registry = registry;
this.supplier = supplier;
@ -27,6 +29,7 @@ public class InstanceContext<T, A extends Annotation> {
this.requestedValueType = requestedValueType;
this.lifeCycle = supplier.getLifeCycle(annotation);
this.ref = StringUtil.convertEmptyToNull(supplier.getRef(annotation));
this.declaredDependencies = declaredDependencies;
}
public int getInstanceId() {
@ -73,12 +76,16 @@ public class InstanceContext<T, A extends Annotation> {
return annotation;
}
public Set<InstanceContext<?, ?>> getDependencies() {
return dependencies;
public Set<InstanceContext<?, ?>> getDependents() {
return dependents;
}
public void registerDependency(InstanceContext<?, ?> instanceContext) {
dependencies.add(instanceContext);
public List<Dependency> getDeclaredDependencies() {
return declaredDependencies;
}
public void registerDependent(InstanceContext<?, ?> instanceContext) {
dependents.add(instanceContext);
}
public void addNote(String key, Object value) {

View File

@ -11,6 +11,10 @@ import java.util.Objects;
import java.util.Set;
import org.keycloak.testframework.TestFrameworkExecutor;
import org.keycloak.testframework.injection.predicates.DependencyPredicates;
import org.keycloak.testframework.injection.predicates.InstanceContextPredicates;
import org.keycloak.testframework.injection.predicates.RequestedInstancePredicates;
import org.keycloak.testframework.injection.predicates.TestFrameworkExecutorPredicates;
import org.keycloak.testframework.server.KeycloakServer;
import org.junit.jupiter.api.extension.ExtensionContext;
@ -37,6 +41,10 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
return logger;
}
Extensions getExtensions() {
return extensions;
}
public ExtensionContext getCurrentContext() {
return currentContext;
}
@ -47,6 +55,12 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
public <T> T getDependency(Class<T> typeClass, String ref, InstanceContext dependent) {
ref = StringUtil.convertEmptyToNull(ref);
List<Dependency> declaredDependencies = dependent.getDeclaredDependencies();
if (declaredDependencies.stream().noneMatch(DependencyPredicates.matches(typeClass, ref))) {
throw new RuntimeException("Tried to retrieve non-declared dependency " + typeClass.getSimpleName() + ":" + ref);
}
T dependency;
dependency = getDeployedDependency(typeClass, ref, dependent);
if (dependency != null) {
@ -55,11 +69,6 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
dependency = getRequestedDependency(typeClass, ref, dependent);
if (dependency != null) {
return dependency;
} else {
dependency = getUnConfiguredDependency(typeClass, ref, dependent);
if (dependency != null) {
return dependency;
}
}
}
@ -77,7 +86,7 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
private <T> T getDeployedDependency(Class<T> typeClass, String ref, InstanceContext dependent) {
InstanceContext dependency = getDeployedInstance(typeClass, ref);
if (dependency != null) {
dependency.registerDependency(dependent);
dependency.registerDependent(dependent);
logger.logDependencyInjection(dependent, dependency, RegistryLogger.InjectionType.EXISTING);
@ -89,9 +98,9 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
private <T> T getRequestedDependency(Class<T> typeClass, String ref, InstanceContext dependent) {
RequestedInstance requestedDependency = getRequestedInstance(typeClass, ref);
if (requestedDependency != null) {
InstanceContext dependency = new InstanceContext<Object, Annotation>(requestedDependency.getInstanceId(), this, requestedDependency.getSupplier(), requestedDependency.getAnnotation(), requestedDependency.getValueType());
InstanceContext dependency = new InstanceContext<Object, Annotation>(requestedDependency.getInstanceId(), this, requestedDependency.getSupplier(), requestedDependency.getAnnotation(), requestedDependency.getValueType(), requestedDependency.getDeclaredDependencies());
dependency.setValue(requestedDependency.getSupplier().getValue(dependency));
dependency.registerDependency(dependent);
dependency.registerDependent(dependent);
deployedInstances.add(dependency);
requestedInstances.remove(requestedDependency);
@ -103,22 +112,6 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
return null;
}
private <T> T getUnConfiguredDependency(Class<T> typeClass, String ref, InstanceContext dependent) {
InstanceContext dependency;
Supplier<?, ?> supplier = extensions.findSupplierByType(typeClass);
Annotation defaultAnnotation = DefaultAnnotationProxy.proxy(supplier.getAnnotationClass(), ref);
dependency = new InstanceContext(-1, this, supplier, defaultAnnotation, typeClass);
dependency.registerDependency(dependent);
dependency.setValue(supplier.getValue(dependency));
deployedInstances.add(dependency);
logger.logDependencyInjection(dependent, dependency, RegistryLogger.InjectionType.UN_CONFIGURED);
return (T) dependency.getValue();
}
public void beforeEach(Object testInstance, Method testMethod) {
findRequestedInstances(testInstance, testMethod);
destroyIncompatibleInstances();
@ -178,16 +171,9 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
}
}
List<RequiredDependencies.RequiredDependency> dependencies = requestedInstances.stream().flatMap(r -> r.getSupplier().getDependencies().getList().stream()).toList();
for (RequiredDependencies.RequiredDependency dependency : dependencies) {
boolean dependencyRequested = requestedInstances.stream().anyMatch(r -> r.getValueType().equals(dependency.valueType()) && Objects.equals(r.getRef(), dependency.ref()));
if (!dependencyRequested) {
Supplier<?, ?> supplier = extensions.findSupplierByType(dependency.valueType());
Annotation defaultAnnotation = DefaultAnnotationProxy.proxy(supplier.getAnnotationClass(), dependency.ref());
RequestedInstance<?, ?> requestDependency = createRequestedInstance(new Annotation[]{ defaultAnnotation }, dependency.valueType());
requestedInstances.add(requestDependency);
}
}
DependencyGraphResolver dependencyGraphResolver = new DependencyGraphResolver(this);
List<RequestedInstance<?, ?>> missingInstances = dependencyGraphResolver.getMissingInstances();
requestedInstances.addAll(missingInstances);
logger.logRequestedInstances(requestedInstances);
}
@ -225,13 +211,13 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
RequestedInstance requestedInstance = requestedInstances.remove(0);
if (getDeployedInstance(requestedInstance) == null) {
InstanceContext instance = new InstanceContext(requestedInstance.getInstanceId(), this, requestedInstance.getSupplier(), requestedInstance.getAnnotation(), requestedInstance.getValueType());
InstanceContext instance = new InstanceContext(requestedInstance.getInstanceId(), this, requestedInstance.getSupplier(), requestedInstance.getAnnotation(), requestedInstance.getValueType(), requestedInstance.getDeclaredDependencies());
instance.setValue(requestedInstance.getSupplier().getValue(instance));
deployedInstances.add(instance);
if (!requestedInstance.getDependencies().isEmpty()) {
Set<InstanceContext<?,?>> dependencies = requestedInstance.getDependencies();
dependencies.forEach(instance::registerDependency);
if (!requestedInstance.getDependents().isEmpty()) {
Set<InstanceContext<?,?>> dependencies = requestedInstance.getDependents();
dependencies.forEach(instance::registerDependent);
}
logger.logCreatedInstance(requestedInstance, instance);
@ -251,16 +237,16 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
public void afterAll() {
logger.logAfterAll();
List<InstanceContext<?, ?>> destroy = deployedInstances.stream().filter(i -> i.getLifeCycle().equals(LifeCycle.CLASS)).toList();
List<InstanceContext<?, ?>> destroy = deployedInstances.stream().filter(InstanceContextPredicates.hasLifeCycle(LifeCycle.CLASS)).toList();
destroy.forEach(this::destroy);
}
public void afterEach() {
logger.logAfterEach();
List<InstanceContext<?, ?>> destroy = deployedInstances.stream().filter(i -> i.getLifeCycle().equals(LifeCycle.METHOD)).toList();
List<InstanceContext<?, ?>> destroy = deployedInstances.stream().filter(InstanceContextPredicates.hasLifeCycle(LifeCycle.METHOD)).toList();
destroy.forEach(this::destroy);
List<InstanceContext<?, ?>> cleanup = deployedInstances.stream().filter(i -> i.getValue() instanceof ManagedTestResource).toList();
List<InstanceContext<?, ?>> cleanup = deployedInstances.stream().filter(InstanceContextPredicates.isInstanceof(ManagedTestResource.class)).toList();
for (InstanceContext<?, ?> c : cleanup) {
ManagedTestResource managedTestResource = (ManagedTestResource) c.getValue();
if (managedTestResource.isDirty()) {
@ -283,7 +269,7 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
return extensions.getSuppliers();
}
private RequestedInstance<?, ?> createRequestedInstance(Annotation[] annotations, Class<?> valueType) {
RequestedInstance<?, ?> createRequestedInstance(Annotation[] annotations, Class<?> valueType) {
if (annotations != null) {
for (Annotation annotation : annotations) {
Supplier<?, ?> supplier = extensions.findSupplierByAnnotation(annotation);
@ -294,7 +280,7 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
} else {
Supplier<?, ?> supplier = extensions.findSupplierByType(valueType);
if (supplier != null) {
Annotation defaultAnnotation = DefaultAnnotationProxy.proxy(supplier.getAnnotationClass(), "");
Annotation defaultAnnotation = DefaultAnnotationProxy.proxy(supplier.getAnnotationClass(), null);
return new RequestedInstance(supplier, defaultAnnotation, valueType);
}
}
@ -318,7 +304,7 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
private void destroy(InstanceContext instanceContext) {
boolean removed = deployedInstances.remove(instanceContext);
if (removed) {
Set<InstanceContext> dependencies = instanceContext.getDependencies();
Set<InstanceContext> dependencies = instanceContext.getDependents();
dependencies.forEach(this::destroy);
instanceContext.getSupplier().close(instanceContext);
@ -347,13 +333,13 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
private InstanceContext getDeployedInstance(Class typeClass, String ref) {
return deployedInstances.stream()
.filter(i -> i.getSupplier().getValueType().equals(typeClass) && Objects.equals(i.getRef(), ref))
.filter(InstanceContextPredicates.matches(typeClass, ref))
.findFirst().orElse(null);
}
private RequestedInstance getRequestedInstance(Class typeClass, String ref) {
return requestedInstances.stream()
.filter(i -> i.getSupplier().getValueType().equals(typeClass) && Objects.equals(i.getRef(), ref))
.filter(RequestedInstancePredicates.matches(typeClass, ref))
.findFirst().orElse(null);
}
@ -364,7 +350,7 @@ public class Registry implements ExtensionContext.Store.CloseableResource {
}
private TestFrameworkExecutor getExecutor(Method testMethod) {
return extensions.getTestFrameworkExecutors().stream().filter(e -> e.shouldExecute(testMethod)).findFirst().orElse(null);
return extensions.getTestFrameworkExecutors().stream().filter(TestFrameworkExecutorPredicates.shouldExecute(testMethod)).findFirst().orElse(null);
}
private static class RequestedInstanceComparator implements Comparator<RequestedInstance> {

View File

@ -2,6 +2,7 @@ package org.keycloak.testframework.injection;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class RequestedInstance<T, A extends Annotation> {
@ -9,10 +10,11 @@ public class RequestedInstance<T, A extends Annotation> {
private final int instanceId;
private final Supplier<T, A> supplier;
private final A annotation;
private final Set<InstanceContext<?, ?>> dependencies = new HashSet<>();
private final Set<InstanceContext<?, ?>> dependents = new HashSet<>();
private final Class<? extends T> valueType;
private final LifeCycle lifeCycle;
private final String ref;
private List<Dependency> declaredDependencies;
public RequestedInstance(Supplier<T, A> supplier, A annotation, Class<? extends T> valueType) {
this.instanceId = this.hashCode();
@ -47,11 +49,19 @@ public class RequestedInstance<T, A extends Annotation> {
return ref;
}
public void registerDependency(InstanceContext<?, ?> instanceContext) {
dependencies.add(instanceContext);
public void registerDependent(InstanceContext<?, ?> instanceContext) {
dependents.add(instanceContext);
}
public Set<InstanceContext<?, ?>> getDependencies() {
return dependencies;
public Set<InstanceContext<?, ?>> getDependents() {
return dependents;
}
public List<Dependency> getDeclaredDependencies() {
return declaredDependencies;
}
public void setDeclaredDependencies(List<Dependency> declaredDependencies) {
this.declaredDependencies = declaredDependencies;
}
}

View File

@ -1,6 +1,7 @@
package org.keycloak.testframework.injection;
import java.lang.annotation.Annotation;
import java.util.List;
public interface Supplier<T, S extends Annotation> {
@ -44,8 +45,8 @@ public interface Supplier<T, S extends Annotation> {
return SupplierOrder.DEFAULT;
}
default RequiredDependencies getDependencies() {
return RequiredDependencies.none();
default List<Dependency> getDependencies(RequestedInstance<T, S> instanceContext) {
return List.of();
}
}

View File

@ -0,0 +1,14 @@
package org.keycloak.testframework.injection.predicates;
import java.util.Objects;
import java.util.function.Predicate;
import org.keycloak.testframework.injection.Dependency;
public interface DependencyPredicates {
static Predicate<Dependency> matches(Class<?> typeClass, String ref) {
return d -> d.valueType().equals(typeClass) && Objects.equals(d.ref(), ref);
}
}

View File

@ -0,0 +1,23 @@
package org.keycloak.testframework.injection.predicates;
import java.util.Objects;
import java.util.function.Predicate;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.LifeCycle;
public interface InstanceContextPredicates {
static Predicate<InstanceContext<?, ?>> hasLifeCycle(LifeCycle lifeCycle) {
return i -> i.getLifeCycle().equals(lifeCycle);
}
static Predicate<InstanceContext<?, ?>> isInstanceof(Class<?> valueTypeClass) {
return i -> valueTypeClass.isInstance(i.getValue());
}
static Predicate<InstanceContext<?, ?>> matches(Class<?> typeClass, String ref) {
return i -> i.getSupplier().getValueType().equals(typeClass) && Objects.equals(i.getRef(), ref);
}
}

View File

@ -0,0 +1,14 @@
package org.keycloak.testframework.injection.predicates;
import java.util.Objects;
import java.util.function.Predicate;
import org.keycloak.testframework.injection.RequestedInstance;
public interface RequestedInstancePredicates {
static Predicate<RequestedInstance<?, ?>> matches(Class<?> typeClass, String ref) {
return r -> r.getSupplier().getValueType().equals(typeClass) && Objects.equals(r.getRef(), ref);
}
}

View File

@ -0,0 +1,14 @@
package org.keycloak.testframework.injection.predicates;
import java.lang.reflect.Method;
import java.util.function.Predicate;
import org.keycloak.testframework.TestFrameworkExecutor;
public interface TestFrameworkExecutorPredicates {
static Predicate<TestFrameworkExecutor> shouldExecute(Method method) {
return r -> r.shouldExecute(method);
}
}

View File

@ -9,6 +9,8 @@ import jakarta.ws.rs.core.Response.Status;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.testframework.annotations.InjectClient;
import org.keycloak.testframework.injection.DependenciesBuilder;
import org.keycloak.testframework.injection.Dependency;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.RequestedInstance;
import org.keycloak.testframework.injection.Supplier;
@ -17,6 +19,11 @@ import org.keycloak.testframework.util.ApiUtil;
public class ClientSupplier implements Supplier<ManagedClient, InjectClient> {
@Override
public List<Dependency> getDependencies(RequestedInstance<ManagedClient, InjectClient> instanceContext) {
return DependenciesBuilder.create(ManagedRealm.class, instanceContext.getAnnotation().realmRef()).build();
}
@Override
public ManagedClient getValue(InstanceContext<ManagedClient, InjectClient> instanceContext) {
ManagedRealm realm = instanceContext.getDependency(ManagedRealm.class, instanceContext.getAnnotation().realmRef());

View File

@ -1,10 +1,14 @@
package org.keycloak.testframework.realm;
import java.util.List;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testframework.annotations.InjectRealm;
import org.keycloak.testframework.injection.AbstractInterceptorHelper;
import org.keycloak.testframework.injection.DependenciesBuilder;
import org.keycloak.testframework.injection.Dependency;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.Registry;
import org.keycloak.testframework.injection.RequestedInstance;
@ -15,6 +19,12 @@ import org.keycloak.testframework.server.KeycloakServer;
public class RealmSupplier implements Supplier<ManagedRealm, InjectRealm> {
@Override
public List<Dependency> getDependencies(RequestedInstance<ManagedRealm, InjectRealm> instanceContext) {
return DependenciesBuilder.create(KeycloakServer.class)
.add(Keycloak.class, "bootstrap-client").build();
}
@Override
public ManagedRealm getValue(InstanceContext<ManagedRealm, InjectRealm> instanceContext) {
KeycloakServer server = instanceContext.getDependency(KeycloakServer.class);

View File

@ -1,5 +1,7 @@
package org.keycloak.testframework.realm;
import java.util.List;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
@ -7,6 +9,8 @@ import jakarta.ws.rs.core.Response.Status;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testframework.annotations.InjectUser;
import org.keycloak.testframework.injection.DependenciesBuilder;
import org.keycloak.testframework.injection.Dependency;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.RequestedInstance;
import org.keycloak.testframework.injection.Supplier;
@ -17,6 +21,11 @@ public class UserSupplier implements Supplier<ManagedUser, InjectUser> {
private static final String USER_UUID_KEY = "userUuid";
@Override
public List<Dependency> getDependencies(RequestedInstance<ManagedUser, InjectUser> instanceContext) {
return DependenciesBuilder.create(ManagedRealm.class, instanceContext.getAnnotation().realmRef()).build();
}
@Override
public ManagedUser getValue(InstanceContext<ManagedUser, InjectUser> instanceContext) {
ManagedRealm realm = instanceContext.getDependency(ManagedRealm.class, instanceContext.getAnnotation().realmRef());

View File

@ -1,16 +1,19 @@
package org.keycloak.testframework.server;
import java.util.List;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.config.Config;
import org.keycloak.testframework.database.TestDatabase;
import org.keycloak.testframework.https.ManagedCertificates;
import org.keycloak.testframework.infinispan.InfinispanServer;
import org.keycloak.testframework.injection.AbstractInterceptorHelper;
import org.keycloak.testframework.injection.DependenciesBuilder;
import org.keycloak.testframework.injection.Dependency;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.LifeCycle;
import org.keycloak.testframework.injection.Registry;
import org.keycloak.testframework.injection.RequestedInstance;
import org.keycloak.testframework.injection.RequiredDependencies;
import org.keycloak.testframework.injection.Supplier;
import org.keycloak.testframework.injection.SupplierHelpers;
import org.keycloak.testframework.injection.SupplierOrder;
@ -20,23 +23,25 @@ import org.jboss.logging.Logger;
public abstract class AbstractKeycloakServerSupplier implements Supplier<KeycloakServer, KeycloakIntegrationTest> {
@Override
public KeycloakServer getValue(InstanceContext<KeycloakServer, KeycloakIntegrationTest> instanceContext) {
KeycloakIntegrationTest annotation = instanceContext.getAnnotation();
KeycloakServerConfig serverConfig = SupplierHelpers.getInstance(annotation.config());
public List<Dependency> getDependencies(RequestedInstance<KeycloakServer, KeycloakIntegrationTest> instanceContext) {
KeycloakServerConfigBuilder command = getKeycloakServerConfigBuilder(instanceContext.getAnnotation());
KeycloakServerConfigBuilder command = KeycloakServerConfigBuilder.startDev()
.bootstrapAdminClient(Config.getAdminClientId(), Config.getAdminClientSecret())
.bootstrapAdminUser(Config.getAdminUsername(), Config.getAdminPassword());
command.log().handlers(KeycloakServerConfigBuilder.LogHandlers.CONSOLE);
String supplierConfig = Config.getSupplierConfig(KeycloakServer.class);
if (supplierConfig != null) {
KeycloakServerConfig serverConfigOverride = SupplierHelpers.getInstance(supplierConfig);
serverConfigOverride.configure(command);
DependenciesBuilder builder = DependenciesBuilder.create(ManagedCertificates.class);
if (requiresDatabase()) {
builder.add(TestDatabase.class);
}
command = serverConfig.configure(command);
if (command.isExternalInfinispanEnabled()) {
builder.add(InfinispanServer.class);
}
return builder.build();
}
@Override
public KeycloakServer getValue(InstanceContext<KeycloakServer, KeycloakIntegrationTest> instanceContext) {
KeycloakServerConfigBuilder command = getKeycloakServerConfigBuilder(instanceContext.getAnnotation());
// Database startup and Keycloak connection setup
if (requiresDatabase()) {
@ -81,6 +86,24 @@ public abstract class AbstractKeycloakServerSupplier implements Supplier<Keycloa
return server;
}
private static KeycloakServerConfigBuilder getKeycloakServerConfigBuilder(KeycloakIntegrationTest annotation) {
KeycloakServerConfig serverConfig = SupplierHelpers.getInstance(annotation.config());
KeycloakServerConfigBuilder command = KeycloakServerConfigBuilder.startDev()
.bootstrapAdminClient(Config.getAdminClientId(), Config.getAdminClientSecret())
.bootstrapAdminUser(Config.getAdminUsername(), Config.getAdminPassword());
command.log().handlers(KeycloakServerConfigBuilder.LogHandlers.CONSOLE);
String supplierConfig = Config.getSupplierConfig(KeycloakServer.class);
if (supplierConfig != null) {
KeycloakServerConfig serverConfigOverride = SupplierHelpers.getInstance(supplierConfig);
serverConfigOverride.configure(command);
}
command = serverConfig.configure(command);
return command;
}
@Override
public LifeCycle getDefaultLifecycle() {
return LifeCycle.GLOBAL;
@ -96,11 +119,6 @@ public abstract class AbstractKeycloakServerSupplier implements Supplier<Keycloa
instanceContext.getValue().stop();
}
@Override
public RequiredDependencies getDependencies() {
return RequiredDependencies.create(ManagedCertificates.class);
}
public abstract KeycloakServer getServer();
public abstract boolean requiresDatabase();

View File

@ -1,12 +1,21 @@
package org.keycloak.testframework.server;
import java.util.List;
import org.keycloak.testframework.annotations.InjectKeycloakUrls;
import org.keycloak.testframework.injection.DependenciesBuilder;
import org.keycloak.testframework.injection.Dependency;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.RequestedInstance;
import org.keycloak.testframework.injection.Supplier;
public class KeycloakUrlsSupplier implements Supplier<KeycloakUrls, InjectKeycloakUrls> {
@Override
public List<Dependency> getDependencies(RequestedInstance<KeycloakUrls, InjectKeycloakUrls> instanceContext) {
return DependenciesBuilder.create(KeycloakServer.class).build();
}
@Override
public KeycloakUrls getValue(InstanceContext<KeycloakUrls, InjectKeycloakUrls> instanceContext) {
KeycloakServer server = instanceContext.getDependency(KeycloakServer.class);

View File

@ -21,6 +21,15 @@ public class DefaultAnnotationProxyTest {
Assertions.assertEquals("else", proxy.something());
}
@Test
public void testEmptyRefConvertedToEmptyString() {
MockAnnotation proxy = DefaultAnnotationProxy.proxy(MockAnnotation.class, null);
Assertions.assertEquals(LifeCycle.CLASS, proxy.lifecycle());
Assertions.assertEquals(LinkedList.class, proxy.config());
Assertions.assertEquals("", proxy.ref());
Assertions.assertEquals("else", proxy.something());
}
@Test
public void testCustomRef() {
MockAnnotation proxy = DefaultAnnotationProxy.proxy(MockAnnotation.class, "myref");

View File

@ -1,5 +1,9 @@
package org.keycloak.testframework.injection.mocks;
import java.util.List;
import org.keycloak.testframework.injection.DependenciesBuilder;
import org.keycloak.testframework.injection.Dependency;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.LifeCycle;
import org.keycloak.testframework.injection.RequestedInstance;
@ -13,6 +17,11 @@ public class MockChildSupplier implements Supplier<MockChildValue, MockChildAnno
DEFAULT_LIFECYCLE = LifeCycle.CLASS;
}
@Override
public List<Dependency> getDependencies(RequestedInstance<MockChildValue, MockChildAnnotation> instanceContext) {
return DependenciesBuilder.create(MockParentValue.class, instanceContext.getAnnotation().parentRef()).build();
}
@Override
public MockChildValue getValue(InstanceContext<MockChildValue, MockChildAnnotation> instanceContext) {
MockParentValue mockParentValue = instanceContext.getDependency(MockParentValue.class, instanceContext.getAnnotation().parentRef());

View File

@ -1,6 +1,10 @@
package org.keycloak.testframework.oauth;
import java.util.List;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.testframework.injection.DependenciesBuilder;
import org.keycloak.testframework.injection.Dependency;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.RequestedInstance;
import org.keycloak.testframework.injection.Supplier;
@ -18,6 +22,15 @@ import org.apache.http.impl.client.CloseableHttpClient;
public class OAuthClientSupplier implements Supplier<OAuthClient, InjectOAuthClient> {
@Override
public List<Dependency> getDependencies(RequestedInstance<OAuthClient, InjectOAuthClient> instanceContext) {
return DependenciesBuilder.create(KeycloakUrls.class)
.add(HttpClient.class)
.add(ManagedWebDriver.class)
.add(TestApp.class)
.add(ManagedRealm.class, instanceContext.getAnnotation().realmRef()).build();
}
@Override
public OAuthClient getValue(InstanceContext<OAuthClient, InjectOAuthClient> instanceContext) {
InjectOAuthClient annotation = instanceContext.getAnnotation();

View File

@ -1,5 +1,9 @@
package org.keycloak.testframework.oauth;
import java.util.List;
import org.keycloak.testframework.injection.DependenciesBuilder;
import org.keycloak.testframework.injection.Dependency;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.RequestedInstance;
import org.keycloak.testframework.injection.Supplier;
@ -10,6 +14,11 @@ import com.sun.net.httpserver.HttpServer;
public class OAuthIdentityProviderSupplier implements Supplier<OAuthIdentityProvider, InjectOAuthIdentityProvider> {
@Override
public List<Dependency> getDependencies(RequestedInstance<OAuthIdentityProvider, InjectOAuthIdentityProvider> instanceContext) {
return DependenciesBuilder.create(HttpServer.class).build();
}
@Override
public OAuthIdentityProvider getValue(InstanceContext<OAuthIdentityProvider, InjectOAuthIdentityProvider> instanceContext) {
HttpServer httpServer = instanceContext.getDependency(HttpServer.class);

View File

@ -1,5 +1,9 @@
package org.keycloak.testframework.oauth;
import java.util.List;
import org.keycloak.testframework.injection.DependenciesBuilder;
import org.keycloak.testframework.injection.Dependency;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.RequestedInstance;
import org.keycloak.testframework.injection.Supplier;
@ -9,6 +13,11 @@ import com.sun.net.httpserver.HttpServer;
public class TestAppSupplier implements Supplier<TestApp, InjectTestApp> {
@Override
public List<Dependency> getDependencies(RequestedInstance<TestApp, InjectTestApp> instanceContext) {
return DependenciesBuilder.create(HttpServer.class).build();
}
@Override
public TestApp getValue(InstanceContext<TestApp, InjectTestApp> instanceContext) {
HttpServer httpServer = instanceContext.getDependency(HttpServer.class);

View File

@ -2,7 +2,10 @@ package org.keycloak.testframework.remote.runonserver;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import org.keycloak.testframework.injection.DependenciesBuilder;
import org.keycloak.testframework.injection.Dependency;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.LifeCycle;
import org.keycloak.testframework.injection.RequestedInstance;
@ -16,6 +19,15 @@ import org.apache.http.client.HttpClient;
public class RunOnServerSupplier implements Supplier<RunOnServerClient, InjectRunOnServer> {
@Override
public List<Dependency> getDependencies(RequestedInstance<RunOnServerClient, InjectRunOnServer> instanceContext) {
return DependenciesBuilder.create(HttpClient.class)
.add(KeycloakServer.class)
.add(ManagedRealm.class, instanceContext.getAnnotation().realmRef())
.add(RemoteProviders.class)
.add(TestClassServer.class).build();
}
@Override
public RunOnServerClient getValue(InstanceContext<RunOnServerClient, InjectRunOnServer> instanceContext) {
KeycloakServer server = instanceContext.getDependency(KeycloakServer.class);

View File

@ -1,5 +1,9 @@
package org.keycloak.testframework.remote.runonserver;
import java.util.List;
import org.keycloak.testframework.injection.DependenciesBuilder;
import org.keycloak.testframework.injection.Dependency;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.RequestedInstance;
import org.keycloak.testframework.injection.Supplier;
@ -8,6 +12,11 @@ import com.sun.net.httpserver.HttpServer;
public class TestClassServerSupplier implements Supplier<TestClassServer, InjectTestClassServer> {
@Override
public List<Dependency> getDependencies(RequestedInstance<TestClassServer, InjectTestClassServer> instanceContext) {
return DependenciesBuilder.create(HttpServer.class).build();
}
@Override
public TestClassServer getValue(InstanceContext<TestClassServer, InjectTestClassServer> instanceContext) {
HttpServer httpServer = instanceContext.getDependency(HttpServer.class);

View File

@ -1,5 +1,9 @@
package org.keycloak.testframework.remote.timeoffset;
import java.util.List;
import org.keycloak.testframework.injection.DependenciesBuilder;
import org.keycloak.testframework.injection.Dependency;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.LifeCycle;
import org.keycloak.testframework.injection.RequestedInstance;
@ -12,6 +16,12 @@ import org.apache.http.client.HttpClient;
public class TimeOffsetSupplier implements Supplier<TimeOffSet, InjectTimeOffSet> {
@Override
public List<Dependency> getDependencies(RequestedInstance<TimeOffSet, InjectTimeOffSet> instanceContext) {
return DependenciesBuilder.create(HttpClient.class)
.add(RemoteProviders.class).add(KeycloakUrls.class).build();
}
@Override
public TimeOffSet getValue(InstanceContext<TimeOffSet, InjectTimeOffSet> instanceContext) {
var httpClient = instanceContext.getDependency(HttpClient.class);

View File

@ -1,5 +1,9 @@
package org.keycloak.testframework.ui.page;
import java.util.List;
import org.keycloak.testframework.injection.DependenciesBuilder;
import org.keycloak.testframework.injection.Dependency;
import org.keycloak.testframework.injection.InstanceContext;
import org.keycloak.testframework.injection.RequestedInstance;
import org.keycloak.testframework.injection.Supplier;
@ -8,6 +12,11 @@ import org.keycloak.testframework.ui.webdriver.ManagedWebDriver;
public class PageSupplier implements Supplier<AbstractPage, InjectPage> {
@Override
public List<Dependency> getDependencies(RequestedInstance<AbstractPage, InjectPage> instanceContext) {
return DependenciesBuilder.create(ManagedWebDriver.class).build();
}
@Override
public AbstractPage getValue(InstanceContext<AbstractPage, InjectPage> instanceContext) {
ManagedWebDriver webDriver = instanceContext.getDependency(ManagedWebDriver.class);