From fc697fcb044436a030d4d00d31e1641f108e55ec Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Thu, 26 Aug 2021 13:37:13 +1000 Subject: [PATCH] Improve DevServices test integration Also improce the KeyCloak test client, and modify a KC test to use DevServices. This adds a new DevServicesContext which can be injected into QuarkusTest and QuarkusIntegrationTest, as well as TestResourceManager implementation (to allow the dev service to be configured). --- .../runner/bootstrap/StartupActionImpl.java | 12 +++ .../asciidoc/getting-started-testing.adoc | 19 ++++ .../keycloak/DevServicesConfig.java | 6 ++ .../KeycloakDevServicesProcessor.java | 10 +- .../quarkus/bootstrap/app/StartupAction.java | 2 + .../oidc-token-propagation/pom.xml | 96 +------------------ .../src/main/resources/application.properties | 4 +- .../KeycloakRealmResourceManager.java | 49 ++++++---- .../keycloak/OidcTokenPropagationITCase.java | 7 ++ .../OidcTokenPropagationInGraalITCase.java | 7 -- .../it/keycloak/OidcTokenPropagationTest.java | 18 +--- .../test/common/DevServicesContext.java | 35 +++++++ .../test/common/TestResourceManager.java | 16 ++++ .../test/junit/QuarkusIntegrationTest.java | 14 +-- .../QuarkusIntegrationTestExtension.java | 26 ++--- .../test/junit/QuarkusTestExtension.java | 7 +- ...TestAdmin.java => KeycloakTestClient.java} | 15 ++- 17 files changed, 173 insertions(+), 170 deletions(-) create mode 100644 integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationITCase.java delete mode 100644 integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationInGraalITCase.java create mode 100644 test-framework/common/src/main/java/io/quarkus/test/common/DevServicesContext.java rename test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/{KeycloakTestAdmin.java => KeycloakTestClient.java} (85%) diff --git a/core/deployment/src/main/java/io/quarkus/runner/bootstrap/StartupActionImpl.java b/core/deployment/src/main/java/io/quarkus/runner/bootstrap/StartupActionImpl.java index 84d61e473f705..97dbb820bb5c1 100644 --- a/core/deployment/src/main/java/io/quarkus/runner/bootstrap/StartupActionImpl.java +++ b/core/deployment/src/main/java/io/quarkus/runner/bootstrap/StartupActionImpl.java @@ -9,6 +9,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardOpenOption; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -25,6 +26,7 @@ import io.quarkus.bootstrap.classloading.QuarkusClassLoader; import io.quarkus.builder.BuildResult; import io.quarkus.deployment.builditem.ApplicationClassNameBuildItem; +import io.quarkus.deployment.builditem.DevServicesLauncherConfigResultBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; import io.quarkus.deployment.builditem.MainClassBuildItem; @@ -221,6 +223,16 @@ public ClassLoader getClassLoader() { return runtimeClassLoader; } + @Override + public Map getDevServicesProperties() { + DevServicesLauncherConfigResultBuildItem result = buildResult + .consumeOptional(DevServicesLauncherConfigResultBuildItem.class); + if (result == null) { + return Collections.emptyMap(); + } + return new HashMap<>(result.getConfig()); + } + private Map extractTransformers(Set eagerClasses) { Map ret = new HashMap<>(); TransformedClassesBuildItem transformers = buildResult.consume(TransformedClassesBuildItem.class); diff --git a/docs/src/main/asciidoc/getting-started-testing.adoc b/docs/src/main/asciidoc/getting-started-testing.adoc index 748a8a41f56a7..c96b16f49e871 100644 --- a/docs/src/main/asciidoc/getting-started-testing.adoc +++ b/docs/src/main/asciidoc/getting-started-testing.adoc @@ -1235,3 +1235,22 @@ The `settings.json` placed in the root of your project directory or in the works === IntelliJ JUnit template Nothing needed in IntelliJ because the IDE will pick the `systemPropertyVariables` from the surefire plugin configuration in `pom.xml`. + +== Testing Dev Services + +By default tests should just work with link:dev-services[Dev Services], however from some use cases you may need access to +the automatically configured properties in your tests. + +You can do this with `io.quarkus.test.common.DevServicesContext`, which can be injected directly into any `@QuarkusTest` +or `@QuarkusIntegrationTest`. All you need to do is define a field of type `DevServicesContext` and it will be automatically +injected. Using this you can retrieve any properties that have been set. Generally this is used to directly connect to a +resource from the test itself, e.g. to connect to kafka to send messages to the application under test. + +Injection is also supported into objects that implement `io.quarkus.test.common.DevServicesContext.ContextAware`. If you +have a field that implements `io.quarkus.test.common.DevServicesContext.ContextAware` Quarkus will call the +`setIntegrationTestContext` method to pass the context into this object. This allows client logic to be encapsulated in +a utility class. + +`QuarkusTestResourceLifecycleManager` implementations can also implement `ContextAware` to get access to these properties, +which allows you to setup the resource before Quarkus starts (e.g. configure a KeyCloak instance, add data to a database etc). + diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java index f4c37befb9b44..2bbed7946eca6 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java @@ -33,6 +33,11 @@ public class DevServicesConfig { @ConfigItem public Optional realmPath; + /** + * The JAVA_OPTS passed to the keycloak JVM + */ + @ConfigItem + public Optional javaOpts; /** * The Keycloak realm. * This property will be used to create the realm if the realm file pointed to by the 'realm-path' property does not exist. @@ -134,6 +139,7 @@ public boolean equals(Object o) { && Objects.equals(realmPath, that.realmPath) && Objects.equals(realmName, that.realmName) && Objects.equals(users, that.users) + && Objects.equals(javaOpts, that.javaOpts) && Objects.equals(roles, that.roles); } diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java index b1a1274ddbc5d..7c67693cc96d9 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java @@ -65,6 +65,7 @@ public class KeycloakDevServicesProcessor { private static final String KEYCLOAK_URL_KEY = "keycloak.url"; private static final int KEYCLOAK_EXPOSED_PORT = 8080; + private static final String JAVA_OPTS = "JAVA_OPTS"; private static final String KEYCLOAK_DOCKER_REALM_PATH = "/tmp/realm.json"; private static final String KEYCLOAK_USER_PROP = "KEYCLOAK_USER"; private static final String KEYCLOAK_PASSWORD_PROP = "KEYCLOAK_PASSWORD"; @@ -228,7 +229,7 @@ private StartResult startContainer(boolean useSharedContainer) { QuarkusOidcContainer oidcContainer = new QuarkusOidcContainer(dockerImageName, capturedDevServicesConfiguration.port, useSharedContainer, - capturedDevServicesConfiguration.realmPath); + capturedDevServicesConfiguration.realmPath, capturedDevServicesConfiguration.javaOpts); oidcContainer.start(); @@ -263,13 +264,15 @@ private static class QuarkusOidcContainer extends GenericContainer { private final Optional realm; private boolean realmFileExists; private String hostName = null; + private final Optional javaOpts; public QuarkusOidcContainer(DockerImageName dockerImageName, OptionalInt fixedExposedPort, boolean useSharedNetwork, - Optional realm) { + Optional realm, Optional javaOpts) { super(dockerImageName); this.fixedExposedPort = fixedExposedPort; this.useSharedNetwork = useSharedNetwork; this.realm = realm; + this.javaOpts = javaOpts; } @Override @@ -293,6 +296,9 @@ protected void configure() { addEnv(KEYCLOAK_USER_PROP, KEYCLOAK_ADMIN_USER); addEnv(KEYCLOAK_PASSWORD_PROP, KEYCLOAK_ADMIN_PASSWORD); addEnv(KEYCLOAK_VENDOR_PROP, KEYCLOAK_DB_VENDOR); + if (javaOpts.isPresent()) { + addEnv(JAVA_OPTS, javaOpts.get()); + } if (realm.isPresent()) { if (Thread.currentThread().getContextClassLoader().getResource(realm.get()) != null) { diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/StartupAction.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/StartupAction.java index 889ceda339f71..f4598b0e8dbc1 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/StartupAction.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/StartupAction.java @@ -14,6 +14,8 @@ public interface StartupAction { ClassLoader getClassLoader(); + Map getDevServicesProperties(); + /** * Runs the application by running the main method of the main class. As this is a blocking method a new * thread is created to run this task. diff --git a/integration-tests/oidc-token-propagation/pom.xml b/integration-tests/oidc-token-propagation/pom.xml index 0031060a1ef16..36e49370363d9 100644 --- a/integration-tests/oidc-token-propagation/pom.xml +++ b/integration-tests/oidc-token-propagation/pom.xml @@ -14,10 +14,6 @@ Quarkus - Integration Tests - OpenID Connect Token Propagation Module that contains OpenID Connect Token Propagation tests - - http://localhost:8180/auth - - org.keycloak @@ -30,7 +26,7 @@ io.quarkus - quarkus-junit5 + quarkus-test-keycloak-server test @@ -157,18 +153,12 @@ maven-surefire-plugin false - - ${keycloak.url} - maven-failsafe-plugin false - - ${keycloak.url} - @@ -186,90 +176,6 @@ - - docker-keycloak - - - start-containers - - - - http://localhost:8180/auth - - - - - io.fabric8 - docker-maven-plugin - - - - ${keycloak.docker.image} - quarkus-test-keycloak - - - 8180:8080 - - - admin - admin - -Dkeycloak.profile.feature.token_exchange=enabled -Dkeycloak.profile=preview - - - Keycloak: - default - cyan - - - - - http://localhost:8180 - - - - - - - true - - - - docker-start - compile - - stop - start - - - - docker-stop - post-integration-test - - stop - - - - - - org.codehaus.mojo - exec-maven-plugin - - - docker-prune - generate-resources - - exec - - - ${basedir}/../../.github/docker-prune.sh - - - - - - - - diff --git a/integration-tests/oidc-token-propagation/src/main/resources/application.properties b/integration-tests/oidc-token-propagation/src/main/resources/application.properties index 2647ca74308fb..c2e276a45a68b 100644 --- a/integration-tests/oidc-token-propagation/src/main/resources/application.properties +++ b/integration-tests/oidc-token-propagation/src/main/resources/application.properties @@ -1,7 +1,9 @@ -quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus/ +%prod.quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus/ quarkus.oidc.client-id=quarkus-app quarkus.oidc.credentials.secret=secret +quarkus.keycloak.devservices.java-opts=-Dkeycloak.profile.feature.token_exchange=enabled -Dkeycloak.profile=preview + quarkus.oidc-client.auth-server-url=${quarkus.oidc.auth-server-url} quarkus.oidc-client.client-id=${quarkus.oidc.client-id} quarkus.oidc-client.credentials.secret=${quarkus.oidc.credentials.secret} diff --git a/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/KeycloakRealmResourceManager.java b/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/KeycloakRealmResourceManager.java index d97e530d0d356..02d1fd7c861ab 100644 --- a/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/KeycloakRealmResourceManager.java +++ b/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/KeycloakRealmResourceManager.java @@ -1,6 +1,8 @@ package io.quarkus.it.keycloak; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -16,13 +18,14 @@ import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.util.JsonSerialization; +import io.quarkus.test.common.DevServicesContext; import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; import io.restassured.RestAssured; -public class KeycloakRealmResourceManager implements QuarkusTestResourceLifecycleManager { +public class KeycloakRealmResourceManager implements QuarkusTestResourceLifecycleManager, DevServicesContext.ContextAware { - private static final String KEYCLOAK_SERVER_URL = System.getProperty("keycloak.url", "http://localhost:8180/auth"); private static final String KEYCLOAK_REALM = "quarkus"; + String authServerUrl; @Override public Map start() { @@ -38,13 +41,21 @@ public Map start() { realm.getUsers().add(createUser("bob", "user")); try { + //dev services sets up a realm for us, we delete it because we want different settings + RestAssured + .given() + .auth().oauth2(getAdminAccessToken()) + .contentType("application/json") + .when() + .delete(authServerUrl + "/admin/realms/quarkus").then() + .statusCode(204); RestAssured .given() .auth().oauth2(getAdminAccessToken()) .contentType("application/json") .body(JsonSerialization.writeValueAsBytes(realm)) .when() - .post(KEYCLOAK_SERVER_URL + "/admin/realms").then() + .post(authServerUrl + "/admin/realms").then() .statusCode(201); } catch (IOException e) { throw new RuntimeException(e); @@ -52,7 +63,7 @@ public Map start() { return Collections.emptyMap(); } - private static String getAdminAccessToken() { + private String getAdminAccessToken() { return RestAssured .given() .param("grant_type", "password") @@ -60,11 +71,11 @@ private static String getAdminAccessToken() { .param("password", "admin") .param("client_id", "admin-cli") .when() - .post(KEYCLOAK_SERVER_URL + "/realms/master/protocol/openid-connect/token") + .post(authServerUrl + "/realms/master/protocol/openid-connect/token") .as(AccessTokenResponse.class).getToken(); } - private static RealmRepresentation createRealm(String name) { + private RealmRepresentation createRealm(String name) { RealmRepresentation realm = new RealmRepresentation(); realm.setRealm(name); @@ -85,7 +96,7 @@ private static RealmRepresentation createRealm(String name) { return realm; } - private static ClientRepresentation createClient(String clientId) { + private ClientRepresentation createClient(String clientId) { ClientRepresentation client = new ClientRepresentation(); client.setClientId(clientId); @@ -98,7 +109,7 @@ private static ClientRepresentation createClient(String clientId) { return client; } - private static UserRepresentation createUser(String username, String... realmRoles) { + private UserRepresentation createUser(String username, String... realmRoles) { UserRepresentation user = new UserRepresentation(); user.setUsername(username); @@ -125,19 +136,17 @@ public void stop() { .given() .auth().oauth2(getAdminAccessToken()) .when() - .delete(KEYCLOAK_SERVER_URL + "/admin/realms/" + KEYCLOAK_REALM).then().statusCode(204); + .delete(authServerUrl + "/admin/realms/" + KEYCLOAK_REALM).then().statusCode(204); } - public static String getAccessToken(String userName) { - return RestAssured - .given() - .param("grant_type", "password") - .param("username", userName) - .param("password", userName) - .param("client_id", "quarkus-app") - .param("client_secret", "secret") - .when() - .post(KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM + "/protocol/openid-connect/token") - .as(AccessTokenResponse.class).getToken(); + @Override + public void setIntegrationTestContext(DevServicesContext context) { + try { + var uri = new URI(context.devServicesProperties().get("quarkus.oidc.auth-server-url")); + authServerUrl = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), "/auth", null, null) + .toString(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } } } diff --git a/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationITCase.java b/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationITCase.java new file mode 100644 index 0000000000000..37228cbfd216a --- /dev/null +++ b/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationITCase.java @@ -0,0 +1,7 @@ +package io.quarkus.it.keycloak; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class OidcTokenPropagationITCase extends OidcTokenPropagationTest { +} diff --git a/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationInGraalITCase.java b/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationInGraalITCase.java deleted file mode 100644 index 01ee8ab1acbd6..0000000000000 --- a/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationInGraalITCase.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.quarkus.it.keycloak; - -import io.quarkus.test.junit.NativeImageTest; - -@NativeImageTest -public class OidcTokenPropagationInGraalITCase extends OidcTokenPropagationTest { -} diff --git a/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationTest.java b/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationTest.java index b92aa025e375b..758e65299401c 100644 --- a/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationTest.java +++ b/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationTest.java @@ -4,16 +4,17 @@ import static org.hamcrest.Matchers.equalTo; import org.junit.jupiter.api.Test; -import org.keycloak.representations.AccessTokenResponse; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.keycloak.server.KeycloakTestClient; import io.restassured.RestAssured; @QuarkusTest @QuarkusTestResource(KeycloakRealmResourceManager.class) public class OidcTokenPropagationTest { - public static final String KEYCLOAK_SERVER_URL = System.getProperty("keycloak.url", "http://localhost:8180/auth"); + + final KeycloakTestClient client = new KeycloakTestClient(); @Test public void testGetUserNameWithJwtTokenPropagation() { @@ -54,16 +55,7 @@ public void testGetUserNameFromServiceAccount() { .body(equalTo("alice")); } - public static String getAccessToken(String userName) { - return RestAssured - .given() - .param("grant_type", "password") - .param("username", userName) - .param("password", userName) - .param("client_id", "quarkus-app") - .param("client_secret", "secret") - .when() - .post(KEYCLOAK_SERVER_URL + "/realms/quarkus/protocol/openid-connect/token") - .as(AccessTokenResponse.class).getToken(); + public String getAccessToken(String userName) { + return client.getAccessToken(userName, userName, "quarkus-app", "secret"); } } diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/DevServicesContext.java b/test-framework/common/src/main/java/io/quarkus/test/common/DevServicesContext.java new file mode 100644 index 0000000000000..d975ef80e38b8 --- /dev/null +++ b/test-framework/common/src/main/java/io/quarkus/test/common/DevServicesContext.java @@ -0,0 +1,35 @@ +package io.quarkus.test.common; + +import java.util.Map; + +/** + * Interface that can be used to get properties from DevServices for {@link QuarkusTest} and {@link QuarkusIntegrationTest} + * based tests. + * + * This can be injected into fields on a test class, or injected into {@link ContextAware} objects on the test class + * or {@link io.quarkus.test.common.TestResourceManager} implementations. + * + */ +public interface DevServicesContext { + + /** + * Returns a map containing all the properties creates by potentially launched dev services. + * If no dev services where launched, the map will be empty. + */ + Map devServicesProperties(); + + /** + * Interface that can be implemented to allow automatic injection of the context. + * + * If you have a field on a test class that implements this interface the then context + * will be injected into it. + * + * {@link io.quarkus.test.common.QuarkusTestResourceLifecycleManager} implementations can also + * implement this. This allows for them to setup the resource created by Dev Services after + * it has been started. + * + */ + interface ContextAware { + void setIntegrationTestContext(DevServicesContext context); + } +} diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/TestResourceManager.java b/test-framework/common/src/main/java/io/quarkus/test/common/TestResourceManager.java index 617a47e2208dc..745c7ca11ead2 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/TestResourceManager.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/TestResourceManager.java @@ -47,6 +47,11 @@ public TestResourceManager(Class testClass) { public TestResourceManager(Class testClass, Class profileClass, List additionalTestResources, boolean disableGlobalTestResources) { + this(testClass, profileClass, additionalTestResources, disableGlobalTestResources, Collections.emptyMap()); + } + + public TestResourceManager(Class testClass, Class profileClass, List additionalTestResources, + boolean disableGlobalTestResources, Map devServicesProperties) { this.parallelTestResourceEntries = new ArrayList<>(); this.sequentialTestResourceEntries = new ArrayList<>(); @@ -63,6 +68,17 @@ public TestResourceManager(Class testClass, Class profileClass, List(sequentialTestResourceEntries); this.allTestResourceEntries.addAll(parallelTestResourceEntries); + DevServicesContext context = new DevServicesContext() { + @Override + public Map devServicesProperties() { + return devServicesProperties; + } + }; + for (var i : allTestResourceEntries) { + if (i.getTestResource() instanceof DevServicesContext.ContextAware) { + ((DevServicesContext.ContextAware) i.getTestResource()).setIntegrationTestContext(context); + } + } } public void init() { diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusIntegrationTest.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusIntegrationTest.java index 8651516b5657a..c63013d4a910f 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusIntegrationTest.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusIntegrationTest.java @@ -4,10 +4,11 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.util.Map; import org.junit.jupiter.api.extension.ExtendWith; +import io.quarkus.test.common.DevServicesContext; + /** * Annotation that indicates that this test should be run the result of the Quarkus build. * That means that if a jar was created, that jar is launched using {@code java -jar ...} @@ -36,13 +37,12 @@ /** * If used as a field of class annotated with {@link QuarkusIntegrationTest}, the field is populated * with an implementation that allows accessing contextual test information + * + * @deprecated Use {@link DevServicesContext} instead. */ - interface Context { + @Deprecated + interface Context extends DevServicesContext { - /** - * Returns a map containing all the properties creates by potentially launched dev services. - * If no dev services where launched, the map will be empty. - */ - Map devServicesProperties(); } + } diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusIntegrationTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusIntegrationTestExtension.java index 4da90e8867b46..fdccc6794d873 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusIntegrationTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusIntegrationTestExtension.java @@ -9,7 +9,6 @@ import static io.quarkus.test.junit.IntegrationTestUtil.readQuarkusArtifactProperties; import static io.quarkus.test.junit.IntegrationTestUtil.startLauncher; -import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.nio.file.Path; import java.util.Collections; @@ -30,6 +29,7 @@ import io.quarkus.runtime.test.TestHttpEndpointProvider; import io.quarkus.test.common.ArtifactLauncher; +import io.quarkus.test.common.DevServicesContext; import io.quarkus.test.common.RestAssuredURLManager; import io.quarkus.test.common.TestResourceManager; import io.quarkus.test.common.TestScopeManager; @@ -133,7 +133,8 @@ private IntegrationTestExtensionState doProcessStart(Properties quarkusArtifactP testResourceManager = new TestResourceManager(requiredTestClass, quarkusTestProfile, Collections.emptyList(), testProfileAndProperties.testProfile != null - && testProfileAndProperties.testProfile.disableGlobalTestResources()); + && testProfileAndProperties.testProfile.disableGlobalTestResources(), + devServicesProps); testResourceManager.init(); hasPerTestResources = testResourceManager.hasPerTestResources(); @@ -222,22 +223,13 @@ private void injectTestContext(Object testInstance) { throw new RuntimeException("Unable to set field '" + f.getName() + "' with the proper test context", e); } - } else { - Constructor testContextAware = null; + } else if (DevServicesContext.ContextAware.class.isAssignableFrom(f.getType())) { + f.setAccessible(true); try { - testContextAware = f.getType().getConstructor(QuarkusIntegrationTest.Context.class); - } catch (Exception t) { - continue; - } - if (testContextAware != null) { - try { - Object testContextAwareInstance = testContextAware.newInstance(createTestContext()); - f.setAccessible(true); - f.set(testInstance, testContextAwareInstance); - } catch (Exception e) { - throw new RuntimeException("Unable to set field '" + f.getName() - + "' with the proper test context", e); - } + DevServicesContext.ContextAware val = (DevServicesContext.ContextAware) f.get(testInstance); + val.setIntegrationTestContext(createTestContext()); + } catch (Exception e) { + throw new RuntimeException("Unable to inject context into field " + f.getName(), e); } } } diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java index 9858d8e8228a6..6a4574d813a6d 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java @@ -369,11 +369,12 @@ public Thread newThread(Runnable r) { //must be done after the TCCL has been set testResourceManager = (Closeable) startupAction.getClassLoader().loadClass(TestResourceManager.class.getName()) - .getConstructor(Class.class, Class.class, List.class, boolean.class) + .getConstructor(Class.class, Class.class, List.class, boolean.class, Map.class) .newInstance(requiredTestClass, profile != null ? profile : null, getAdditionalTestResources(profileInstance, startupAction.getClassLoader()), - profileInstance != null && profileInstance.disableGlobalTestResources()); + profileInstance != null && profileInstance.disableGlobalTestResources(), + startupAction.getDevServicesProperties()); testResourceManager.getClass().getMethod("init").invoke(testResourceManager); Map properties = (Map) testResourceManager.getClass().getMethod("start") .invoke(testResourceManager); @@ -1135,7 +1136,7 @@ public void afterAll(ExtensionContext context) throws Exception { } private void runAfterAllCallbacks(ExtensionContext context) throws Exception { - if (isNativeOrIntegrationTest(context.getRequiredTestClass())) { + if (isNativeOrIntegrationTest(context.getRequiredTestClass()) || failedBoot) { return; } if (afterAllCallbacks.isEmpty()) { diff --git a/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestAdmin.java b/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestClient.java similarity index 85% rename from test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestAdmin.java rename to test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestClient.java index 91261146315b7..f1120c07776b8 100644 --- a/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestAdmin.java +++ b/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestClient.java @@ -4,10 +4,11 @@ import org.keycloak.representations.AccessTokenResponse; import io.quarkus.runtime.configuration.ConfigurationException; +import io.quarkus.test.common.DevServicesContext; import io.quarkus.test.junit.QuarkusIntegrationTest; import io.restassured.RestAssured; -public class KeycloakTestAdmin { +public class KeycloakTestClient implements DevServicesContext.ContextAware { private final static String AUTH_SERVER_URL_PROP = "quarkus.oidc.auth-server-url"; private final static String CLIENT_ID_PROP = "quarkus.oidc.client-id"; @@ -17,13 +18,13 @@ public class KeycloakTestAdmin { RestAssured.useRelaxedHTTPSValidation(); } - private QuarkusIntegrationTest.Context testContext; + private DevServicesContext testContext; - public KeycloakTestAdmin() { + public KeycloakTestClient() { } - public KeycloakTestAdmin(QuarkusIntegrationTest.Context testContext) { + public KeycloakTestClient(QuarkusIntegrationTest.Context testContext) { this.testContext = testContext; } @@ -58,7 +59,7 @@ private String getClientSecret() { return getPropertyValue(CLIENT_SECRET_PROP, "secret"); } - private String getAuthServerUrl() { + public String getAuthServerUrl() { String authServerUrl = getPropertyValue(AUTH_SERVER_URL_PROP, null); if (authServerUrl == null) { throw new ConfigurationException(AUTH_SERVER_URL_PROP + " is not configured"); @@ -76,4 +77,8 @@ private String getDevProperty(String prop, String defaultValue) { return value == null ? defaultValue : value; } + @Override + public void setIntegrationTestContext(DevServicesContext context) { + this.testContext = context; + } }