From aa6890f539a78dbb9a6cd5134f826d29e5ed95ab Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Wed, 17 Dec 2025 13:15:42 +0100 Subject: [PATCH] Support running test methods on the server side (#44937) Closes #44936 Signed-off-by: stianst --- .../KeycloakIntegrationTestExtension.java | 68 ++++++++--------- .../testframework/TestFrameworkExecutor.java | 18 +++++ .../annotations/KeycloakIntegrationTest.java | 4 + .../testframework/injection/Extensions.java | 18 ++++- .../testframework/injection/Registry.java | 44 ++++++++++- .../org.junit.jupiter.api.extension.Extension | 1 - .../testframework/injection/RegistryTest.java | 76 +++++++++++-------- .../test/examples/RunTestOnServerTest.java | 29 +++++++ .../main/resources/junit-platform.properties | 1 - .../remote/RemoteTestFrameworkExtension.java | 38 +++++++++- .../remote/annotations/TestOnServer.java | 14 ++++ .../remote/runonserver/RunTestOnServer.java | 31 ++++++++ 12 files changed, 271 insertions(+), 71 deletions(-) create mode 100644 test-framework/core/src/main/java/org/keycloak/testframework/TestFrameworkExecutor.java delete mode 100644 test-framework/core/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension create mode 100644 test-framework/examples/tests/src/test/java/org/keycloak/test/examples/RunTestOnServerTest.java create mode 100644 test-framework/remote/src/main/java/org/keycloak/testframework/remote/annotations/TestOnServer.java create mode 100644 test-framework/remote/src/main/java/org/keycloak/testframework/remote/runonserver/RunTestOnServer.java diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/KeycloakIntegrationTestExtension.java b/test-framework/core/src/main/java/org/keycloak/testframework/KeycloakIntegrationTestExtension.java index ebc6035ded7..1166371854e 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/KeycloakIntegrationTestExtension.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/KeycloakIntegrationTestExtension.java @@ -1,8 +1,8 @@ package org.keycloak.testframework; +import java.lang.reflect.Method; import java.util.Optional; -import org.keycloak.testframework.annotations.KeycloakIntegrationTest; import org.keycloak.testframework.injection.Registry; import org.junit.jupiter.api.extension.AfterAllCallback; @@ -10,82 +10,82 @@ import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.extension.ReflectiveInvocationContext; import org.junit.jupiter.api.extension.TestWatcher; -public class KeycloakIntegrationTestExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, AfterAllCallback, TestWatcher { +public class KeycloakIntegrationTestExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, AfterAllCallback, TestWatcher, InvocationInterceptor, ParameterResolver { private static final LogHandler logHandler = new LogHandler(); @Override public void beforeAll(ExtensionContext context) { - if (isExtensionEnabled(context)) { - logHandler.beforeAll(context); - } + logHandler.beforeAll(context); } @Override public void beforeEach(ExtensionContext context) { - if (isExtensionEnabled(context)) { - logHandler.beforeEachStarting(context); - getRegistry(context).beforeEach(context.getRequiredTestInstance()); - logHandler.beforeEachCompleted(context); - } + logHandler.beforeEachStarting(context); + getRegistry(context).beforeEach(context.getRequiredTestInstance(), context.getRequiredTestMethod()); + logHandler.beforeEachCompleted(context); } @Override public void afterEach(ExtensionContext context) { - if (isExtensionEnabled(context)) { - logHandler.afterEachStarting(context); - getRegistry(context).afterEach(); - logHandler.afterEachCompleted(context); - } + logHandler.afterEachStarting(context); + getRegistry(context).afterEach(); + logHandler.afterEachCompleted(context); } @Override public void afterAll(ExtensionContext context) { - if (isExtensionEnabled(context)) { - logHandler.afterAll(context); - getRegistry(context).afterAll(); - } + logHandler.afterAll(context); + getRegistry(context).afterAll(); } @Override public void testFailed(ExtensionContext context, Throwable cause) { - if (isExtensionEnabled(context)) { - logHandler.testFailed(context); - } + logHandler.testFailed(context); } @Override public void testDisabled(ExtensionContext context, Optional reason) { - if (isExtensionEnabled(context)) { - logHandler.testDisabled(context); - } + logHandler.testDisabled(context); } @Override public void testSuccessful(ExtensionContext context) { - if (isExtensionEnabled(context)) { - logHandler.testSuccessful(context); - } + logHandler.testSuccessful(context); } @Override public void testAborted(ExtensionContext context, Throwable cause) { - if (isExtensionEnabled(context)) { - logHandler.testAborted(context); - } + logHandler.testAborted(context); } - private boolean isExtensionEnabled(ExtensionContext context) { - return context.getRequiredTestClass().isAnnotationPresent(KeycloakIntegrationTest.class); + @Override + public void interceptTestMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + getRegistry(extensionContext).intercept(invocation, invocationContext); } - private Registry getRegistry(ExtensionContext context) { + public static Registry getRegistry(ExtensionContext context) { ExtensionContext.Store store = context.getRoot().getStore(ExtensionContext.Namespace.GLOBAL); Registry registry = (Registry) store.getOrComputeIfAbsent(Registry.class, r -> new Registry()); registry.setCurrentContext(context); return registry; } + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext context) throws ParameterResolutionException { + return getRegistry(context).supportsParameter(parameterContext, context); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext context) throws ParameterResolutionException { + // As this is only used by custom test executors for now they are responsible for injecting the parameter, hence returning null here + return null; + } } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/TestFrameworkExecutor.java b/test-framework/core/src/main/java/org/keycloak/testframework/TestFrameworkExecutor.java new file mode 100644 index 00000000000..3e3c88e79f6 --- /dev/null +++ b/test-framework/core/src/main/java/org/keycloak/testframework/TestFrameworkExecutor.java @@ -0,0 +1,18 @@ +package org.keycloak.testframework; + +import java.lang.reflect.Method; +import java.util.List; + +import org.keycloak.testframework.injection.Registry; + +public interface TestFrameworkExecutor { + + List> getMethodValueTypes(Method method); + + boolean supportsParameter(Method method, Class parameterType); + + boolean shouldExecute(Method testMethod); + + void execute(Registry registry, Class testClass, Method testMethod); + +} diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/KeycloakIntegrationTest.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/KeycloakIntegrationTest.java index b3b0c811e39..ea89ecc9ba1 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/KeycloakIntegrationTest.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/KeycloakIntegrationTest.java @@ -5,11 +5,15 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.keycloak.testframework.KeycloakIntegrationTestExtension; import org.keycloak.testframework.server.DefaultKeycloakServerConfig; import org.keycloak.testframework.server.KeycloakServerConfig; +import org.junit.jupiter.api.extension.ExtendWith; + @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) +@ExtendWith({KeycloakIntegrationTestExtension.class}) public @interface KeycloakIntegrationTest { Class config() default DefaultKeycloakServerConfig.class; diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/injection/Extensions.java b/test-framework/core/src/main/java/org/keycloak/testframework/injection/Extensions.java index ab0840e1841..21f442dcb96 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/injection/Extensions.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/injection/Extensions.java @@ -2,6 +2,7 @@ package org.keycloak.testframework.injection; import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedList; @@ -9,6 +10,7 @@ import java.util.List; import java.util.ServiceLoader; import java.util.Set; +import org.keycloak.testframework.TestFrameworkExecutor; import org.keycloak.testframework.TestFrameworkExtension; import org.keycloak.testframework.config.Config; @@ -22,6 +24,7 @@ public class Extensions { private final List> alwaysEnabledValueTypes; private static Extensions INSTANCE; + private final List extensions; public static Extensions getInstance() { if (INSTANCE == null) { @@ -35,7 +38,7 @@ public class Extensions { } private Extensions() { - List extensions = loadExtensions(); + extensions = loadExtensions(); valueTypeAlias = loadValueTypeAlias(extensions); Config.registerValueTypeAlias(valueTypeAlias); logger = new RegistryLogger(valueTypeAlias); @@ -55,6 +58,19 @@ public class Extensions { return alwaysEnabledValueTypes; } + public List getTestFrameworkExecutors() { + return extensions.stream() + .filter(e -> e instanceof TestFrameworkExecutor) + .map(e -> (TestFrameworkExecutor) e) + .toList(); + } + + public List> getMethodValueTypes(Method method) { + return getTestFrameworkExecutors() + .stream() + .flatMap(e -> e.getMethodValueTypes(method).stream()).toList(); + } + @SuppressWarnings("unchecked") public Supplier findSupplierByType(Class typeClass) { return (Supplier) suppliers.stream().filter(s -> s.getValueType().equals(typeClass)).findFirst().orElse(null); diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/injection/Registry.java b/test-framework/core/src/main/java/org/keycloak/testframework/injection/Registry.java index d8b2f9d8db5..f2cfbe48c10 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/injection/Registry.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/injection/Registry.java @@ -2,6 +2,7 @@ package org.keycloak.testframework.injection; import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.Comparator; import java.util.Iterator; import java.util.LinkedList; @@ -9,7 +10,12 @@ import java.util.List; import java.util.Objects; import java.util.Set; +import org.keycloak.testframework.TestFrameworkExecutor; + import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ReflectiveInvocationContext; @SuppressWarnings({"rawtypes", "unchecked"}) public class Registry implements ExtensionContext.Store.CloseableResource { @@ -112,8 +118,8 @@ public class Registry implements ExtensionContext.Store.CloseableResource { return (T) dependency.getValue(); } - public void beforeEach(Object testInstance) { - findRequestedInstances(testInstance); + public void beforeEach(Object testInstance, Method testMethod) { + findRequestedInstances(testInstance, testMethod); destroyIncompatibleInstances(); matchDeployedInstancesWithRequestedInstances(); deployRequestedInstances(); @@ -121,7 +127,27 @@ public class Registry implements ExtensionContext.Store.CloseableResource { invokeBeforeEachOnSuppliers(); } - private void findRequestedInstances(Object testInstance) { + public void intercept(InvocationInterceptor.Invocation invocation, ReflectiveInvocationContext invocationContext) throws Throwable { + Class testClass = invocationContext.getTargetClass(); + Method testMethod = invocationContext.getExecutable(); + + TestFrameworkExecutor testFrameworkExecutor = getExecutor(testMethod); + if (testFrameworkExecutor != null) { + testFrameworkExecutor.execute(this, testClass, testMethod); + invocation.skip(); + } else { + invocation.proceed(); + } + } + + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + Method testMethod = (Method) parameterContext.getParameter().getDeclaringExecutable(); + Class parameterType = parameterContext.getParameter().getType(); + TestFrameworkExecutor testFrameworkExecutor = getExecutor(testMethod); + return testFrameworkExecutor != null && testFrameworkExecutor.supportsParameter(testMethod, parameterType); + } + + private void findRequestedInstances(Object testInstance, Method testMethod) { List> alwaysEnabledValueTypes = extensions.getAlwaysEnabledValueTypes(); for (Class valueType : alwaysEnabledValueTypes) { RequestedInstance requestedInstance = createRequestedInstance(null, valueType); @@ -130,6 +156,14 @@ public class Registry implements ExtensionContext.Store.CloseableResource { } } + List> methodValueTypes = extensions.getMethodValueTypes(testMethod); + for (Class valueType : methodValueTypes) { + RequestedInstance requestedInstance = createRequestedInstance(null, valueType); + if (requestedInstance != null) { + requestedInstances.add(requestedInstance); + } + } + Class testClass = testInstance.getClass(); RequestedInstance requestedServerInstance = createRequestedInstance(testClass.getAnnotations(), null); if (requestedServerInstance != null) { @@ -317,6 +351,10 @@ public class Registry implements ExtensionContext.Store.CloseableResource { } } + private TestFrameworkExecutor getExecutor(Method testMethod) { + return extensions.getTestFrameworkExecutors().stream().filter(e -> e.shouldExecute(testMethod)).findFirst().orElse(null); + } + private static class RequestedInstanceComparator implements Comparator { static final RequestedInstanceComparator INSTANCE = new RequestedInstanceComparator(); diff --git a/test-framework/core/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/test-framework/core/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension deleted file mode 100644 index 4ad237052ea..00000000000 --- a/test-framework/core/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension +++ /dev/null @@ -1 +0,0 @@ -org.keycloak.testframework.KeycloakIntegrationTestExtension \ No newline at end of file diff --git a/test-framework/core/src/test/java/org/keycloak/testframework/injection/RegistryTest.java b/test-framework/core/src/test/java/org/keycloak/testframework/injection/RegistryTest.java index 6878da772fc..a6f81d4815e 100644 --- a/test-framework/core/src/test/java/org/keycloak/testframework/injection/RegistryTest.java +++ b/test-framework/core/src/test/java/org/keycloak/testframework/injection/RegistryTest.java @@ -1,5 +1,6 @@ package org.keycloak.testframework.injection; +import java.lang.reflect.Method; import java.util.List; import org.keycloak.testframework.config.Config; @@ -36,7 +37,7 @@ public class RegistryTest { MockParentSupplier.DEFAULT_LIFECYCLE = LifeCycle.GLOBAL; ParentTest test = new ParentTest(); - registry.beforeEach(test); + runBeforeEach(test); MockParentValue value1 = test.parent; assertRunning(value1); @@ -45,7 +46,7 @@ public class RegistryTest { assertRunning(value1); ParentTest test2 = new ParentTest(); - registry.beforeEach(test2); + runBeforeEach(test2); MockParentValue value2 = test2.parent; Assertions.assertSame(value1, value2); @@ -59,14 +60,14 @@ public class RegistryTest { public void testClassLifeCycle() { ParentTest test = new ParentTest(); - registry.beforeEach(test); + runBeforeEach(test); MockParentValue value1 = test.parent; assertRunning(value1); registry.afterEach(); assertRunning(value1); - registry.beforeEach(test); + runBeforeEach(test); MockParentValue value2 = test.parent; Assertions.assertSame(value1, value2); @@ -77,7 +78,7 @@ public class RegistryTest { assertClosed(value1); ParentTest test2 = new ParentTest(); - registry.beforeEach(test2); + runBeforeEach(test2); MockParentValue value3 = test2.parent; Assertions.assertNotSame(value1, value3); @@ -89,14 +90,14 @@ public class RegistryTest { ParentTest test = new ParentTest(); - registry.beforeEach(test); + runBeforeEach(test); MockParentValue value1 = test.parent; assertRunning(value1); registry.afterEach(); assertClosed(value1); - registry.beforeEach(test); + runBeforeEach(test); MockParentValue value2 = test.parent; Assertions.assertNotSame(value1, value2); } @@ -109,14 +110,14 @@ public class RegistryTest { ParentAndChildTest parentAndChildTest = new ParentAndChildTest(); MockChildValue child1 = parentAndChildTest.child; - registry.beforeEach(parentAndChildTest); + runBeforeEach(parentAndChildTest); assertRunning(parentAndChildTest.parent, parentAndChildTest.child); Assertions.assertSame(parentAndChildTest.parent, parentAndChildTest.child.getParent()); registry.afterEach(); assertClosed(parentAndChildTest.parent, parentAndChildTest.child); - registry.beforeEach(parentAndChildTest); + runBeforeEach(parentAndChildTest); Assertions.assertNotSame(child1, parentAndChildTest.child); } @@ -126,7 +127,7 @@ public class RegistryTest { ParentTest parentTest = new ParentTest(); - registry.beforeEach(parentTest); + runBeforeEach(parentTest); registry.afterEach(); registry.afterAll(); assertRunning(parentTest.parent); @@ -134,7 +135,7 @@ public class RegistryTest { MockParentSupplier.DEFAULT_LIFECYCLE = LifeCycle.CLASS; ParentTest parentTest2 = new ParentTest(); - registry.beforeEach(parentTest2); + runBeforeEach(parentTest2); assertRunning(parentTest2.parent); assertClosed(parentTest.parent); @@ -145,13 +146,13 @@ public class RegistryTest { public void testRecreateIfNotCompatible() { ParentTest parentTest = new ParentTest(); - registry.beforeEach(parentTest); + runBeforeEach(parentTest); MockParentValue parent1 = parentTest.parent; registry.afterEach(); MockParentSupplier.COMPATIBLE = false; - registry.beforeEach(parentTest); + runBeforeEach(parentTest); registry.afterEach(); MockParentValue parent2 = parentTest.parent; @@ -191,7 +192,7 @@ public class RegistryTest { public void testDependencyCreatedOnDemand() { ChildTest childTest = new ChildTest(); - registry.beforeEach(childTest); + runBeforeEach(childTest); assertRunning(childTest.child, childTest.child.getParent()); } @@ -199,7 +200,7 @@ public class RegistryTest { public void testDependencyRequestedBefore() { ParentAndChildTest test = new ParentAndChildTest(); - registry.beforeEach(test); + runBeforeEach(test); assertRunning(test.child, test.child.getParent()); } @@ -207,14 +208,14 @@ public class RegistryTest { public void testDependencyRequestedAfter() { ChildAndParentTest test = new ChildAndParentTest(); - registry.beforeEach(test); + runBeforeEach(test); assertRunning(test.child, test.child.getParent()); } @Test public void testMultiplRef() { MultipleRefTest refTest = new MultipleRefTest(); - registry.beforeEach(refTest); + runBeforeEach(refTest); MockParentValue def1 = refTest.def; MockParentValue a1 = refTest.a; @@ -228,7 +229,7 @@ public class RegistryTest { registry.afterEach(); - registry.beforeEach(refTest); + runBeforeEach(refTest); assertRunning(refTest.def, refTest.a2, refTest.b); Assertions.assertSame(def1, refTest.def); @@ -244,7 +245,7 @@ public class RegistryTest { @Test public void testRealmRef() { RealmRefTest test = new RealmRefTest(); - registry.beforeEach(test); + runBeforeEach(test); assertRunning(test.childABC, test.childABC.getParent(), test.child123, test.parent123); Assertions.assertNotSame(test.childABC.getParent(), test.parent123); @@ -254,7 +255,7 @@ public class RegistryTest { @Test public void testConfigurableSupplier() { ParentTest parentTest = new ParentTest(); - registry.beforeEach(parentTest); + runBeforeEach(parentTest); Assertions.assertNull(parentTest.parent.getStringOption()); Assertions.assertTrue(parentTest.parent.isBooleanOption()); @@ -267,7 +268,7 @@ public class RegistryTest { Extensions.reset(); registry = new Registry(); parentTest = new ParentTest(); - registry.beforeEach(parentTest); + runBeforeEach(parentTest); Assertions.assertEquals("some string", parentTest.parent.getStringOption()); Assertions.assertFalse(parentTest.parent.isBooleanOption()); @@ -282,7 +283,7 @@ public class RegistryTest { public void testIncompatibleParent() { MockParentSupplier.COMPATIBLE = false; RealmIncompatibleParentTest test = new RealmIncompatibleParentTest(); - registry.beforeEach(test); + runBeforeEach(test); MockParentValue parent1 = test.parent; MockChildValue child1 = test.child; @@ -292,7 +293,7 @@ public class RegistryTest { registry.afterEach(); - registry.beforeEach(test); + runBeforeEach(test); Assertions.assertNotNull(test.parent); Assertions.assertNotEquals(parent1, test.parent); @@ -300,6 +301,15 @@ public class RegistryTest { Assertions.assertNotEquals(child1, test.child); } + private void runBeforeEach(T testInstance) { + try { + Method testMethod = testInstance.getClass().getMethod("test"); + registry.beforeEach(testInstance, testMethod); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + public static void assertRunning(Object... values) { MatcherAssert.assertThat(MockInstances.INSTANCES, Matchers.hasItems(values)); MatcherAssert.assertThat(MockInstances.INSTANCES, Matchers.hasSize(values.length)); @@ -310,7 +320,13 @@ public class RegistryTest { MatcherAssert.assertThat(MockInstances.CLOSED_INSTANCES, Matchers.hasSize(values.length)); } - public static final class ParentAndChildTest { + public static abstract class AbstractTest { + @Test + public void test() { + } + } + + public static final class ParentAndChildTest extends AbstractTest { @MockParentAnnotation MockParentValue parent; @@ -318,17 +334,17 @@ public class RegistryTest { MockChildValue child; } - public static final class ParentTest { + public static final class ParentTest extends AbstractTest { @MockParentAnnotation MockParentValue parent; } - public static final class ChildTest { + public static final class ChildTest extends AbstractTest { @MockChildAnnotation MockChildValue child; } - public static final class ChildAndParentTest { + public static final class ChildAndParentTest extends AbstractTest { @MockChildAnnotation MockChildValue child; @@ -336,7 +352,7 @@ public class RegistryTest { MockParentValue parent; } - public static final class MultipleRefTest { + public static final class MultipleRefTest extends AbstractTest { @MockParentAnnotation() MockParentValue def; @@ -350,7 +366,7 @@ public class RegistryTest { MockParentValue b; } - public static final class RealmRefTest { + public static final class RealmRefTest extends AbstractTest { @MockParentAnnotation(ref = "123") MockParentValue parent123; @@ -361,7 +377,7 @@ public class RegistryTest { MockChildValue childABC; } - public static final class RealmIncompatibleParentTest { + public static final class RealmIncompatibleParentTest extends AbstractTest { @MockChildAnnotation MockChildValue child; diff --git a/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/RunTestOnServerTest.java b/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/RunTestOnServerTest.java new file mode 100644 index 00000000000..3bd5c266223 --- /dev/null +++ b/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/RunTestOnServerTest.java @@ -0,0 +1,29 @@ +package org.keycloak.test.examples; + +import org.keycloak.models.KeycloakSession; +import org.keycloak.testframework.annotations.InjectRealm; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.testframework.realm.ManagedRealm; +import org.keycloak.testframework.remote.annotations.TestOnServer; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +@KeycloakIntegrationTest +public class RunTestOnServerTest { + + @InjectRealm + ManagedRealm realm; + + @TestOnServer + public void test(KeycloakSession session) throws Throwable { + Assertions.assertNotNull(session); + Assertions.assertNull(realm); + } + + @Test + public void test2() { + Assertions.assertNotNull(realm); + } + +} diff --git a/test-framework/junit5-config/src/main/resources/junit-platform.properties b/test-framework/junit5-config/src/main/resources/junit-platform.properties index 58d88e022b5..7b4c145a8de 100644 --- a/test-framework/junit5-config/src/main/resources/junit-platform.properties +++ b/test-framework/junit5-config/src/main/resources/junit-platform.properties @@ -1,2 +1 @@ -junit.jupiter.extensions.autodetection.enabled=true junit.jupiter.testclass.order.default=org.keycloak.testframework.ServerConfigClassOrderer \ No newline at end of file diff --git a/test-framework/remote/src/main/java/org/keycloak/testframework/remote/RemoteTestFrameworkExtension.java b/test-framework/remote/src/main/java/org/keycloak/testframework/remote/RemoteTestFrameworkExtension.java index 63d44046fe7..661eebdd952 100644 --- a/test-framework/remote/src/main/java/org/keycloak/testframework/remote/RemoteTestFrameworkExtension.java +++ b/test-framework/remote/src/main/java/org/keycloak/testframework/remote/RemoteTestFrameworkExtension.java @@ -1,14 +1,22 @@ package org.keycloak.testframework.remote; +import java.lang.reflect.Method; +import java.util.Collections; import java.util.List; +import org.keycloak.models.KeycloakSession; +import org.keycloak.testframework.TestFrameworkExecutor; import org.keycloak.testframework.TestFrameworkExtension; +import org.keycloak.testframework.injection.Registry; import org.keycloak.testframework.injection.Supplier; +import org.keycloak.testframework.remote.annotations.TestOnServer; +import org.keycloak.testframework.remote.runonserver.RunOnServerClient; import org.keycloak.testframework.remote.runonserver.RunOnServerSupplier; +import org.keycloak.testframework.remote.runonserver.RunTestOnServer; import org.keycloak.testframework.remote.runonserver.TestClassServerSupplier; import org.keycloak.testframework.remote.timeoffset.TimeOffsetSupplier; -public class RemoteTestFrameworkExtension implements TestFrameworkExtension { +public class RemoteTestFrameworkExtension implements TestFrameworkExtension, TestFrameworkExecutor { @Override public List> suppliers() { return List.of( @@ -23,4 +31,32 @@ public class RemoteTestFrameworkExtension implements TestFrameworkExtension { public List> alwaysEnabledValueTypes() { return List.of(RemoteProviders.class); } + + @Override + public List> getMethodValueTypes(Method method) { + return isTestOnServer(method) ? List.of(RunOnServerClient.class) : Collections.emptyList(); + } + + @Override + public boolean supportsParameter(Method method, Class parameterType) { + return isTestOnServer(method) && parameterType.equals(KeycloakSession.class); + } + + @Override + public boolean shouldExecute(Method testMethod) { + return isTestOnServer(testMethod); + } + + @Override + public void execute(Registry registry, Class testClass, Method testMethod) { + RunOnServerClient value = (RunOnServerClient) registry.getDeployedInstances().stream().filter(i -> i.getRequestedValueType() != null && i.getRequestedValueType().equals(RunOnServerClient.class)).findFirst().get().getValue(); + + RunTestOnServer runTestOnServer = new RunTestOnServer(testClass.getName(), testMethod.getName()); + value.run(runTestOnServer); + } + + private boolean isTestOnServer(Method method) { + return method.isAnnotationPresent(TestOnServer.class); + } + } diff --git a/test-framework/remote/src/main/java/org/keycloak/testframework/remote/annotations/TestOnServer.java b/test-framework/remote/src/main/java/org/keycloak/testframework/remote/annotations/TestOnServer.java new file mode 100644 index 00000000000..44449149a94 --- /dev/null +++ b/test-framework/remote/src/main/java/org/keycloak/testframework/remote/annotations/TestOnServer.java @@ -0,0 +1,14 @@ +package org.keycloak.testframework.remote.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Test; + +@Test +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface TestOnServer { +} diff --git a/test-framework/remote/src/main/java/org/keycloak/testframework/remote/runonserver/RunTestOnServer.java b/test-framework/remote/src/main/java/org/keycloak/testframework/remote/runonserver/RunTestOnServer.java new file mode 100644 index 00000000000..c2442e992a8 --- /dev/null +++ b/test-framework/remote/src/main/java/org/keycloak/testframework/remote/runonserver/RunTestOnServer.java @@ -0,0 +1,31 @@ +package org.keycloak.testframework.remote.runonserver; + +import java.io.IOException; +import java.lang.reflect.Method; + +import org.keycloak.common.VerificationException; +import org.keycloak.models.KeycloakSession; +import org.keycloak.testframework.remote.providers.runonserver.RunOnServer; + +public class RunTestOnServer implements RunOnServer { + + private final String testClass; + private final String testMethod; + + public RunTestOnServer(String testClass, String testMethod) { + this.testClass = testClass; + this.testMethod = testMethod; + } + + @Override + public void run(KeycloakSession session) throws IOException, VerificationException { + try { + Class clazz = this.getClass().getClassLoader().loadClass(testClass); + Object test = clazz.getDeclaredConstructor().newInstance(); + Method method = clazz.getDeclaredMethod(testMethod, KeycloakSession.class); + method.invoke(test, session); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +}