parent
b730d861e7
commit
2b890eb79d
20 changed files with 856 additions and 167 deletions
|
@ -826,6 +826,19 @@ because this is not UI testing). For debugging purposes you can override the hea
|
||||||
For changing the hostname in the hostname tests (e.g. [DefaultHostnameTest](https://github.com/keycloak/keycloak/blob/main/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/url/DefaultHostnameTest.java)),
|
For changing the hostname in the hostname tests (e.g. [DefaultHostnameTest](https://github.com/keycloak/keycloak/blob/main/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/url/DefaultHostnameTest.java)),
|
||||||
we rely on [nip.io](https://nip.io) for DNS switching, so tests will work everywhere without fiddling with `etc/hosts` locally.
|
we rely on [nip.io](https://nip.io) for DNS switching, so tests will work everywhere without fiddling with `etc/hosts` locally.
|
||||||
|
|
||||||
|
### Tips & Tricks:
|
||||||
|
Although it _should_ work in general, you may experience an exception like this:
|
||||||
|
```
|
||||||
|
java.lang.RuntimeException: java.net.UnknownHostException: keycloak.127.0.0.1.nip.io: nodename nor servname provided,
|
||||||
|
or not known at org.keycloak.testsuite.util.OAuthClient.doWellKnownRequest(OAuthClient.java:1032)
|
||||||
|
at org.keycloak.testsuite.url.DefaultHostnameTest.assertBackendForcedToFrontendWithMatchingHostname(
|
||||||
|
DefaultHostnameTest.java:226)
|
||||||
|
...
|
||||||
|
```
|
||||||
|
when running these tests on your local machine. This happens when something on your machine or network is blocking DNS queries to [nip.io](https://nip.io)
|
||||||
|
In order to avoid using external services for DNS resolution, the tests are executed using a local host file by setting the `-Djdk.net.hosts.file=${project.build.testOutputDirectory}/hosts_file`
|
||||||
|
system property.
|
||||||
|
|
||||||
## Running base testsuite with Map storage
|
## Running base testsuite with Map storage
|
||||||
|
|
||||||
To run base testsuite with new storage run the following command (this will execute testsuite with ConcurrentHashMap storage):
|
To run base testsuite with new storage run the following command (this will execute testsuite with ConcurrentHashMap storage):
|
||||||
|
@ -933,18 +946,28 @@ export TESTCONTAINERS_RYUK_DISABLED=true #not recommended - see above!
|
||||||
To use Testcontainers with Docker it is necessary to
|
To use Testcontainers with Docker it is necessary to
|
||||||
[make Docker available for non-root users](https://docs.docker.com/engine/install/linux-postinstall/).
|
[make Docker available for non-root users](https://docs.docker.com/engine/install/linux-postinstall/).
|
||||||
|
|
||||||
### Tips & Tricks:
|
### Zero downtime tests
|
||||||
Although it _should_ work in general, you may experience an exception like this:
|
|
||||||
```
|
By default tests are enabled and runs when `map-storage-hotrod`, `map-storage-postgres` or `map-storage-jpa-cockroach` profile is enabled.
|
||||||
java.lang.RuntimeException: java.net.UnknownHostException: keycloak.127.0.0.1.nip.io: nodename nor servname provided,
|
Other may be added in future. It supports both `auth-server-undertow` and `auth-server-quarkus`.
|
||||||
or not known at org.keycloak.testsuite.util.OAuthClient.doWellKnownRequest(OAuthClient.java:1032)
|
|
||||||
at org.keycloak.testsuite.url.DefaultHostnameTest.assertBackendForcedToFrontendWithMatchingHostname(
|
#### Default behavior
|
||||||
DefaultHostnameTest.java:226)
|
1. Before test current `auth-server` is stopped as well as ( postgres/crdb/hotrod ) container.
|
||||||
...
|
2. New ( postgres/crdb/hotrod ) container is spawned using testcontainers.
|
||||||
```
|
3. Legacy keycloak (latest from https://quay.io/) is started.
|
||||||
when running these tests on your local machine. This happens when something on your machine or network is blocking DNS queries to [nip.io](https://nip.io)
|
4. Test realm is imported into legacy keycloak.
|
||||||
One possible workaround is to add a commonly used public dns server (e.g. 8.8.8.8 for google dns server) to your local
|
5. Current `auth-server` is started.
|
||||||
networks dns configuration and run the tests.
|
|
||||||
|
#### Notes
|
||||||
|
- version of legacy keycloak could be specified by `keycloak.legacy.version.zero.downtime` property
|
||||||
|
- legacy keycloak is by default listening on http://localhost:8091
|
||||||
|
- when running only zero downtime tests, e.g. locally, it could be speeded up by skipping start of suite
|
||||||
|
( postgres/crdb/hotrod ) container by `-Dcockroachdb.start-container=false`, `-Dpostgres.start-container=false`
|
||||||
|
or `-Dkeycloak.testsuite.start-hotrod-container=false` and also suite `auth-server` by `-Dkeycloak.testsuite.skip.start.auth.server=true`
|
||||||
|
- to run tests with an external instance of ( postgres/crdb/hotrod ) container
|
||||||
|
`-Dpostgres.start-container-for-zero-downtime=false`; `-Dcockroachdb.start-container-for-zero-downtime`; `-Dstart-hotrod-container-for-zero-downtime`
|
||||||
|
together with appropriate properties (see sections above).
|
||||||
|
|
||||||
|
|
||||||
## FIPS 140-2 testing
|
## FIPS 140-2 testing
|
||||||
|
|
||||||
|
|
|
@ -374,6 +374,12 @@ public class AuthServerTestEnricher {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startAuthContainer(@Observes(precedence = 0) StartSuiteContainers event) {
|
public void startAuthContainer(@Observes(precedence = 0) StartSuiteContainers event) {
|
||||||
|
// this property can be used to skip start of auth-server before suite
|
||||||
|
// it might be useful for running some specific tests locally, e.g. when running standalone ZeroDowtime*Test
|
||||||
|
if (Boolean.getBoolean("keycloak.testsuite.skip.start.auth.server")) {
|
||||||
|
log.debug("Skipping the start of auth server before suite");
|
||||||
|
return;
|
||||||
|
}
|
||||||
//frontend-only (either load-balancer or auth-server)
|
//frontend-only (either load-balancer or auth-server)
|
||||||
log.debug("Starting auth server before suite");
|
log.debug("Starting auth server before suite");
|
||||||
|
|
||||||
|
@ -734,6 +740,7 @@ public class AuthServerTestEnricher {
|
||||||
}
|
}
|
||||||
|
|
||||||
TestContext testContext = testContextProducer.get();
|
TestContext testContext = testContextProducer.get();
|
||||||
|
testContext.runAfterClassActions();
|
||||||
|
|
||||||
Keycloak adminClient = testContext.getAdminClient();
|
Keycloak adminClient = testContext.getAdminClient();
|
||||||
KeycloakTestingClient testingClient = testContext.getTestingClient();
|
KeycloakTestingClient testingClient = testContext.getTestingClient();
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.testsuite.arquillian;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import org.jboss.arquillian.container.spi.event.StartSuiteContainers;
|
||||||
|
import org.jboss.arquillian.core.api.annotation.Observes;
|
||||||
|
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||||
|
import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider;
|
||||||
|
import org.jboss.arquillian.test.spi.event.suite.AfterSuite;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.testcontainers.containers.CockroachContainer;
|
||||||
|
import org.testcontainers.utility.DockerImageName;
|
||||||
|
|
||||||
|
public class CockroachdbContainerProvider implements ResourceProvider {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(CockroachdbContainerProvider.class);
|
||||||
|
|
||||||
|
private static final Boolean START_CONTAINER = Boolean.valueOf(System.getProperty("cockroachdb.start-container", "false"));
|
||||||
|
private static final String COCKROACHDB_DOCKER_IMAGE_NAME = System.getProperty("keycloak.map.storage.cockroachdb.docker.image", "cockroachdb/cockroach:v22.1.0");
|
||||||
|
|
||||||
|
public static final String COCKROACHDB_DB_USER = System.getProperty("keycloak.map.storage.connectionsJpa.user", "keycloak");
|
||||||
|
public static final String COCKROACHDB_DB_PASSWORD = System.getProperty("keycloak.map.storage.connectionsJpa.password", "pass");
|
||||||
|
|
||||||
|
private static CockroachContainer cockroachDbContainer;
|
||||||
|
|
||||||
|
public void beforeContainerStarted(@Observes(precedence = 1) StartSuiteContainers event) {
|
||||||
|
if (START_CONTAINER) {
|
||||||
|
cockroachDbContainer = createContainer();
|
||||||
|
cockroachDbContainer.start();
|
||||||
|
|
||||||
|
System.setProperty("keycloak.map.storage.connectionsJpa.url", cockroachDbContainer.getJdbcUrl());
|
||||||
|
|
||||||
|
log.infof("DatabaseInfo: %s, user=%s, pass=%s", cockroachDbContainer.getJdbcUrl(), COCKROACHDB_DB_USER, COCKROACHDB_DB_PASSWORD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void afterSuite(@Observes(precedence = 4) AfterSuite event) {
|
||||||
|
if (START_CONTAINER) {
|
||||||
|
cockroachDbContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CockroachContainer createContainer() {
|
||||||
|
return new CockroachContainer(DockerImageName.parse(COCKROACHDB_DOCKER_IMAGE_NAME).asCompatibleSubstituteFor("cockroachdb"))
|
||||||
|
// Using the environment variables for now where using the withXXX() method is not supported, yet.
|
||||||
|
// https://github.com/testcontainers/testcontainers-java/issues/6299
|
||||||
|
.withEnv("COCKROACH_DATABASE", "keycloak")
|
||||||
|
.withEnv("COCKROACH_USER", COCKROACHDB_DB_USER)
|
||||||
|
// password is not used/supported in insecure mode
|
||||||
|
.withCommand("start-single-node", "--insecure");
|
||||||
|
}
|
||||||
|
|
||||||
|
public CockroachContainer getContainer() {
|
||||||
|
return cockroachDbContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canProvide(Class<?> type) {
|
||||||
|
return type.equals(CockroachdbContainerProvider.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object lookup(ArquillianResource ar, Annotation... antns) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,52 +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.testsuite.arquillian;
|
|
||||||
|
|
||||||
import org.jboss.arquillian.container.spi.event.StartSuiteContainers;
|
|
||||||
import org.jboss.arquillian.core.api.annotation.Observes;
|
|
||||||
import org.jboss.arquillian.test.spi.event.suite.AfterSuite;
|
|
||||||
import org.testcontainers.containers.CockroachContainer;
|
|
||||||
import org.testcontainers.utility.DockerImageName;
|
|
||||||
|
|
||||||
public class CockroachdbContainerTestEnricher {
|
|
||||||
|
|
||||||
private static final Boolean START_CONTAINER = Boolean.valueOf(System.getProperty("cockroachdb.start-container", "false"));
|
|
||||||
private static final String COCKROACHDB_DOCKER_IMAGE_NAME = System.getProperty("keycloak.map.storage.cockroachdb.docker.image", "cockroachdb/cockroach:v22.1.0");
|
|
||||||
private static final CockroachContainer COCKROACHDB_CONTAINER = new CockroachContainer(DockerImageName.parse(COCKROACHDB_DOCKER_IMAGE_NAME).asCompatibleSubstituteFor("cockroachdb"));
|
|
||||||
private static final String COCKROACHDB_DB_USER = System.getProperty("keycloak.map.storage.connectionsJpa.user", "keycloak");
|
|
||||||
|
|
||||||
public void beforeContainerStarted(@Observes(precedence = 1) StartSuiteContainers event) {
|
|
||||||
if (START_CONTAINER) {
|
|
||||||
COCKROACHDB_CONTAINER
|
|
||||||
// Using the environment variables for now where using the withXXX() method is not supported, yet.
|
|
||||||
// https://github.com/testcontainers/testcontainers-java/issues/6299
|
|
||||||
.withEnv("COCKROACH_DATABASE", "keycloak")
|
|
||||||
.withEnv("COCKROACH_USER", COCKROACHDB_DB_USER)
|
|
||||||
// password is not used/supported in insecure mode
|
|
||||||
.withCommand("start-single-node", "--insecure")
|
|
||||||
.start();
|
|
||||||
|
|
||||||
System.setProperty("keycloak.map.storage.connectionsJpa.url", COCKROACHDB_CONTAINER.getJdbcUrl());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void afterSuite(@Observes(precedence = 4) AfterSuite event) {
|
|
||||||
if (START_CONTAINER) {
|
|
||||||
COCKROACHDB_CONTAINER.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.testsuite.arquillian;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import org.jboss.arquillian.container.spi.event.StartSuiteContainers;
|
||||||
|
import org.jboss.arquillian.core.api.annotation.Observes;
|
||||||
|
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||||
|
import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider;
|
||||||
|
import org.jboss.arquillian.test.spi.event.suite.AfterSuite;
|
||||||
|
import org.keycloak.testsuite.util.InfinispanContainer;
|
||||||
|
|
||||||
|
|
||||||
|
public class HotRodContainerProvider implements ResourceProvider {
|
||||||
|
|
||||||
|
public static final String HOT_ROD_STORE_HOST_PROPERTY = "keycloak.connectionsHotRod.host";
|
||||||
|
|
||||||
|
public static final boolean HOT_ROD_START_CONTAINER = Boolean.parseBoolean(System.getProperty("keycloak.testsuite.start-hotrod-container", "false"));
|
||||||
|
|
||||||
|
private static InfinispanContainer infinispanContainer;
|
||||||
|
|
||||||
|
public void beforeContainerStarted(@Observes(precedence = 1) StartSuiteContainers event) {
|
||||||
|
if (!HOT_ROD_START_CONTAINER) return;
|
||||||
|
infinispanContainer = new InfinispanContainer();
|
||||||
|
infinispanContainer.start();
|
||||||
|
|
||||||
|
// Add env variable, so it can be picked up by Keycloak
|
||||||
|
System.setProperty(HOT_ROD_STORE_HOST_PROPERTY, infinispanContainer.getHost());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void afterSuite(@Observes(precedence = 4) AfterSuite event) {
|
||||||
|
if (!HOT_ROD_START_CONTAINER) return;
|
||||||
|
if (infinispanContainer != null) infinispanContainer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InfinispanContainer getContainer() {
|
||||||
|
return infinispanContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canProvide(Class<?> type) {
|
||||||
|
return type.equals(HotRodContainerProvider.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object lookup(ArquillianResource ar, Annotation... antns) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,30 +0,0 @@
|
||||||
package org.keycloak.testsuite.arquillian;
|
|
||||||
|
|
||||||
import org.jboss.arquillian.container.spi.event.StartSuiteContainers;
|
|
||||||
import org.jboss.arquillian.core.api.annotation.Observes;
|
|
||||||
import org.jboss.arquillian.test.spi.event.suite.AfterSuite;
|
|
||||||
import org.keycloak.testsuite.util.InfinispanContainer;
|
|
||||||
|
|
||||||
|
|
||||||
public class HotRodStoreTestEnricher {
|
|
||||||
|
|
||||||
public static final String HOT_ROD_STORE_HOST_PROPERTY = "keycloak.connectionsHotRod.host";
|
|
||||||
|
|
||||||
public static final boolean HOT_ROD_START_CONTAINER = Boolean.parseBoolean(System.getProperty("keycloak.testsuite.start-hotrod-container", "false"));
|
|
||||||
|
|
||||||
private InfinispanContainer hotRodContainer;
|
|
||||||
|
|
||||||
public void beforeContainerStarted(@Observes(precedence = 1) StartSuiteContainers event) {
|
|
||||||
if (!HOT_ROD_START_CONTAINER) return;
|
|
||||||
hotRodContainer = new InfinispanContainer();
|
|
||||||
hotRodContainer.start();
|
|
||||||
|
|
||||||
// Add env variable, so it can be picked up by Keycloak
|
|
||||||
System.setProperty(HOT_ROD_STORE_HOST_PROPERTY, hotRodContainer.getHost());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void afterSuite(@Observes(precedence = 4) AfterSuite event) {
|
|
||||||
if (!HOT_ROD_START_CONTAINER) return;
|
|
||||||
if (hotRodContainer != null) hotRodContainer.stop();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -56,7 +56,11 @@ public class KeycloakArquillianExtension implements LoadableExtension {
|
||||||
.service(ResourceProvider.class, TestContextProvider.class)
|
.service(ResourceProvider.class, TestContextProvider.class)
|
||||||
.service(ResourceProvider.class, AdminClientProvider.class)
|
.service(ResourceProvider.class, AdminClientProvider.class)
|
||||||
.service(ResourceProvider.class, OAuthClientProvider.class)
|
.service(ResourceProvider.class, OAuthClientProvider.class)
|
||||||
.service(ResourceProvider.class, LoadBalancerControllerProvider.class);
|
.service(ResourceProvider.class, LoadBalancerControllerProvider.class)
|
||||||
|
.service(ResourceProvider.class, PostgresContainerProvider.class)
|
||||||
|
.service(ResourceProvider.class, CockroachdbContainerProvider.class)
|
||||||
|
.service(ResourceProvider.class, HotRodContainerProvider.class)
|
||||||
|
.service(ResourceProvider.class, LegacyKeycloakContainerProvider.class);
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.service(DeploymentScenarioGenerator.class, DeploymentTargetModifier.class)
|
.service(DeploymentScenarioGenerator.class, DeploymentTargetModifier.class)
|
||||||
|
@ -66,9 +70,9 @@ public class KeycloakArquillianExtension implements LoadableExtension {
|
||||||
.observer(AuthServerTestEnricher.class)
|
.observer(AuthServerTestEnricher.class)
|
||||||
.observer(AppServerTestEnricher.class)
|
.observer(AppServerTestEnricher.class)
|
||||||
.observer(CrossDCTestEnricher.class)
|
.observer(CrossDCTestEnricher.class)
|
||||||
.observer(HotRodStoreTestEnricher.class)
|
.observer(HotRodContainerProvider.class)
|
||||||
.observer(PostgresContainerTestEnricher.class)
|
.observer(PostgresContainerProvider.class)
|
||||||
.observer(CockroachdbContainerTestEnricher.class)
|
.observer(CockroachdbContainerProvider.class)
|
||||||
.observer(H2TestEnricher.class);
|
.observer(H2TestEnricher.class);
|
||||||
builder
|
builder
|
||||||
.service(TestExecutionDecider.class, MigrationTestExecutionDecider.class)
|
.service(TestExecutionDecider.class, MigrationTestExecutionDecider.class)
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.testsuite.arquillian;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||||
|
import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider;
|
||||||
|
import org.keycloak.testsuite.util.InfinispanContainer;
|
||||||
|
import org.keycloak.testsuite.util.LegacyKeycloakContainer;
|
||||||
|
|
||||||
|
public class LegacyKeycloakContainerProvider implements ResourceProvider {
|
||||||
|
|
||||||
|
public static final ZeroDowntimeContainer CONTAINER = processSystemProperties();
|
||||||
|
public enum ZeroDowntimeContainer {
|
||||||
|
POSTGRES,
|
||||||
|
COCKROACH,
|
||||||
|
HOTROD,
|
||||||
|
NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String legacyKeycloakPort = System.getProperty("keycloak.legacy.port", "8091");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canProvide(Class<?> type) {
|
||||||
|
return type.equals(LegacyKeycloakContainerProvider.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object lookup(ArquillianResource ar, Annotation... antns) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ZeroDowntimeContainer processSystemProperties() {
|
||||||
|
String mapStorageProfileconfig = System.getProperty("auth.server.quarkus.mapStorage.profile.config");
|
||||||
|
if (mapStorageProfileconfig == null) return ZeroDowntimeContainer.NONE;
|
||||||
|
|
||||||
|
switch (mapStorageProfileconfig) {
|
||||||
|
|
||||||
|
case "jpa":
|
||||||
|
String storageConnectionsVendor = System.getProperty("keycloak.storage.connections.vendor");
|
||||||
|
if (storageConnectionsVendor == null) return ZeroDowntimeContainer.NONE;
|
||||||
|
|
||||||
|
switch (storageConnectionsVendor) {
|
||||||
|
|
||||||
|
case "postgres":
|
||||||
|
return ZeroDowntimeContainer.POSTGRES;
|
||||||
|
case "cockroach":
|
||||||
|
return ZeroDowntimeContainer.COCKROACH;
|
||||||
|
default:
|
||||||
|
return ZeroDowntimeContainer.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "hotrod":
|
||||||
|
return ZeroDowntimeContainer.HOTROD;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ZeroDowntimeContainer.NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LegacyKeycloakContainer createLegacyKeycloakContainer() {
|
||||||
|
return new LegacyKeycloakContainer(System.getProperty("keycloak.legacy.version.zero.downtime", "latest"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LegacyKeycloakContainer get() {
|
||||||
|
switch (CONTAINER) {
|
||||||
|
|
||||||
|
case POSTGRES:
|
||||||
|
return createLegacyKeycloakContainer().withCommand("start-dev",
|
||||||
|
"--storage=jpa",
|
||||||
|
"--http-port=" + legacyKeycloakPort,
|
||||||
|
"--db-url=" + System.getProperty("keycloak.map.storage.connectionsJpa.url"),
|
||||||
|
"--db-username=" + PostgresContainerProvider.POSTGRES_DB_USER,
|
||||||
|
"--db-password=" + PostgresContainerProvider.POSTGRES_DB_PASSWORD);
|
||||||
|
|
||||||
|
case COCKROACH:
|
||||||
|
return createLegacyKeycloakContainer().withCommand("start-dev",
|
||||||
|
"--storage=jpa",
|
||||||
|
"--http-port=" + legacyKeycloakPort,
|
||||||
|
"--db-url=" + System.getProperty("keycloak.map.storage.connectionsJpa.url"),
|
||||||
|
"--db-username=" + CockroachdbContainerProvider.COCKROACHDB_DB_USER,
|
||||||
|
"--db-password=" + CockroachdbContainerProvider.COCKROACHDB_DB_PASSWORD);
|
||||||
|
|
||||||
|
case HOTROD:
|
||||||
|
return createLegacyKeycloakContainer().withCommand("start-dev",
|
||||||
|
"--storage=hotrod",
|
||||||
|
"--http-port=" + legacyKeycloakPort,
|
||||||
|
"--storage-hotrod-host=" + System.getProperty(HotRodContainerProvider.HOT_ROD_STORE_HOST_PROPERTY),
|
||||||
|
"--storage-hotrod-port=" + InfinispanContainer.PORT,
|
||||||
|
"--storage-hotrod-username=" + InfinispanContainer.USERNAME,
|
||||||
|
"--storage-hotrod-password=" + InfinispanContainer.PASSWORD);
|
||||||
|
case NONE:
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.testsuite.arquillian;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import org.jboss.arquillian.container.spi.event.StartSuiteContainers;
|
||||||
|
import org.jboss.arquillian.core.api.annotation.Observes;
|
||||||
|
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||||
|
import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider;
|
||||||
|
import org.jboss.arquillian.test.spi.event.suite.AfterSuite;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.testcontainers.containers.PostgreSQLContainer;
|
||||||
|
import org.testcontainers.utility.DockerImageName;
|
||||||
|
|
||||||
|
public class PostgresContainerProvider implements ResourceProvider {
|
||||||
|
|
||||||
|
private final Logger log = Logger.getLogger(PostgresContainerProvider.class);
|
||||||
|
|
||||||
|
private static final Boolean START_CONTAINER = Boolean.valueOf(System.getProperty("postgres.start-container", "false"));
|
||||||
|
private static final String POSTGRES_DOCKER_IMAGE_NAME = System.getProperty("keycloak.map.storage.postgres.docker.image", "postgres:alpine");
|
||||||
|
|
||||||
|
public static final String POSTGRES_DB_USER = System.getProperty("keycloak.map.storage.connectionsJpa.user", "keycloak");
|
||||||
|
public static final String POSTGRES_DB_PASSWORD = System.getProperty("keycloak.map.storage.connectionsJpa.password", "pass");
|
||||||
|
|
||||||
|
private static PostgreSQLContainer postgresContainer;
|
||||||
|
|
||||||
|
public void beforeContainerStarted(@Observes(precedence = 1) StartSuiteContainers event) {
|
||||||
|
if (START_CONTAINER) {
|
||||||
|
postgresContainer = createContainer();
|
||||||
|
postgresContainer.start();
|
||||||
|
|
||||||
|
System.setProperty("keycloak.map.storage.connectionsJpa.url", postgresContainer.getJdbcUrl());
|
||||||
|
|
||||||
|
log.infof("DatabaseInfo: %s, user=%s, pass=%s", postgresContainer.getJdbcUrl(), POSTGRES_DB_USER, POSTGRES_DB_PASSWORD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void afterSuite(@Observes(precedence = 4) AfterSuite event) {
|
||||||
|
if (START_CONTAINER) {
|
||||||
|
postgresContainer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PostgreSQLContainer createContainer() {
|
||||||
|
return new PostgreSQLContainer(DockerImageName.parse(POSTGRES_DOCKER_IMAGE_NAME).asCompatibleSubstituteFor("postgres"))
|
||||||
|
.withDatabaseName("keycloak")
|
||||||
|
.withUsername(POSTGRES_DB_USER)
|
||||||
|
.withPassword(POSTGRES_DB_PASSWORD);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PostgreSQLContainer getContainer() {
|
||||||
|
return postgresContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canProvide(Class<?> type) {
|
||||||
|
return type.equals(PostgresContainerProvider.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object lookup(ArquillianResource ar, Annotation... antns) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -1,50 +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.testsuite.arquillian;
|
|
||||||
|
|
||||||
import org.jboss.arquillian.container.spi.event.StartSuiteContainers;
|
|
||||||
import org.jboss.arquillian.core.api.annotation.Observes;
|
|
||||||
import org.jboss.arquillian.test.spi.event.suite.AfterSuite;
|
|
||||||
import org.testcontainers.containers.PostgreSQLContainer;
|
|
||||||
import org.testcontainers.utility.DockerImageName;
|
|
||||||
|
|
||||||
public class PostgresContainerTestEnricher {
|
|
||||||
|
|
||||||
private static final Boolean START_CONTAINER = Boolean.valueOf(System.getProperty("postgres.start-container", "false"));
|
|
||||||
private static final String POSTGRES_DOCKER_IMAGE_NAME = System.getProperty("keycloak.map.storage.postgres.docker.image", "postgres:alpine");
|
|
||||||
private static final PostgreSQLContainer POSTGRES_CONTAINER = new PostgreSQLContainer(DockerImageName.parse(POSTGRES_DOCKER_IMAGE_NAME).asCompatibleSubstituteFor("postgres"));
|
|
||||||
private static final String POSTGRES_DB_USER = System.getProperty("keycloak.map.storage.connectionsJpa.user", "keycloak");
|
|
||||||
private static final String POSTGRES_DB_PASSWORD = System.getProperty("keycloak.map.storage.connectionsJpa.password", "pass");
|
|
||||||
|
|
||||||
public void beforeContainerStarted(@Observes(precedence = 1) StartSuiteContainers event) {
|
|
||||||
if (START_CONTAINER) {
|
|
||||||
POSTGRES_CONTAINER
|
|
||||||
.withDatabaseName("keycloak")
|
|
||||||
.withUsername(POSTGRES_DB_USER)
|
|
||||||
.withPassword(POSTGRES_DB_PASSWORD)
|
|
||||||
.start();
|
|
||||||
|
|
||||||
System.setProperty("keycloak.map.storage.connectionsJpa.url", POSTGRES_CONTAINER.getJdbcUrl());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void afterSuite(@Observes(precedence = 4) AfterSuite event) {
|
|
||||||
if (START_CONTAINER) {
|
|
||||||
POSTGRES_CONTAINER.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,6 +18,7 @@ package org.keycloak.testsuite.arquillian;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -58,6 +59,8 @@ public final class TestContext {
|
||||||
// Key is realmName, value are objects to clean after the test method
|
// Key is realmName, value are objects to clean after the test method
|
||||||
private final Map<String, TestCleanup> cleanups = new ConcurrentHashMap<>();
|
private final Map<String, TestCleanup> cleanups = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private final Set<Runnable> afterClassActions = new HashSet<>();
|
||||||
|
|
||||||
public TestContext(SuiteContext suiteContext, Class testClass) {
|
public TestContext(SuiteContext suiteContext, Class testClass) {
|
||||||
this.suiteContext = suiteContext;
|
this.suiteContext = suiteContext;
|
||||||
this.testClass = testClass;
|
this.testClass = testClass;
|
||||||
|
@ -201,6 +204,14 @@ public final class TestContext {
|
||||||
return cleanups;
|
return cleanups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void registerAfterClassAction(Runnable afterClassAction) {
|
||||||
|
afterClassActions.add(afterClassAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void runAfterClassActions() {
|
||||||
|
afterClassActions.forEach(Runnable::run);
|
||||||
|
afterClassActions.clear();
|
||||||
|
}
|
||||||
|
|
||||||
public String getAppServerContainerName() {
|
public String getAppServerContainerName() {
|
||||||
if (isAdapterContainerEnabled()) { //standalone app server
|
if (isAdapterContainerEnabled()) { //standalone app server
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
package org.keycloak.testsuite.util;
|
package org.keycloak.testsuite.util;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.testsuite.arquillian.HotRodStoreTestEnricher;
|
import org.keycloak.testsuite.arquillian.HotRodContainerProvider;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import org.testcontainers.containers.wait.strategy.Wait;
|
import org.testcontainers.containers.wait.strategy.Wait;
|
||||||
import org.testcontainers.utility.MountableFile;
|
import org.testcontainers.utility.MountableFile;
|
||||||
|
@ -33,10 +33,10 @@ import java.util.regex.Pattern;
|
||||||
public class InfinispanContainer extends GenericContainer<InfinispanContainer> {
|
public class InfinispanContainer extends GenericContainer<InfinispanContainer> {
|
||||||
|
|
||||||
private final Logger LOG = Logger.getLogger(getClass());
|
private final Logger LOG = Logger.getLogger(getClass());
|
||||||
private static final String PORT = System.getProperty("keycloak.connectionsHotRod.port", "11222");
|
public static final String PORT = System.getProperty("keycloak.connectionsHotRod.port", "11222");
|
||||||
private static String HOST = System.getProperty(HotRodStoreTestEnricher.HOT_ROD_STORE_HOST_PROPERTY);
|
private static String HOST = System.getProperty(HotRodContainerProvider.HOT_ROD_STORE_HOST_PROPERTY);
|
||||||
private static final String USERNAME = System.getProperty("keycloak.connectionsHotRod.username", "admin");
|
public static final String USERNAME = System.getProperty("keycloak.connectionsHotRod.username", "admin");
|
||||||
private static final String PASSWORD = System.getProperty("keycloak.connectionsHotRod.password", "admin");
|
public static final String PASSWORD = System.getProperty("keycloak.connectionsHotRod.password", "admin");
|
||||||
|
|
||||||
private static final String ZERO_TO_255
|
private static final String ZERO_TO_255
|
||||||
= "(\\d{1,2}|(0|1)\\"
|
= "(\\d{1,2}|(0|1)\\"
|
||||||
|
@ -67,10 +67,12 @@ public class InfinispanContainer extends GenericContainer<InfinispanContainer> {
|
||||||
MountableFile mountableFile = MountableFile.forHostPath(timeTaskPath, 0666);
|
MountableFile mountableFile = MountableFile.forHostPath(timeTaskPath, 0666);
|
||||||
withCopyFileToContainer(mountableFile, "/opt/infinispan/server/lib/integration-arquillian-testsuite-providers.jar");
|
withCopyFileToContainer(mountableFile, "/opt/infinispan/server/lib/integration-arquillian-testsuite-providers.jar");
|
||||||
|
|
||||||
withStartupTimeout(Duration.ofMinutes(5));
|
//order of waitingFor and withStartupTimeout matters as the latter sets the timeout for WaitStrategy set by waitingFor
|
||||||
waitingFor(Wait.forLogMessage(".*Infinispan Server.*started in.*", 1));
|
waitingFor(Wait.forLogMessage(".*Infinispan Server.*started in.*", 1));
|
||||||
|
withStartupTimeout(Duration.ofMinutes(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getHost() {
|
public String getHost() {
|
||||||
if (HOST == null && this.isRunning()) {
|
if (HOST == null && this.isRunning()) {
|
||||||
Matcher matcher = IP_ADDRESS_PATTERN.matcher(getLogs());
|
Matcher matcher = IP_ADDRESS_PATTERN.matcher(getLogs());
|
||||||
|
@ -88,11 +90,6 @@ public class InfinispanContainer extends GenericContainer<InfinispanContainer> {
|
||||||
return HOST;
|
return HOST;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
super.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPort() {
|
public String getPort() {
|
||||||
return PORT;
|
return PORT;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.testsuite.util;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.time.Duration;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
import org.testcontainers.containers.GenericContainer;
|
||||||
|
import org.testcontainers.containers.wait.strategy.Wait;
|
||||||
|
|
||||||
|
public class LegacyKeycloakContainer extends GenericContainer<LegacyKeycloakContainer> {
|
||||||
|
|
||||||
|
private static final String USERNAME = "admin";
|
||||||
|
private static final String PASSWORD = "admin";
|
||||||
|
|
||||||
|
public LegacyKeycloakContainer(String tagName) {
|
||||||
|
super("quay.io/keycloak/keycloak" + getManifestDigestOfImageByName(tagName));
|
||||||
|
|
||||||
|
withEnv("KEYCLOAK_ADMIN", USERNAME);
|
||||||
|
withEnv("KEYCLOAK_ADMIN_PASSWORD", PASSWORD);
|
||||||
|
withNetworkMode("host");
|
||||||
|
|
||||||
|
//order of waitingFor and withStartupTimeout matters as the latter sets the timeout for WaitStrategy set by waitingFor
|
||||||
|
waitingFor(Wait.forLogMessage(".*Running the server in development mode..*", 1));
|
||||||
|
withStartupTimeout(Duration.ofMinutes(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For tag names as "latest" or "nightly" it may happen that some older version keycloak could be cached.
|
||||||
|
* Therefore fetching container by its name wouldn't be reliable.
|
||||||
|
*
|
||||||
|
* This method obtains image "manifest_digest" by the name from quay.io and returns it with prefix "@".
|
||||||
|
* It can be then used to fetch the container with:
|
||||||
|
*
|
||||||
|
* <p>quay.io/keycloak/keycloak@sha256:...</p>
|
||||||
|
*
|
||||||
|
* @param tagName Name of the image tag.
|
||||||
|
* @return "manifest_digest" of the image if "latest" or "nightly" with prefix "@", {@code tagName} with ":" prefix otherwise
|
||||||
|
*/
|
||||||
|
private static String getManifestDigestOfImageByName(String tagName) {
|
||||||
|
if ("latest".equals(tagName) || "nightly".equals(tagName)) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
URI uri = new URI("https://quay.io/api/v1/repository/keycloak/keycloak/tag/?specificTag=" + tagName);
|
||||||
|
HttpResponse<String> response = HttpClient.newBuilder()
|
||||||
|
.followRedirects(HttpClient.Redirect.ALWAYS)
|
||||||
|
.build()
|
||||||
|
.send(HttpRequest.newBuilder(uri).GET().build(), HttpResponse.BodyHandlers.ofString());
|
||||||
|
if (response.statusCode() == HttpURLConnection.HTTP_OK) {
|
||||||
|
JsonNode manifestDigest = JsonSerialization.mapper.readTree(response.body()).findValue("manifest_digest");
|
||||||
|
if (manifestDigest != null) {
|
||||||
|
return "@" + manifestDigest.asText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new RuntimeException(String.format("Unable to get manifest_digest for image with tag %s from quay.io. Response: %d, %s. ", tagName, response.statusCode(), response.body()));
|
||||||
|
} catch (URISyntaxException | IOException | InterruptedException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return ":" + tagName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,13 +23,17 @@ import org.keycloak.representations.idm.ClientScopeRepresentation;
|
||||||
*/
|
*/
|
||||||
public class ClientScopeBuilder {
|
public class ClientScopeBuilder {
|
||||||
|
|
||||||
private ClientScopeRepresentation rep;
|
private final ClientScopeRepresentation rep;
|
||||||
|
|
||||||
public static ClientScopeBuilder create() {
|
public static ClientScopeBuilder create() {
|
||||||
ClientScopeRepresentation rep = new ClientScopeRepresentation();
|
ClientScopeRepresentation rep = new ClientScopeRepresentation();
|
||||||
return new ClientScopeBuilder(rep);
|
return new ClientScopeBuilder(rep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ClientScopeBuilder edit(ClientScopeRepresentation rep) {
|
||||||
|
return new ClientScopeBuilder(rep);
|
||||||
|
}
|
||||||
|
|
||||||
private ClientScopeBuilder(ClientScopeRepresentation rep) {
|
private ClientScopeBuilder(ClientScopeRepresentation rep) {
|
||||||
this.rep = rep;
|
this.rep = rep;
|
||||||
}
|
}
|
||||||
|
@ -43,6 +47,11 @@ public class ClientScopeBuilder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ClientScopeBuilder description(String description) {
|
||||||
|
rep.setDescription(description);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public ClientScopeBuilder protocol(String protocol) {
|
public ClientScopeBuilder protocol(String protocol) {
|
||||||
rep.setProtocol(protocol);
|
rep.setProtocol(protocol);
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -34,6 +34,10 @@ public class GroupBuilder {
|
||||||
return new GroupBuilder(rep);
|
return new GroupBuilder(rep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static GroupBuilder edit(GroupRepresentation rep) {
|
||||||
|
return new GroupBuilder(rep);
|
||||||
|
}
|
||||||
|
|
||||||
private GroupBuilder(GroupRepresentation rep) {
|
private GroupBuilder(GroupRepresentation rep) {
|
||||||
this.rep = rep;
|
this.rep = rep;
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,11 @@ public class RealmBuilder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RealmBuilder displayName(String displayName) {
|
||||||
|
rep.setDisplayName(displayName);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public RealmBuilder publicKey(String publicKey) {
|
public RealmBuilder publicKey(String publicKey) {
|
||||||
rep.setPublicKey(publicKey);
|
rep.setPublicKey(publicKey);
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -31,13 +31,18 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
public class RoleBuilder {
|
public class RoleBuilder {
|
||||||
|
|
||||||
private RoleRepresentation rep = new RoleRepresentation();
|
private final RoleRepresentation rep;
|
||||||
|
|
||||||
public static RoleBuilder create() {
|
public static RoleBuilder create() {
|
||||||
return new RoleBuilder();
|
return new RoleBuilder(new RoleRepresentation());
|
||||||
}
|
}
|
||||||
|
|
||||||
private RoleBuilder() {
|
public static RoleBuilder edit(RoleRepresentation rep) {
|
||||||
|
return new RoleBuilder(rep);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RoleBuilder(RoleRepresentation rep) {
|
||||||
|
this.rep = rep;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RoleBuilder id(String id) {
|
public RoleBuilder id(String id) {
|
||||||
|
@ -84,7 +89,7 @@ public class RoleBuilder {
|
||||||
checkCompositesNull();
|
checkCompositesNull();
|
||||||
|
|
||||||
if (rep.getComposites().getRealm() == null) {
|
if (rep.getComposites().getRealm() == null) {
|
||||||
rep.getComposites().setRealm(new HashSet<String>());
|
rep.getComposites().setRealm(new HashSet<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
rep.getComposites().getRealm().add(compositeRole);
|
rep.getComposites().getRealm().add(compositeRole);
|
||||||
|
@ -99,11 +104,11 @@ public class RoleBuilder {
|
||||||
checkCompositesNull();
|
checkCompositesNull();
|
||||||
|
|
||||||
if (rep.getComposites().getClient() == null) {
|
if (rep.getComposites().getClient() == null) {
|
||||||
rep.getComposites().setClient(new HashMap<String, List<String>>());
|
rep.getComposites().setClient(new HashMap<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rep.getComposites().getClient().get(client) == null) {
|
if (rep.getComposites().getClient().get(client) == null) {
|
||||||
rep.getComposites().getClient().put(client, new LinkedList<String>());
|
rep.getComposites().getClient().put(client, new LinkedList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
rep.getComposites().getClient().get(client).add(compositeRole);
|
rep.getComposites().getClient().get(client).add(compositeRole);
|
||||||
|
|
|
@ -0,0 +1,333 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 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.testsuite.zerodowntime;
|
||||||
|
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.hasItem;
|
||||||
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
|
import org.jboss.arquillian.container.test.api.ContainerController;
|
||||||
|
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||||
|
import org.junit.Assume;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.admin.client.Keycloak;
|
||||||
|
import org.keycloak.admin.client.KeycloakBuilder;
|
||||||
|
import org.keycloak.common.util.Retry;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
||||||
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||||
|
import org.keycloak.testsuite.arquillian.CockroachdbContainerProvider;
|
||||||
|
import org.keycloak.testsuite.arquillian.HotRodContainerProvider;
|
||||||
|
import org.keycloak.testsuite.arquillian.LegacyKeycloakContainerProvider;
|
||||||
|
import org.keycloak.testsuite.arquillian.LegacyKeycloakContainerProvider.ZeroDowntimeContainer;
|
||||||
|
import static org.keycloak.testsuite.arquillian.LegacyKeycloakContainerProvider.ZeroDowntimeContainer.COCKROACH;
|
||||||
|
import static org.keycloak.testsuite.arquillian.LegacyKeycloakContainerProvider.ZeroDowntimeContainer.HOTROD;
|
||||||
|
import static org.keycloak.testsuite.arquillian.LegacyKeycloakContainerProvider.ZeroDowntimeContainer.NONE;
|
||||||
|
import static org.keycloak.testsuite.arquillian.LegacyKeycloakContainerProvider.ZeroDowntimeContainer.POSTGRES;
|
||||||
|
import org.keycloak.testsuite.arquillian.PostgresContainerProvider;
|
||||||
|
import org.keycloak.testsuite.auth.page.AuthRealm;
|
||||||
|
import org.keycloak.testsuite.updaters.SetSystemProperty;
|
||||||
|
import org.keycloak.testsuite.util.AdminClientUtil;
|
||||||
|
import org.keycloak.testsuite.util.ClientBuilder;
|
||||||
|
import org.keycloak.testsuite.util.ClientScopeBuilder;
|
||||||
|
import org.keycloak.testsuite.util.GroupBuilder;
|
||||||
|
import org.keycloak.testsuite.util.InfinispanContainer;
|
||||||
|
import org.keycloak.testsuite.util.LegacyKeycloakContainer;
|
||||||
|
import org.keycloak.testsuite.util.RealmBuilder;
|
||||||
|
import org.keycloak.testsuite.util.RoleBuilder;
|
||||||
|
import org.keycloak.testsuite.util.RolesBuilder;
|
||||||
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
|
import org.testcontainers.containers.CockroachContainer;
|
||||||
|
import org.testcontainers.containers.PostgreSQLContainer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It tests that object stored by legacy version of keycloak could be read by
|
||||||
|
* current one, then object is modified, therefore stored in current version and
|
||||||
|
* then it's verified it could be read by legacy keycloak.
|
||||||
|
*
|
||||||
|
* See HOW-TO-RUN.md for some additional notes.
|
||||||
|
*
|
||||||
|
* @author vramik
|
||||||
|
*/
|
||||||
|
public class ZeroDowntimeTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
|
@ArquillianResource private ContainerController controller;
|
||||||
|
@ArquillianResource private LegacyKeycloakContainerProvider legacyKeycloakServerProvider;
|
||||||
|
@ArquillianResource private PostgresContainerProvider postgresProvider;
|
||||||
|
@ArquillianResource private CockroachdbContainerProvider cockroachdbProvider;
|
||||||
|
@ArquillianResource private HotRodContainerProvider hotrodProvider;
|
||||||
|
|
||||||
|
private final String legacyKeycloakHost = System.getProperty("keycloak.legacy.host", "localhost");
|
||||||
|
private final String legacyKeycloakPort = System.getProperty("keycloak.legacy.port", "8091");
|
||||||
|
private final String legacyKeycloakUrl = "http://" + legacyKeycloakHost + ":" + legacyKeycloakPort;
|
||||||
|
|
||||||
|
private String currentAuthServer;
|
||||||
|
private SetSystemProperty sysProp;
|
||||||
|
private static boolean initialized;
|
||||||
|
private static Keycloak legacyAdminClient;
|
||||||
|
|
||||||
|
// junit4 enforces to use static method when using @BeforeClass or @ClassRule
|
||||||
|
// TODO: after upgrade to junit5: replace @Before with @BeforeAll and get rid of the "initialized" flag
|
||||||
|
@Before
|
||||||
|
public void before() throws Exception {
|
||||||
|
Assume.assumeFalse(LegacyKeycloakContainerProvider.CONTAINER.equals(ZeroDowntimeContainer.NONE));
|
||||||
|
|
||||||
|
if (initialized) return;
|
||||||
|
|
||||||
|
currentAuthServer = suiteContext.getAuthServerInfo().getArquillianContainer().getName();
|
||||||
|
|
||||||
|
// stop current auth-server
|
||||||
|
if (controller.isStarted(currentAuthServer)) {
|
||||||
|
log.debug("Stopping auth server");
|
||||||
|
controller.stop(currentAuthServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop and create new container
|
||||||
|
handleContainer();
|
||||||
|
|
||||||
|
//start legacy container
|
||||||
|
LegacyKeycloakContainer legacyKeycloakServer = legacyKeycloakServerProvider.get();
|
||||||
|
legacyKeycloakServer.start();
|
||||||
|
legacyAdminClient = KeycloakBuilder.builder()
|
||||||
|
.serverUrl(legacyKeycloakUrl)
|
||||||
|
.realm(AuthRealm.MASTER)
|
||||||
|
.username(AuthRealm.ADMIN)
|
||||||
|
.password(AuthRealm.ADMIN)
|
||||||
|
.clientId(Constants.ADMIN_CLI_CLIENT_ID)
|
||||||
|
.resteasyClient(AdminClientUtil.createResteasyClient())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// import test realm
|
||||||
|
legacyAdminClient.realms().create(RealmBuilder.create()
|
||||||
|
.name(AuthRealm.TEST)
|
||||||
|
.attribute("attr", "val")
|
||||||
|
.client(ClientBuilder.create()
|
||||||
|
.clientId("client"))
|
||||||
|
.clientScope(ClientScopeBuilder.create().name("client_scope"))
|
||||||
|
.roles(RolesBuilder.create()
|
||||||
|
.clientRole("client", RoleBuilder.create().name("client_role").singleAttribute("attr", "val").build())
|
||||||
|
.realmRole(RoleBuilder.create().name("realm_role").singleAttribute("attr", "val").build()))
|
||||||
|
.group(GroupBuilder.create().name("group").build())
|
||||||
|
.user(UserBuilder.create().username("user").addAttribute("attr", "val"))
|
||||||
|
.build());
|
||||||
|
|
||||||
|
// start current auth-server
|
||||||
|
controller.start(currentAuthServer);
|
||||||
|
reconnectAdminClient();
|
||||||
|
|
||||||
|
testContext.registerAfterClassAction(() -> {
|
||||||
|
legacyKeycloakServer.stop();
|
||||||
|
if (sysProp != null) sysProp.revert();
|
||||||
|
});
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleContainer() {
|
||||||
|
switch (LegacyKeycloakContainerProvider.CONTAINER) {
|
||||||
|
case POSTGRES:
|
||||||
|
handlePostgres();
|
||||||
|
break;
|
||||||
|
case COCKROACH:
|
||||||
|
handleCockroach();
|
||||||
|
break;
|
||||||
|
case HOTROD:
|
||||||
|
handleHotRod();
|
||||||
|
break;
|
||||||
|
case NONE:
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unknown container!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePostgres() {
|
||||||
|
PostgreSQLContainer postgres = postgresProvider.getContainer();
|
||||||
|
//if suite container is running stop it
|
||||||
|
if (postgres != null && postgres.isRunning()) {
|
||||||
|
postgres.stop();
|
||||||
|
}
|
||||||
|
if (Boolean.parseBoolean(System.getProperty("postgres.start-container-for-zero-downtime", "true"))) {
|
||||||
|
postgres = postgresProvider.createContainer();
|
||||||
|
postgres.start();
|
||||||
|
|
||||||
|
log.infof("DatabaseInfo: %s, user=%s, pass=%s", postgres.getJdbcUrl(), PostgresContainerProvider.POSTGRES_DB_USER, PostgresContainerProvider.POSTGRES_DB_PASSWORD);
|
||||||
|
|
||||||
|
sysProp = new SetSystemProperty("keycloak.map.storage.connectionsJpa.url", postgres.getJdbcUrl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleCockroach() {
|
||||||
|
final CockroachContainer cockroach = cockroachdbProvider.getContainer();
|
||||||
|
//if suite container is running stop it
|
||||||
|
if (cockroach != null && cockroach.isRunning()) {
|
||||||
|
cockroach.stop();
|
||||||
|
|
||||||
|
//it needs some time to stop
|
||||||
|
Retry.executeWithBackoff(i -> {
|
||||||
|
if (cockroach.isRunning()) {
|
||||||
|
throw new AssertionError(String.format("Stopping CockroachDB container was not successful. Number of attempts: %d", i));
|
||||||
|
}
|
||||||
|
}, Duration.ofMinutes(1), 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
// spawn new container
|
||||||
|
if (Boolean.parseBoolean(System.getProperty("cockroachdb.start-container-for-zero-downtime", "true"))) {
|
||||||
|
CockroachContainer newCockroach = cockroachdbProvider.createContainer();
|
||||||
|
newCockroach.withStartupAttempts(3); // with podman it sometimes fails with com.sun.jna.LastErrorException: https://github.com/testcontainers/testcontainers-java/issues/6640
|
||||||
|
newCockroach.start();
|
||||||
|
|
||||||
|
log.infof("DatabaseInfo: %s, user=%s, pass=%s", newCockroach.getJdbcUrl(), CockroachdbContainerProvider.COCKROACHDB_DB_USER, CockroachdbContainerProvider.COCKROACHDB_DB_PASSWORD);
|
||||||
|
|
||||||
|
sysProp = new SetSystemProperty("keycloak.map.storage.connectionsJpa.url", newCockroach.getJdbcUrl());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CRDB container fails to connect intermitently with:
|
||||||
|
* org.postgresql.util.PSQLException: FATAL: password authentication failed for user "keycloak"
|
||||||
|
*
|
||||||
|
* It seems it needs some time to set correct permission for a user after fresh start
|
||||||
|
* Waiting for successful connection.
|
||||||
|
*/
|
||||||
|
Retry.executeWithBackoff(i -> {
|
||||||
|
try {
|
||||||
|
DriverManager.getConnection(newCockroach.getJdbcUrl(), CockroachdbContainerProvider.COCKROACHDB_DB_USER, CockroachdbContainerProvider.COCKROACHDB_DB_PASSWORD);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new AssertionError(String.format("Establishing connection was not successful. Number of attempts: %d", i));
|
||||||
|
}
|
||||||
|
}, Duration.ofMinutes(1), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleHotRod() {
|
||||||
|
InfinispanContainer hotRodContainer = hotrodProvider.getContainer();
|
||||||
|
//if suite container is running stop it
|
||||||
|
if (hotRodContainer != null && hotRodContainer.isRunning()) {
|
||||||
|
hotRodContainer.stop();
|
||||||
|
}
|
||||||
|
// spawn new container
|
||||||
|
if (Boolean.parseBoolean(System.getProperty("start-hotrod-container-for-zero-downtime", "true"))) {
|
||||||
|
hotRodContainer = new InfinispanContainer();
|
||||||
|
hotRodContainer.start();
|
||||||
|
|
||||||
|
sysProp = new SetSystemProperty(HotRodContainerProvider.HOT_ROD_STORE_HOST_PROPERTY, hotRodContainer.getHost());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void realmSmokeTest() throws Exception {
|
||||||
|
//realm
|
||||||
|
RealmRepresentation testRealmRep = adminClient.realm(AuthRealm.TEST).toRepresentation();
|
||||||
|
String realmAttribute = testRealmRep.getAttributes().get("attr");
|
||||||
|
assertThat(realmAttribute, equalTo("val"));
|
||||||
|
adminClient.realm(AuthRealm.TEST).update(RealmBuilder.edit(testRealmRep).displayName("displayName").build());
|
||||||
|
|
||||||
|
//verify objects can be read by legacy server
|
||||||
|
RealmRepresentation returnedRealmRep = legacyAdminClient.realm(AuthRealm.TEST).toRepresentation();
|
||||||
|
assertThat(returnedRealmRep.getDisplayName(), is("displayName"));
|
||||||
|
assertThat(returnedRealmRep.getAttributes().get("attr"), is("val"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void clientSmokeTest () throws Exception {
|
||||||
|
//client
|
||||||
|
List<ClientRepresentation> clients = adminClient.realm(AuthRealm.TEST).clients().findByClientId("client");
|
||||||
|
assertThat(clients, hasSize(1));
|
||||||
|
ClientRepresentation clientRep = clients.get(0);
|
||||||
|
adminClient.realm(AuthRealm.TEST).clients().get(clientRep.getId()).update(ClientBuilder.edit(clientRep).addRedirectUri("*").build());
|
||||||
|
|
||||||
|
//client role
|
||||||
|
List<RoleRepresentation> clientRoles = adminClient.realm(AuthRealm.TEST).clients().get(clientRep.getId()).roles().list();
|
||||||
|
assertThat(clientRoles, hasSize(1));
|
||||||
|
RoleRepresentation clientRoleRep = clientRoles.get(0);
|
||||||
|
assertThat(clientRoleRep.getName(), is("client_role"));
|
||||||
|
adminClient.realm(AuthRealm.TEST).clients().get(clientRep.getId()).roles().get(clientRoleRep.getName()).update(RoleBuilder.edit(clientRoleRep).description("desc").build());
|
||||||
|
|
||||||
|
//verify objects can be read by legacy server
|
||||||
|
ClientRepresentation returnedClientRep = legacyAdminClient.realm(AuthRealm.TEST).clients().get(clientRep.getId()).toRepresentation();
|
||||||
|
assertThat(returnedClientRep.getRedirectUris(), hasItem("*"));
|
||||||
|
|
||||||
|
RoleRepresentation returnedClientRoleRep = legacyAdminClient.realm(AuthRealm.TEST).clients().get(clientRep.getId()).roles().get(clientRoleRep.getName()).toRepresentation();
|
||||||
|
assertThat(returnedClientRoleRep.getDescription(), is("desc"));
|
||||||
|
assertThat(returnedClientRoleRep.getAttributes().get("attr"), hasItem("val"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void clientScopeSmokeTest () throws Exception {
|
||||||
|
//client scope
|
||||||
|
ClientScopeRepresentation clientScopeRep = adminClient.realm(AuthRealm.TEST).clientScopes().findAll().stream().filter(scope -> scope.getName().equals("client_scope")).findFirst().orElse(null);
|
||||||
|
assertThat(clientScopeRep, notNullValue());
|
||||||
|
adminClient.realm(AuthRealm.TEST).clientScopes().get(clientScopeRep.getId()).update(ClientScopeBuilder.edit(clientScopeRep).description("desc").build());
|
||||||
|
|
||||||
|
//verify objects can be read by legacy server
|
||||||
|
ClientScopeRepresentation returnedClientScopeRep = legacyAdminClient.realm(AuthRealm.TEST).clientScopes().get(clientScopeRep.getId()).toRepresentation();
|
||||||
|
assertThat(returnedClientScopeRep.getDescription(), is("desc"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void userRoleSmokeTest() throws Exception {
|
||||||
|
//user
|
||||||
|
List<UserRepresentation> users = adminClient.realm(AuthRealm.TEST).users().searchByUsername("user", Boolean.TRUE);
|
||||||
|
assertThat(users, hasSize(1));
|
||||||
|
UserRepresentation userRep = users.get(0);
|
||||||
|
adminClient.realm(AuthRealm.TEST).users().get(userRep.getId()).update(UserBuilder.edit(userRep).email("test@test").build());
|
||||||
|
|
||||||
|
//verify objects can be read by legacy server
|
||||||
|
UserRepresentation returnedUserRep = legacyAdminClient.realm(AuthRealm.TEST).users().get(userRep.getId()).toRepresentation();
|
||||||
|
assertThat(returnedUserRep.getEmail(), is("test@test"));
|
||||||
|
assertThat(returnedUserRep.getAttributes().get("attr"), hasItem("val"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void roleSmokeTest() throws Exception {
|
||||||
|
//realm role
|
||||||
|
RoleRepresentation realmRoleRep = adminClient.realm(AuthRealm.TEST).roles().get("realm_role").toRepresentation();
|
||||||
|
adminClient.realm(AuthRealm.TEST).roles().get("realm_role").update(RoleBuilder.edit(realmRoleRep).description("desc").build());
|
||||||
|
|
||||||
|
//verify objects can be read by legacy server
|
||||||
|
RoleRepresentation returnedRoleRep = legacyAdminClient.realm(AuthRealm.TEST).roles().get(realmRoleRep.getName()).toRepresentation();
|
||||||
|
assertThat(returnedRoleRep.getDescription(), is("desc"));
|
||||||
|
assertThat(returnedRoleRep.getAttributes().get("attr"), hasItem("val"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void groupSmokeTest() throws Exception {
|
||||||
|
//group
|
||||||
|
List<GroupRepresentation> groups = adminClient.realm(AuthRealm.TEST).groups().groups();
|
||||||
|
assertThat(groups, hasSize(1));
|
||||||
|
GroupRepresentation groupRep = groups.get(0);
|
||||||
|
adminClient.realm(AuthRealm.TEST).groups().group(groupRep.getId()).update(GroupBuilder.edit(groupRep).singleAttribute("attr", "val").build());
|
||||||
|
|
||||||
|
//verify objects can be read by legacy server
|
||||||
|
GroupRepresentation returnedGroupRep = legacyAdminClient.realm(AuthRealm.TEST).groups().group(groupRep.getId()).toRepresentation();
|
||||||
|
assertThat(returnedGroupRep.getAttributes().get("attr"), hasItem("val"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,3 +45,4 @@ validation,6
|
||||||
vault,4
|
vault,4
|
||||||
welcomepage,6
|
welcomepage,6
|
||||||
x509,4
|
x509,4
|
||||||
|
zerodowntime,1
|
||||||
|
|
|
@ -17,3 +17,4 @@ TransactionsTest
|
||||||
UserProfileTest
|
UserProfileTest
|
||||||
org.keycloak.testsuite.admin.**
|
org.keycloak.testsuite.admin.**
|
||||||
org.keycloak.testsuite.authz.**ManagementTest
|
org.keycloak.testsuite.authz.**ManagementTest
|
||||||
|
org.keycloak.testsuite.zerodowntime.**
|
||||||
|
|
Loading…
Reference in a new issue