fix: simplifying quarkus integration test annotations (#48977)

* fix: simplifying quarkus integration test annotations

closes: #48796

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

* refining quarkus integration tests to use KeycloakDistributionDecorator

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

* implementing review feedback.

KeycloakRunner replaces KeycloakDistributionDecorator

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

---------

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steven Hawkins 2026-05-20 07:05:36 -04:00 committed by GitHub
parent 710539ca14
commit 03624df8db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
63 changed files with 933 additions and 1372 deletions

View file

@ -21,6 +21,7 @@ import java.util.List;
import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.ForkJoinPool;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import jakarta.enterprise.context.ApplicationScoped;
@ -58,6 +59,8 @@ import static org.keycloak.quarkus.runtime.Environment.hasEarlyExitLaunchMode;
@ApplicationScoped
public class KeycloakMain implements QuarkusApplication {
public static final String KC_SERVER_PRINT_RUNNING = "kc.server.print_running";
public static final String RUNNING_MESSAGE = "The server is running";
private static AbstractNonServerCommand COMMAND;
private static Consumer<Throwable> ERROR_HANDLER;
@ -163,8 +166,8 @@ public class KeycloakMain implements QuarkusApplication {
*/
@Override
public int run(String... args) throws Exception {
QuarkusKeycloakApplication application = Arc.container().instance(QuarkusKeycloakApplication.class).get();
if (COMMAND != null) {
QuarkusKeycloakApplication application = Arc.container().instance(QuarkusKeycloakApplication.class).get();
QuarkusKeycloakSessionFactory sessionFactory = Arc.container().instance(QuarkusKeycloakSessionFactory.class).get();
COMMAND.onStart(application, sessionFactory);
}
@ -173,6 +176,15 @@ public class KeycloakMain implements QuarkusApplication {
// we should be managing this behavior more dynamically depending on the tests requirements (short/long lived)
Quarkus.asyncExit(ApplicationLifecycleManager.getExitCode());
} else {
if (Boolean.getBoolean(KC_SERVER_PRINT_RUNNING)) {
BiConsumer<Void, Throwable> started = (v, t) -> {
if (t == null) {
System.out.println("\n" + RUNNING_MESSAGE);
}
};
application.getBootstrapFuture().ifPresentOrElse(future -> future.whenComplete(started),
() -> started.accept(null, null));
}
Quarkus.waitForExit();
}

View file

@ -17,6 +17,7 @@
package org.keycloak.quarkus.runtime.integration.jaxrs;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import jakarta.enterprise.event.Observes;
@ -59,6 +60,8 @@ public class QuarkusKeycloakApplication extends KeycloakApplication {
private static final String KEYCLOAK_ADMIN_PASSWORD_ENV_VAR = "KEYCLOAK_ADMIN_PASSWORD";
private static final Logger logger = Logger.getLogger(QuarkusKeycloakApplication.class);
private CompletableFuture<Void> bootstrapFuture;
@Override
protected String getDataDir() {
@ -77,12 +80,16 @@ public class QuarkusKeycloakApplication extends KeycloakApplication {
startup();
} else {
ManagedExecutor executor = Arc.container().instance(ManagedExecutor.class).get();
CompletableFuture.runAsync(this::startup, executor).exceptionally(cause -> {
bootstrapFuture = CompletableFuture.runAsync(this::startup, executor).exceptionally(cause -> {
KeycloakMain.asyncExit(1, cause);
return null;
});
}
}
public Optional<CompletableFuture<Void>> getBootstrapFuture() {
return Optional.ofNullable(bootstrapFuture);
}
void onShutdownEvent(@Observes ShutdownEvent event) {
shutdown();

View file

@ -8,8 +8,8 @@ import java.util.function.Consumer;
import org.keycloak.it.TestProvider;
import org.keycloak.it.junit5.extension.BeforeStartDistribution;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawKeycloakDistribution;
import org.keycloak.quarkus.runtime.cli.command.StartDev;
@ -32,13 +32,13 @@ abstract class AbstractPathDistTest {
@BeforeStartDistribution(AddCustomScriptsProvider.class)
@RawDistOnly(reason = "Testing installation path handling")
@Test
void testApplicationBuildAndStart(KeycloakDistribution dist) throws IOException {
RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class);
void testApplicationBuildAndStart(KeycloakRunner runner) throws IOException {
RawKeycloakDistribution rawDist = runner.getDistribution(RawKeycloakDistribution.class);
Path distPath = rawDist.getDistPath();
Path newPath = distPath.getParent().resolve(getSubPath()).resolve(distPath.getFileName());
rawDist.setDistPath(newPath);
try {
CLIResult result = dist.run(StartDev.NAME);
CLIResult result = runner.run(StartDev.NAME);
result.assertStartedDevMode();
assertThat(result.getOutput(), containsString("Updating the configuration and installing your custom providers, if any. Please wait."));
} finally {
@ -46,11 +46,10 @@ abstract class AbstractPathDistTest {
}
}
public static final class AddCustomScriptsProvider implements Consumer<KeycloakDistribution> {
public static final class AddCustomScriptsProvider implements Consumer<RawKeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
RawKeycloakDistribution rawDist = distribution.unwrap(RawKeycloakDistribution.class);
rawDist.copyProvider(new ScriptProviderForTest());
public void accept(RawKeycloakDistribution distribution) {
distribution.copyProvider(new ScriptProviderForTest());
}
}

View file

@ -19,9 +19,10 @@ package org.keycloak.it.cli.dist;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.junit5.extension.WithEnvVars;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawKeycloakDistribution;
import io.quarkus.test.junit.main.Launch;
@ -87,14 +88,15 @@ public class BootstrapAdminDistTest {
@Test
@WithEnvVars({"MY_SECRET", "admin123"})
void createAndUseSericeAccountAdmin(KeycloakDistribution dist) throws Exception {
RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class);
CLIResult result = rawDist.run("bootstrap-admin", "service", "--db=dev-file", "--client-id=admin", "--client-secret:env=MY_SECRET");
void createAndUseSericeAccountAdmin(KeycloakRunner runner) throws Exception {
RawKeycloakDistribution rawDist = runner.getDistribution(RawKeycloakDistribution.class);
CLIResult result = runner.run("bootstrap-admin", "service", "--db=dev-file", "--client-id=admin", "--client-secret:env=MY_SECRET");
assertTrue(result.getErrorOutput().isEmpty(), result.getErrorOutput());
rawDist.setManualStop(true);
rawDist.run("start-dev");
runner.setStopServer(Mode.MANUAL);
result = runner.run("start-dev");
result.assertStartedDevMode();
CLIResult adminResult = rawDist.kcadm("get", "clients", "--server", "http://localhost:8080", "--realm", "master", "--client", "admin", "--secret", "admin123");

View file

@ -19,10 +19,11 @@ package org.keycloak.it.cli.dist;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.DryRun;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.StopServer;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.junit5.extension.WithEnvVars;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawKeycloakDistribution;
import io.quarkus.test.junit.main.Launch;
@ -36,21 +37,20 @@ import static org.keycloak.quarkus.runtime.cli.command.AbstractAutoBuildCommand.
import static org.junit.jupiter.api.Assertions.assertTrue;
@WithEnvVars({"KC_CACHE", "local"}) // avoid flakey port conflicts
@DistributionTest
@RawDistOnly(reason = "Containers are immutable")
@TestMethodOrder(OrderAnnotation.class)
@Tag(DistributionTest.WIN)
public class BuildAndStartDistTest {
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
void testBuildAndStart(KeycloakDistribution dist) {
RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class);
void testBuildAndStart(KeycloakRunner runner) {
RawKeycloakDistribution rawDist = runner.getDistribution(RawKeycloakDistribution.class);
// start using based on the build options set via CLI
CLIResult cliResult = rawDist.run("build", "--db=dev-file");
CLIResult cliResult = runner.run("build", "--db=dev-file");
cliResult.assertBuild();
cliResult = rawDist.run("start", "--http-enabled=true", "--hostname-strict=false", OPTIMIZED_BUILD_OPTION_LONG);
cliResult = runner.run("start", "--http-enabled=true", "--hostname-strict=false", OPTIMIZED_BUILD_OPTION_LONG);
cliResult.assertNoBuild();
assertTrue(cliResult.getErrorOutput().isBlank());
@ -59,19 +59,19 @@ public class BuildAndStartDistTest {
rawDist.setProperty("hostname-strict", "false");
rawDist.setProperty("http-relative-path", "/auth");
rawDist.setProperty("db", "dev-file");
cliResult = rawDist.run("build");
cliResult = runner.run("build");
cliResult.assertBuild();
cliResult = rawDist.run("start", OPTIMIZED_BUILD_OPTION_LONG);
cliResult = runner.run("start", OPTIMIZED_BUILD_OPTION_LONG);
cliResult.assertNoBuild();
assertTrue(cliResult.getErrorOutput().isBlank(), cliResult.getErrorOutput());
// running start without optimized flag should not cause a build
cliResult = rawDist.run("start");
cliResult = runner.run("start");
cliResult.assertNoBuild();
assertTrue(cliResult.getErrorOutput().isBlank());
// remove the build option from conf file to force a build during start
rawDist.removeProperty("http-relative-path");
cliResult = rawDist.run("start");
cliResult = runner.run("start");
cliResult.assertBuild();
assertTrue(cliResult.getErrorOutput().isBlank());
}
@ -79,31 +79,31 @@ public class BuildAndStartDistTest {
@Test
@WithEnvVars({"KEYCLOAK_ADMIN", "oldadmin123", "KEYCLOAK_ADMIN_PASSWORD", "oldadmin123"})
@Launch({"start-dev"})
void testCreateLegacyAdmin(KeycloakDistribution dist, LaunchResult result) {
assertAdminCreation(dist, result, "oldadmin123", "oldadmin123", "oldadmin123");
void testCreateLegacyAdmin(KeycloakRunner runner, LaunchResult result) {
assertAdminCreation(runner, result, "oldadmin123", "oldadmin123", "oldadmin123");
}
@Test
@WithEnvVars({"KC_BOOTSTRAP_ADMIN_USERNAME", "admin123", "KC_BOOTSTRAP_ADMIN_PASSWORD", "admin123"})
@Launch({"start-dev"})
void testCreateAdmin(KeycloakDistribution dist, LaunchResult result) {
assertAdminCreation(dist, result, "admin123", "admin123", "admin123");
void testCreateAdmin(KeycloakRunner runner, LaunchResult result) {
assertAdminCreation(runner, result, "admin123", "admin123", "admin123");
}
@Test
@WithEnvVars({"KC_BOOTSTRAP_ADMIN_USERNAME", "admin123", "KC_BOOTSTRAP_ADMIN_PASSWORD", "admin123"})
@Launch({"start-dev"})
void testCreateDifferentAdmin(KeycloakDistribution dist, LaunchResult result) {
assertAdminCreation(dist, result, "admin123", "new-admin", "new-admin");
void testCreateDifferentAdmin(KeycloakRunner runner, LaunchResult result) {
assertAdminCreation(runner, result, "admin123", "new-admin", "new-admin");
}
private void assertAdminCreation(KeycloakDistribution dist, LaunchResult result, String initialUsername, String nextUsername, String password) {
private void assertAdminCreation(KeycloakRunner runner, LaunchResult result, String initialUsername, String nextUsername, String password) {
assertTrue(result.getOutput().contains("Created temporary admin user with username " + initialUsername),
() -> "The Output:\n" + result.getOutput() + "doesn't contains the expected string.");
dist.setEnvVar("KC_BOOTSTRAP_ADMIN_USERNAME", nextUsername);
dist.setEnvVar("KC_BOOTSTRAP_ADMIN_PASSWORD", password);
CLIResult cliResult = dist.run("start-dev", "--log-level=org.keycloak.services:debug");
runner.setEnvVar("KC_BOOTSTRAP_ADMIN_USERNAME", nextUsername);
runner.setEnvVar("KC_BOOTSTRAP_ADMIN_PASSWORD", password);
CLIResult cliResult = runner.run("start-dev", "--log-level=org.keycloak.services:debug");
cliResult.assertNoMessage("Added temporary admin user '");
cliResult.assertStartedDevMode();

View file

@ -22,9 +22,11 @@ import java.nio.file.Paths;
import org.keycloak.config.database.Database;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.WithEnvVars;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawKeycloakDistribution;
import io.quarkus.test.junit.main.Launch;
import org.junit.jupiter.api.Test;
@ -84,20 +86,21 @@ class BuildCommandDistTest {
@Test
@RawDistOnly(reason = "Raw is enough and we avoid issues with including custom conf file in the container")
public void testFailInvalidOptionInConf(KeycloakDistribution distribution) {
CLIResult cliResult = distribution.run(CONFIG_FILE_LONG_NAME + "=" + Paths.get("src/test/resources/BuildCommandDistTest/keycloak.conf").toAbsolutePath().normalize(), "build");
public void testFailInvalidOptionInConf(KeycloakRunner runner) {
CLIResult cliResult = runner.run(CONFIG_FILE_LONG_NAME + "=" + Paths.get("src/test/resources/BuildCommandDistTest/keycloak.conf").toAbsolutePath().normalize(), "build");
cliResult.assertError("Invalid value for option 'kc.db' in keycloak.conf: foo. Expected values are: dev-file, dev-mem, mariadb, mssql, mysql, oracle, postgres");
}
@Test
@RawDistOnly(reason = "Containers are immutable")
void testDoNotRecordRuntimeOptionsDuringBuild(KeycloakDistribution distribution) {
distribution.setProperty("db-url", "invalid");
CLIResult cliResult = distribution.run("build");
void testDoNotRecordRuntimeOptionsDuringBuild(KeycloakRunner runner) {
RawKeycloakDistribution rawDist = runner.getDistribution(RawKeycloakDistribution.class);
rawDist.setProperty("db-url", "invalid");
CLIResult cliResult = runner.run("build");
cliResult.assertBuild();
distribution.removeProperty("db-url");
rawDist.removeProperty("db-url");
CLIResult result = distribution.run("start", "--hostname=mykeycloak", "--cache=local", "--http-enabled=true", OPTIMIZED_BUILD_OPTION_LONG);
CLIResult result = runner.run("start", "--hostname=mykeycloak", "--cache=local", "--http-enabled=true", OPTIMIZED_BUILD_OPTION_LONG);
result.assertStarted();
}

View file

@ -24,21 +24,20 @@ import java.util.Arrays;
import org.keycloak.config.CachingOptions;
import org.keycloak.config.Option;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.DryRun;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.SkipRealmBootstrap;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.junit5.extension.StopServer;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.junit.jupiter.api.Test;
@DistributionTest
@SkipRealmBootstrap
@DistributionTest(stopServer = Mode.BEFORE_BOOTSTRAP)
public class CacheEmbeddedMtlsDistTest {
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@RawDistOnly(reason = "Containers are immutable")
public void testCacheEmbeddedMtlsDisabled(KeycloakDistribution dist) {
public void testCacheEmbeddedMtlsDisabled(KeycloakRunner runner) {
for (var option : Arrays.asList(
CachingOptions.CACHE_EMBEDDED_MTLS_TRUSTSTORE,
CachingOptions.CACHE_EMBEDDED_MTLS_KEYSTORE,
@ -46,24 +45,24 @@ public class CacheEmbeddedMtlsDistTest {
CachingOptions.CACHE_EMBEDDED_MTLS_TRUSTSTORE_PASSWORD,
CachingOptions.CACHE_EMBEDDED_MTLS_ROTATION
)) {
var result = dist.run("start-dev", "--cache=ispn", "--cache-embedded-mtls-enabled=false", "--%s=1".formatted(option.getKey()));
var result = runner.run("start-dev", "--cache=ispn", "--cache-embedded-mtls-enabled=false", "--%s=1".formatted(option.getKey()));
result.assertError("Disabled option: '--%s'. Available only when property 'cache-embedded-mtls-enabled' is enabled".formatted(option.getKey()));
}
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@RawDistOnly(reason = "Containers are immutable")
public void testCacheEmbeddedMtlsFileValidation(KeycloakDistribution dist) {
doFileAndPasswordValidation(dist, CachingOptions.CACHE_EMBEDDED_MTLS_KEYSTORE, CachingOptions.CACHE_EMBEDDED_MTLS_KEYSTORE_PASSWORD);
doFileAndPasswordValidation(dist, CachingOptions.CACHE_EMBEDDED_MTLS_TRUSTSTORE, CachingOptions.CACHE_EMBEDDED_MTLS_TRUSTSTORE_PASSWORD);
public void testCacheEmbeddedMtlsFileValidation(KeycloakRunner runner) {
doFileAndPasswordValidation(runner, CachingOptions.CACHE_EMBEDDED_MTLS_KEYSTORE, CachingOptions.CACHE_EMBEDDED_MTLS_KEYSTORE_PASSWORD);
doFileAndPasswordValidation(runner, CachingOptions.CACHE_EMBEDDED_MTLS_TRUSTSTORE, CachingOptions.CACHE_EMBEDDED_MTLS_TRUSTSTORE_PASSWORD);
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@RawDistOnly(reason = "Containers are immutable")
public void testCacheEmbeddedMtlsFileExistsValidation(KeycloakDistribution dist) throws IOException {
var result = dist.run(
public void testCacheEmbeddedMtlsFileExistsValidation(KeycloakRunner runner) throws IOException {
var result = runner.run(
"start-dev",
"--cache=ispn",
"--cache-embedded-mtls-enabled=true",
@ -75,7 +74,7 @@ public class CacheEmbeddedMtlsDistTest {
result.assertError("The 'cache-embedded-mtls-key-store-file' file 'keystore.p12' does not exist");
File keystore = Util.createTempFile("key", ".p12");
result = dist.run(
result = runner.run(
"start-dev",
"--cache=ispn",
"--cache-embedded-mtls-enabled=true",
@ -87,37 +86,37 @@ public class CacheEmbeddedMtlsDistTest {
result.assertError("The 'cache-embedded-mtls-trust-store-file' file 'truststore.p12' does not exist");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@RawDistOnly(reason = "Containers are immutable")
public void testCacheEmbeddedMtlsValidation(KeycloakDistribution dist) {
public void testCacheEmbeddedMtlsValidation(KeycloakRunner runner) {
var key = CachingOptions.CACHE_EMBEDDED_MTLS_ROTATION.getKey();
// test zero
var result = dist.run("start-dev", "--cache=ispn", "--cache-embedded-mtls-enabled=true", "--%s=0".formatted(key));
var result = runner.run("start-dev", "--cache=ispn", "--cache-embedded-mtls-enabled=true", "--%s=0".formatted(key));
result.assertError("JGroups MTLS certificate rotation in '%s' option must positive.".formatted(key));
// test negative
result = dist.run("start-dev", "--cache=ispn", "--cache-embedded-mtls-enabled=true", "--%s=-1".formatted(key));
result = runner.run("start-dev", "--cache=ispn", "--cache-embedded-mtls-enabled=true", "--%s=-1".formatted(key));
result.assertError("JGroups MTLS certificate rotation in '%s' option must positive.".formatted(key));
// test blank
result = dist.run("start-dev", "--cache=ispn", "--cache-embedded-mtls-enabled=true", "--%s=".formatted(key));
result = runner.run("start-dev", "--cache=ispn", "--cache-embedded-mtls-enabled=true", "--%s=".formatted(key));
result.assertError("Invalid empty value for option '--%s'".formatted(key));
}
@Test
@RawDistOnly(reason = "Containers are immutable")
public void testCacheEmbeddedMtlsEnabled(KeycloakDistribution dist) {
var result = dist.run("start-dev", "--cache=ispn", "--cache-embedded-mtls-enabled=true");
public void testCacheEmbeddedMtlsEnabled(KeycloakRunner runner) {
var result = runner.run("start-dev", "--cache=ispn", "--cache-embedded-mtls-enabled=true");
result.assertMessage("JGroups JDBC_PING discovery enabled.");
result.assertMessage("JGroups Encryption enabled (mTLS).");
}
private void doFileAndPasswordValidation(KeycloakDistribution dist, Option<String> fileOption, Option<String> passwordOption) {
var result = dist.run("start-dev", "--cache=ispn", "--cache-embedded-mtls-enabled=true", "--%s=file".formatted(fileOption.getKey()));
private void doFileAndPasswordValidation(KeycloakRunner runner, Option<String> fileOption, Option<String> passwordOption) {
var result = runner.run("start-dev", "--cache=ispn", "--cache-embedded-mtls-enabled=true", "--%s=file".formatted(fileOption.getKey()));
result.assertError("The option '%s' requires '%s' to be enabled.".formatted(fileOption.getKey(), passwordOption.getKey()));
result = dist.run("start-dev", "--cache=ispn", "--cache-embedded-mtls-enabled=true", "--%s=secret".formatted(passwordOption.getKey()));
result = runner.run("start-dev", "--cache=ispn", "--cache-embedded-mtls-enabled=true", "--%s=secret".formatted(passwordOption.getKey()));
result.assertError("The option '%s' requires '%s' to be enabled.".formatted(passwordOption.getKey(), fileOption.getKey()));
}
}

View file

@ -25,10 +25,10 @@ import java.util.function.Consumer;
import org.keycloak.it.junit5.extension.BeforeStartDistribution;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.SkipRealmBootstrap;
import org.keycloak.it.junit5.extension.Storage;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.utils.RawKeycloakDistribution;
import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
@ -45,10 +45,8 @@ import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.io.TempDir;
import org.testcontainers.shaded.com.google.common.io.Files;
@DistributionTest(defaultOptions = {"--db=dev-file", "--http-enabled=true", "--hostname-strict=false"})
@DistributionTest(localCache = false, stopServer = Mode.BEFORE_BOOTSTRAP, defaultOptions = {"--db=dev-file", "--http-enabled=true", "--hostname-strict=false"})
@RawDistOnly(reason = "Not possible to mount files using docker.")
@Storage(defaultLocalCache = false)
@SkipRealmBootstrap
@Tag(DistributionTest.SMOKE)
@Tag(DistributionTest.SLOW)
@TestMethodOrder(OrderAnnotation.class)
@ -210,19 +208,19 @@ public class ClusterConfigDistTest {
}
@Test
void testAbsoluteCacheFile(KeycloakDistribution dist, @TempDir Path tempDir) throws Exception {
void testAbsoluteCacheFile(KeycloakRunner runner, @TempDir Path tempDir) throws Exception {
File customCacheFile = tempDir.resolve("my-custom-cache.xml").toFile();
File missingCacheFile = tempDir.resolve("my-missing-cache.xml").toFile();
try (InputStream is = ClusterConfigDistTest.class.getResourceAsStream("/cache-ispn-custom-cache.xml")) {
Files.write(is.readAllBytes(), customCacheFile);
}
LaunchResult result = dist.run("start", "--cache-config-file=" + customCacheFile.getAbsolutePath());
LaunchResult result = runner.run("start", "--cache-config-file=" + customCacheFile.getAbsolutePath());
Assertions.assertEquals(0, result.exitCode());
MatcherAssert.assertThat(result.getOutput(), Matchers.containsString(WARN_DEFAULT_CACHE_MUTATIONS));
String absolutePath = missingCacheFile.getAbsolutePath();
result = dist.run("start", "--cache-config-file=" + absolutePath);
result = runner.run("start", "--cache-config-file=" + absolutePath);
Assertions.assertEquals(2, result.exitCode());
MatcherAssert.assertThat(result.getErrorOutput(), Matchers.matchesRegex("Cache config file '" + absolutePath + "' does not exist"));
}
@ -264,18 +262,18 @@ public class ClusterConfigDistTest {
result.assertNoMessage("Ignoring unbounded max-count for cache 'sessions'");
}
public static class ConfigureCacheUsingAsyncEncryption implements Consumer<KeycloakDistribution> {
public static class ConfigureCacheUsingAsyncEncryption implements Consumer<RawKeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
public void accept(RawKeycloakDistribution distribution) {
distribution.copyOrReplaceFileFromClasspath("/cache-ispn-asym-enc.xml", Path.of("conf", "cache-ispn-asym-enc.xml"));
}
}
public static class ConfigureCustomCache implements Consumer<KeycloakDistribution> {
public static class ConfigureCustomCache implements Consumer<RawKeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
public void accept(RawKeycloakDistribution distribution) {
distribution.copyOrReplaceFileFromClasspath("/cache-ispn-custom-cache.xml", Path.of("conf", "cache-ispn-custom-cache.xml"));
}
}

View file

@ -27,10 +27,11 @@ import java.util.stream.Stream;
import org.keycloak.config.CachingOptions;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.junit5.extension.TestProvider;
import org.keycloak.it.resource.realm.TestRealmResourceTestProvider;
import org.keycloak.it.utils.KeycloakDistribution;
import com.google.common.base.CaseFormat;
import org.infinispan.commons.dataconversion.MediaType;
@ -51,12 +52,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* @author Ryan Emerson <remerson@redhat.com>
*/
@DistributionTest(keepAlive = true, enableTls = true)
@DistributionTest(stopServer = Mode.MANUAL, enableTls = true)
@RawDistOnly(reason = "Containers are immutable")
public class ClusterConfigKeepAliveDistTest {
@Test
@TestProvider(TestRealmResourceTestProvider.class)
void testMaxCountApplied(KeycloakDistribution dist) {
void testMaxCountApplied(KeycloakRunner runner) {
int maxCount = 100;
Set<String> maxCountCaches = Stream.of(CachingOptions.LOCAL_MAX_COUNT_CACHES, CachingOptions.CLUSTERED_MAX_COUNT_CACHES)
.flatMap(Arrays::stream)
@ -67,7 +68,7 @@ public class ClusterConfigKeepAliveDistTest {
sb.append(" --").append(CachingOptions.cacheMaxCountProperty(cache)).append("=").append(maxCount);
String args = sb.toString();
dist.run(args.split(" "));
runner.run(args.split(" "));
for (String cache : maxCountCaches) {
Configuration config = getCacheConfiguration(cache);
@ -77,19 +78,19 @@ public class ClusterConfigKeepAliveDistTest {
@Test
@TestProvider(TestRealmResourceTestProvider.class)
void testNumOwnersWithPersistentSessions(KeycloakDistribution dist) {
doNumOwnerTest(dist, false);
void testNumOwnersWithPersistentSessions(KeycloakRunner runner) {
doNumOwnerTest(runner, false);
}
@Test
@TestProvider(TestRealmResourceTestProvider.class)
void testNumOwnersWithVolatileSessions(KeycloakDistribution dist) {
doNumOwnerTest(dist, true);
void testNumOwnersWithVolatileSessions(KeycloakRunner runner) {
doNumOwnerTest(runner, true);
}
@Test
@TestProvider(TestRealmResourceTestProvider.class)
void testCheckMinimumNumOwners(KeycloakDistribution dist) {
void testCheckMinimumNumOwners(KeycloakRunner runner) {
List<String> args = new ArrayList<>();
args.add("start-dev");
args.add("--cache=ispn");
@ -99,13 +100,13 @@ public class ClusterConfigKeepAliveDistTest {
.map(cache -> CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, cache))
.map("--spi-cache-embedded--default--%s-owners=1"::formatted)
.forEach(args::add);
dist.run(args);
runner.run(args);
// forces the numOwner to 2 to prevent data loss.
assertNumOwner(Arrays.stream(CLUSTERED_CACHE_NUM_OWNERS), 2);
}
private void doNumOwnerTest(KeycloakDistribution dist, boolean volatileSessions) {
private void doNumOwnerTest(KeycloakRunner runner, boolean volatileSessions) {
final int owners = 5;
List<String> args = new ArrayList<>();
args.add("start-dev");
@ -118,7 +119,7 @@ public class ClusterConfigKeepAliveDistTest {
.map(cache -> CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, cache))
.map(cache -> "--spi-cache-embedded--default--%s-owners=%s".formatted(cache, owners))
.forEach(args::add);
dist.run(args);
runner.run(args);
Stream<String> caches = Arrays.stream(CLUSTERED_CACHE_NUM_OWNERS);
if (!volatileSessions) {

View file

@ -19,9 +19,9 @@ package org.keycloak.it.cli.dist;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.TestProvider;
import org.keycloak.it.utils.KeycloakDistribution;
import com.acme.provider.legacy.jpa.entity.CustomJpaEntityProvider;
import io.quarkus.test.junit.main.Launch;
@ -37,13 +37,13 @@ public class CustomJpaEntityProviderDistTest {
private static final String MULTIPLE_DATASOURCES_MSG = "Multiple datasources are specified: <default>, client-store, new-user-store, pu-without-dialect-store";
@Test
void dbKindSpecifiedInBuildTime(KeycloakDistribution dist) {
var result = dist.run("build", "--db=dev-file", "--db-kind-new-user-store=dev-mem", "--db-kind-pu-without-dialect-store=dev-mem");
void dbKindSpecifiedInBuildTime(KeycloakRunner runner) {
var result = runner.run("build", "--db=dev-file", "--db-kind-new-user-store=dev-mem", "--db-kind-pu-without-dialect-store=dev-mem");
result.assertMessage(MULTIPLE_DATASOURCES_MSG);
result.assertMessage("You have set DB kind for 'client-store' datasource via a Quarkus property. This approach is deprecated and you should use the Keycloak 'db-kind-client-store' property.");
result.assertBuild();
result = dist.run("start", "--optimized", "--http-enabled=true", "--hostname-strict=false");
result = runner.run("start", "--optimized", "--http-enabled=true", "--hostname-strict=false");
result.assertNoError("Detected additional named datasources. You need to explicitly set the DB kind for the datasource(s) to properly work as: db-kind-user-store");
result.assertMessage("Datasource 'client-store' was deactivated automatically because its URL is not set");

View file

@ -21,8 +21,8 @@ import java.nio.file.Path;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawKeycloakDistribution;
import org.junit.jupiter.api.Tag;
@ -36,48 +36,48 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class ExportDistTest {
@Test
void testExport(KeycloakDistribution dist) {
CLIResult cliResult = dist.run("build");
void testExport(KeycloakRunner runner) {
CLIResult cliResult = runner.run("build");
cliResult = dist.run("export", "--realm=master", "--dir=.");
cliResult = runner.run("export", "--realm=master", "--dir=.");
cliResult.assertMessage("Export of realm 'master' requested.");
cliResult.assertMessage("Export finished successfully");
cliResult.assertNoMessage("Changes detected in configuration");
cliResult.assertNoMessage("Listening on: http");
cliResult = dist.run("export", "--realm=master");
cliResult = runner.run("export", "--realm=master");
cliResult.assertError("Must specify either --dir or --file options.");
cliResult = dist.run("export", "--file=master", "--users=skip");
cliResult = runner.run("export", "--file=master", "--users=skip");
cliResult.assertError("Property '--users' can be used only when exporting to a directory, or value set to 'same_file' when exporting to a file.");
cliResult = dist.run("export", "--file=some-file", "--users=same_file");
cliResult = runner.run("export", "--file=some-file", "--users=same_file");
cliResult.assertNoError("Property '--users' can be used only when exporting to a directory, or value set to 'same_file' when exporting to a file.");
cliResult.assertMessage("Exporting model into file");
cliResult = dist.run("export", "--dir=some-dir", "--users=skip");
cliResult = runner.run("export", "--dir=some-dir", "--users=skip");
cliResult.assertMessage("Realm 'master' - data exported");
}
@Test
void testExportRealmFGAPEnabled(KeycloakDistribution dist) {
RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class);
void testExportRealmFGAPEnabled(KeycloakRunner runner) {
RawKeycloakDistribution rawDist = runner.getDistribution(RawKeycloakDistribution.class);
Path importDir = rawDist.getDistPath().resolve("data").resolve("import");
assertTrue(importDir.toFile().mkdirs());
dist.copyOrReplaceFileFromClasspath("/fgap-realm.json", importDir.resolve("fgap-realm.json"));
rawDist.run("start-dev","-v", "--import-realm", "--features=admin-fine-grained-authz:v2");
rawDist.copyOrReplaceFileFromClasspath("/fgap-realm.json", importDir.resolve("fgap-realm.json"));
runner.run("start-dev","-v", "--import-realm", "--features=admin-fine-grained-authz:v2");
rawDist.stop();
CLIResult cliResult = rawDist.run("export", "--realm=fgap", "--dir=" + importDir.toAbsolutePath(), "--features=admin-fine-grained-authz:v2");
CLIResult cliResult = runner.run("export", "--realm=fgap", "--dir=" + importDir.toAbsolutePath(), "--features=admin-fine-grained-authz:v2");
cliResult.assertMessage("Export of realm 'fgap' requested.");
cliResult.assertMessage("Export finished successfully");
}
@Test
void testExportNonExistent(KeycloakDistribution dist) {
CLIResult cliResult = dist.run("build");
void testExportNonExistent(KeycloakRunner runner) {
CLIResult cliResult = runner.run("build");
cliResult = dist.run("export", "--realm=non-existent-realm", "--dir=.");
cliResult = runner.run("export", "--realm=non-existent-realm", "--dir=.");
cliResult.assertMessage("realm not found by realm name 'non-existent-realm'");
}

View file

@ -7,9 +7,9 @@ import java.util.stream.Collectors;
import org.keycloak.common.Profile;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.SkipRealmBootstrap;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.quarkus.runtime.cli.command.Build;
import org.keycloak.quarkus.runtime.cli.command.Start;
import org.keycloak.quarkus.runtime.cli.command.StartDev;
@ -27,11 +27,10 @@ import static org.keycloak.quarkus.runtime.cli.command.AbstractAutoBuildCommand.
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
@DistributionTest
@DistributionTest(stopServer = Mode.BEFORE_BOOTSTRAP)
@RawDistOnly(reason = "Containers are immutable")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Tag(DistributionTest.SMOKE)
@SkipRealmBootstrap
public class FeaturesDistTest {
private static final String PREVIEW_FEATURES_EXPECTED_LOG = "Preview features enabled: " + Arrays.stream(Profile.Feature.values())
@ -48,12 +47,12 @@ public class FeaturesDistTest {
.collect(Collectors.joining(", "));
@Test
public void testEnableOnBuild(KeycloakDistribution dist) {
CLIResult cliResult = dist.run(Build.NAME, "--db=dev-file", "--features=preview");
public void testEnableOnBuild(KeycloakRunner runner) {
CLIResult cliResult = runner.run(Build.NAME, "--db=dev-file", "--features=preview");
cliResult.assertBuild();
assertPreviewFeaturesEnabled(cliResult);
cliResult = dist.run(Start.NAME, "--http-enabled=true", "--hostname-strict=false", OPTIMIZED_BUILD_OPTION_LONG);
cliResult = runner.run(Start.NAME, "--http-enabled=true", "--hostname-strict=false", OPTIMIZED_BUILD_OPTION_LONG);
assertPreviewFeaturesEnabled(cliResult);
}

View file

@ -22,15 +22,16 @@ import java.nio.file.Path;
import org.keycloak.crypto.fips.FIPS1402Provider;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.utils.RawKeycloakDistribution;
import io.quarkus.test.junit.main.Launch;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@DistributionTest(keepAlive = true, defaultOptions = { "--db=dev-file", "--features=fips", "--http-enabled=true", "--hostname-strict=false" })
@DistributionTest(stopServer = Mode.MANUAL, defaultOptions = { "--db=dev-file", "--features=fips", "--http-enabled=true", "--hostname-strict=false" })
@RawDistOnly(reason = "Containers are immutable")
@Tag(DistributionTest.SLOW)
public class FipsDistTest {
@ -38,9 +39,9 @@ public class FipsDistTest {
private static final String BCFIPS_VERSION = "BCFIPS version 2.0102";
@Test
void testFipsNonApprovedMode(KeycloakDistribution dist) {
runOnFipsEnabledDistribution(dist, () -> {
CLIResult cliResult = dist.run("start");
void testFipsNonApprovedMode(KeycloakRunner runner) {
runOnFipsEnabledDistribution(runner, () -> {
CLIResult cliResult = runner.run("start");
cliResult.assertStarted();
// Not shown as FIPS is not a preview anymore
cliResult.assertMessageWasShownExactlyNumberOfTimes("Preview features enabled: fips:v1", 0);
@ -49,17 +50,17 @@ public class FipsDistTest {
}
@Test
void testFipsApprovedMode(KeycloakDistribution dist) {
runOnFipsEnabledDistribution(dist, () -> {
dist.setEnvVar("KC_BOOTSTRAP_ADMIN_USERNAME", "admin");
dist.setEnvVar("KC_BOOTSTRAP_ADMIN_PASSWORD", "admin");
void testFipsApprovedMode(KeycloakRunner runner) {
runOnFipsEnabledDistribution(runner, () -> {
runner.setEnvVar("KC_BOOTSTRAP_ADMIN_USERNAME", "admin");
runner.setEnvVar("KC_BOOTSTRAP_ADMIN_PASSWORD", "admin");
CLIResult cliResult = dist.run("start", "--fips-mode=strict");
CLIResult cliResult = runner.run("start", "--fips-mode=strict");
cliResult.assertMessage("password must be at least 112 bits");
cliResult.assertMessage("FIPS1402Provider created: KC(" + BCFIPS_VERSION + " Approved Mode, FIPS-JVM: " + FIPS1402Provider.isSystemFipsEnabled() + ")");
dist.setEnvVar("KC_BOOTSTRAP_ADMIN_PASSWORD", "adminadminadmin");
cliResult = dist.run("start", "--fips-mode=strict");
runner.setEnvVar("KC_BOOTSTRAP_ADMIN_PASSWORD", "adminadminadmin");
cliResult = runner.run("start", "--fips-mode=strict");
cliResult.assertStarted();
cliResult.assertMessage("Created temporary admin user with username admin");
});
@ -72,101 +73,101 @@ public class FipsDistTest {
}
@Test
void testUnsupportedHttpsJksKeyStoreInStrictMode(KeycloakDistribution dist) {
runOnFipsEnabledDistribution(dist, () -> {
dist.copyOrReplaceFileFromClasspath("/server.keystore", Path.of("conf", "server.keystore"));
CLIResult cliResult = dist.run("start", "--fips-mode=strict");
void testUnsupportedHttpsJksKeyStoreInStrictMode(KeycloakRunner runner) {
runOnFipsEnabledDistribution(runner, () -> {
runner.getDistribution(RawKeycloakDistribution.class).copyOrReplaceFileFromClasspath("/server.keystore", Path.of("conf", "server.keystore"));
CLIResult cliResult = runner.run("start", "--fips-mode=strict");
cliResult.assertMessage("ERROR: java.lang.IllegalArgumentException: malformed sequence");
});
}
@Test
void testHttpsBcfksKeyStoreInStrictMode(KeycloakDistribution dist) {
runOnFipsEnabledDistribution(dist, () -> {
dist.copyOrReplaceFileFromClasspath("/server.keystore.bcfks", Path.of("conf", "server.keystore"));
CLIResult cliResult = dist.run("start", "--fips-mode=strict", "--https-key-store-password=passwordpassword");
void testHttpsBcfksKeyStoreInStrictMode(KeycloakRunner runner) {
runOnFipsEnabledDistribution(runner, () -> {
runner.getDistribution(RawKeycloakDistribution.class).copyOrReplaceFileFromClasspath("/server.keystore.bcfks", Path.of("conf", "server.keystore"));
CLIResult cliResult = runner.run("start", "--fips-mode=strict", "--https-key-store-password=passwordpassword");
cliResult.assertStarted();
});
}
@Test
void testHttpsBcfksTrustStoreInStrictMode(KeycloakDistribution dist) {
runOnFipsEnabledDistribution(dist, () -> {
dist.copyOrReplaceFileFromClasspath("/server.keystore.bcfks", Path.of("conf", "server.keystore"));
RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class);
void testHttpsBcfksTrustStoreInStrictMode(KeycloakRunner runner) {
runOnFipsEnabledDistribution(runner, () -> {
RawKeycloakDistribution rawDist = runner.getDistribution(RawKeycloakDistribution.class);
rawDist.copyOrReplaceFileFromClasspath("/server.keystore.bcfks", Path.of("conf", "server.keystore"));
Path truststorePath = rawDist.getDistPath().resolve("conf").resolve("server.keystore").toAbsolutePath();
// https-trust-store-type should be automatically set to bcfks in fips-mode=strict
CLIResult cliResult = dist.run("--verbose", "start", "--fips-mode=strict", "--https-key-store-password=passwordpassword",
CLIResult cliResult = runner.run("--verbose", "start", "--fips-mode=strict", "--https-key-store-password=passwordpassword",
"--https-trust-store-file=" + truststorePath, "--https-trust-store-password=passwordpassword");
cliResult.assertStarted();
});
}
@Test
void testUnencryptedPkcs12TrustStoreInStrictMode(KeycloakDistribution dist) {
runOnFipsEnabledDistribution(dist, () -> {
void testUnencryptedPkcs12TrustStoreInStrictMode(KeycloakRunner runner) {
runOnFipsEnabledDistribution(runner, () -> {
String truststoreName = "keycloak-truststore.p12";
dist.copyOrReplaceFileFromClasspath("/" + truststoreName, Path.of("conf", truststoreName));
RawKeycloakDistribution rawDist = runner.getDistribution(RawKeycloakDistribution.class);
rawDist.copyOrReplaceFileFromClasspath("/" + truststoreName, Path.of("conf", truststoreName));
RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class);
Path truststorePath = rawDist.getDistPath().resolve("conf").resolve(truststoreName).toAbsolutePath();
CLIResult cliResult = dist.run("--verbose", "start", "--fips-mode=strict", "--truststore-paths=" + truststorePath);
CLIResult cliResult = runner.run("--verbose", "start", "--fips-mode=strict", "--truststore-paths=" + truststorePath);
cliResult.assertStarted();
});
}
@Test
void testUnsupportedHttpsPkcs12KeyStoreInStrictMode(KeycloakDistribution dist) {
runOnFipsEnabledDistribution(dist, () -> {
dist.copyOrReplaceFileFromClasspath("/server.keystore.pkcs12", Path.of("conf", "server.keystore"));
CLIResult cliResult = dist.run("start", "--fips-mode=strict", "--https-key-store-password=passwordpassword");
void testUnsupportedHttpsPkcs12KeyStoreInStrictMode(KeycloakRunner runner) {
runOnFipsEnabledDistribution(runner, () -> {
runner.getDistribution(RawKeycloakDistribution.class).copyOrReplaceFileFromClasspath("/server.keystore.pkcs12", Path.of("conf", "server.keystore"));
CLIResult cliResult = runner.run("start", "--fips-mode=strict", "--https-key-store-password=passwordpassword");
cliResult.assertMessage("ERROR: java.lang.IllegalArgumentException: malformed sequence");
});
}
@Test
void testHttpsPkcs12KeyStoreInNonApprovedMode(KeycloakDistribution dist) {
runOnFipsEnabledDistribution(dist, () -> {
dist.copyOrReplaceFileFromClasspath("/server.keystore.pkcs12", Path.of("conf", "server.keystore"));
CLIResult cliResult = dist.run("start", "--fips-mode=non-strict", "--https-key-store-password=passwordpassword");
void testHttpsPkcs12KeyStoreInNonApprovedMode(KeycloakRunner runner) {
runOnFipsEnabledDistribution(runner, () -> {
runner.getDistribution(RawKeycloakDistribution.class).copyOrReplaceFileFromClasspath("/server.keystore.pkcs12", Path.of("conf", "server.keystore"));
CLIResult cliResult = runner.run("start", "--fips-mode=non-strict", "--https-key-store-password=passwordpassword");
cliResult.assertStarted();
});
}
@Test
void testHttpsPkcs12TrustStoreInNonApprovedMode(KeycloakDistribution dist) {
runOnFipsEnabledDistribution(dist, () -> {
dist.copyOrReplaceFileFromClasspath("/server.keystore.pkcs12", Path.of("conf", "server.keystore"));
void testHttpsPkcs12TrustStoreInNonApprovedMode(KeycloakRunner runner) {
runOnFipsEnabledDistribution(runner, () -> {
RawKeycloakDistribution rawDist = runner.getDistribution(RawKeycloakDistribution.class);
rawDist.copyOrReplaceFileFromClasspath("/server.keystore.pkcs12", Path.of("conf", "server.keystore"));
RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class);
Path truststorePath = rawDist.getDistPath().resolve("conf").resolve("server.keystore").toAbsolutePath();
CLIResult cliResult = dist.run("--verbose", "start", "--fips-mode=non-strict", "--https-key-store-password=passwordpassword",
CLIResult cliResult = runner.run("--verbose", "start", "--fips-mode=non-strict", "--https-key-store-password=passwordpassword",
"--https-trust-store-file=" + truststorePath, "--https-trust-store-password=passwordpassword");
cliResult.assertMessage("Unable to determine 'https-trust-store-type' automatically. Adjust the file extension or specify the property.");
dist.stop();
runner.stop();
dist.copyOrReplaceFileFromClasspath("/server.keystore.pkcs12", Path.of("conf", "server.p12"));
rawDist.copyOrReplaceFileFromClasspath("/server.keystore.pkcs12", Path.of("conf", "server.p12"));
rawDist = dist.unwrap(RawKeycloakDistribution.class);
rawDist = runner.getDistribution(RawKeycloakDistribution.class);
truststorePath = rawDist.getDistPath().resolve("conf").resolve("server.p12").toAbsolutePath();
cliResult = dist.run("--verbose", "start", "--fips-mode=non-strict", "--https-key-store-password=passwordpassword",
cliResult = runner.run("--verbose", "start", "--fips-mode=non-strict", "--https-key-store-password=passwordpassword",
"--https-trust-store-file=" + truststorePath, "--https-trust-store-password=passwordpassword");
cliResult.assertStarted();
});
}
private void runOnFipsEnabledDistribution(KeycloakDistribution dist, Runnable runnable) {
installBcFips(dist);
private void runOnFipsEnabledDistribution(KeycloakRunner runner, Runnable runnable) {
installBcFips(runner);
runnable.run();
}
private void installBcFips(KeycloakDistribution dist) {
RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class);
private void installBcFips(KeycloakRunner runner) {
RawKeycloakDistribution rawDist = runner.getDistribution(RawKeycloakDistribution.class);
rawDist.copyProvider("org.bouncycastle", "bc-fips");
rawDist.copyProvider("org.bouncycastle", "bctls-fips");
rawDist.copyProvider("org.bouncycastle", "bcpkix-fips");

View file

@ -23,7 +23,8 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
@ -36,7 +37,7 @@ import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@DistributionTest(keepAlive = true,
@DistributionTest(stopServer = Mode.MANUAL,
requestPort = 9000,
containerExposedPorts = {8080, 9000})
@Tag(DistributionTest.SLOW)
@ -44,16 +45,16 @@ public class HealthDistTest {
@Test
@Launch({ "start-dev" })
void testHealthEndpointNotEnabled(KeycloakDistribution distribution) {
void testHealthEndpointNotEnabled(KeycloakRunner runner) {
assertThrows(IOException.class, () -> when().get("/health"), "Connection refused must be thrown");
distribution.setRequestPort(8080);
runner.setRequestPort(8080);
when().get("/health").then()
.statusCode(404);
}
@Test
@Launch({ "start-dev", "--health-enabled=true" })
void testHealthEndpoint(KeycloakDistribution distribution) {
void testHealthEndpoint(KeycloakRunner runner) {
when().get("/health").then()
.statusCode(200);
when().get("/health/live").then()
@ -67,15 +68,15 @@ public class HealthDistTest {
.statusCode(404);
// still nothing on main
distribution.setRequestPort(8080);
runner.setRequestPort(8080);
when().get("/health/ready").then()
.statusCode(404);
}
@Test
@Launch({ "start-dev", "--health-enabled=true", "--http-management-health-enabled=false" })
void testHealthEndpointOnMain(KeycloakDistribution distribution) {
distribution.setRequestPort(8080);
void testHealthEndpointOnMain(KeycloakRunner runner) {
runner.setRequestPort(8080);
when().get("/health/ready").then().statusCode(200);
}
@ -95,7 +96,7 @@ public class HealthDistTest {
private static final String BOOTSTRAP_COMPLETED = "Bootstrap completed";
@Test
@Launch({ "start", "--health-enabled=true", "--http-enabled=true", "--hostname-strict=false" })
@Launch({ "start", "--cache=ispn", "--health-enabled=true", "--http-enabled=true", "--hostname-strict=false" })
void testAsyncStartupEnabled(LaunchResult result) {
when().get("/health/live").then()
.statusCode(200);
@ -140,21 +141,21 @@ public class HealthDistTest {
}
@Test
void testUsingRelativePath(KeycloakDistribution distribution) {
void testUsingRelativePath(KeycloakRunner runner) {
for (String relativePath : List.of("/auth", "/auth/", "auth")) {
distribution.run("start-dev", "--health-enabled=true", "--http-management-relative-path=" + relativePath);
runner.run("start-dev", "--health-enabled=true", "--http-management-relative-path=" + relativePath);
if (!relativePath.endsWith("/")) {
relativePath = relativePath + "/";
}
when().get(relativePath + "health").then().statusCode(200);
distribution.stop();
runner.stop();
}
}
@Test
void testMultipleRequests(KeycloakDistribution distribution) throws Exception {
void testMultipleRequests(KeycloakRunner runner) throws Exception {
for (String relativePath : List.of("/", "/auth/", "auth")) {
distribution.run("start-dev", "--health-enabled=true", "--http-management-relative-path=" + relativePath);
runner.run("start-dev", "--health-enabled=true", "--http-management-relative-path=" + relativePath);
CompletableFuture<?> future = CompletableFuture.completedFuture(null);
for (int i = 0; i < 3; i++) {
@ -173,7 +174,7 @@ public class HealthDistTest {
future.get(5, TimeUnit.MINUTES);
distribution.stop();
runner.stop();
}
}
}

View file

@ -23,9 +23,9 @@ import java.util.List;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.WithEnvVars;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.cli.command.BootstrapAdmin;
import org.keycloak.quarkus.runtime.cli.command.BootstrapAdminService;
@ -48,7 +48,7 @@ import org.junit.jupiter.api.Test;
import static org.keycloak.quarkus.runtime.cli.command.AbstractAutoBuildCommand.OPTIMIZED_BUILD_OPTION_LONG;
@WithEnvVars({"KEYCLOAK_COMMAND_MODE", "ALL", "KEYCLOAK_HELP_WIDTH", "80"})
@DistributionTest
@DistributionTest(localCache = false)
@RawDistOnly(reason = "Verifying the help message output doesn't need long spin-up of docker dist tests.")
@Tag(DistributionTest.WIN)
public class HelpCommandDistTest {
@ -188,7 +188,7 @@ public class HelpCommandDistTest {
}
@Test
public void testHelpDoesNotStartReAugJvm(KeycloakDistribution dist) {
public void testHelpDoesNotStartReAugJvm(KeycloakRunner runner) {
for (String helpCmd : List.of("-h", "--help", "--help-all")) {
for (String cmd : List.of("", "start", "start-dev", "build")) {
String debugOption = "--debug";
@ -197,7 +197,7 @@ public class HelpCommandDistTest {
debugOption = "--debug=8787";
}
CLIResult run = dist.run(debugOption, cmd, helpCmd);
CLIResult run = runner.run(debugOption, cmd, helpCmd);
assertSingleJvmStarted(run);
}
}

View file

@ -20,7 +20,7 @@ package org.keycloak.it.cli.dist;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.SkipRealmBootstrap;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
@ -29,9 +29,8 @@ import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
@DistributionTest
@DistributionTest(stopServer = Mode.BEFORE_BOOTSTRAP)
@RawDistOnly(reason = "Containers are immutable")
@SkipRealmBootstrap
public class HostnameV2DistTest {
@Test
@Launch({"start", "--db=dev-file", "--http-enabled=true"})

View file

@ -26,10 +26,11 @@ import java.util.concurrent.Executors;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.junit5.extension.TestProvider;
import org.keycloak.it.resource.realm.TestRealmResourceTestProvider;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawKeycloakDistribution;
import io.quarkus.test.junit.main.Launch;
@ -44,13 +45,13 @@ import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author Vaclav Muzikar <vmuzikar@redhat.com>
*/
@DistributionTest(keepAlive = true, enableTls = true)
@DistributionTest(stopServer = Mode.MANUAL, enableTls = true)
@RawDistOnly(reason = "Containers are immutable")
public class HttpDistTest {
@Test
@TestProvider(TestRealmResourceTestProvider.class)
public void maxQueuedRequestsTest(KeycloakDistribution dist) {
dist.run("start-dev", "--http-max-queued-requests=1", "--http-pool-max-threads=1");
public void maxQueuedRequestsTest(KeycloakRunner runner) {
runner.run("start-dev", "--http-max-queued-requests=1", "--http-pool-max-threads=1");
ExecutorService executor = Executors.newFixedThreadPool(5);
try {
@ -93,24 +94,24 @@ public class HttpDistTest {
}
@Test
public void httpStoreTypeValidation(KeycloakDistribution dist) {
CLIResult result = dist.run("start", "--https-key-store-file=not-there.ks", "--hostname-strict=false");
public void httpStoreTypeValidation(KeycloakRunner runner) {
CLIResult result = runner.run("start", "--https-key-store-file=not-there.ks", "--hostname-strict=false");
result.assertExitCode(-1);
result.assertMessage("ERROR: Unable to determine 'https-key-store-type' automatically. Adjust the file extension or specify the property");
result = dist.run("start", "--https-trust-store-file=not-there.ks", "--hostname-strict=false");
result = runner.run("start", "--https-trust-store-file=not-there.ks", "--hostname-strict=false");
result.assertExitCode(-1);
result.assertMessage("ERROR: Unable to determine 'https-trust-store-type' automatically. Adjust the file extension or specify the property");
result = dist.run("start", "--https-key-store-file=not-there.ks", "--hostname-strict=false", "--https-key-store-type=jdk");
result = runner.run("start", "--https-key-store-file=not-there.ks", "--hostname-strict=false", "--https-key-store-type=jdk");
result.assertExitCode(-1);
result.assertMessage("ERROR: Failed to load 'https-*' material: NoSuchFileException not-there.ks");
dist.copyOrReplaceFileFromClasspath("/server.keystore.pkcs12", Path.of("conf", "server.p12"));
RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class);
RawKeycloakDistribution rawDist = runner.getDistribution(RawKeycloakDistribution.class);
rawDist.copyOrReplaceFileFromClasspath("/server.keystore.pkcs12", Path.of("conf", "server.p12"));
Path truststorePath = rawDist.getDistPath().resolve("conf").resolve("server.p12").toAbsolutePath();
result = dist.run("start", "--https-trust-store-file=" + truststorePath, "--hostname-strict=false");
result = runner.run("start", "--https-trust-store-file=" + truststorePath, "--hostname-strict=false");
result.assertExitCode(-1);
result.assertMessage("ERROR: No trust store password provided");
}
@ -123,9 +124,9 @@ public class HttpDistTest {
}
@Test
public void testShutdownParametersNegativeValue(KeycloakDistribution dist) {
public void testShutdownParametersNegativeValue(KeycloakRunner runner) {
// Test that negative values are rejected
CLIResult result = dist.run("start-dev", "--shutdown-delay=-1s");
CLIResult result = runner.run("start-dev", "--shutdown-delay=-1s");
result.assertError("Invalid duration '-1s'. Duration must be zero or positive");
}
}

View file

@ -25,8 +25,8 @@ import java.util.function.Consumer;
import org.keycloak.it.junit5.extension.BeforeStartDistribution;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawKeycloakDistribution;
import io.quarkus.deployment.util.FileUtil;
@ -50,15 +50,15 @@ public class ImportAtStartupDistTest {
@Test
@BeforeStartDistribution(CreateRealmConfigurationFile.class)
void testMultipleImport(KeycloakDistribution dist) throws IOException {
RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class);
void testMultipleImport(KeycloakRunner runner) throws IOException {
RawKeycloakDistribution rawDist = runner.getDistribution(RawKeycloakDistribution.class);
Path dir = rawDist.getDistPath().resolve("data").resolve("import");
// add another realm
Files.write(dir.resolve("realm2.json"), Files.readAllLines(dir.resolve("realm.json")).stream()
.map(s -> s.replace("quickstart-realm", "other-realm")).toList());
CLIResult cliResult = dist.run("start-dev", "--import-realm");
CLIResult cliResult = runner.run("start-dev", "--import-realm");
cliResult.assertMessage("Realm 'quickstart-realm' imported");
cliResult.assertMessage("Realm 'other-realm' imported");
}
@ -80,13 +80,13 @@ public class ImportAtStartupDistTest {
@Test
@BeforeStartDistribution(CreateRealmConfigurationFile.class)
void testImportFromFileCreatedByExportAllRealms(KeycloakDistribution dist) throws IOException {
dist.run("start-dev", "--import-realm");
dist.run("--profile=dev", "export", "--file=../data/import/realm.json", "--verbose");
void testImportFromFileCreatedByExportAllRealms(KeycloakRunner runner) throws IOException {
runner.run("start-dev", "--import-realm");
runner.run("--profile=dev", "export", "--file=../data/import/realm.json", "--verbose");
dist.unwrap(RawKeycloakDistribution.class).resetH2Dir();
runner.getDistribution(RawKeycloakDistribution.class).resetH2Dir();
CLIResult result = dist.run("start-dev", "--import-realm");
CLIResult result = runner.run("start-dev", "--import-realm");
result.assertMessage("Realm 'quickstart-realm' imported");
result.assertMessage("Realm 'master' imported");
result.assertNoMessage("Realm 'master' already exists. Import skipped");
@ -94,56 +94,53 @@ public class ImportAtStartupDistTest {
@Test
@BeforeStartDistribution(CreateRealmConfigurationFile.class)
void testImportFromFileCreatedByExportSingleRealm(KeycloakDistribution dist) throws IOException {
dist.run("start-dev", "--import-realm");
dist.run("--profile=dev", "export", "--realm=quickstart-realm", "--file=../data/import/realm.json");
void testImportFromFileCreatedByExportSingleRealm(KeycloakRunner runner) throws IOException {
runner.run("start-dev", "--import-realm");
runner.run("--profile=dev", "export", "--realm=quickstart-realm", "--file=../data/import/realm.json");
dist.unwrap(RawKeycloakDistribution.class).resetH2Dir();
runner.getDistribution(RawKeycloakDistribution.class).resetH2Dir();
CLIResult result = dist.run("start-dev", "--import-realm");
CLIResult result = runner.run("start-dev", "--import-realm");
result.assertMessage("Realm 'quickstart-realm' imported");
result.assertNoMessage("Not importing realm master from file");
}
@Test
@BeforeStartDistribution(CreateRealmConfigurationFile.class)
void testImportFromDirCreatedByExport(KeycloakDistribution dist) throws IOException {
dist.run("start-dev", "--import-realm");
RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class);
void testImportFromDirCreatedByExport(KeycloakRunner runner) throws IOException {
runner.run("start-dev", "--import-realm");
RawKeycloakDistribution rawDist = runner.getDistribution(RawKeycloakDistribution.class);
FileUtil.deleteDirectory(rawDist.getDistPath().resolve("data").resolve("import").toAbsolutePath());
dist.run("--profile=dev", "export", "--dir=../data/import");
runner.run("--profile=dev", "export", "--dir=../data/import");
dist.unwrap(RawKeycloakDistribution.class).resetH2Dir();
runner.getDistribution(RawKeycloakDistribution.class).resetH2Dir();
CLIResult result = dist.run("start-dev", "--import-realm");
CLIResult result = runner.run("start-dev", "--import-realm");
result.assertMessage("Realm 'quickstart-realm' imported");
result.assertNoMessage("Not importing realm master from file");
}
public static class CreateRealmConfigurationFile implements Consumer<KeycloakDistribution> {
public static class CreateRealmConfigurationFile implements Consumer<RawKeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
public void accept(RawKeycloakDistribution distribution) {
distribution.copyOrReplaceFileFromClasspath("/quickstart-realm.json", Path.of("data", "import", "realm.json"));
}
}
public static class CreateRealmConfigurationFileAndDir implements Consumer<KeycloakDistribution> {
public static class CreateRealmConfigurationFileAndDir implements Consumer<RawKeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
public void accept(RawKeycloakDistribution distribution) {
distribution.copyOrReplaceFileFromClasspath("/quickstart-realm.json", Path.of("data", "import", "realm.json"));
RawKeycloakDistribution rawDist = distribution.unwrap(RawKeycloakDistribution.class);
rawDist.getDistPath().resolve("data").resolve("import").resolve("sub-dir").toFile().mkdirs();
distribution.getDistPath().resolve("data").resolve("import").resolve("sub-dir").toFile().mkdirs();
}
}
public static class CreateRealmConfigurationFileWithUnsupportedExtension implements Consumer<KeycloakDistribution> {
public static class CreateRealmConfigurationFileWithUnsupportedExtension implements Consumer<RawKeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
public void accept(RawKeycloakDistribution distribution) {
distribution.copyOrReplaceFileFromClasspath("/quickstart-realm.json", Path.of("data", "import", "realm"));
}
}

View file

@ -23,8 +23,10 @@ import java.io.IOException;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.junit5.extension.StopServer;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.utils.RawKeycloakDistribution;
import org.keycloak.representations.idm.RealmRepresentation;
@ -46,12 +48,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class ImportDistTest {
@Test
void testImport(KeycloakDistribution dist) throws IOException {
CLIResult cliResult = dist.run("build");
void testImport(KeycloakRunner runner) throws IOException {
CLIResult cliResult = runner.run("build");
File dir = new File("target");
cliResult = dist.run("export", "--realm=master", "--dir=" + dir.getAbsolutePath());
cliResult = runner.run("export", "--realm=master", "--dir=" + dir.getAbsolutePath());
cliResult.assertMessage("Export of realm 'master' requested.");
cliResult.assertMessage("Export finished successfully");
cliResult.assertNoMessage("local_addr");
@ -63,31 +65,31 @@ public class ImportDistTest {
node.put("enabled", "${REALM_ENABLED}");
mapper.writer().writeValue(file, node);
dist.setEnvVar("REALM_ENABLED", "true");
dist.setEnvVar("KC_HOSTNAME_STRICT", "false");
dist.setEnvVar("KC_CACHE", "ispn");
cliResult = dist.run("import", "--dir=" + dir.getAbsolutePath());
runner.setEnvVar("REALM_ENABLED", "true");
runner.setEnvVar("KC_HOSTNAME_STRICT", "false");
runner.setEnvVar("KC_CACHE", "ispn");
cliResult = runner.run("import", "--dir=" + dir.getAbsolutePath());
cliResult.assertMessage("Realm 'master' imported");
cliResult.assertMessage("Import finished successfully");
cliResult.assertNoMessage("Changes detected in configuration");
cliResult.assertNoMessage("Listening on: http");
cliResult.assertNoMessage("local_addr");
cliResult = dist.run("import");
cliResult = runner.run("import");
cliResult.assertError("Must specify either --dir or --file options.");
}
@StopServer(Mode.MANUAL)
@Test
void testImportNewRealm(KeycloakDistribution dist) throws IOException {
dist.setEnvVar("MY_SECRET", "admin123");
void testImportNewRealm(KeycloakRunner runner) throws IOException {
runner.setEnvVar("MY_SECRET", "admin123");
RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class);
CLIResult result = rawDist.run("bootstrap-admin", "service", "--db=dev-file", "--client-id=admin", "--client-secret:env=MY_SECRET");
RawKeycloakDistribution rawDist = runner.getDistribution(RawKeycloakDistribution.class);
CLIResult result = rawDist.kc("bootstrap-admin", "service", "--db=dev-file", "--client-id=admin", "--client-secret:env=MY_SECRET");
assertTrue(result.getErrorOutput().isEmpty(), result.getErrorOutput());
rawDist.setManualStop(true);
result = rawDist.run("start-dev", "--db-url-properties=;AUTO_SERVER=TRUE;DB_CLOSE_ON_EXIT=TRUE");
result = runner.run("start-dev", "--db-url-properties=;AUTO_SERVER=TRUE;DB_CLOSE_ON_EXIT=TRUE");
File file = new File("target/realm.json");

View file

@ -23,6 +23,7 @@ import java.net.ConnectException;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import io.quarkus.test.junit.main.Launch;
import org.junit.jupiter.api.Test;
@ -32,7 +33,7 @@ import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
@DistributionTest(keepAlive = true, defaultOptions = { "--db=dev-file", "--http-enabled=true", "--hostname-strict=false"})
@DistributionTest(stopServer = Mode.MANUAL, defaultOptions = { "--db=dev-file", "--http-enabled=true", "--hostname-strict=false"})
@RawDistOnly(reason = "Containers are immutable")
public class IpStackDistTest {

View file

@ -18,8 +18,8 @@
package org.keycloak.it.cli.dist;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.DryRun;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.junit5.extension.WithEnvVars;
import io.quarkus.test.junit.main.Launch;
@ -34,8 +34,7 @@ import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.matchesPattern;
@DryRun
@DistributionTest
@DistributionTest(stopServer = Mode.BEFORE_QUARKUS)
@RawDistOnly(reason = "No need to test script again on container")
@WithEnvVars({"PRINT_ENV", "true"})
@Tag(DistributionTest.WIN)

View file

@ -22,9 +22,10 @@ import java.util.concurrent.TimeUnit;
import org.keycloak.it.jaxrs.filter.TestFilterTestProvider;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.junit5.extension.TestProvider;
import org.keycloak.it.utils.KeycloakDistribution;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Tag;
@ -33,15 +34,15 @@ import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.when;
import static org.junit.jupiter.api.Assertions.assertEquals;
@DistributionTest(keepAlive = true)
@DistributionTest(stopServer = Mode.MANUAL)
@RawDistOnly(reason = "Containers are immutable")
@Tag(DistributionTest.SMOKE)
public class JaxRsDistTest {
@Test
@TestProvider(TestFilterTestProvider.class)
public void requestFilterTest(KeycloakDistribution dist) {
CLIResult cliResult = dist.run("start-dev");
public void requestFilterTest(KeycloakRunner runner) {
CLIResult cliResult = runner.run("start-dev");
cliResult.assertStartedDevMode();

View file

@ -3,8 +3,8 @@ package org.keycloak.it.cli.dist;
import java.io.IOException;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawKeycloakDistribution;
import io.quarkus.deployment.util.FileUtil;
@ -17,16 +17,16 @@ import org.junit.jupiter.api.Test;
public class LiquibaseDistTest {
@Test
public void dbLockMultipleExecution(KeycloakDistribution distribution) throws IOException {
public void dbLockMultipleExecution(KeycloakRunner runner) throws IOException {
// force a full db initialization
RawKeycloakDistribution rawDist = distribution.unwrap(RawKeycloakDistribution.class);
RawKeycloakDistribution rawDist = runner.getDistribution(RawKeycloakDistribution.class);
FileUtil.deleteDirectory(rawDist.getDistPath().resolve("data").resolve("h2").toAbsolutePath());
var result = distribution.run("start-dev", "--log-level=org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockService:trace");
var result = runner.run("start-dev", "--log-level=org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockService:trace");
result.assertMessage("Initialize Database Lock Table, current locks []");
result.assertMessage("Initialized record in the database lock table");
// the code block in the CustomLockService should not be executed for the second time
result = distribution.run("start-dev", "--log-level=org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockService:trace");
result = runner.run("start-dev", "--log-level=org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockService:trace");
result.assertNoMessage("Initialize Database Lock Table, current locks");
result.assertNoMessage("Initialized record in the database lock table");
}

View file

@ -32,9 +32,10 @@ import org.keycloak.connections.httpclient.HttpClientBuilder;
import org.keycloak.cookie.CookieType;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.DryRun;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.junit5.extension.StopServer;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.utils.RawDistRootPath;
import org.keycloak.it.utils.RawKeycloakDistribution;
@ -61,7 +62,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@DistributionTest(keepAlive = true)
@DistributionTest(stopServer = Mode.MANUAL)
@RawDistOnly(reason = "Too verbose for docker and enough to check raw dist")
@Tag(DistributionTest.SLOW)
public class LoggingDistTest {
@ -107,18 +108,18 @@ public class LoggingDistTest {
}
@Test
void testJsonFormatApplied(KeycloakDistribution dist) throws IOException {
dist.unwrap(RawKeycloakDistribution.class).resetH2Dir();
CLIResult cliResult = dist.run("start-dev", "--log-console-output=json");
void testJsonFormatApplied(KeycloakRunner runner) throws IOException {
runner.getDistribution(RawKeycloakDistribution.class).resetH2Dir();
CLIResult cliResult = runner.run("start-dev", "--log-console-output=json");
cliResult.assertJsonLogDefaultsApplied();
cliResult.assertStartedDevMode();
assertFalse(cliResult.getOutput().contains("\"loggerName\":\"liquibase.servicelocator\",\"level\":\"FINE\""));
}
@Test
void testLogLevelSettingsAppliedWhenJsonEnabled(KeycloakDistribution dist) throws IOException {
dist.unwrap(RawKeycloakDistribution.class).resetH2Dir();
CLIResult cliResult = dist.run("start-dev", "--log-level=off,org.keycloak:debug,liquibase:debug", "--log-console-output=json");
void testLogLevelSettingsAppliedWhenJsonEnabled(KeycloakRunner runner) throws IOException {
runner.getDistribution(RawKeycloakDistribution.class).resetH2Dir();
CLIResult cliResult = runner.run("start-dev", "--log-level=off,org.keycloak:debug,liquibase:debug", "--log-console-output=json");
assertFalse(cliResult.getOutput().contains("\"loggerName\":\"io.quarkus\",\"level\":\"INFO\")"));
assertTrue(cliResult.getOutput().contains("\"loggerName\":\"org.keycloak.services.resources.KeycloakApplication\",\"level\":\"DEBUG\""));
assertTrue(cliResult.getOutput().contains("\"loggerName\":\"liquibase.servicelocator\",\"level\":\"FINE\""));
@ -147,16 +148,16 @@ public class LoggingDistTest {
}
@Test
void failUnknownHandlersInConfFile(KeycloakDistribution dist) {
dist.copyOrReplaceFileFromClasspath("/logging/keycloak.conf", Paths.get("conf", "keycloak.conf"));
CLIResult cliResult = dist.run("start-dev");
void failUnknownHandlersInConfFile(KeycloakRunner runner) {
runner.getDistribution(RawKeycloakDistribution.class).copyOrReplaceFileFromClasspath("/logging/keycloak.conf", Paths.get("conf", "keycloak.conf"));
CLIResult cliResult = runner.run("start-dev");
cliResult.assertError("Invalid value for option 'kc.log' in keycloak.conf: foo. Expected values are: console, file, syslog");
}
@Test
void failEmptyLogErrorFromConfFileError(KeycloakDistribution dist) {
dist.copyOrReplaceFileFromClasspath("/logging/emptylog.conf", Paths.get("conf", "emptylog.conf"));
CLIResult cliResult = dist.run(CONFIG_FILE_LONG_NAME+"=../conf/emptylog.conf", "start-dev");
void failEmptyLogErrorFromConfFileError(KeycloakRunner runner) {
runner.getDistribution(RawKeycloakDistribution.class).copyOrReplaceFileFromClasspath("/logging/emptylog.conf", Paths.get("conf", "emptylog.conf"));
CLIResult cliResult = runner.run(CONFIG_FILE_LONG_NAME+"=../conf/emptylog.conf", "start-dev");
cliResult.assertError("Invalid value for option 'kc.log' in emptylog.conf: . Expected values are: console, file, syslog");
}
@ -182,14 +183,14 @@ public class LoggingDistTest {
@Test
@Launch({"start-dev", "--log-console-level=wrong"})
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
void wrongLevelForHandlers(CLIResult cliResult) {
cliResult.assertError("Invalid value for option '--log-console-level': wrong. Expected values are (case insensitive): off, fatal, error, warn, info, debug, trace, all");
}
@Test
@Launch({"start-dev", "--log-level-org.keycloak=wrong"})
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
void wrongLevelForCategory(CLIResult cliResult) {
cliResult.assertError("Invalid log level: wrong. Possible values are: warn, trace, debug, error, fatal, info.");
}
@ -324,7 +325,7 @@ public class LoggingDistTest {
// HTTP Access log
@Test
@Launch({"start-dev", "--http-access-log-enabled=true", "--http-access-log-pattern='%A %{METHOD} %{REQUEST_URL} %{i,User-Agent}'", "--http-access-log-exclude=/realms/master/clients/.*"})
void httpAccessLogNotNamedPattern(CLIResult cliResult, KeycloakDistribution dist, RawDistRootPath path) {
void httpAccessLogNotNamedPattern(CLIResult cliResult, KeycloakRunner runner, RawDistRootPath path) {
when().get("http://127.0.0.1:8080/realms/master/.well-known/openid-configuration").then()
.statusCode(200);
Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAsserted(
@ -337,7 +338,7 @@ public class LoggingDistTest {
cliResult.assertNoMessage("127.0.0.1 GET /realms/master/clients/account/redirect");
// file
CLIResult fileCliResult = dist.run("start-dev", "--http-access-log-enabled=true", "--http-access-log-file-enabled=true", "--http-access-log-pattern='%A %{METHOD} %{REQUEST_URL} %{i,User-Agent}'", "--http-access-log-exclude=/realms/master/clients/.*");
CLIResult fileCliResult = runner.run("start-dev", "--http-access-log-enabled=true", "--http-access-log-file-enabled=true", "--http-access-log-pattern='%A %{METHOD} %{REQUEST_URL} %{i,User-Agent}'", "--http-access-log-exclude=/realms/master/clients/.*");
fileCliResult.assertStartedDevMode();
when().get("http://127.0.0.1:8080/realms/master/.well-known/openid-configuration").then()
.statusCode(200);

View file

@ -21,7 +21,8 @@ import java.io.IOException;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.DistributionType;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
@ -37,7 +38,7 @@ import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;
@DistributionTest(keepAlive = true,
@DistributionTest(stopServer = Mode.MANUAL,
defaultOptions = {"--db=dev-file", "--health-enabled=true", "--metrics-enabled=true"},
requestPort = 9000,
containerExposedPorts = {9000, 8080, 9005})
@ -57,14 +58,14 @@ public class ManagementDistTest {
@Test
@Order(2)
@Launch({"start-dev", "--legacy-observability-interface=true"})
void testManagementDisabled(LaunchResult result, KeycloakDistribution distribution) {
void testManagementDisabled(LaunchResult result, KeycloakRunner runner) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertNoMessage("Management interface listening on");
assertThrows(IOException.class, () -> when().get("/"), "Connection refused must be thrown");
assertThrows(IOException.class, () -> when().get("/health"), "Connection refused must be thrown");
distribution.setRequestPort(8080);
runner.setRequestPort(8080);
when().get("/health").then()
.statusCode(200);
@ -124,11 +125,11 @@ public class ManagementDistTest {
@Test
@Launch({"start-dev", "--http-management-port=9005"})
void testManagementDifferentPort(LaunchResult result, KeycloakDistribution distribution) {
void testManagementDifferentPort(LaunchResult result, KeycloakRunner runner) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertMessage("Management interface listening on http://0.0.0.0:9005");
distribution.setRequestPort(9005);
runner.setRequestPort(9005);
when().get("/").then()
.statusCode(200)
@ -158,10 +159,10 @@ public class ManagementDistTest {
@Test
@Launch({"start-dev", "--http-relative-path=/auth", "--http-management-relative-path=/management"})
void testManagementRootRedirects(LaunchResult result, KeycloakDistribution distribution) {
void testManagementRootRedirects(LaunchResult result, KeycloakRunner runner) {
assertRelativePath(result, "/management");
distribution.setRequestPort(8080);
runner.setRequestPort(8080);
given().redirects().follow(false).when().get("/").then().statusCode(302).header("Location", is("/auth"));
when().get("/").then().statusCode(200).body(containsString("Welcome to Keycloak"));

View file

@ -19,6 +19,7 @@ package org.keycloak.it.cli.dist;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
@ -30,7 +31,7 @@ import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.when;
import static org.hamcrest.CoreMatchers.containsString;
@DistributionTest(keepAlive = true,
@DistributionTest(stopServer = Mode.MANUAL,
enableTls = true,
defaultOptions = {"--db=dev-file", "--health-enabled=true", "--metrics-enabled=true"},
requestPort = 9000)

View file

@ -20,6 +20,7 @@ import java.io.IOException;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
@ -28,7 +29,7 @@ import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.when;
import static org.junit.jupiter.api.Assertions.assertThrows;
@DistributionTest(keepAlive = true,
@DistributionTest(stopServer = Mode.MANUAL,
requestPort = 9000,
containerExposedPorts = {9000, 8080})
public class ManagementOffDistTest {

View file

@ -24,7 +24,8 @@ import java.util.concurrent.TimeUnit;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import io.quarkus.test.junit.main.Launch;
import org.junit.jupiter.api.Tag;
@ -37,7 +38,7 @@ import static org.hamcrest.Matchers.matchesPattern;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertThrows;
@DistributionTest(keepAlive = true,
@DistributionTest(stopServer = Mode.MANUAL,
requestPort = 9000,
containerExposedPorts = {8080, 9000})
@Tag(DistributionTest.SLOW)
@ -45,11 +46,11 @@ public class MetricsDistTest {
@Test
@Launch({ "start-dev" })
void testMetricsEndpointNotEnabled(KeycloakDistribution distribution) {
void testMetricsEndpointNotEnabled(KeycloakRunner runner) {
assertThrows(IOException.class, () -> when().get("/metrics"), "Connection refused must be thrown");
assertThrows(IOException.class, () -> when().get("/q/metrics"), "Connection refused must be thrown");
distribution.setRequestPort(8080);
runner.setRequestPort(8080);
when().get("/metrics").then()
.statusCode(404);
@ -138,10 +139,10 @@ public class MetricsDistTest {
@Test
@Launch({ "start-dev", "--metrics-enabled=true", "--tracing-enabled=true" })
void testMetricsEndpointWithCacheMetricsHistogramsAndExemplars(KeycloakDistribution distribution) {
runClientCredentialGrantWithUnknownClientId(distribution);
void testMetricsEndpointWithCacheMetricsHistogramsAndExemplars(KeycloakRunner runner) {
runClientCredentialGrantWithUnknownClientId(runner);
distribution.setRequestPort(9000);
runner.setRequestPort(9000);
// Exemplars are only present when metrics and traces are enabled
given().accept("application/openmetrics-text; version=1.0.0; charset=utf-8");
when().get("/metrics").then()
@ -153,10 +154,10 @@ public class MetricsDistTest {
@Test
@Launch({ "start-dev", "--metrics-enabled=true", "--features=user-event-metrics", "--event-metrics-user-enabled=true" })
void testMetricsEndpointWithUserEventMetrics(KeycloakDistribution distribution) {
runClientCredentialGrantWithUnknownClientId(distribution);
void testMetricsEndpointWithUserEventMetrics(KeycloakRunner runner) {
runClientCredentialGrantWithUnknownClientId(runner);
distribution.setRequestPort(9000);
runner.setRequestPort(9000);
when().get("/metrics").then()
.statusCode(200)
.body(containsString("keycloak_user_events_total{error=\"client_not_found\",event=\"client_login\",realm=\"master\"}"));
@ -165,18 +166,18 @@ public class MetricsDistTest {
@Test
@Launch({ "start-dev", "--metrics-enabled=true", "--features=user-event-metrics", "--event-metrics-user-enabled=false" })
void testMetricsEndpointWithoutUserEventMetrics(KeycloakDistribution distribution) {
runClientCredentialGrantWithUnknownClientId(distribution);
void testMetricsEndpointWithoutUserEventMetrics(KeycloakRunner runner) {
runClientCredentialGrantWithUnknownClientId(runner);
distribution.setRequestPort(9000);
runner.setRequestPort(9000);
when().get("/metrics").then()
.statusCode(200)
.body(not(containsString("keycloak_user_events_total{error=\"client_not_found\",event=\"client_login\",realm=\"master\"}")));
}
private static void runClientCredentialGrantWithUnknownClientId(KeycloakDistribution distribution) {
distribution.setRequestPort(8080);
private static void runClientCredentialGrantWithUnknownClientId(KeycloakRunner runner) {
runner.setRequestPort(8080);
given().formParam("grant_type", "client_credentials")
.formParam("client_id", "unknown")
.formParam("client_secret", "unknown").
@ -186,21 +187,21 @@ public class MetricsDistTest {
}
@Test
void testUsingRelativePath(KeycloakDistribution distribution) {
void testUsingRelativePath(KeycloakRunner runner) {
for (String relativePath : List.of("/auth", "/auth/", "auth")) {
distribution.run("start-dev", "--metrics-enabled=true", "--http-management-relative-path=" + relativePath);
runner.run("start-dev", "--metrics-enabled=true", "--http-management-relative-path=" + relativePath);
if (!relativePath.endsWith("/")) {
relativePath = relativePath + "/";
}
when().get(relativePath + "metrics").then().statusCode(200);
distribution.stop();
runner.stop();
}
}
@Test
void testMultipleRequests(KeycloakDistribution distribution) throws Exception {
void testMultipleRequests(KeycloakRunner runner) throws Exception {
for (String relativePath : List.of("/", "/auth/", "auth")) {
distribution.run("start-dev", "--metrics-enabled=true", "--http-management-relative-path=" + relativePath);
runner.run("start-dev", "--metrics-enabled=true", "--http-management-relative-path=" + relativePath);
CompletableFuture<Void> future = CompletableFuture.completedFuture(null);
for (int i = 0; i < 3; i++) {
@ -222,7 +223,7 @@ public class MetricsDistTest {
future.get(5, TimeUnit.MINUTES);
distribution.stop();
runner.stop();
}
}

View file

@ -21,7 +21,9 @@ import java.io.IOException;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.DryRun;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.StopServer;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.utils.KeycloakDistribution;
import io.quarkus.test.junit.main.Launch;
@ -36,7 +38,7 @@ import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertThrows;
@DistributionTest(keepAlive = true, requestPort = 9000, containerExposedPorts = {8080, 9000})
@DistributionTest(stopServer = Mode.MANUAL, requestPort = 9000, containerExposedPorts = {8080, 9000})
@Tag(DistributionTest.SLOW)
public class OpenApiDistTest {
@ -48,11 +50,11 @@ public class OpenApiDistTest {
@Test
@Launch({"start-dev", FEATURES_OPTION})
void testOpenApiEndpointNotEnabled(KeycloakDistribution distribution) {
void testOpenApiEndpointNotEnabled(KeycloakRunner runner) {
assertThrows(IOException.class, () -> when().get(OPENAPI_ENDPOINT), "Connection refused must be thrown");
assertThrows(IOException.class, () -> when().get(OPENAPI_UI_ENDPOINT), "Connection refused must be thrown");
distribution.setRequestPort(8080);
runner.setRequestPort(8080);
when().get(OPENAPI_ENDPOINT).then()
.statusCode(404);
@ -76,20 +78,20 @@ public class OpenApiDistTest {
.statusCode(200);
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "start-dev", "--openapi-ui-enabled=true", FEATURES_OPTION})
void testOpenApiUiFailsWhenOpenApiIsNotEnabled(CLIResult cliResult) {
cliResult.assertError("Disabled option: '--openapi-ui-enabled'. Available only when OpenAPI Endpoint is enabled");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
void testOpenApiRequiresFeatures(KeycloakDistribution dist) {
CLIResult cliResult = dist.run("start-dev", "--openapi-enabled=true", "--features=openapi");
void testOpenApiRequiresFeatures(KeycloakRunner runner) {
CLIResult cliResult = runner.run("start-dev", "--openapi-enabled=true", "--features=openapi");
cliResult.assertError("ERROR: Feature openapi depends on disabled feature client-admin-api-v2");
cliResult = dist.run("start-dev", "--openapi-enabled=true", "--features=client-admin-api:v2");
cliResult = runner.run("start-dev", "--openapi-enabled=true", "--features=client-admin-api:v2");
cliResult.assertError("Disabled option: '--openapi-enabled'. Available only when OpenAPI feature is enabled");
}

View file

@ -21,10 +21,11 @@ import java.nio.file.Paths;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.DryRun;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.StopServer;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.junit5.extension.WithEnvVars;
import org.keycloak.it.utils.KeycloakDistribution;
import io.quarkus.test.junit.main.Launch;
import org.junit.jupiter.api.MethodOrderer;
@ -38,7 +39,7 @@ import static org.keycloak.quarkus.runtime.cli.command.Main.CONFIG_FILE_LONG_NAM
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class OptionsDistTest {
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Order(1)
@Launch({"build", "--db=invalid"})
@ -46,7 +47,7 @@ public class OptionsDistTest {
result.assertError("Invalid value for option '--db': invalid. Expected values are: dev-file, dev-mem, mariadb, mssql, mysql, oracle, postgres");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Order(2)
@Launch({"start", "--db=dev-file", "--test=invalid"})
@ -54,7 +55,7 @@ public class OptionsDistTest {
result.assertError("Unknown option: '--test'");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Order(3)
@Launch({"start", "--db=dev-file", "--log=console", "--log-file-output=json", "--http-enabled=true", "--hostname-strict=false"})
@ -63,7 +64,7 @@ public class OptionsDistTest {
result.assertError("Possible solutions: --log-console-output, --log-level, --log, --log-console-level, --log-console-format, --log-console-async, --log-async");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Order(4)
@Launch({"start", "--db=dev-file", "--log=file", "--log-file-output=json", "--http-enabled=true", "--hostname-strict=false"})
@ -82,13 +83,13 @@ public class OptionsDistTest {
cliResult.assertStarted();
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Order(6)
@RawDistOnly(reason = "Raw is enough and we avoid issues with including custom conf file in the container")
public void testExpressionsInConfigFile(KeycloakDistribution distribution) {
distribution.setEnvVar("MY_LOG_LEVEL", "warn");
CLIResult result = distribution.run(CONFIG_FILE_LONG_NAME + "=" + Paths.get("src/test/resources/OptionsDistTest/keycloak.conf").toAbsolutePath().normalize(), "start", "--db=dev-file", "--http-enabled=true", "--hostname-strict=false");
public void testExpressionsInConfigFile(KeycloakRunner runner) {
runner.setEnvVar("MY_LOG_LEVEL", "warn");
CLIResult result = runner.run(CONFIG_FILE_LONG_NAME + "=" + Paths.get("src/test/resources/OptionsDistTest/keycloak.conf").toAbsolutePath().normalize(), "start", "--db=dev-file", "--http-enabled=true", "--hostname-strict=false");
result.assertNoMessage("INFO [io.quarkus]");
result.assertNoMessage("Listening on:");
@ -100,7 +101,7 @@ public class OptionsDistTest {
// Start-dev should be executed as last tests - build is done for development mode
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Order(7)
@Launch({"start-dev", "--test=invalid"})
@ -108,7 +109,7 @@ public class OptionsDistTest {
result.assertError("Unknown option: '--test'");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Order(8)
@Launch({"start-dev", "--log=console", "--log-file-output=json"})
@ -117,7 +118,7 @@ public class OptionsDistTest {
result.assertError("Possible solutions: --log-console-output, --log-level, --log, --log-console-level, --log-console-format, --log-console-async, --log-async");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Order(9)
@Launch({"start-dev", "--log=file", "--log-file-output=json", "--log-console-color=true"})
@ -127,7 +128,7 @@ public class OptionsDistTest {
result.assertError("Possible solutions: --log, --log-file, --log-level, --log-file-level, --log-file-json-format, --log-file-format, --log-file-async");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Order(10)
@Launch({"start-dev", "--cache-remote-host=localhost"})
@ -135,7 +136,7 @@ public class OptionsDistTest {
result.assertError( "cache-remote-host available only when feature 'multi-site' or 'clusterless' is set");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Order(11)
@Launch({"start-dev", "--cache-remote-port=11222"})
@ -143,7 +144,7 @@ public class OptionsDistTest {
assertDisabledDueToMissingRemoteHost(result, "--cache-remote-port");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Order(12)
@Launch({"start-dev", "--cache-remote-username=user"})
@ -151,7 +152,7 @@ public class OptionsDistTest {
assertDisabledDueToMissingRemoteHost(result, "--cache-remote-username");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Order(13)
@Launch({"start-dev", "--cache-remote-password=pass"})
@ -159,7 +160,7 @@ public class OptionsDistTest {
assertDisabledDueToMissingRemoteHost(result, "--cache-remote-password");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Order(14)
@Launch({"start-dev", "--cache-remote-tls-enabled=false"})
@ -167,7 +168,7 @@ public class OptionsDistTest {
assertDisabledDueToMissingRemoteHost(result, "--cache-remote-tls-enabled");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Order(15)
@Launch({"start-dev", "--features=multi-site"})
@ -175,7 +176,7 @@ public class OptionsDistTest {
result.assertError("- cache-remote-host: Required when feature 'multi-site' or 'clusterless' is set.");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Order(16)
@Launch({"start-dev", "--features=multi-site", "--cache-remote-host=localhost", "--cache-remote-username=user"})
@ -183,7 +184,7 @@ public class OptionsDistTest {
result.assertError("The option 'cache-remote-password' is required when 'cache-remote-username' is set.");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Order(17)
@Launch({"start-dev", "--features=multi-site", "--cache-remote-host=localhost", "--cache-remote-password=secret"})

View file

@ -19,11 +19,12 @@ package org.keycloak.it.cli.dist;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.junit5.extension.TestProvider;
import org.keycloak.it.junit5.extension.WithEnvVars;
import org.keycloak.it.resource.realm.TestRealmResourceTestProvider;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
import io.quarkus.test.junit.main.Launch;
@ -40,7 +41,7 @@ import static io.restassured.RestAssured.given;
import static io.restassured.RestAssured.when;
import static org.hamcrest.Matchers.containsString;
@DistributionTest(keepAlive = true, enableTls = true)
@DistributionTest(stopServer = Mode.MANUAL, enableTls = true)
@WithEnvVars({"KC_BOOTSTRAP_ADMIN_USERNAME", "admin123", "KC_BOOTSTRAP_ADMIN_PASSWORD", "admin123"})
@RawDistOnly(reason = "Containers are immutable")
public class ProxyHostnameV2DistTest {
@ -66,14 +67,14 @@ public class ProxyHostnameV2DistTest {
}
@Test
void testTrustedProxiesWithoutProxyHeaders(KeycloakDistribution distribution) {
CLIResult result = distribution.run("start-dev", "--proxy-trusted-addresses=1.0.0.0");
void testTrustedProxiesWithoutProxyHeaders(KeycloakRunner runner) {
CLIResult result = runner.run("start-dev", "--proxy-trusted-addresses=1.0.0.0");
result.assertError("proxy-trusted-addresses available only when proxy-headers is set");
}
@Test
void testTrustedProxiesWithInvalidAddress(KeycloakDistribution distribution) {
CLIResult result = distribution.run("start-dev", "--proxy-headers=xforwarded", "--proxy-trusted-addresses=1.0.0.0:8080");
void testTrustedProxiesWithInvalidAddress(KeycloakRunner runner) {
CLIResult result = runner.run("start-dev", "--proxy-headers=xforwarded", "--proxy-trusted-addresses=1.0.0.0:8080");
result.assertError("1.0.0.0:8080 is not a valid IP address (IPv4 or IPv6) nor valid CIDR notation.");
}

View file

@ -24,7 +24,7 @@ import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.WithEnvVars;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawKeycloakDistribution;
import io.quarkus.test.junit.main.Launch;
import org.junit.jupiter.api.Disabled;
@ -124,35 +124,35 @@ public class QuarkusPropertiesAutoBuildDistTest {
cliResult.assertStarted();
}
public static class EnableAdditionalConsoleHandler implements Consumer<KeycloakDistribution> {
public static class EnableAdditionalConsoleHandler implements Consumer<RawKeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
public void accept(RawKeycloakDistribution distribution) {
distribution.setQuarkusProperty("quarkus.log.handler.console.\"console-2\".enable", "true");
distribution.setQuarkusProperty("quarkus.log.handler.console.\"console-2\".format", "Keycloak is the best");
distribution.setQuarkusProperty("quarkus.log.handlers", "console-2");
}
}
public static class DisableAdditionalConsoleHandler implements Consumer<KeycloakDistribution> {
public static class DisableAdditionalConsoleHandler implements Consumer<RawKeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
public void accept(RawKeycloakDistribution distribution) {
distribution.setQuarkusProperty("quarkus.log.handler.console.\"console-2\".enable", "false");
}
}
public static class AddAdditionalDatasource implements Consumer<KeycloakDistribution> {
public static class AddAdditionalDatasource implements Consumer<RawKeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
public void accept(RawKeycloakDistribution distribution) {
distribution.setProperty("db-kind-user-store", "dev-mem");
distribution.setProperty("db-username-user-store", "sa");
distribution.setProperty("db-url-full-user-store", "jdbc:h2:mem:user-store;DB_CLOSE_DELAY=-1");
}
}
public static class AddAdditionalDatasource2 implements Consumer<KeycloakDistribution> {
public static class AddAdditionalDatasource2 implements Consumer<RawKeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
public void accept(RawKeycloakDistribution distribution) {
distribution.setProperty("db-kind-user-store2", "dev-mem");
distribution.setProperty("transaction-xa-enabled-user-store2", "true");
distribution.setProperty("db-username-user-store2", "sa");
@ -160,9 +160,9 @@ public class QuarkusPropertiesAutoBuildDistTest {
}
}
public static class AddNonXADatasource implements Consumer<KeycloakDistribution> {
public static class AddNonXADatasource implements Consumer<RawKeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
public void accept(RawKeycloakDistribution distribution) {
distribution.setProperty("db-kind-user-store3", "dev-mem");
distribution.setProperty("transaction-xa-enabled-user-store3", "false");
distribution.setProperty("db-username-user-store3", "sa");
@ -170,24 +170,23 @@ public class QuarkusPropertiesAutoBuildDistTest {
}
}
public static class ChangeAdditionalDatasourceUsername implements Consumer<KeycloakDistribution> {
public static class ChangeAdditionalDatasourceUsername implements Consumer<RawKeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
public void accept(RawKeycloakDistribution distribution) {
distribution.setProperty("db-username-user-store", "foo");
}
}
public static class ChangeAdditionalDatasourceDbKind implements Consumer<KeycloakDistribution> {
public static class ChangeAdditionalDatasourceDbKind implements Consumer<RawKeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
public void accept(RawKeycloakDistribution distribution) {
distribution.setProperty("db-kind-user-store", "dev-mem");
}
}
public static class SetDatabaseKind implements Consumer<KeycloakDistribution> {
public static class SetDatabaseKind implements Consumer<RawKeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
distribution.setManualStop(true);
public void accept(RawKeycloakDistribution distribution) {
distribution.setQuarkusProperty("quarkus.datasource.db-kind", "postgres");
}
}

View file

@ -24,11 +24,10 @@ import java.util.function.Consumer;
import org.keycloak.it.junit5.extension.BeforeStartDistribution;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.DryRun;
import org.keycloak.it.junit5.extension.KeepServerAlive;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.WithEnvVars;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.junit5.extension.StopServer;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.utils.RawKeycloakDistribution;
import io.quarkus.test.junit.main.Launch;
import io.restassured.RestAssured;
@ -47,7 +46,6 @@ import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertTrue;
@DistributionTest(defaultOptions = "--db=dev-file")
@WithEnvVars({"KC_CACHE", "local"}) // avoid flakey port conflicts
@RawDistOnly(reason = "Containers are immutable")
@Tag(DistributionTest.WIN)
@TestMethodOrder(OrderAnnotation.class)
@ -64,7 +62,7 @@ public class QuarkusPropertiesDistTest {
cliResult.assertMessage("Keycloak is the best");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@BeforeStartDistribution(UpdateConsoleHandlerFromKeycloakConf.class)
@Launch({"build"})
@ -74,7 +72,7 @@ public class QuarkusPropertiesDistTest {
cliResult.assertBuild();
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@BeforeStartDistribution(UpdateConsoleHandlerFromQuarkusProps.class)
@Launch({"start", "--http-enabled=true", "--hostname-strict=false"})
@ -95,7 +93,7 @@ public class QuarkusPropertiesDistTest {
@Test
@BeforeStartDistribution(UpdateHibernateMetricsFromQuarkusProps.class)
@KeepServerAlive
@StopServer(Mode.MANUAL)
@Launch({ "start", "--http-enabled=true", "--hostname-strict=false", "--metrics-enabled=true"})
@Order(8)
void testUnknownQuarkusBuildTimePropertyApplied(CLIResult cliResult) {
@ -170,45 +168,45 @@ public class QuarkusPropertiesDistTest {
cliResult.assertMessage("ERROR: Failed to load 'https-*' material: NoSuchFileException C:");
}
public static class AddConsoleHandlerFromQuarkusProps implements Consumer<KeycloakDistribution> {
public static class AddConsoleHandlerFromQuarkusProps implements Consumer<RawKeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
public void accept(RawKeycloakDistribution distribution) {
distribution.setQuarkusProperty(QUARKUS_RUNTIME_CONSOLE_HANDLER_ENABLED_KEY, "true");
distribution.setQuarkusProperty("quarkus.log.handler.console.\"console-2\".format", "Keycloak is the best");
distribution.setQuarkusProperty("quarkus.log.handlers", "console-2");
}
}
public static class UpdateConsoleHandlerFromKeycloakConf implements Consumer<KeycloakDistribution> {
public static class UpdateConsoleHandlerFromKeycloakConf implements Consumer<RawKeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
public void accept(RawKeycloakDistribution distribution) {
distribution.deleteQuarkusProperties();
distribution.setProperty(QUARKUS_RUNTIME_CONSOLE_HANDLER_ENABLED_KEY, "false");
}
}
public static class UpdateConsoleHandlerFromQuarkusProps implements Consumer<KeycloakDistribution> {
public static class UpdateConsoleHandlerFromQuarkusProps implements Consumer<RawKeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
public void accept(RawKeycloakDistribution distribution) {
distribution.deleteQuarkusProperties();
distribution.setQuarkusProperty(QUARKUS_RUNTIME_CONSOLE_HANDLER_ENABLED_KEY, "true");
}
}
public static class UpdateHibernateMetricsFromQuarkusProps implements Consumer<KeycloakDistribution> {
public static class UpdateHibernateMetricsFromQuarkusProps implements Consumer<RawKeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
public void accept(RawKeycloakDistribution distribution) {
distribution.deleteQuarkusProperties();
distribution.setQuarkusProperty(QUARKUS_BUILDTIME_HIBERNATE_METRICS_KEY, "true");
}
}
public static class CopyKeystoreToConf implements Consumer<KeycloakDistribution> {
public static class CopyKeystoreToConf implements Consumer<RawKeycloakDistribution> {
@Override
public void accept(KeycloakDistribution distribution) {
public void accept(RawKeycloakDistribution distribution) {
distribution.copyOrReplaceFileFromClasspath("/keystore", Path.of("conf", "keystore"));
}
}

View file

@ -1,14 +1,14 @@
package org.keycloak.it.cli.dist;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.DryRun;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.StopServer;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.junit5.extension.WithEnvVars;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.quarkus.runtime.cli.command.ShowConfig;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
@ -26,23 +26,23 @@ import static org.hamcrest.Matchers.not;
@DistributionTest
public class ShowConfigCommandDistTest {
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@RawDistOnly(reason = "Containers are immutable")
void testShowConfigPicksUpRightConfigDependingOnCurrentMode(KeycloakDistribution distribution) {
CLIResult initialResult = distribution.run("show-config");
void testShowConfigPicksUpRightConfigDependingOnCurrentMode(KeycloakRunner runner) {
CLIResult initialResult = runner.run("show-config");
initialResult.assertMessage("Current Mode: production");
initialResult.assertNoMessage("kc.db = dev-file");
distribution.run("start-dev");
runner.run("start-dev");
CLIResult devModeResult = distribution.run("show-config");
CLIResult devModeResult = runner.run("show-config");
devModeResult.assertMessage("Current Mode: development");
devModeResult.assertMessage("kc.db = dev-file");
distribution.run("build", "--db=dev-file");
runner.run("build", "--db=dev-file");
CLIResult resetResult = distribution.run("show-config");
CLIResult resetResult = runner.run("show-config");
resetResult.assertMessage("Current Mode: production");
resetResult.assertMessage("kc.db = dev-file");
}
@ -65,8 +65,8 @@ public class ShowConfigCommandDistTest {
@Test
@RawDistOnly(reason = "Containers are immutable")
void testShowConfigCommandHidesCredentialsInProfiles(KeycloakDistribution distribution) {
CLIResult result = distribution.run(String.format("%s=%s", CONFIG_FILE_LONG_NAME, Paths.get("src/test/resources/ShowConfigCommandTest/keycloak.conf").toAbsolutePath().normalize()), ShowConfig.NAME, "all");
void testShowConfigCommandHidesCredentialsInProfiles(KeycloakRunner runner) {
CLIResult result = runner.run(String.format("%s=%s", CONFIG_FILE_LONG_NAME, Paths.get("src/test/resources/ShowConfigCommandTest/keycloak.conf").toAbsolutePath().normalize()), ShowConfig.NAME, "all");
String output = result.getOutput();
Assertions.assertFalse(output.contains("testpw1"));
Assertions.assertFalse(output.contains("testpw2"));
@ -76,9 +76,9 @@ public class ShowConfigCommandDistTest {
@Test
@RawDistOnly(reason = "Containers are immutable")
void testSmallRyeKeyStoreConfigSource(KeycloakDistribution distribution) {
void testSmallRyeKeyStoreConfigSource(KeycloakRunner runner) {
// keystore is shared with QuarkusPropertiesDistTest#testSmallRyeKeyStoreConfigSource
CLIResult result = distribution.run(
CLIResult result = runner.run(
String.format("%s=%s", CONFIG_FILE_LONG_NAME, Paths.get("src/test/resources/ShowConfigCommandTest/keycloak-keystore.conf").toAbsolutePath().normalize()),
"--config-keystore=" + Paths.get("src/test/resources/keystore").toAbsolutePath().normalize(),
ShowConfig.NAME, "all");
@ -104,14 +104,14 @@ public class ShowConfigCommandDistTest {
@Test
@RawDistOnly(reason = "Containers are immutable")
void testConfigSourceNames(KeycloakDistribution distribution) {
CLIResult result = distribution.run("build");
void testConfigSourceNames(KeycloakRunner runner) {
CLIResult result = runner.run("build");
result.assertBuild();
distribution.setEnvVar("KC_LOG", "file");
distribution.copyOrReplaceFile(Paths.get("src/test/resources/ShowConfigCommandTest/quarkus.properties"), Path.of("conf", "quarkus.properties"));
runner.setEnvVar("KC_LOG", "file");
runner.getDistribution().copyConfigFile(Paths.get("src/test/resources/ShowConfigCommandTest/quarkus.properties"));
result = distribution.run(
result = runner.run(
String.format("%s=%s", CONFIG_FILE_LONG_NAME, Paths.get("src/test/resources/ShowConfigCommandTest/keycloak-keystore.conf").toAbsolutePath().normalize()),
"--config-keystore=" + Paths.get("src/test/resources/keystore").toAbsolutePath().normalize(),
ShowConfig.NAME, "all", "--db=dev-file");

View file

@ -19,8 +19,10 @@ package org.keycloak.it.cli.dist;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.DryRun;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.StopServer;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.junit5.extension.TestProvider;
import org.keycloak.it.utils.KeycloakDistribution;
@ -40,7 +42,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class StartAutoBuildDistTest {
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "--verbose", "start", "--db=dev-file", "--http-enabled=true", "--hostname-strict=false" })
@Order(1)
@ -54,7 +56,7 @@ public class StartAutoBuildDistTest {
assertTrue(cliResult.getErrorOutput().isBlank());
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "start", "--db=dev-file", "--http-enabled=true", "--hostname-strict=false" })
@Order(2)
@ -63,7 +65,7 @@ public class StartAutoBuildDistTest {
assertTrue(cliResult.getErrorOutput().isBlank());
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "start", "--db=dev-mem", "--http-enabled=true", "--hostname-strict=false" })
@Order(3)
@ -72,7 +74,7 @@ public class StartAutoBuildDistTest {
assertTrue(cliResult.getErrorOutput().isBlank());
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "start", "--db=dev-mem", "--http-enabled=true", "--hostname-strict=false" })
@Order(4)
@ -81,7 +83,7 @@ public class StartAutoBuildDistTest {
assertTrue(cliResult.getErrorOutput().isBlank());
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "build", "--db=postgres" })
@Order(5)
@ -89,7 +91,7 @@ public class StartAutoBuildDistTest {
cliResult.assertBuild();
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "start", "--db=dev-file", "--http-enabled=true", "--hostname-strict=false" })
@Order(6)
@ -98,7 +100,7 @@ public class StartAutoBuildDistTest {
assertTrue(cliResult.getErrorOutput().isBlank());
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "start", "--db=postgres", "--http-enabled=true", "--hostname-strict=false" })
@Order(7)
@ -106,7 +108,7 @@ public class StartAutoBuildDistTest {
cliResult.assertBuild();
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "start", "--db=dev-file", "--http-enabled=true", "--hostname-strict=false", OPTIMIZED_BUILD_OPTION_LONG})
@Order(8)
@ -114,7 +116,7 @@ public class StartAutoBuildDistTest {
cliResult.assertNoBuild();
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "start-dev" })
@Order(8)
@ -123,7 +125,7 @@ public class StartAutoBuildDistTest {
cliResult.assertStartedDevMode();
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "start-dev" })
@Order(9)
@ -133,46 +135,46 @@ public class StartAutoBuildDistTest {
cliResult.assertStartedDevMode();
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@TestProvider(CustomUserProvider.class)
@Order(10)
void testSpiAutoBuild(KeycloakDistribution dist) {
CLIResult cliResult = dist.run("start-dev", "--spi-user-provider=custom_jpa", "--spi-user-jpa-enabled=false");
void testSpiAutoBuild(KeycloakRunner runner) {
CLIResult cliResult = runner.run("start-dev", "--spi-user-provider=custom_jpa", "--spi-user-jpa-enabled=false");
cliResult.assertMessage("Updating the configuration");
cliResult.assertStartedDevMode();
dist.stop();
runner.stop();
// we should persist the spi provider and know not to rebuild
cliResult = dist.run("start-dev", "--spi-user-provider=custom_jpa", "--spi-user-jpa-enabled=false");
cliResult = runner.run("start-dev", "--spi-user-provider=custom_jpa", "--spi-user-jpa-enabled=false");
cliResult.assertNoMessage("Updating the configuration");
cliResult.assertStartedDevMode();
}
@Test
@Order(11)
void testLogLevelNotPeristed(KeycloakDistribution dist) {
CLIResult cliResult = dist.run("start", "--db=dev-file", "--log-level=org.hibernate.SQL:debug", "--http-enabled=true", "--hostname-strict=false");
void testLogLevelNotPeristed(KeycloakRunner runner) {
CLIResult cliResult = runner.run("start", "--db=dev-file", "--log-level=org.hibernate.SQL:debug", "--http-enabled=true", "--hostname-strict=false");
cliResult.assertMessage("DEBUG [org.hibernate.SQL]");
cliResult.assertStarted();
dist.stop();
runner.stop();
// logging runtime defaults should not be used
cliResult = dist.run("start", "--db=dev-file", "--http-enabled=true", "--hostname-strict=false");
cliResult = runner.run("start", "--db=dev-file", "--http-enabled=true", "--hostname-strict=false");
cliResult.assertNoMessage("DEBUG [org.hibernate.SQL]");
cliResult.assertStarted();
}
@Test
@Order(12)
void testLogLevelWildcardNotPeristed(KeycloakDistribution dist) {
CLIResult cliResult = dist.run("start-dev", "--log-level-org.hibernate.SQL=debug");
void testLogLevelWildcardNotPeristed(KeycloakRunner runner) {
CLIResult cliResult = runner.run("start-dev", "--log-level-org.hibernate.SQL=debug");
cliResult.assertMessage("DEBUG [org.hibernate.SQL]");
cliResult.assertStartedDevMode();
dist.stop();
runner.stop();
// logging runtime defaults should not be used
cliResult = dist.run("start-dev");
cliResult = runner.run("start-dev");
cliResult.assertNoMessage("DEBUG [org.hibernate.SQL]");
cliResult.assertStartedDevMode();
}

View file

@ -21,8 +21,10 @@ import java.util.concurrent.TimeUnit;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.DryRun;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.StopServer;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.junit5.extension.TestProvider;
import org.keycloak.it.junit5.extension.WithEnvVars;
import org.keycloak.it.resource.realm.TestRealmResourceTestProvider;
@ -32,7 +34,6 @@ import org.keycloak.it.utils.RawKeycloakDistribution;
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.main.Launch;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Tag;
@ -47,14 +48,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@WithEnvVars({"KC_CACHE", "local"}) // avoid flakey port conflicts
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@DistributionTest
@Tag(DistributionTest.WIN)
@QuarkusTestResource(RawDistributionLifecycleManager.class)
public class StartCommandDistTest {
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "start", "--db=dev-file", "--hostname-strict=false" })
void failNoTls(CLIResult cliResult) {
@ -62,7 +62,7 @@ public class StartCommandDistTest {
() -> "The Output:\n" + cliResult.getErrorOutput() + "doesn't contains the expected string.");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "start", "--db=dev-file", "--spi-events-listener-jboss-logging-success-level" })
void failSpiArgMissingValue(CLIResult cliResult) {
@ -70,7 +70,7 @@ public class StartCommandDistTest {
() -> "The Output:\n" + cliResult.getErrorOutput() + "doesn't contains the expected string.");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "build", "--db=dev-file", "--spi-events-listener-jboss-logging-success-level=debug" })
void warnSpiRuntimeAtBuildtime(CLIResult cliResult) {
@ -78,47 +78,47 @@ public class StartCommandDistTest {
() -> "The Output:\n" + cliResult.getOutput() + "doesn't contains the expected string.");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@RawDistOnly(reason = "Containers are immutable")
void errorSpiBuildtimeAtRuntime(KeycloakDistribution dist) {
CLIResult cliResult = dist.run("build", "--db=dev-file");
void errorSpiBuildtimeAtRuntime(KeycloakRunner runner) {
CLIResult cliResult = runner.run("build", "--db=dev-file");
cliResult.assertBuild();
cliResult = dist.run("start", "--optimized", "--http-enabled=true", "--hostname-strict=false", "--spi-events-listener--jboss-logging--enabled=false");
cliResult = runner.run("start", "--optimized", "--http-enabled=true", "--hostname-strict=false", "--spi-events-listener--jboss-logging--enabled=false");
cliResult.assertError("The following build time options have values that differ from what is persisted - the new values will NOT be used until another build is run: kc.spi-events-listener--jboss-logging--enabled");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@WithEnvVars({"KC_SPI_EVENTS_LISTENER__JBOSS_LOGGING__ENABLED", "false"})
@Test
@RawDistOnly(reason = "Containers are immutable")
void noErrorSpiBuildtimeNotChanged(KeycloakDistribution dist) {
CLIResult cliResult = dist.run("build", "--db=dev-file");
void noErrorSpiBuildtimeNotChanged(KeycloakRunner runner) {
CLIResult cliResult = runner.run("build", "--db=dev-file");
cliResult.assertBuild();
cliResult = dist.run("start", "--optimized", "--http-enabled=true", "--hostname-strict=false");
cliResult = runner.run("start", "--optimized", "--http-enabled=true", "--hostname-strict=false");
cliResult.assertNoError("The following build time options");
}
@Test
@RawDistOnly(reason = "Containers are immutable")
void terminateStartOptimized(KeycloakDistribution dist) {
CLIResult cliResult = dist.run("build", "--db=dev-file");
void terminateStartOptimized(KeycloakRunner runner) {
CLIResult cliResult = runner.run("build", "--db=dev-file");
cliResult.assertBuild();
dist.setManualStop(true);
cliResult = dist.run("start", "--optimized", "--http-enabled=true", "--hostname-strict=false");
runner.setStopServer(Mode.MANUAL);
cliResult = runner.run("start", "--optimized", "--http-enabled=true", "--hostname-strict=false");
cliResult.assertStarted();
// if the child java process does not clean up, then subsequent start will fail
dist.stop();
runner.stop();
cliResult = dist.run("start", "--optimized", "--http-enabled=true", "--hostname-strict=false");
cliResult = runner.run("start", "--optimized", "--http-enabled=true", "--hostname-strict=false");
cliResult.assertStarted();
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "--profile=dev", "start", "--db=dev-file" })
void failUsingDevProfile(CLIResult cliResult) {
@ -132,7 +132,7 @@ public class StartCommandDistTest {
cliResult.assertStarted();
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "--profile=dev", "start", "--http-enabled=true", "--hostname-strict=false" })
void failIfAutoBuildUsingDevProfile(CLIResult cliResult) {
@ -140,7 +140,7 @@ public class StartCommandDistTest {
assertEquals(4, cliResult.getErrorStream().size());
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@WithEnvVars({"KC_HTTP_ENABLED", "true", "KC_HOSTNAME_STRICT", "false"})
@Test
@Launch({ "start", "--optimized" })
@ -149,7 +149,7 @@ public class StartCommandDistTest {
cliResult.assertError("The '--optimized' flag was used for first ever server start.");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "start", "--optimized", "--http-enabled=true", "--hostname-strict=false" })
@Order(2)
@ -164,7 +164,7 @@ public class StartCommandDistTest {
() -> "The Output:\n" + cliResult.getOutput() + "doesn't contains the expected string.");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "start", "--db=dev-file", "--http-enabled=true", "--hostname-strict=false", "--metrics-enabled=true" })
void testStartUsingAutoBuild(CLIResult cliResult) {
@ -178,14 +178,14 @@ public class StartCommandDistTest {
assertTrue(cliResult.getErrorOutput().isBlank(), cliResult.getErrorOutput());
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "start", "--db=dev-file", "--http-enabled=true", "--cache-remote-host=localhost", "--hostname-strict=false", "--cache-remote-tls-enabled=false", "--transaction-xa-enabled=true" })
void testStartNoWarningOnDisabledRuntimeOption(CLIResult cliResult) {
cliResult.assertNoMessage("cache-remote-tls-enabled: Available only when remote host is set");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@WithEnvVars({"KC_LOG", "invalid"})
@Launch({ "start", "--db=dev-file", "--http-enabled=false", "--hostname-strict=false" })
@ -193,13 +193,13 @@ public class StartCommandDistTest {
cliResult.assertError("Invalid value for option 'KC_LOG': invalid. Expected values are: console, file, syslog");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@RawDistOnly(reason = "Containers are immutable")
void testWarningWhenOverridingBuildOptionsDuringStart(KeycloakDistribution dist) {
CLIResult cliResult = dist.run("build", "--db=postgres", "--features=preview");
void testWarningWhenOverridingBuildOptionsDuringStart(KeycloakRunner runner) {
CLIResult cliResult = runner.run("build", "--db=postgres", "--features=preview");
cliResult.assertBuild();
cliResult = dist.run("start", "--db=dev-file", "--hostname=localhost", "--http-enabled=true");
cliResult = runner.run("start", "--db=dev-file", "--hostname=localhost", "--http-enabled=true");
cliResult.assertMessage("The previous optimized build will be overridden with the following build options:");
cliResult.assertMessage("- db=postgres > db=dev-file"); // back to the default value
cliResult.assertMessage("- features=preview > features=<unset>"); // no default value, the <unset> is shown
@ -207,51 +207,51 @@ public class StartCommandDistTest {
assertTrue(cliResult.getErrorOutput().isBlank());
// should not show warning if the re-augmentation did not happen through the build command
// an optimized server image should ideally be created by running a build
cliResult = dist.run("start", "--db=dev-mem", "--hostname=localhost", "--http-enabled=true");
cliResult = runner.run("start", "--db=dev-mem", "--hostname=localhost", "--http-enabled=true");
cliResult.assertNoMessage("The previous optimized build will be overridden with the following build options:");
assertTrue(cliResult.getErrorOutput().isBlank());
dist.run("build", "--db=postgres");
cliResult = dist.run("start", "--db=dev-file", "--hostname=localhost", "--http-enabled=true");
runner.run("build", "--db=postgres");
cliResult = runner.run("start", "--db=dev-file", "--hostname=localhost", "--http-enabled=true");
cliResult.assertMessage("- db=postgres > db=dev-file");
cliResult.assertNoMessage("- features=preview > features=<unset>");
assertTrue(cliResult.getErrorOutput().isBlank());
dist.run("build", "--db=postgres");
cliResult = dist.run("start", "--db=dev-mem", "--hostname=localhost", "--http-enabled=true");
runner.run("build", "--db=postgres");
cliResult = runner.run("start", "--db=dev-mem", "--hostname=localhost", "--http-enabled=true");
cliResult.assertMessage("- db=postgres > db=dev-mem"); // option overridden during the start
assertTrue(cliResult.getErrorOutput().isBlank());
dist.run("build", "--db=dev-mem");
cliResult = dist.run("start", "--db=dev-mem", "--hostname=localhost", "--http-enabled=true");
runner.run("build", "--db=dev-mem");
cliResult = runner.run("start", "--db=dev-mem", "--hostname=localhost", "--http-enabled=true");
cliResult.assertNoMessage("- db=postgres > db=postgres"); // option did not change not need to show
assertTrue(cliResult.getErrorOutput().isBlank());
dist.run("build", "--db=dev-mem");
cliResult = dist.run("start", "--db=dev-mem", "--cache=local", "--hostname=localhost", "--http-enabled=true");
runner.run("build", "--db=dev-mem");
cliResult = runner.run("start", "--db=dev-mem", "--cache=local", "--hostname=localhost", "--http-enabled=true");
cliResult.assertNoMessage("The previous optimized build will be overridden with the following build options:"); // no message, same values provided during auto-build
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@RawDistOnly(reason = "Containers are immutable")
void testStartAfterStartDev(KeycloakDistribution dist) {
CLIResult cliResult = dist.run("start-dev");
void testStartAfterStartDev(KeycloakRunner runner) {
CLIResult cliResult = runner.run("start-dev");
cliResult.assertStartedDevMode();
cliResult = dist.run("start", "--db=dev-file", "--http-enabled", "true", "--hostname-strict", "false");
cliResult = runner.run("start", "--db=dev-file", "--http-enabled", "true", "--hostname-strict", "false");
cliResult.assertNotDevMode();
assertTrue(cliResult.getErrorOutput().isBlank());
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@RawDistOnly(reason = "Containers are immutable")
void testErrorWhenOverridingNonCliBuildOptionsDuringStart(KeycloakDistribution dist) {
CLIResult cliResult = dist.run("build", "--db=dev-file", "--features=preview");
void testErrorWhenOverridingNonCliBuildOptionsDuringStart(KeycloakRunner runner) {
CLIResult cliResult = runner.run("build", "--db=dev-file", "--features=preview");
cliResult.assertBuild();
dist.setEnvVar("KC_DB", "postgres");
cliResult = dist.run("start", "--optimized", "--hostname=localhost", "--http-enabled=true");
runner.setEnvVar("KC_DB", "postgres");
cliResult = runner.run("start", "--optimized", "--hostname=localhost", "--http-enabled=true");
cliResult.assertError("The following build time options have values that differ from what is persisted - the new values will NOT be used until another build is run: kc.db");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({CONFIG_FILE_LONG_NAME + "=src/test/resources/non-existing.conf", "start", "--db=dev-file"})
void testInvalidConfigFileOption(CLIResult cliResult) {
@ -259,7 +259,7 @@ public class StartCommandDistTest {
cliResult.assertError(String.format("Try '%s --help' for more information on the available options.", KeycloakDistribution.SCRIPT_CMD));
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({CONFIG_FILE_LONG_NAME + "=src/test/resources/keycloak.properties", "start", "--db=dev-file"})
void testConfigFileWithWrongExtension(CLIResult cliResult) {
@ -268,20 +268,20 @@ public class StartCommandDistTest {
@RawDistOnly(reason = "Containers are immutable")
@Test
void testRuntimeValuesAreNotCaptured(KeycloakDistribution dist) {
void testRuntimeValuesAreNotCaptured(KeycloakRunner runner) {
// confirm that the invalid value prevents startup - if this passes, then we need to use a different
// spi provider
CLIResult cliResult = dist.run("start", "--db=dev-file", "--spi-events-listener-jboss-logging-success-level=invalid", "--http-enabled", "true", "--hostname-strict", "false");
CLIResult cliResult = runner.run("start", "--db=dev-file", "--spi-events-listener-jboss-logging-success-level=invalid", "--http-enabled", "true", "--hostname-strict", "false");
cliResult.assertError("Failed to start quarkus");
// if there was no auto-build use an explicit build to potentially capture the runtime default
if (!cliResult.getOutput().contains("Server configuration updated and persisted")) {
cliResult = dist.run("build", "--db=dev-file", "--spi-events-listener-jboss-logging-success-level=invalid");
cliResult = runner.run("build", "--db=dev-file", "--spi-events-listener-jboss-logging-success-level=invalid");
cliResult.assertBuild();
}
// the invalid value should not be the default
cliResult = dist.run("start", "--db=dev-file", "--http-enabled", "true", "--hostname-strict", "false");
cliResult = runner.run("start", "--db=dev-file", "--http-enabled", "true", "--hostname-strict", "false");
cliResult.assertNoBuild();
cliResult.assertStarted();
}
@ -289,15 +289,15 @@ public class StartCommandDistTest {
@RawDistOnly(reason = "Containers are immutable")
@Test
@TestProvider(TestRealmResourceTestProvider.class)
void testAsyncBootstrapFails(KeycloakDistribution dist) {
RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class);
dist.setManualStop(true);
CLIResult result = dist.run("start", "--server-async-bootstrap=true", "--hostname-strict=false", "--db=dev-file", "--http-enabled=true", "--spi-realm-restapi-extension--test-resources--fail=true");
Awaitility.await().atMost(2, TimeUnit.MINUTES).until(() -> !rawDist.isRunning());
dist.stop();
void testAsyncBootstrapFails(KeycloakRunner runner) {
RawKeycloakDistribution rawDist = runner.getDistribution(RawKeycloakDistribution.class);
runner.setStopServer(Mode.MANUAL);
CLIResult result = runner.run("start", "--server-async-bootstrap=true", "--hostname-strict=false", "--db=dev-file", "--http-enabled=true", "--spi-realm-restapi-extension--test-resources--fail=true");
rawDist.waitFor(false, TimeUnit.MINUTES.toMillis(2));
runner.stop();
result.assertMessage("Failed to start server");
result.assertMessage("I've failed");
assertEquals(1, dist.getExitCode());
assertEquals(1, rawDist.getExitCode());
}
}

View file

@ -22,10 +22,10 @@ import java.nio.file.Paths;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.DryRun;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawKeycloakDistribution;
import org.keycloak.it.junit5.extension.StopServer;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import io.quarkus.test.junit.main.Launch;
import org.junit.jupiter.api.MethodOrderer;
@ -42,7 +42,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag(DistributionTest.WIN)
public class StartDevCommandDistTest {
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "start-dev" })
void testDevModeWarning(CLIResult cliResult) {
@ -53,7 +53,7 @@ public class StartDevCommandDistTest {
assertFalse(out.contains("0.0.0.0") || out.contains("all addresses"));
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "start-dev", "--db=dev-mem" })
void testBuildPropertyAvailable(CLIResult cliResult) {
@ -70,7 +70,7 @@ public class StartDevCommandDistTest {
cliResult.assertNoMessage("Build time property cannot");
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "build", "--debug", "--db=dev-file" })
void testBuildMustNotRunTwoJVMs(CLIResult cliResult) {
@ -78,7 +78,7 @@ public class StartDevCommandDistTest {
cliResult.assertBuild();
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
@Launch({ "start-dev", "--verbose" })
void testVerboseAfterCommand(CLIResult cliResult) {
@ -86,8 +86,8 @@ public class StartDevCommandDistTest {
}
@Test
void testConfigKeystoreAbsolutePath(KeycloakDistribution dist) {
CLIResult cliResult = dist.run("start-dev", "--config-keystore=" + Paths.get("src/test/resources/keystore").toAbsolutePath().normalize(),
void testConfigKeystoreAbsolutePath(KeycloakRunner runner) {
CLIResult cliResult = runner.run("start-dev", "--config-keystore=" + Paths.get("src/test/resources/keystore").toAbsolutePath().normalize(),
"--config-keystore-password=secret");
// keytool -importpass -alias kc.log-level -keystore keystore -storepass secret -storetype PKCS12 -v (with "org.keycloak.timer:debug" as the stored password)
@ -99,17 +99,16 @@ public class StartDevCommandDistTest {
cliResult.assertStartedDevMode();
}
@DryRun
@StopServer(Mode.BEFORE_QUARKUS)
@Test
void testStartDevThenImportRebuild(KeycloakDistribution dist) throws Exception {
RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class);
CLIResult result = rawDist.run("start-dev");
void testStartDevThenImportRebuild(KeycloakRunner runner) throws Exception {
CLIResult result = runner.run("start-dev");
assertTrue(result.getErrorOutput().isEmpty(), result.getErrorOutput());
File target = new File("./target");
// feature change should trigger a build
result = rawDist.run("--profile=dev", "export", "--features=docker", "--dir=" + target.getAbsolutePath());
result = runner.run("--profile=dev", "export", "--features=docker", "--dir=" + target.getAbsolutePath());
result.assertMessage("Updating the configuration and installing your custom providers, if any. Please wait.");
}

View file

@ -20,7 +20,7 @@ package org.keycloak.it.cli.dist;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.SkipRealmBootstrap;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
@ -30,9 +30,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@DistributionTest
@DistributionTest(stopServer = Mode.BEFORE_BOOTSTRAP)
@RawDistOnly(reason = "Containers are immutable")
@SkipRealmBootstrap
public class TracingDistTest {
static void assertTracingEnabled(CLIResult result) {

View file

@ -21,10 +21,11 @@ import java.io.IOException;
import java.util.Map;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.junit5.extension.TestProvider;
import org.keycloak.it.resource.realm.TestRealmResourceTestProvider;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.util.JsonSerialization;
import com.fasterxml.jackson.core.type.TypeReference;
@ -34,38 +35,38 @@ import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.when;
import static org.hamcrest.MatcherAssert.assertThat;
@DistributionTest(keepAlive = true)
@DistributionTest(stopServer = Mode.MANUAL)
@RawDistOnly(reason = "Containers are immutable")
public class TransactionDistTest {
@Test
void testZeroTransactionTimeout(KeycloakDistribution dist) {
var result = dist.run("start-dev", "--transaction-default-timeout=0s");
void testZeroTransactionTimeout(KeycloakRunner runner) {
var result = runner.run("start-dev", "--transaction-default-timeout=0s");
result.assertError("Invalid duration '0s' for option 'transaction-default-timeout. Duration must be positive.");
result = dist.run("start-dev", "--transaction-setup-timeout=0s");
result = runner.run("start-dev", "--transaction-setup-timeout=0s");
result.assertError("Invalid duration '0s' for option 'transaction-setup-timeout. Duration must be positive.");
}
@Test
void testNegativeTransactionTimeout(KeycloakDistribution dist) {
var result = dist.run("start-dev", "--transaction-default-timeout=-1s");
void testNegativeTransactionTimeout(KeycloakRunner runner) {
var result = runner.run("start-dev", "--transaction-default-timeout=-1s");
result.assertError("Invalid duration '-1s' for option 'transaction-default-timeout. Duration must be positive.");
result = dist.run("start-dev", "--transaction-setup-timeout=-2s");
result = runner.run("start-dev", "--transaction-setup-timeout=-2s");
result.assertError("Invalid duration '-2s' for option 'transaction-setup-timeout. Duration must be positive.");
}
@Test
void testNonNumberTransactionTimeout(KeycloakDistribution dist) {
var result = dist.run("start-dev", "--transaction-default-timeout=abc");
void testNonNumberTransactionTimeout(KeycloakRunner runner) {
var result = runner.run("start-dev", "--transaction-default-timeout=abc");
result.assertError("Invalid duration format 'abc' for option 'transaction-default-timeout'. May be an ISO 8601 duration value, an integer number of seconds, or an integer followed by one of [ms, h, m, s, d].");
result = dist.run("start-dev", "--transaction-setup-timeout=def");
result = runner.run("start-dev", "--transaction-setup-timeout=def");
result.assertError("Invalid duration format 'def' for option 'transaction-setup-timeout'. May be an ISO 8601 duration value, an integer number of seconds, or an integer followed by one of [ms, h, m, s, d].");
}
@Test
@TestProvider(TestRealmResourceTestProvider.class)
void testValidTransactionTimeout(KeycloakDistribution dist) throws IOException {
var result = dist.run("start-dev", "--transaction-default-timeout=123s", "--transaction-setup-timeout=456s");
void testValidTransactionTimeout(KeycloakRunner runner) throws IOException {
var result = runner.run("start-dev", "--transaction-default-timeout=123s", "--transaction-setup-timeout=456s");
result.assertStartedDevMode();
assertTimeouts();
}

View file

@ -22,8 +22,9 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.utils.RawKeycloakDistribution;
import org.keycloak.truststore.TruststoreBuilder;
@ -35,7 +36,7 @@ import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
@DistributionTest(keepAlive = true)
@DistributionTest(stopServer = Mode.MANUAL)
@RawDistOnly(reason = "Containers are immutable")
@Tag(DistributionTest.SMOKE)
public class TruststoreDistTest {
@ -46,19 +47,19 @@ public class TruststoreDistTest {
}
@Test
void testMutualAuthWithTruststorePaths(KeycloakDistribution dist) {
void testMutualAuthWithTruststorePaths(KeycloakRunner runner) {
String[] truststoreNames = new String[] { "keycloak-truststore.p12", "self-signed.pem" };
RawKeycloakDistribution rawDist = runner.getDistribution(RawKeycloakDistribution.class);
Stream.of(truststoreNames).forEach(truststoreName -> {
dist.copyOrReplaceFileFromClasspath("/" + truststoreName, Path.of("conf", truststoreName));
rawDist.copyOrReplaceFileFromClasspath("/" + truststoreName, Path.of("conf", truststoreName));
});
RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class);
String paths = Stream.of(truststoreNames).map(truststoreName -> rawDist.getDistPath().resolve("conf")
.resolve(truststoreName).toAbsolutePath().toString()).collect(Collectors.joining(","));
dist.copyOrReplaceFileFromClasspath("/self-signed.p12", Path.of("conf", "self-signed.p12"));
rawDist.copyOrReplaceFileFromClasspath("/self-signed.p12", Path.of("conf", "self-signed.p12"));
Path keyStore = rawDist.getDistPath().resolve("conf").resolve("self-signed.p12").toAbsolutePath();
rawDist.run("--verbose", "start", "--db=dev-file", "--http-enabled=true", "--hostname=mykeycloak.org",
runner.run("--verbose", "start", "--db=dev-file", "--http-enabled=true", "--hostname=mykeycloak.org",
"--truststore-paths=" + paths, "--https-client-auth=required", "--https-key-store-file=" + keyStore);
given().trustStore(TruststoreDistTest.class.getResource("/self-signed-truststore.p12").getPath(), TruststoreBuilder.DUMMY_PASSWORD)
@ -67,17 +68,17 @@ public class TruststoreDistTest {
}
@Test
void testMutualAuthWithDefaultTruststoresDir(KeycloakDistribution dist) {
void testMutualAuthWithDefaultTruststoresDir(KeycloakRunner runner) {
String[] truststoreNames = new String[] { "keycloak-truststore.p12", "self-signed.pem" };
RawKeycloakDistribution rawDist = runner.getDistribution(RawKeycloakDistribution.class);
Stream.of(truststoreNames).forEach(truststoreName -> {
dist.copyOrReplaceFileFromClasspath("/" + truststoreName, Path.of("conf", "truststores", truststoreName));
rawDist.copyOrReplaceFileFromClasspath("/" + truststoreName, Path.of("conf", "truststores", truststoreName));
});
RawKeycloakDistribution rawDist = dist.unwrap(RawKeycloakDistribution.class);
dist.copyOrReplaceFileFromClasspath("/self-signed.p12", Path.of("conf", "self-signed.p12"));
rawDist.copyOrReplaceFileFromClasspath("/self-signed.p12", Path.of("conf", "self-signed.p12"));
Path keyStore = rawDist.getDistPath().resolve("conf").resolve("self-signed.p12").toAbsolutePath();
rawDist.run("--verbose", "start", "--db=dev-file", "--http-enabled=true", "--hostname=mykeycloak.org",
runner.run("--verbose", "start", "--db=dev-file", "--http-enabled=true", "--hostname=mykeycloak.org",
"--https-client-auth=required", "--https-key-store-file=" + keyStore);
given().trustStore(TruststoreDistTest.class.getResource("/self-signed-truststore.p12").getPath(), TruststoreBuilder.DUMMY_PASSWORD)

View file

@ -29,8 +29,9 @@ import java.util.concurrent.TimeUnit;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.KeycloakRunner;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawDistRootPath;
import org.keycloak.it.utils.RawKeycloakDistribution;
import org.junit.jupiter.api.AfterEach;
@ -57,20 +58,16 @@ public class WindowsServiceDistTest {
private static final int SERVICE_START_TIMEOUT_SECONDS = 120;
private static final int SERVICE_STOP_TIMEOUT_SECONDS = 60;
private RawKeycloakDistribution rawDist;
private Path distPath;
private String testServiceName;
private boolean serviceCreated = false;
private boolean prunsrvAvailable = false;
@BeforeEach
void setUp(KeycloakDistribution dist) {
this.rawDist = dist.unwrap(RawKeycloakDistribution.class);
this.distPath = rawDist.getDistPath();
void setUp(KeycloakRunner runner, RawDistRootPath path) {
this.testServiceName = TEST_SERVICE_NAME_PREFIX + System.currentTimeMillis();
// Check if prunsrv.exe is available in the distribution
Path prunsrvPath = distPath.resolve("bin").resolve("prunsrv.exe");
Path prunsrvPath = path.getDistRootPath().resolve("bin").resolve("prunsrv.exe");
if (!Files.exists(prunsrvPath)) {
String prunsrvSystemPath = findPrunsrvInSystem();
if (prunsrvSystemPath != null) {
@ -87,7 +84,7 @@ public class WindowsServiceDistTest {
}
@AfterEach
void tearDown() {
void tearDown(KeycloakRunner runner) {
if (serviceCreated) {
try {
stopService();
@ -95,7 +92,7 @@ public class WindowsServiceDistTest {
System.err.println("Failed to stop service during cleanup: " + e.getMessage());
}
try {
deleteService();
deleteService(runner);
} catch (Exception e) {
System.err.println("Failed to delete service during cleanup: " + e.getMessage());
}
@ -103,20 +100,21 @@ public class WindowsServiceDistTest {
}
@Test
void testServiceLifecycle() throws Exception {
void testServiceLifecycle(KeycloakRunner runner) throws Exception {
assertPrunsrvAvailable();
assertAdminPrivileges();
String customDisplayName = "Keycloak Test Service " + testServiceName;
String customDescription = "Keycloak integration test service";
RawKeycloakDistribution rawDist = runner.getDistribution(RawKeycloakDistribution.class);
rawDist.setProperty("http-enabled", "true");
rawDist.setProperty("hostname-strict", "false");
rawDist.setProperty("log", "console,file");
rawDist.setProperty("log-file", distPath.resolve("log").resolve("keycloak.log").toString());
rawDist.setProperty("log-file", rawDist.getDistPath().resolve("log").resolve("keycloak.log").toString());
// Install the service with custom name and display name
CLIResult installResult = rawDist.run("tools", "windows-service", "install",
CLIResult installResult = runner.run("tools", "windows-service", "install",
"--name=" + testServiceName,
"--display-name=" + customDisplayName,
"--description=" + customDescription,
@ -137,7 +135,7 @@ public class WindowsServiceDistTest {
assertEquals("RUNNING", getServiceState(testServiceName), "Service should be in RUNNING state");
// Verify log file was created and contains startup message
Path logFile = distPath.resolve("log").resolve("keycloak.log");
Path logFile = rawDist.getDistPath().resolve("log").resolve("keycloak.log");
assertTrue(waitForLogFile(logFile), "Log file should be created");
String logContent = Files.readString(logFile);
assertThat("Log should contain Keycloak startup message", logContent, containsString("Listening on:"));
@ -148,7 +146,7 @@ public class WindowsServiceDistTest {
assertFalse(isKeycloakAccessible(), "Keycloak should not be accessible after service stop");
// Test service uninstall
CLIResult uninstallResult = rawDist.run("tools", "windows-service", "uninstall", "--name=" + testServiceName);
CLIResult uninstallResult = runner.run("tools", "windows-service", "uninstall", "--name=" + testServiceName);
assertEquals(0, uninstallResult.exitCode(), "Service uninstallation failed: " + uninstallResult.getOutput());
assertThat(uninstallResult.getOutput(), containsString("uninstalled successfully"));
serviceCreated = false;
@ -237,8 +235,8 @@ public class WindowsServiceDistTest {
return true;
}
private void deleteService() {
rawDist.run("tools", "windows-service", "uninstall", "--name=" + testServiceName);
private void deleteService(KeycloakRunner runner) {
runner.run("tools", "windows-service", "uninstall", "--name=" + testServiceName);
}
private boolean isServiceCreated(String serviceName) {

View file

@ -22,6 +22,7 @@ import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.InfinispanContainer;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.junit5.extension.WithExternalInfinispan;
import io.quarkus.test.junit.main.Launch;
@ -30,7 +31,7 @@ import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.when;
@DistributionTest(keepAlive = true)
@DistributionTest(stopServer = Mode.MANUAL)
@WithExternalInfinispan
@Tag(DistributionTest.STORAGE)
public class ExternalInfinispanTest {

View file

@ -28,7 +28,7 @@ import io.quarkus.test.junit.main.Launch;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@DistributionTest(removeBuildOptionsAfterBuild = true)
@DistributionTest
@WithDatabase(alias = "mariadb")
public class MariaDBDistTest extends MariaDBTest {

View file

@ -23,7 +23,7 @@ import org.keycloak.it.storage.database.MssqlSQLTest;
import org.junit.jupiter.api.Tag;
@DistributionTest(removeBuildOptionsAfterBuild = true)
@DistributionTest
@WithDatabase(alias = "mssql")
@Tag(DistributionTest.STORAGE)
public class MssqlDistTest extends MssqlSQLTest {

View file

@ -14,7 +14,7 @@ import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
@DistributionTest(removeBuildOptionsAfterBuild = true)
@DistributionTest
@WithDatabase(alias = "mysql")
public class MySQLDistTest extends MySQLTest {

View file

@ -17,11 +17,15 @@
package org.keycloak.it.storage.database.dist;
import java.util.function.Consumer;
import org.keycloak.it.junit5.extension.BeforeStartDistribution;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.WithDatabase;
import org.keycloak.it.storage.database.PostgreSQLTest;
import org.keycloak.it.utils.RawDistRootPath;
import org.keycloak.it.utils.RawKeycloakDistribution;
import org.keycloak.quarkus.runtime.cli.command.AbstractAutoBuildCommand;
import io.quarkus.test.junit.main.Launch;
@ -31,16 +35,24 @@ import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
@DistributionTest(removeBuildOptionsAfterBuild = true)
@DistributionTest
@WithDatabase(alias = "postgres")
@Tag(DistributionTest.STORAGE)
public class PostgreSQLDistTest extends PostgreSQLTest {
@BeforeStartDistribution(RemoveDB.class)
@Test
@Launch("show-config")
public void testDbOptionFromPersistedConfigSource(CLIResult cliResult) {
assertThat(cliResult.getOutput(),containsString("postgres (Persisted)"));
}
public static final class RemoveDB implements Consumer<RawKeycloakDistribution> {
@Override
public void accept(RawKeycloakDistribution distribution) {
distribution.removeProperty("db");
}
}
@Tag(DistributionTest.STORAGE)
@Test

View file

@ -23,7 +23,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.function.Consumer;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawKeycloakDistribution;
/**
* {@link BeforeStartDistribution} is used to perform additional steps prior to starting the distribution.
@ -32,6 +32,6 @@ import org.keycloak.it.utils.KeycloakDistribution;
@Retention(RetentionPolicy.RUNTIME)
public @interface BeforeStartDistribution {
Class<? extends Consumer<KeycloakDistribution>> value();
Class<? extends Consumer<RawKeycloakDistribution>> value();
}

View file

@ -22,6 +22,7 @@ import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import org.keycloak.Keycloak;
@ -29,7 +30,6 @@ import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawDistRootPath;
import org.keycloak.it.utils.RawKeycloakDistribution;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.cli.command.DryRunMixin;
import org.keycloak.quarkus.runtime.cli.command.Start;
import org.keycloak.quarkus.runtime.cli.command.StartDev;
@ -50,7 +50,7 @@ import static org.keycloak.quarkus.runtime.Environment.forceExitAfterStartLaunch
public class CLITestExtension extends QuarkusMainTestExtension {
private KeycloakDistribution dist;
private KeycloakRunner runner;
private DatabaseContainer databaseContainer;
private InfinispanContainer infinispanContainer;
private CLIResult result;
@ -74,9 +74,9 @@ public class CLITestExtension extends QuarkusMainTestExtension {
});
}
if (isRaw() && distConfig != null && dist != null) {
if (isRaw() && distConfig != null && runner != null) {
try {
dist.unwrap(RawKeycloakDistribution.class).reset(beforeAll);
runner.getDistribution(RawKeycloakDistribution.class).reset(beforeAll);
beforeAll = false;
} catch (Exception cause) {
throw new RuntimeException("Failed to partially reset", cause);
@ -87,12 +87,10 @@ public class CLITestExtension extends QuarkusMainTestExtension {
infinispanContainer = configureExternalInfinispan(context);
if (distConfig != null) {
if (dist == null) {
dist = createDistribution(distConfig, getStoreConfig(context), getDatabaseConfig(context));
if (runner == null) {
runner = createDistribution(distConfig);
}
onKeepServerAlive(context.getRequiredTestMethod().getAnnotation(KeepServerAlive.class), true);
copyTestProvider(context.getRequiredTestClass().getAnnotation(TestProvider.class));
copyTestProvider(context.getRequiredTestMethod().getAnnotation(TestProvider.class));
onBeforeStartDistribution(context.getRequiredTestClass().getAnnotation(BeforeStartDistribution.class));
@ -100,19 +98,13 @@ public class CLITestExtension extends QuarkusMainTestExtension {
configureEnvVars(context.getRequiredTestClass().getAnnotation(WithEnvVars.class));
configureEnvVars(context.getRequiredTestMethod().getAnnotation(WithEnvVars.class));
boolean dryRun = context.getRequiredTestClass().getAnnotation(DryRun.class) != null
|| context.getRequiredTestMethod().getAnnotation(DryRun.class) != null;
if (dryRun && isRaw()) {
dist.setEnvVar(DryRunMixin.KC_DRY_RUN_ENV, "true");
dist.setEnvVar(DryRunMixin.KC_DRY_RUN_BUILD_ENV, "true");
}
if (isRaw() && (context.getRequiredTestClass().getAnnotation(SkipRealmBootstrap.class) != null
|| context.getRequiredTestMethod().getAnnotation(SkipRealmBootstrap.class) != null)) {
dist.unwrap(RawKeycloakDistribution.class).setLaunchMode(Environment.LAUNCH_MODE_EXIT_BEFORE_BOOTSTRAP);
}
var stopServer = Optional.ofNullable(context.getRequiredTestMethod().getAnnotation(StopServer.class)).map(StopServer::value).orElse(distConfig.stopServer());
runner.setStopServer(stopServer);
if (launch != null) {
result = dist.run(List.of(launch.value()));
result = runner.run(List.of(launch.value()));
}
} else {
if (!Keycloak.initSys(launch == null ? new String[] {} : launch.value())) {
@ -123,10 +115,6 @@ public class CLITestExtension extends QuarkusMainTestExtension {
}
}
private static Storage getStoreConfig(ExtensionContext context) {
return context.getTestClass().get().getDeclaredAnnotation(Storage.class);
}
private void copyTestProvider(TestProvider provider) {
if (provider == null) {
return;
@ -134,7 +122,7 @@ public class CLITestExtension extends QuarkusMainTestExtension {
if (isRaw()) {
try {
dist.unwrap(RawKeycloakDistribution.class).copyProvider(provider.value().getDeclaredConstructor().newInstance());
runner.getDistribution(RawKeycloakDistribution.class).copyProvider(provider.value().getDeclaredConstructor().newInstance());
} catch (Exception cause) {
throw new RuntimeException("Failed to instantiate test provider: " + provider.getClass(), cause);
}
@ -148,7 +136,7 @@ public class CLITestExtension extends QuarkusMainTestExtension {
@Override
public void interceptTestMethod(Invocation<Void> invocation,
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable {
if (dist == null) {
if (runner == null) {
super.interceptTestMethod(invocation, invocationContext, extensionContext);
} else {
invocation.proceed();
@ -158,34 +146,20 @@ public class CLITestExtension extends QuarkusMainTestExtension {
private void onBeforeStartDistribution(BeforeStartDistribution annotation) {
if (annotation != null) {
try {
annotation.value().getDeclaredConstructor().newInstance().accept(dist);
annotation.value().getDeclaredConstructor().newInstance().accept(runner.getDistribution(RawKeycloakDistribution.class));
} catch (Exception cause) {
throw new RuntimeException("Error when invoking " + annotation.value() + " instance before starting distribution", cause);
}
}
}
private void onKeepServerAlive(KeepServerAlive annotation, boolean setting) {
if(annotation != null && dist != null) {
try {
dist.setManualStop(setting);
} catch (Exception cause) {
throw new RuntimeException("Error when invoking " + annotation, cause);
}
}
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
DistributionTest distConfig = getDistributionConfig(context);
if (dist != null) {
onKeepServerAlive(context.getRequiredTestMethod().getAnnotation(KeepServerAlive.class), false);
dist.stop();
dist.clearEnv();
if (isRaw()) {
dist.unwrap(RawKeycloakDistribution.class).setLaunchMode(Environment.LAUNCH_MODE_EXIT_AFTER_START);
}
if (runner != null) {
runner.stop();
runner.getDistribution().clearEnv();
}
super.afterEach(context);
@ -229,7 +203,7 @@ public class CLITestExtension extends QuarkusMainTestExtension {
DistributionTest distConfig = getDistributionConfig(context);
if (distConfig != null) {
dist = createDistribution(distConfig, getStoreConfig(context), getDatabaseConfig(context));
runner = createDistribution(distConfig);
} else {
forceExitAfterStartLaunchMode();
}
@ -239,15 +213,15 @@ public class CLITestExtension extends QuarkusMainTestExtension {
@Override
public void afterAll(ExtensionContext context) throws Exception {
if (dist != null) {
if (runner != null) {
// just to make sure the server is stopped after all tests
dist.stop();
runner.stop();
}
super.afterAll(context);
}
private KeycloakDistribution createDistribution(DistributionTest config, Storage storeConfig, WithDatabase databaseConfig) {
return new KeycloakDistributionDecorator(storeConfig, databaseConfig, config, DistributionType.getCurrent().orElse(RAW).newInstance(config));
private KeycloakRunner createDistribution(DistributionTest config) {
return new KeycloakRunner(config, DistributionType.getCurrent().orElse(RAW).newInstance(config));
}
@Override
@ -274,12 +248,16 @@ public class CLITestExtension extends QuarkusMainTestExtension {
//assuming the path to the distribution directory
return getDistPath();
}
if (type.equals(KeycloakRunner.class)) {
return this.runner;
}
if (type.equals(KeycloakDistribution.class)) {
if (KeycloakDistribution.class.isAssignableFrom(type)) {
if (context.getTestClass().orElse(Object.class).getDeclaredAnnotation(DistributionTest.class) == null) {
throw new RuntimeException("Only tests annotated with " + DistributionTest.class + " can inject a distribution instance");
}
return dist;
return runner.getDistribution((Class<? extends KeycloakDistribution>) type);
}
// for now, no support for manual launching using QuarkusMainLauncher
@ -290,7 +268,7 @@ public class CLITestExtension extends QuarkusMainTestExtension {
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
Class<?> type = parameterContext.getParameter().getType();
return type == LaunchResult.class || type == CLIResult.class || type == RawDistRootPath.class || type == KeycloakDistribution.class;
return type == LaunchResult.class || type == CLIResult.class || type == RawDistRootPath.class || type == KeycloakDistribution.class || type == KeycloakRunner.class;
}
private void configureProfile(ExtensionContext context) {
@ -310,7 +288,7 @@ public class CLITestExtension extends QuarkusMainTestExtension {
WithDatabase database = getDatabaseConfig(context);
if (database != null) {
if (dist == null) {
if (runner == null) {
configureDevServices();
setProperty("kc.db", database.alias());
setProperty("kc.db-password", DatabaseContainer.DEFAULT_PASSWORD);
@ -319,19 +297,20 @@ public class CLITestExtension extends QuarkusMainTestExtension {
databaseContainer.start();
RawKeycloakDistribution rawDist = runner.getDistribution(RawKeycloakDistribution.class);
if (database.buildOptions().length == 0) {
dist.setProperty("db", database.alias());
rawDist.setProperty("db", database.alias());
} else {
for (String option : database.buildOptions()) {
dist.setProperty(option.substring(0, option.indexOf('=')), option.substring(option.indexOf('=') + 1));
rawDist.setProperty(option.substring(0, option.indexOf('=')), option.substring(option.indexOf('=') + 1));
}
}
databaseContainer.configureDistribution(dist);
databaseContainer.configureDistribution(rawDist);
dist.run("build");
runner.run("build");
}
} else if (dist == null) {
} else if (runner == null) {
// This is for re-creating the H2 database instead of using the default in home
setProperty("kc.db-url-path", Keycloak.initTempDirectory("h2-home").toFile().getAbsolutePath());
}
@ -374,7 +353,7 @@ public class CLITestExtension extends QuarkusMainTestExtension {
}
for (int i=0; i<envVars.value().length; i=i+2) {
dist.setEnvVar(envVars.value()[i], envVars.value()[i+1]);
runner.getDistribution().setEnvVar(envVars.value()[i], envVars.value()[i+1]);
}
}
@ -393,6 +372,6 @@ public class CLITestExtension extends QuarkusMainTestExtension {
}
private RawDistRootPath getDistPath(){
return new RawDistRootPath(dist.unwrap(RawKeycloakDistribution.class).getDistPath());
return new RawDistRootPath(runner.getDistribution(RawKeycloakDistribution.class).getDistPath());
}
}

View file

@ -20,7 +20,7 @@ package org.keycloak.it.junit5.extension;
import java.time.Duration;
import java.util.logging.Logger;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawKeycloakDistribution;
import org.jboss.logmanager.Level;
import org.jboss.logmanager.LogManager;
@ -53,7 +53,7 @@ public class DatabaseContainer {
return container.isRunning();
}
void configureDistribution(KeycloakDistribution dist) {
void configureDistribution(RawKeycloakDistribution dist) {
dist.setProperty("db-username", getUsername());
dist.setProperty("db-password", getPassword());
dist.setProperty("db-url", getJdbcUrl());

View file

@ -26,9 +26,12 @@ import org.junit.jupiter.api.extension.ExtendWith;
/**
* A Test that runs against a Keycloak distribution, which can be from a Docker container or zip (raw).
* <br>
* <p>
* Note with the raw distribution test methods are not completely isolated for performance reasons.
* Only a single distribution will be installed and it will only have its augmentation state reset before each test class.
* <p>
* Also the test logic assumes defaults, such as local cache mode (see {@link DistributionTest#localCache()} and
* a 0 second shutdown delay.
*/
@Target(ElementType.TYPE)
@ExtendWith({ CLITestExtension.class })
@ -42,16 +45,11 @@ public @interface DistributionTest {
boolean debug() default false;
/**
* If the distribution should be left running after the launch.
* Controls how the server stops.
*/
boolean keepAlive() default false;
StopServer.Mode stopServer() default StopServer.Mode.AFTER_START;
boolean enableTls() default false;
/**
* If any build option must be unset after the running the build command.
*/
boolean removeBuildOptionsAfterBuild() default false;
/**
* If any option must be set when starting the server.
*/
@ -66,4 +64,9 @@ public @interface DistributionTest {
* Default port for making HTTP requests with RestAssured
*/
int requestPort() default 8080;
/**
* If the cache should default to local even when the start command is used.
*/
boolean localCache() default true;
}

View file

@ -32,20 +32,11 @@ public enum DistributionType {
private static KeycloakDistribution createDockerDistribution(DistributionTest config) {
return new DockerKeycloakDistribution(
config.debug(),
config.keepAlive(),
config.requestPort(),
config.containerExposedPorts());
}
private static KeycloakDistribution createRawDistribution(DistributionTest config) {
return new RawKeycloakDistribution(
config.debug(),
config.keepAlive(),
config.enableTls(),
false,
config.removeBuildOptionsAfterBuild(),
config.requestPort());
return new RawKeycloakDistribution(false);
}
private final Function<DistributionTest, KeycloakDistribution> factory;

View file

@ -1,32 +0,0 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.it.junit5.extension;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* {@link DryRun} is used to configure a non-running, non-augmenting distribution
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface DryRun {
}

View file

@ -1,152 +0,0 @@
/*
* Copyright 2022 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.it.junit5.extension;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.keycloak.it.utils.KeycloakDistribution;
public class KeycloakDistributionDecorator implements KeycloakDistribution {
private Storage storageConfig;
private WithDatabase databaseConfig;
private DistributionTest config;
private KeycloakDistribution delegate;
public KeycloakDistributionDecorator(Storage storageConfig, WithDatabase databaseConfig, DistributionTest config,
KeycloakDistribution delegate) {
this.storageConfig = storageConfig;
this.databaseConfig = databaseConfig;
this.config = config;
this.delegate = delegate;
}
@Override
public CLIResult run(List<String> rawArgs) {
List<String> args = new ArrayList<>(rawArgs);
args.addAll(List.of(config.defaultOptions()));
setEnvVar("KC_SHUTDOWN_DELAY", "0s");
return delegate.run(new ServerOptions(storageConfig, databaseConfig, args));
}
@Override
public void stop() {
delegate.stop();
}
@Override
public List<String> getOutputStream() {
return delegate.getOutputStream();
}
@Override
public List<String> getErrorStream() {
return delegate.getErrorStream();
}
@Override
public int getExitCode() {
return delegate.getExitCode();
}
@Override
public boolean isDebug() {
return delegate.isDebug();
}
@Override
public boolean isManualStop() {
return delegate.isManualStop();
}
@Override
public String[] getCliArgs(List<String> arguments) {
return delegate.getCliArgs(arguments);
}
@Override
public void setManualStop(boolean manualStop) {
delegate.setManualStop(manualStop);
}
@Override
public void setQuarkusProperty(String key, String value) {
delegate.setQuarkusProperty(key, value);
}
@Override
public void setProperty(String key, String value) {
delegate.setProperty(key, value);
}
@Override
public void deleteQuarkusProperties() {
delegate.deleteQuarkusProperties();
}
@Override
public void copyOrReplaceFileFromClasspath(String file, Path distDir) {
delegate.copyOrReplaceFileFromClasspath(file, distDir);
}
@Override
public void removeProperty(String name) {
delegate.removeProperty(name);
}
@Override
public void setEnvVar(String name, String value) {
delegate.setEnvVar(name, value);
}
@Override
public void copyOrReplaceFile(Path file, Path targetFile) {
delegate.copyOrReplaceFile(file, targetFile);
}
@Override
public void setRequestPort() {
delegate.setRequestPort();
}
@Override
public void setRequestPort(int port) {
delegate.setRequestPort(port);
}
@Override
public <D extends KeycloakDistribution> D unwrap(Class<D> type) {
if (!KeycloakDistribution.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("Not a " + KeycloakDistribution.class + " type");
}
if (type.isInstance(delegate)) {
//noinspection unchecked
return (D) delegate;
}
throw new IllegalArgumentException("Not a " + type + " type");
}
@Override
public void clearEnv() {
delegate.clearEnv();
}
}

View file

@ -0,0 +1,134 @@
/*
* Copyright 2022 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.it.junit5.extension;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.keycloak.it.junit5.extension.StopServer.Mode;
import org.keycloak.it.utils.KeycloakDistribution;
import org.keycloak.it.utils.RawKeycloakDistribution;
import org.keycloak.quarkus.runtime.cli.command.DryRunMixin;
import io.restassured.RestAssured;
import static org.keycloak.quarkus.runtime.Environment.LAUNCH_MODE;
import static org.keycloak.quarkus.runtime.Environment.LAUNCH_MODE_EXIT_AFTER_START;
import static org.keycloak.quarkus.runtime.Environment.LAUNCH_MODE_EXIT_BEFORE_BOOTSTRAP;
/**
* Wraps the distribution to provide a run methods that configure the distribution
* to match the expectations of the test related annotations.
*/
public class KeycloakRunner {
private DistributionTest config;
private KeycloakDistribution delegate;
private StopServer.Mode stopServer;
private long startTimeout = TimeUnit.SECONDS.toMillis(Long.getLong("keycloak.distribution.start.timeout", 120L));
public KeycloakRunner(DistributionTest config,
KeycloakDistribution delegate) {
this.config = config;
this.delegate = delegate;
}
public CLIResult run(String ... rawArgs) {
return run(List.of(rawArgs));
}
public CLIResult run(List<String> rawArgs) {
delegate.stop();
List<String> args = new ArrayList<>(rawArgs);
args.addAll(List.of(config.defaultOptions()));
if (config.debug() && delegate.supportsDebug()) {
delegate.setEnvVar("KC_DEBUG", "true");
delegate.setEnvVar("KC_DEBUG_SUSPEND", "y");
}
delegate.setEnvVar("KC_SHUTDOWN_DELAY", "0s");
if (config.localCache()) {
delegate.setEnvVar("KC_CACHE", "local");
} else {
args.add("-Djgroups.join_timeout=50");
}
if (stopServer == Mode.BEFORE_QUARKUS) {
delegate.setEnvVar(DryRunMixin.KC_DRY_RUN_ENV, "true");
delegate.setEnvVar(DryRunMixin.KC_DRY_RUN_BUILD_ENV, "true");
} else if (stopServer != Mode.MANUAL) {
args.add("-D" + LAUNCH_MODE + "=" + (stopServer == Mode.BEFORE_BOOTSTRAP ? LAUNCH_MODE_EXIT_BEFORE_BOOTSTRAP : LAUNCH_MODE_EXIT_AFTER_START));
}
if (config.enableTls()) {
getDistribution(RawKeycloakDistribution.class).copyOrReplaceFileFromClasspath("/server.keystore", Path.of("conf", "server.keystore"));
}
try {
delegate.runKc(args);
delegate.waitFor(stopServer == Mode.MANUAL, startTimeout);
} finally {
if (stopServer != Mode.MANUAL) {
delegate.stop();
}
}
setRequestPort(config.requestPort());
return CLIResult.create(delegate.getOutputStream(), delegate.getErrorStream(), delegate.getExitCode());
}
public void stop() {
delegate.stop();
}
public void setStopServer(Mode mode) {
this.stopServer = mode;
}
public void setRequestPort(int port) {
RestAssured.port = delegate.getMappedPort(port);
}
/**
* Get the underlying distribution - NOTE directly calling {@link KeycloakDistribution#runKc(List)}
* on the unwrapped distribution is not recommended. The {@link #run(List)} methods on class should be used
* instead as they ensure the arguments and env match the expectations of the test and method annotations
*/
public <D extends KeycloakDistribution> D getDistribution(Class<D> type) {
if (!KeycloakDistribution.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("Not a " + KeycloakDistribution.class + " type");
}
if (type.isInstance(delegate)) {
//noinspection unchecked
return (D) delegate;
}
throw new IllegalArgumentException("Not a " + type + " type");
}
public KeycloakDistribution getDistribution() {
return this.delegate;
}
public void setEnvVar(String key, String value) {
this.delegate.setEnvVar(key, value);
}
}

View file

@ -1,84 +0,0 @@
/*
* Copyright 2022 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.it.junit5.extension;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import org.keycloak.quarkus.runtime.cli.command.Export;
import org.keycloak.quarkus.runtime.cli.command.Import;
import org.keycloak.quarkus.runtime.cli.command.ShowConfig;
import static org.keycloak.quarkus.runtime.cli.command.AbstractAutoBuildCommand.OPTIMIZED_BUILD_OPTION_LONG;
final class ServerOptions extends ArrayList<String> {
private static final Predicate<String> IGNORED_ARGUMENTS = ((Predicate<String>) s -> false)
.or(OPTIMIZED_BUILD_OPTION_LONG::equals)
.or(Export.NAME::equals)
.or(Import.NAME::equals)
.or("--help"::equals)
.or("--help-all"::equals)
.or("-h"::equals)
.or(ShowConfig.NAME::equals);
private boolean isBuildPhase = false;
ServerOptions(Storage storageConfig, WithDatabase withDatabase, List<String> rawOptions) {
if (rawOptions.isEmpty()) {
return;
}
this.isBuildPhase = rawOptions.contains("build");
for (Map.Entry<String, Predicate<String>> entry : getDefaultOptions(storageConfig, withDatabase).entrySet()) {
if (contains(entry.getKey())) {
continue;
}
if (!rawOptions.stream().anyMatch(entry.getValue())) {
add(entry.getKey());
}
}
addAll(0, rawOptions);
}
private Map<String, Predicate<String>> getDefaultOptions(Storage storageConfig, WithDatabase withDatabase) {
Map<String, Predicate<String>> defaultOptions = new HashMap<>();
if (!isBuildPhase) {
defaultOptions.put("--cache=local", ignoreCacheLocal(storageConfig));
}
return defaultOptions;
}
private Predicate<String> ignoreCacheLocal(Storage storageConfig) {
return new Predicate<String>() {
@Override
public boolean test(String arg) {
return arg.contains("--cache") || storageConfig == null || !storageConfig.defaultLocalCache();
}
}.or(IGNORED_ARGUMENTS);
}
}

View file

@ -1,17 +0,0 @@
package org.keycloak.it.junit5.extension;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Skips realm bootstrap (DB lock, realm creation, admin user setup) after provider initialization.
* Liquibase schema migration and provider {@code postInit()} still run.
* Useful for tests that only assert on log messages from server startup and don't need realm data.
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface SkipRealmBootstrap {
}

View file

@ -22,13 +22,37 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.keycloak.it.utils.KeycloakDistribution;
/**
* {@link KeepServerAlive} is used in a distributiontest to keep the server alive on test / method level.
* Used when when {@link DistributionTest} is invoked on class level not using keepAlive=true param, but
* for a specific test we need to run e.g. RestAssured verifications.
* {@link StopServer} is used to control when a distribution server stops at a
* method level.
*/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface KeepServerAlive {
public @interface StopServer {
public enum Mode {
/**
* Stops the server process before quarkus augmentation or startup.
*/
BEFORE_QUARKUS,
/**
* Stops the server process after database initialization, but before
* bootstrapping (creating the master realm, boostrap admin, etc.).
*/
BEFORE_BOOTSTRAP,
/**
* Stops the server immediately after it successfully starts.
*/
AFTER_START,
/**
* Server will stop if {@link KeycloakDistribution#stop()} is called, another run is called.
* If the server is still running at the end of the method, it will be stopped automatically.
*/
MANUAL
}
Mode value() default Mode.AFTER_START;
}

View file

@ -1,39 +0,0 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.it.junit5.extension;
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.extension.ExtendWith;
/**
* Use this annotation to change the default storage configuration when running a test.
*/
@Target(ElementType.TYPE)
@ExtendWith({ CLITestExtension.class })
@Retention(RetentionPolicy.RUNTIME)
public @interface Storage {
/**
* If {@code true}, the cache is set to local by default.
*/
boolean defaultLocalCache() default true;
}

View file

@ -12,11 +12,9 @@ import java.util.function.Consumer;
import java.util.stream.IntStream;
import org.keycloak.common.Version;
import org.keycloak.it.junit5.extension.CLIResult;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.exception.NotFoundException;
import io.restassured.RestAssured;
import org.jboss.logging.Logger;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.GenericContainer;
@ -59,9 +57,6 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
public static final int STARTUP_TIMEOUT_SECONDS = 120;
private final boolean debug;
private final boolean manualStop;
private final int requestPort;
private final Integer[] exposedPorts;
private int exitCode = -1;
@ -79,17 +74,19 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
private final Map<MountableFile, String> copyToContainer = new HashMap<>();
public DockerKeycloakDistribution(boolean debug, boolean manualStop, int requestPort, int[] exposedPorts) {
this(debug, manualStop, requestPort, exposedPorts, null);
public DockerKeycloakDistribution(int[] exposedPorts) {
this(exposedPorts, null);
}
public DockerKeycloakDistribution(boolean debug, boolean manualStop, int requestPort, int[] exposedPorts, LazyFuture<String> image) {
this.debug = debug;
this.manualStop = manualStop;
this.requestPort = requestPort;
public DockerKeycloakDistribution(int[] exposedPorts, LazyFuture<String> image) {
this.exposedPorts = IntStream.of(exposedPorts).boxed().toArray(Integer[]::new);
this.image = image == null ? createImage(false) : image;
}
@Override
public boolean supportsDebug() {
return false;
}
@Override
public void setEnvVar(String name, String value) {
@ -150,8 +147,10 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
}
@Override
public CLIResult run(List<String> arguments) {
stop();
public void runKc(List<String> arguments) {
if (keycloakContainer != null) {
throw new IllegalStateException("Stop has not been called");
}
try {
this.exitCode = -1;
this.stdout = "";
@ -168,35 +167,17 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
.withCommand(arguments.toArray(new String[0]))
.start();
containerId = keycloakContainer.getContainerId();
waitForStableOutput();
} catch (Exception cause) {
this.exitCode = -1;
this.stdout = backupConsumer.stdOut.toUtf8String();
this.stderr = backupConsumer.stdErr.toUtf8String();
cleanupContainer();
keycloakContainer = null;
LOGGER.warn("Failed to start Keycloak container", cause);
} finally {
if (!manualStop) {
stop();
try {
cleanupContainer();
} catch (Exception stopException) {
cause.addSuppressed(stopException);
}
}
setRequestPort();
return CLIResult.create(getOutputStream(), getErrorStream(), getExitCode());
}
@Override
public void setRequestPort() {
setRequestPort(requestPort);
}
@Override
public void setRequestPort(int port) {
if (keycloakContainer != null) {
RestAssured.port = keycloakContainer.getMappedPort(port);
keycloakContainer = null;
throw new RuntimeException("Failed to start the server", cause);
}
}
@ -212,9 +193,11 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
public void copyConfigFile(Path configFilePath) {
copyToContainer.put(MountableFile.forHostPath(configFilePath), "/opt/keycloak/conf/" + configFilePath.getFileName());
}
// After the web server is responding we are still producing some logs that got checked in the tests
private void waitForStableOutput() {
@Override
public void waitFor(boolean ready, long timeoutMillis) {
// TODO: doesn't differentiate ready, nor implements the timeout
int retry = 10;
String lastLine = "";
boolean stableOutput = false;
@ -319,34 +302,12 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
return this.exitCode;
}
@Override
public boolean isDebug() {
return this.debug;
}
@Override
public boolean isManualStop() {
return this.manualStop;
}
@Override
public <D extends KeycloakDistribution> D unwrap(Class<D> type) {
if (!KeycloakDistribution.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("Not a " + KeycloakDistribution.class + " type");
}
if (type.isInstance(this)) {
return type.cast(this);
}
throw new IllegalArgumentException("Not a " + type + " type");
}
@Override
public void clearEnv() {
this.envVars.clear();
}
@Override
public int getMappedPort(int port) {
if (keycloakContainer == null || !keycloakContainer.isRunning()) {
throw new IllegalStateException("KeycloakContainer is not running.");

View file

@ -3,7 +3,6 @@ package org.keycloak.it.utils;
import java.nio.file.Path;
import java.util.List;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.quarkus.runtime.Environment;
public interface KeycloakDistribution {
@ -12,64 +11,40 @@ public interface KeycloakDistribution {
String SCRIPT_KCADM_CMD = Environment.isWindows() ? "kcadm.bat" : "kcadm.sh";
CLIResult run(List<String> arguments);
default CLIResult run(String... arguments) {
return run(List.of(arguments));
/**
* Run the kc command and immediately return without waiting for the process
*/
void runKc(List<String> arguments);
/**
* Run the kc command and immediately return without waiting for the process
*/
default void runKc(String... arguments) {
runKc(List.of(arguments));
}
void waitFor(boolean ready, long timeoutMillis);
void stop();
int getMappedPort(int port);
List<String> getOutputStream();
List<String> getErrorStream();
/**
* Available after the main process exits, which may require {@link #stop()} to be called
*/
int getExitCode();
boolean supportsDebug();
boolean isDebug();
boolean isManualStop();
void setRequestPort();
void setRequestPort(int port);
default String[] getCliArgs(List<String> arguments) {
throw new RuntimeException("Not implemented");
}
default void setManualStop(boolean manualStop) {
throw new RuntimeException("Not implemented");
}
default void setQuarkusProperty(String key, String value) {
throw new RuntimeException("Not implemented");
}
default void setProperty(String key, String value) {
throw new RuntimeException("Not implemented");
}
default void deleteQuarkusProperties() {
throw new RuntimeException("Not implemented");
}
default void copyOrReplaceFileFromClasspath(String file, Path distDir) {
throw new RuntimeException("Not implemented");
}
default void removeProperty(String name) {
throw new RuntimeException("Not implemented");
}
default void setEnvVar(String name, String value) {
throw new RuntimeException("Not implemented");
}
default void copyOrReplaceFile(Path file, Path targetFile) {
throw new RuntimeException("Not implemented");
}
<D extends KeycloakDistribution> D unwrap(Class<D> type);
void setEnvVar(String name, String value);
void clearEnv();
void copyProvider(String groupId, String artifactId);
void copyConfigFile(Path configFilePath);
}

View file

@ -13,13 +13,7 @@ public class RawDistributionLifecycleManager implements QuarkusTestResourceLifec
@Override
public Map<String, String> start() {
dist = new RawKeycloakDistribution(
false,
false,
false,
true,
false,
8080);
dist = new RawKeycloakDistribution(true);
return null;
}

View file

@ -24,18 +24,12 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -45,31 +39,24 @@ import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Consumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.keycloak.common.Version;
import org.keycloak.it.TestProvider;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.cli.command.Build;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
import org.keycloak.quarkus.runtime.KeycloakMain;
import io.quarkus.deployment.util.FileUtil;
import io.quarkus.fs.util.ZipUtils;
import io.restassured.RestAssured;
import org.apache.commons.io.FileUtils;
import org.awaitility.Awaitility;
import org.jboss.logging.Logger;
@ -91,31 +78,12 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
private Process keycloak;
private int exitCode = -1;
private Path distPath;
private boolean manualStop;
private String relativePath;
private int httpPort;
private int httpsPort;
private final boolean debug;
private final boolean enableTls;
private final boolean reCreate;
private final boolean removeBuildOptionsAfterBuild;
private final int requestPort;
private ExecutorService outputExecutor;
private final Map<String, String> envVars = new HashMap<>();
private final OutputConsumer outputConsumer;
private long startTimeout = TimeUnit.SECONDS.toMillis(Long.getLong("keycloak.distribution.start.timeout", 120L));
private boolean throwErrorIfFailedToStart = false;
private boolean threadDump = true;
private String launchMode = Environment.LAUNCH_MODE_EXIT_AFTER_START;
private final DefaultOutputConsumer outputConsumer;
public RawKeycloakDistribution(boolean debug, boolean manualStop, boolean enableTls, boolean reCreate, boolean removeBuildOptionsAfterBuild, int requestPort) {
this.debug = debug;
this.manualStop = manualStop;
this.enableTls = enableTls;
this.reCreate = reCreate;
this.removeBuildOptionsAfterBuild = removeBuildOptionsAfterBuild;
this.requestPort = requestPort;
this.distPath = prepareDistribution();
public RawKeycloakDistribution(boolean reCreate) {
this.distPath = prepareDistribution(reCreate);
if (reCreate) {
try {
preInitH2(distPath);
@ -125,20 +93,34 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
}
this.outputConsumer = new DefaultOutputConsumer();
}
public RawKeycloakDistribution withThrowErrorIfFailedToStart(boolean throwErrorIfFailedToStart) {
this.throwErrorIfFailedToStart = throwErrorIfFailedToStart;
return this;
@Override
public int getMappedPort(int port) {
return port;
}
@Override
public synchronized void waitFor(boolean ready, long timeoutMillis) {
if (!isRunning()) {
return;
}
try {
if (ready) {
this.outputConsumer.running.get(timeoutMillis, TimeUnit.MILLISECONDS);
} else {
this.keycloak.onExit().get(timeoutMillis, TimeUnit.MILLISECONDS);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
} catch (ExecutionException|TimeoutException e) {
throw new RuntimeException(e);
}
}
public RawKeycloakDistribution withThreadDump(boolean threadDump) {
this.threadDump = threadDump;
return this;
}
public RawKeycloakDistribution withStartTimeout(long startTimeout) {
this.startTimeout = startTimeout;
return this;
@Override
public boolean supportsDebug() {
return true;
}
public CLIResult kc(String... arguments) throws IOException {
@ -160,22 +142,13 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
private CLIResult invoke(String script, List<String> arguments) throws IOException {
List<String> allArgs = new ArrayList<>();
invoke(allArgs, script);
if (this.isDebug()) {
allArgs.add("-x");
}
addPlatformSpecificCommand(allArgs, script);
allArgs.addAll(arguments);
ProcessBuilder pb = new ProcessBuilder(allArgs);
ProcessBuilder builder = pb.directory(distPath.resolve("bin").toFile());
// TODO: it is possible to debug kcadm, but it's more involved
/*if (debug) {
builder.environment().put("DEBUG_SUSPEND", "y");
}*/
addAOTEnvVars();
builder.environment().putAll(envVars);
@ -190,30 +163,23 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
return CLIResult.create(outputConsumer.getStdOut(), outputConsumer.getErrOut(), exitValue);
}
private void invoke(List<String> allArgs, String cmd) {
private void addPlatformSpecificCommand(List<String> allArgs, String cmd) {
if (isWindows()) {
allArgs.add(distPath.resolve("bin") + File.separator + cmd);
} else {
allArgs.add("./" + cmd);
}
}
@Override
public CLIResult run(List<String> arguments) {
stop();
if (manualStop && isRunning()) {
throw new IllegalStateException("Server already running. You should manually stop the server before starting it again.");
@Override
public void runKc(List<String> arguments) {
if (isRunning()) {
throw new IllegalStateException("Stop has not been called");
}
reset();
resetForNextRun();
try {
configureServer();
startServer(arguments);
if (manualStop) {
asyncReadOutput();
waitForReadiness();
} else {
readOutput();
}
asyncReadOutput();
} catch (Exception cause) {
try {
stop();
@ -221,27 +187,6 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
cause.addSuppressed(stopException);
}
throw new RuntimeException("Failed to start the server", cause);
} finally {
if (arguments.contains(Build.NAME) && removeBuildOptionsAfterBuild) {
for (List<PropertyMapper<?>> mappers : PropertyMappers.getBuildTimeMappers().values()) {
for (PropertyMapper<?> mapper : mappers) {
removeProperty(mapper.getFrom().substring(3));
}
}
}
if (!manualStop) {
stop();
}
}
setRequestPort();
return CLIResult.create(getOutputStream(), getErrorStream(), getExitCode());
}
private void configureServer() {
if (enableTls) {
copyOrReplaceFileFromClasspath("/server.keystore", Path.of("conf", "server.keystore"));
}
}
@ -325,124 +270,14 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
return exitCode;
}
@Override
public boolean isDebug() { return this.debug; }
@Override
public boolean isManualStop() { return this.manualStop; }
@Override
public String[] getCliArgs(List<String> arguments) {
List<String> allArgs = new ArrayList<>();
invoke(allArgs, SCRIPT_CMD);
if (this.isDebug()) {
allArgs.add("--debug");
}
if (!this.isManualStop()) {
allArgs.add("-D" + LAUNCH_MODE + "=" + launchMode);
}
allArgs.add("-Djgroups.join_timeout=50");
this.relativePath = arguments.stream().filter(arg -> arg.startsWith("--http-relative-path")).map(arg -> arg.substring(arg.indexOf('=') + 1)).findAny().orElse("/");
this.httpPort = Integer.parseInt(arguments.stream().filter(arg -> arg.startsWith("--http-port")).map(arg -> arg.substring(arg.indexOf('=') + 1)).findAny().orElse("8080"));
this.httpsPort = Integer.parseInt(arguments.stream().filter(arg -> arg.startsWith("--https-port")).map(arg -> arg.substring(arg.indexOf('=') + 1)).findAny().orElse("8443"));
allArgs.add("-Dkc.home.dir=" + distPath + File.separator);
allArgs.addAll(arguments);
return allArgs.toArray(String[]::new);
}
@Override
public void setRequestPort() {
setRequestPort(requestPort);
}
@Override
public void setRequestPort(int port) {
RestAssured.port = port;
}
private void waitForReadiness() throws MalformedURLException {
waitForReadiness("http", httpPort);
if (enableTls) {
waitForReadiness("https", httpsPort);
}
}
private void waitForReadiness(String scheme, int port) throws MalformedURLException {
var myRelativePath = relativePath;
if (!myRelativePath.endsWith("/")) {
myRelativePath += "/";
}
URL contextRoot = new URL(scheme + "://localhost:" + port + myRelativePath + "realms/master/");
HttpURLConnection connection = null;
long startTime = System.currentTimeMillis();
Exception ex = null;
while (true) {
if (System.currentTimeMillis() - startTime > getStartTimeout()) {
threadDump();
throw new IllegalStateException(
"Timeout [" + getStartTimeout() + "] while waiting for Quarkus server", ex);
}
if (!keycloak.isAlive()) {
if (throwErrorIfFailedToStart) {
throw new RuntimeException("Keycloak failed to start: process terminated");
} else {
return;
}
}
try {
// wait before checking for opening a new connection
if ("https".equals(contextRoot.getProtocol())) {
HttpsURLConnection httpsConnection = (HttpsURLConnection) (connection = (HttpURLConnection) contextRoot.openConnection());
httpsConnection.setSSLSocketFactory(createInsecureSslSocketFactory());
httpsConnection.setHostnameVerifier(createInsecureHostnameVerifier());
} else {
connection = (HttpURLConnection) contextRoot.openConnection();
}
connection.setReadTimeout((int) getStartTimeout());
connection.setConnectTimeout((int) getStartTimeout());
connection.connect();
if (connection.getResponseCode() == 200) {
break;
}
} catch (Exception ignore) {
ex = ignore;
} finally {
if (connection != null) {
connection.disconnect();
}
try {
Thread.sleep(1000);
} catch (Exception ignore) {
}
}
}
}
private void threadDump() {
if (!threadDump) {
return;
}
if (Environment.isWindows()) {
return;
}
try {
ProcessBuilder builder = new ProcessBuilder("kill", "-3", String.valueOf(keycloak.pid()));
Process p = builder.start();
p.onExit().get(getStartTimeout(), TimeUnit.MILLISECONDS);
p.onExit().get(DEFAULT_SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS);
} catch (Exception e) {
LOG.warn("A thread dump may not have been successfully triggered", e);
return;
@ -451,43 +286,6 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
.until(() -> getOutputStream().stream().anyMatch(s -> s.contains("JNI global refs")));
}
private long getStartTimeout() {
return startTimeout;
}
private HostnameVerifier createInsecureHostnameVerifier() {
return (s, sslSession) -> true;
}
private SSLSocketFactory createInsecureSslSocketFactory() throws IOException {
TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
@Override
public void checkClientTrusted(final X509Certificate[] chain, final String authType) {
}
@Override
public void checkServerTrusted(final X509Certificate[] chain, final String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}};
SSLContext sslContext;
SSLSocketFactory socketFactory;
try {
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new SecureRandom());
socketFactory = sslContext.getSocketFactory();
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new IOException("Can't create unsecure trust manager");
}
return socketFactory;
}
public boolean isRunning() {
return keycloak != null && keycloak.isAlive();
}
@ -511,7 +309,7 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
}
}
private void reset() {
private void resetForNextRun() {
outputConsumer.reset();
exitCode = -1;
shutdownOutputExecutor();
@ -533,7 +331,7 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
throw new RuntimeException(String.format("ZIP file '%s' doesn't contain any directories", distPath));
}
private Path prepareDistribution() {
public Path prepareDistribution(boolean reCreate) {
try {
Path distRootPath = Paths.get(System.getProperty("java.io.tmpdir")).resolve("kc-tests");
distRootPath.toFile().mkdirs();
@ -575,7 +373,6 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
private void preInitH2(Path dPath) throws IOException {
ProcessHandle.current().info().command().ifPresent(command -> {
Runtime.Version runtimeVersion = Runtime.version();
boolean useAot = false;
if (Boolean.getBoolean("kc.quarkus.tests.aot")) {
if (Runtime.version().feature() < 25) {
@ -658,18 +455,25 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
* @throws Exception if something bad happens
*/
private void startServer(List<String> arguments) throws Exception {
ProcessBuilder pb = new ProcessBuilder(getCliArgs(arguments));
List<String> allArgs = new ArrayList<>();
addPlatformSpecificCommand(allArgs, SCRIPT_CMD);
// used to detect readiness rather than http(s) probing
allArgs.add("-D%s=true".formatted(KeycloakMain.KC_SERVER_PRINT_RUNNING));
allArgs.addAll(arguments);
ProcessBuilder pb = new ProcessBuilder(allArgs);
ProcessBuilder builder = pb.directory(distPath.resolve("bin").toFile());
if (debug) {
builder.environment().put("DEBUG_SUSPEND", "y");
}
addAOTEnvVars();
builder.environment().putAll(envVars);
keycloak = builder.start();
var future = outputConsumer.running;
keycloak.onExit().whenComplete((p, t) -> future.complete(null));
}
private void addAOTEnvVars() {
@ -681,12 +485,6 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
}
}
@Override
public void setManualStop(boolean manualStop) {
this.manualStop = manualStop;
}
@Override
public void setProperty(String key, String value) {
updateProperties(properties -> properties.put(key, value), distPath.resolve("conf").resolve("keycloak.conf").toFile());
}
@ -696,21 +494,14 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
this.envVars.put(name, value);
}
public void setLaunchMode(String launchMode) {
this.launchMode = launchMode;
}
@Override
public void removeProperty(String name) {
updateProperties(properties -> properties.remove(name), distPath.resolve("conf").resolve("keycloak.conf").toFile());
}
@Override
public void setQuarkusProperty(String key, String value) {
updateProperties(properties -> properties.put(key, value), getQuarkusPropertiesFile());
}
@Override
public void deleteQuarkusProperties() {
File file = getQuarkusPropertiesFile();
@ -719,7 +510,6 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
}
}
@Override
public void copyOrReplaceFileFromClasspath(String file, Path targetFile) {
Path path = distPath.resolve(targetFile);
@ -732,7 +522,6 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
}
}
@Override
public void copyOrReplaceFile(Path file, Path targetFile) {
if (!file.toFile().exists()) {
return;
@ -749,6 +538,7 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
}
}
@Override
public void copyProvider(String groupId, String artifactId) {
copyProvider(getDistPath(), groupId, artifactId);
}
@ -766,6 +556,7 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
}
}
@Override
public void copyConfigFile(Path configFilePath) {
try {
Files.copy(configFilePath, distPath.resolve("conf").resolve(configFilePath.getFileName()));
@ -833,19 +624,6 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
providerJar.as(ZipExporter.class).exportTo(getDistPath().resolve("providers").resolve(providerJar.getName()).toFile());
}
@Override
public <D extends KeycloakDistribution> D unwrap(Class<D> type) {
if (!KeycloakDistribution.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("Not a " + KeycloakDistribution.class + " type");
}
if (type.isInstance(this)) {
return type.cast(type);
}
throw new IllegalArgumentException("Not a " + type + " type");
}
@Override
public void clearEnv() {
this.envVars.clear();
@ -855,9 +633,13 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
private final List<String> stdOut = Collections.synchronizedList(new ArrayList<>());
private final List<String> errOut = Collections.synchronizedList(new ArrayList<>());
private CompletableFuture<Void> running = new CompletableFuture<Void>();
@Override
public void onStdOut(String line) {
if (line.equals(KeycloakMain.RUNNING_MESSAGE)) {
running.complete(null);
}
System.out.println(line);
stdOut.add(line);
}
@ -872,6 +654,7 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
public void reset() {
stdOut.clear();
errOut.clear();
this.running = new CompletableFuture<>();
}
@Override
@ -883,6 +666,7 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
public List<String> getStdOut() {
return stdOut;
}
}
public void resetH2Dir() throws IOException {

View file

@ -35,7 +35,6 @@ import org.testcontainers.utility.LazyFuture;
public class ClusteredKeycloakServer implements KeycloakServer {
private static final String CLUSTER_VIEW_REGEX = ".*ISPN000093.*(?<=\\()(%1$d)(?=\\)).*|.*ISPN000094.*(?<=\\()(%1$d)(?=\\)).*";
private static final boolean MANUAL_STOP = true;
private static final int REQUEST_PORT = 8080;
private static final int MANAGEMENT_PORT = 9000;
public static final String SNAPSHOT_IMAGE = "-";
@ -100,13 +99,13 @@ public class ClusteredKeycloakServer implements KeycloakServer {
} else {
resolvedImage = new RemoteDockerImage(DockerImageName.parse(imagePeServer[i]));
}
var container = new DockerKeycloakDistribution(false, MANUAL_STOP, REQUEST_PORT, exposedPorts, resolvedImage);
var container = new DockerKeycloakDistribution(exposedPorts, resolvedImage);
containers[i] = container;
copyProvidersAndConfigs(container, configBuilder);
configureLogConsumers(container, i, clusterLatch);
container.run(configBuilder.toArgs());
container.runKc(configBuilder.toArgs());
}
}
@ -116,12 +115,12 @@ public class ClusteredKeycloakServer implements KeycloakServer {
defaultImage() :
new RemoteDockerImage(DockerImageName.parse(image));
for (int i = 0; i < containers.length; ++i) {
var container = new DockerKeycloakDistribution(false, MANUAL_STOP, REQUEST_PORT, exposedPorts, imageFuture);
var container = new DockerKeycloakDistribution(exposedPorts, imageFuture);
containers[i] = container;
copyProvidersAndConfigs(container, configBuilder);
configureLogConsumers(container, i, clusterLatch);
container.run(configBuilder.toArgs());
container.runKc(configBuilder.toArgs());
}
}