mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-28 04:13:22 -04:00
Add JavaDoc for most important parts of the new test framework
Closes #46170 Signed-off-by: stianst <stianst@gmail.com> Signed-off-by: Stian Thorgersen <stianst@gmail.com> Co-authored-by: Šimon Vacek <86605314+vaceksimon@users.noreply.github.com>
This commit is contained in:
parent
2d3258a209
commit
337e94d5a4
59 changed files with 762 additions and 5 deletions
|
|
@ -1,5 +1,10 @@
|
|||
package org.keycloak.testframework;
|
||||
|
||||
/**
|
||||
* FatalTestClassException is thrown when a test class contains invalid configuration, or there is a non-recoverable
|
||||
* problem when setting up managed resources for a test, for example the server can not be started. When a
|
||||
* FatalTestClassException is thrown subsequent test methods in a test class will be skipped.
|
||||
*/
|
||||
public class FatalTestClassException extends RuntimeException {
|
||||
|
||||
public FatalTestClassException(String message) {
|
||||
|
|
|
|||
|
|
@ -6,14 +6,32 @@ import java.util.Map;
|
|||
|
||||
import org.keycloak.testframework.injection.Supplier;
|
||||
|
||||
/**
|
||||
* Test framework extensions allows adding additional suppliers to the test framework
|
||||
*/
|
||||
public interface TestFrameworkExtension {
|
||||
|
||||
/**
|
||||
* List of suppliers provided by the extension
|
||||
* @return supplier list
|
||||
*/
|
||||
List<Supplier<?, ?>> suppliers();
|
||||
|
||||
/**
|
||||
* List of value types that are always created when running tests. Extensions usually does not need to implement
|
||||
* this method
|
||||
* @return the list of value types that are always requested for tests
|
||||
*/
|
||||
default List<Class<?>> alwaysEnabledValueTypes() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* List of aliases for value types. By default, {@code getSimpleName} is used as the name for a value type, implementing
|
||||
* this method allows setting custom aliases for the value type. For example the core extension has the alias
|
||||
* {@code server} for the value type {@code KeycloakServer}
|
||||
* @return map where key is the value type and value is the alias
|
||||
*/
|
||||
default Map<Class<?>, String> valueTypeAliases() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,18 +6,38 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Injects a {@link org.keycloak.admin.client.Keycloak} instance to access Keycloak Admin APIs
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectAdminClient {
|
||||
|
||||
/**
|
||||
* A ref must be set if a test requires multiple instances
|
||||
*/
|
||||
String ref() default "";
|
||||
|
||||
/**
|
||||
* Set to attach to the non-default realm
|
||||
*/
|
||||
String realmRef() default "";
|
||||
|
||||
/**
|
||||
* <code>BOOTSTRAP</code> attaches to the master realm and global test client, while <code>MANAGED_REALM</code>
|
||||
* attaches to a managed realm using the specified client or user. When using <code>MANAGED_REALM</code> either
|
||||
* client or user has to be set
|
||||
*/
|
||||
Mode mode() default Mode.BOOTSTRAP;
|
||||
|
||||
/**
|
||||
* The client to authenticate as
|
||||
*/
|
||||
String client() default "";
|
||||
|
||||
/**
|
||||
* The user to authenticate as
|
||||
*/
|
||||
String user() default "";
|
||||
|
||||
enum Mode {
|
||||
|
|
|
|||
|
|
@ -8,11 +8,21 @@ import java.lang.annotation.Target;
|
|||
|
||||
import org.keycloak.testframework.injection.LifeCycle;
|
||||
|
||||
/**
|
||||
* Injects a {@link org.keycloak.testframework.admin.AdminClientFactory} instance that can be used to create
|
||||
* {@link org.keycloak.admin.client.Keycloak} instances.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectAdminClientFactory {
|
||||
|
||||
/**
|
||||
* A ref must be set if a test requires multiple instances
|
||||
*/
|
||||
String ref() default "";
|
||||
|
||||
/**
|
||||
* Controls the lifecycle of the resource
|
||||
*/
|
||||
LifeCycle lifecycle() default LifeCycle.CLASS;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,21 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Injects a {@link org.keycloak.testframework.events.AdminEvents} instance that can be used to poll admin events from Keycloak
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectAdminEvents {
|
||||
|
||||
/**
|
||||
* A ref must be set if a test requires multiple instances
|
||||
*/
|
||||
String ref() default "";
|
||||
|
||||
/**
|
||||
* Set to attach to the non-default realm
|
||||
*/
|
||||
String realmRef() default "";
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,16 +9,28 @@ import org.keycloak.testframework.injection.LifeCycle;
|
|||
import org.keycloak.testframework.realm.ClientConfig;
|
||||
import org.keycloak.testframework.realm.DefaultClientConfig;
|
||||
|
||||
/**
|
||||
* Injects a {@link org.keycloak.testframework.realm.ManagedClient} used to create a client within the realm
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectClient {
|
||||
|
||||
/**
|
||||
* Used to define a custom configuration for the client
|
||||
*/
|
||||
Class<? extends ClientConfig> config() default DefaultClientConfig.class;
|
||||
|
||||
/**
|
||||
* Controls the lifecycle of the resource
|
||||
*/
|
||||
LifeCycle lifecycle() default LifeCycle.CLASS;
|
||||
|
||||
String ref() default "";
|
||||
|
||||
/**
|
||||
* Set to attach to the non-default realm
|
||||
*/
|
||||
String realmRef() default "";
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Injects a {@link org.keycloak.testframework.crypto.CryptoHelper} with various crypto utilities
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectCryptoHelper {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Injects dependencies into configuration classes; for example if a {@link org.keycloak.testframework.realm.ClientConfig}
|
||||
* needs to access the {@link org.keycloak.testframework.realm.ManagedRealm} to set the correct configuration.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectDependency {
|
||||
|
|
|
|||
|
|
@ -5,12 +5,21 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Injects a {@link org.keycloak.testframework.events.Events} instance that can be used to poll login events from Keycloak
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectEvents {
|
||||
|
||||
/**
|
||||
* A ref must be set if a test requires multiple instances
|
||||
*/
|
||||
String ref() default "";
|
||||
|
||||
/**
|
||||
* Set to attach to the non-default realm
|
||||
*/
|
||||
String realmRef() default "";
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,14 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Injects a {@link org.apache.http.client.HttpClient} that can be used to do HTTP requests within tests. See
|
||||
* {@link InjectSimpleHttp} as an alternative that provides a simpler API.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectHttpClient {
|
||||
|
||||
boolean followRedirects() default true;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Injects a {@link com.sun.net.httpserver.HttpServer} instance that can be used to register or unregister additional
|
||||
* contexts to the Mock HTTP server used for tests. This should usually only be used by suppliers and not directly
|
||||
* by test.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectHttpServer {
|
||||
|
|
|
|||
|
|
@ -7,9 +7,15 @@ import java.lang.annotation.Target;
|
|||
|
||||
import org.keycloak.testframework.injection.LifeCycle;
|
||||
|
||||
/**
|
||||
* Injects a {@link org.keycloak.testframework.infinispan.InfinispanServer} that starts an external Infinispan server
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface InjectInfinispanServer {
|
||||
|
||||
/**
|
||||
* Controls the lifecycle of the resource
|
||||
*/
|
||||
LifeCycle lifecycle() default LifeCycle.GLOBAL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Injects a {@link org.keycloak.testframework.server.KeycloakUrls} instance that can be used to discover various
|
||||
* endpoints offered by the Keycloak server
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectKeycloakUrls {
|
||||
|
|
|
|||
|
|
@ -13,12 +13,24 @@ import org.keycloak.testframework.realm.RealmConfig;
|
|||
@Target(ElementType.FIELD)
|
||||
public @interface InjectRealm {
|
||||
|
||||
/**
|
||||
* Used to define a custom configuration for the realm
|
||||
*/
|
||||
Class<? extends RealmConfig> config() default DefaultRealmConfig.class;
|
||||
|
||||
/**
|
||||
* Loads custom configuration from a json file on the classpath
|
||||
*/
|
||||
String fromJson() default "";
|
||||
|
||||
/**
|
||||
* Controls the lifecycle of the resource
|
||||
*/
|
||||
LifeCycle lifecycle() default LifeCycle.CLASS;
|
||||
|
||||
/**
|
||||
* A ref must be set if a test requires multiple instances
|
||||
*/
|
||||
String ref() default "";
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Injects a {@link org.keycloak.http.simple.SimpleHttp} that can be used to do HTTP requests within tests.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectSimpleHttp {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Injects a {@link org.keycloak.testframework.events.SysLogServer} that can be used to register listeners to obtain
|
||||
* logging events from Keycloak over syslog.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectSysLogServer {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ import org.keycloak.testframework.injection.LifeCycle;
|
|||
@Target(ElementType.FIELD)
|
||||
public @interface InjectTestDatabase {
|
||||
|
||||
/**
|
||||
* Controls the lifecycle of the resource
|
||||
*/
|
||||
LifeCycle lifecycle() default LifeCycle.GLOBAL;
|
||||
|
||||
Class<? extends DatabaseConfig> config() default DefaultDatabaseConfig.class;
|
||||
|
|
|
|||
|
|
@ -15,8 +15,14 @@ public @interface InjectUser {
|
|||
|
||||
Class<? extends UserConfig> config() default DefaultUserConfig.class;
|
||||
|
||||
/**
|
||||
* Controls the lifecycle of the resource
|
||||
*/
|
||||
LifeCycle lifecycle() default LifeCycle.CLASS;
|
||||
|
||||
/**
|
||||
* A ref must be set if a test requires multiple instances
|
||||
*/
|
||||
String ref() default "";
|
||||
|
||||
String realmRef() default "";
|
||||
|
|
|
|||
|
|
@ -11,11 +11,17 @@ import org.keycloak.testframework.server.KeycloakServerConfig;
|
|||
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
/**
|
||||
* Enables the test framework for tests
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@ExtendWith({KeycloakIntegrationTestExtension.class})
|
||||
public @interface KeycloakIntegrationTest {
|
||||
|
||||
/**
|
||||
* Used to define custom configuration for the Keycloak server
|
||||
*/
|
||||
Class<? extends KeycloakServerConfig> config() default DefaultKeycloakServerConfig.class;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Methods annotated with <code>@TestCleanup</code> are invoked by the test framework after all tests methods are
|
||||
* completed
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface TestCleanup {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Methods annotated with <code>@TestSetup</code> are invoked by the test framework after all managed resources are
|
||||
* injected into the test and before any test methods are executed
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface TestSetup {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import java.lang.annotation.Target;
|
|||
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
/**
|
||||
* Tests annotated with <code>@DisabledForDatabases</code> will be skipped for the specified databases
|
||||
*/
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import java.lang.annotation.Target;
|
|||
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
/**
|
||||
* Tests annotated with <code>@DisabledForServers</code> will be skipped for the specified server modes
|
||||
*/
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
package org.keycloak.testframework.database;
|
||||
|
||||
/**
|
||||
* Declarative configuration for the managed database
|
||||
*/
|
||||
public interface DatabaseConfig {
|
||||
|
||||
DatabaseConfigBuilder configure(DatabaseConfigBuilder database);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,16 +13,34 @@ public class DatabaseConfigBuilder {
|
|||
return new DatabaseConfigBuilder(rep);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a script to initialise the database on startup
|
||||
*
|
||||
* @param initScript path to init script on the classpath
|
||||
* @return
|
||||
*/
|
||||
public DatabaseConfigBuilder initScript(String initScript) {
|
||||
rep.setInitScript(initScript);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the database name to use, defaults to <code>keycloak</code>
|
||||
*
|
||||
* @param database name of the database to use
|
||||
* @return
|
||||
*/
|
||||
public DatabaseConfigBuilder database(String database) {
|
||||
rep.setDatabase(database);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent re-use of the database
|
||||
*
|
||||
* @param preventReuse set to <code>true</code> to prevent re-use of the database
|
||||
* @return
|
||||
*/
|
||||
public DatabaseConfigBuilder preventReuse(boolean preventReuse) {
|
||||
rep.setPreventReuse(preventReuse);
|
||||
return this;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,12 @@ public abstract class AbstractEvents<R> {
|
|||
this.realm = realm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the oldest event within the current window. The window is reset for each started test, which means
|
||||
* any events triggered by previous tests are ignored
|
||||
*
|
||||
* @return the oldest event with the current window
|
||||
*/
|
||||
public R poll() {
|
||||
long currentTimeOffset = getCurrentTimeOffset();
|
||||
if (timeOffset != currentTimeOffset) {
|
||||
|
|
@ -69,14 +75,25 @@ public abstract class AbstractEvents<R> {
|
|||
return events.poll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip the next event
|
||||
*/
|
||||
public void skip() {
|
||||
skip(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip the specified number of events
|
||||
*
|
||||
* @param events number of events to skip
|
||||
*/
|
||||
public void skip(int events) {
|
||||
skip += events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip all current events
|
||||
*/
|
||||
public void skipAll() {
|
||||
try {
|
||||
Thread.sleep(1); // Wait 1 ms to make sure time passes
|
||||
|
|
@ -88,6 +105,9 @@ public abstract class AbstractEvents<R> {
|
|||
events.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all events locally and remotely
|
||||
*/
|
||||
public void clear() {
|
||||
events.clear();
|
||||
clearServerEvents();
|
||||
|
|
@ -109,7 +129,7 @@ public abstract class AbstractEvents<R> {
|
|||
|
||||
protected abstract Logger getLogger();
|
||||
|
||||
public long getCurrentTime() {
|
||||
protected long getCurrentTime() {
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,12 @@ public class AdminEventAssertion {
|
|||
this.expectSuccess = expectSuccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert an expected successfull event
|
||||
*
|
||||
* @param event the event to assert
|
||||
* @return
|
||||
*/
|
||||
public static AdminEventAssertion assertSuccess(AdminEventRepresentation event) {
|
||||
Assertions.assertFalse(event.getOperationType().endsWith("_ERROR"), "Expected successful event");
|
||||
return new AdminEventAssertion(event, true)
|
||||
|
|
@ -38,6 +44,12 @@ public class AdminEventAssertion {
|
|||
.assertValidOperationType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert an expected error event
|
||||
*
|
||||
* @param event the event to assert
|
||||
* @return
|
||||
*/
|
||||
public static AdminEventAssertion assertError(AdminEventRepresentation event) {
|
||||
Assertions.assertTrue(event.getOperationType().endsWith("_ERROR"), "Expected error event");
|
||||
return new AdminEventAssertion(event, false)
|
||||
|
|
@ -45,6 +57,17 @@ public class AdminEventAssertion {
|
|||
.assertValidOperationType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert an expected successfull event, with the additional expected parameters. This method should be avoided,
|
||||
* use method chaining instead.
|
||||
*
|
||||
* @param event
|
||||
* @param operationType
|
||||
* @param resourcePath
|
||||
* @param representation
|
||||
* @param resourceType
|
||||
* @return
|
||||
*/
|
||||
public static AdminEventAssertion assertEvent(AdminEventRepresentation event, OperationType operationType, String resourcePath, Object representation, ResourceType resourceType) {
|
||||
return assertSuccess(event)
|
||||
.operationType(operationType)
|
||||
|
|
@ -53,6 +76,16 @@ public class AdminEventAssertion {
|
|||
.resourceType(resourceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert an expected successfull event, with the additional expected parameters. This method should be avoided,
|
||||
* use method chaining instead.
|
||||
*
|
||||
* @param event
|
||||
* @param operationType
|
||||
* @param resourcePath
|
||||
* @param resourceType
|
||||
* @return
|
||||
*/
|
||||
public static AdminEventAssertion assertEvent(AdminEventRepresentation event, OperationType operationType, String resourcePath, ResourceType resourceType) {
|
||||
return assertSuccess(event)
|
||||
.operationType(operationType)
|
||||
|
|
@ -60,11 +93,24 @@ public class AdminEventAssertion {
|
|||
.resourceType(resourceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the operation type of the event
|
||||
* @param operationType the expected operation type
|
||||
* @return
|
||||
*/
|
||||
public AdminEventAssertion operationType(OperationType operationType) {
|
||||
Assertions.assertEquals(operationType.name(), getOperationType());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the authentication details for the event
|
||||
*
|
||||
* @param expectedRealmId the expected authentication realmId
|
||||
* @param expectedClientId the expected authentication clientId
|
||||
* @param expectedUserId the expected authentication userId
|
||||
* @return
|
||||
*/
|
||||
public AdminEventAssertion auth(String expectedRealmId, String expectedClientId, String expectedUserId) {
|
||||
AuthDetailsRepresentation authDetails = event.getAuthDetails();
|
||||
Assertions.assertEquals(expectedRealmId, authDetails.getRealmId());
|
||||
|
|
@ -73,16 +119,34 @@ public class AdminEventAssertion {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the type of resource for the event
|
||||
*
|
||||
* @param expectedResourceType the expected resource type
|
||||
* @return
|
||||
*/
|
||||
public AdminEventAssertion resourceType(ResourceType expectedResourceType) {
|
||||
Assertions.assertEquals(expectedResourceType.name(), event.getResourceType());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the resource path for the event
|
||||
*
|
||||
* @param expectedResourcePath the expected resource path
|
||||
* @return
|
||||
*/
|
||||
public AdminEventAssertion resourcePath(String... expectedResourcePath) {
|
||||
Assertions.assertEquals(String.join("/", expectedResourcePath), event.getResourcePath());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the representation attached to the event
|
||||
*
|
||||
* @param expectedRep the expected representation
|
||||
* @return
|
||||
*/
|
||||
public AdminEventAssertion representation(Object expectedRep) {
|
||||
String actualRepresentation = event.getRepresentation();
|
||||
if (expectedRep == null) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ import org.keycloak.testframework.realm.ManagedRealm;
|
|||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Poll admin events from the Keycloak server
|
||||
*/
|
||||
public class AdminEvents extends AbstractEvents<AdminEventRepresentation> {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(AdminEvents.class);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import org.hamcrest.MatcherAssert;
|
|||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
||||
/**
|
||||
* Helper to assert login events
|
||||
*/
|
||||
public class EventAssertion {
|
||||
|
||||
private final EventRepresentation event;
|
||||
|
|
@ -18,51 +21,109 @@ public class EventAssertion {
|
|||
this.event = event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert an expected successfull event
|
||||
*
|
||||
* @param event the event to assert
|
||||
* @return
|
||||
*/
|
||||
public static EventAssertion assertSuccess(EventRepresentation event) {
|
||||
Assertions.assertFalse(event.getType().endsWith("_ERROR"), "Expected successful event");
|
||||
return new EventAssertion(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert an expected error event
|
||||
*
|
||||
* @param event the event to assert
|
||||
* @return
|
||||
*/
|
||||
public static EventAssertion assertError(EventRepresentation event) {
|
||||
Assertions.assertTrue(event.getType().endsWith("_ERROR"), "Expected error event");
|
||||
return new EventAssertion(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the error message
|
||||
*
|
||||
* @param error the expected error message
|
||||
* @return
|
||||
*/
|
||||
public EventAssertion error(String error) {
|
||||
Assertions.assertEquals(error, event.getError());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the type of the event
|
||||
*
|
||||
* @param type the expected type of the event
|
||||
* @return
|
||||
*/
|
||||
public EventAssertion type(EventType type) {
|
||||
Assertions.assertEquals(type, EventType.valueOf(event.getType()));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the event has a sessionId set
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public EventAssertion hasSessionId() {
|
||||
MatcherAssert.assertThat(event.getSessionId(), EventMatchers.isSessionId());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the event has the <code>code_id</code> details set
|
||||
* @return
|
||||
*/
|
||||
public EventAssertion isCodeId() {
|
||||
MatcherAssert.assertThat(event.getDetails().get(Details.CODE_ID), EventMatchers.isCodeId());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the clientId for the event
|
||||
*
|
||||
* @param clientId the expected clientId
|
||||
* @return
|
||||
*/
|
||||
public EventAssertion clientId(String clientId) {
|
||||
Assertions.assertEquals(clientId, event.getClientId());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the sessionId for the event
|
||||
*
|
||||
* @param sessionId the expected sessionId
|
||||
* @return
|
||||
*/
|
||||
public EventAssertion sessionId(String sessionId) {
|
||||
Assertions.assertEquals(sessionId, event.getSessionId());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the userId (sub) of the event
|
||||
*
|
||||
* @param userId the expected userId
|
||||
* @return
|
||||
*/
|
||||
public EventAssertion userId(String userId) {
|
||||
Assertions.assertEquals(userId, event.getUserId());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the event has an entry in the details map with the specified key and value
|
||||
*
|
||||
* @param key the expected details key
|
||||
* @param value the expected details value
|
||||
* @return
|
||||
*/
|
||||
public EventAssertion details(String key, String value) {
|
||||
if (value != null) {
|
||||
MatcherAssert.assertThat(event.getDetails(), Matchers.hasEntry(key, value));
|
||||
|
|
@ -72,6 +133,12 @@ public class EventAssertion {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the event details map does not contain the specified keys
|
||||
*
|
||||
* @param keys the list of keys that are not expected in the details map
|
||||
* @return
|
||||
*/
|
||||
public EventAssertion withoutDetails(String... keys) {
|
||||
for (String key : keys) {
|
||||
MatcherAssert.assertThat(event.getDetails(), Matchers.not(Matchers.hasKey(key)));
|
||||
|
|
|
|||
|
|
@ -8,23 +8,40 @@ import org.hamcrest.Matcher;
|
|||
import org.hamcrest.Matchers;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
|
||||
/**
|
||||
* Matchers to assert event properties
|
||||
*/
|
||||
public class EventMatchers {
|
||||
|
||||
/**
|
||||
* Check if value is a UUID
|
||||
* @return
|
||||
*/
|
||||
public static Matcher<String> isUUID() {
|
||||
return new UUIDMatcher();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if value is a code_id
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Matcher<String> isCodeId() {
|
||||
// Make the tests pass with the old and the new encoding of code IDs
|
||||
return Matchers.anyOf(isBase64WithAtLeast128Bits(), isUUID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if value is a session_id
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Matcher<String> isSessionId() {
|
||||
// Make the tests pass with the old and the new encoding of sessions
|
||||
return Matchers.anyOf(isBase64WithAtLeast128Bits(), isUUID());
|
||||
}
|
||||
|
||||
public static Matcher<String> isBase64WithAtLeast128Bits() {
|
||||
private static Matcher<String> isBase64WithAtLeast128Bits() {
|
||||
return new TypeSafeMatcher<>() {
|
||||
private static final Pattern BASE64 = Pattern.compile("[-A-Za-z0-9+/_]*");
|
||||
|
||||
|
|
@ -43,7 +60,7 @@ public class EventMatchers {
|
|||
private EventMatchers() {
|
||||
}
|
||||
|
||||
public static class UUIDMatcher extends TypeSafeMatcher<String> {
|
||||
private static class UUIDMatcher extends TypeSafeMatcher<String> {
|
||||
|
||||
@Override
|
||||
protected boolean matchesSafely(String item) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package org.keycloak.testframework.https;
|
||||
|
||||
/**
|
||||
* Declarative configuration for managed certificates
|
||||
*/
|
||||
public interface CertificatesConfig {
|
||||
|
||||
CertificatesConfigBuilder configure(CertificatesConfigBuilder config);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,12 @@ public class CertificatesConfigBuilder {
|
|||
public CertificatesConfigBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the specified keystore format
|
||||
*
|
||||
* @param keystoreFormat the keystore format to use
|
||||
* @return
|
||||
*/
|
||||
public CertificatesConfigBuilder keystoreFormat(KeystoreUtil.KeystoreFormat keystoreFormat) {
|
||||
this.keystoreFormat = keystoreFormat;
|
||||
return this;
|
||||
|
|
@ -20,6 +26,12 @@ public class CertificatesConfigBuilder {
|
|||
return this.keystoreFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable TLS
|
||||
*
|
||||
* @param tlsEnabled <code>true</code> if tls should be enabled
|
||||
* @return
|
||||
*/
|
||||
public CertificatesConfigBuilder tlsEnabled(boolean tlsEnabled) {
|
||||
this.tlsEnabled = tlsEnabled;
|
||||
return this;
|
||||
|
|
@ -29,6 +41,12 @@ public class CertificatesConfigBuilder {
|
|||
return tlsEnabled || mTlsEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable mTLS authentication between Keycloak and clients
|
||||
*
|
||||
* @param mTlsEnabled <code>true</code> if mTLS should be enabled
|
||||
* @return
|
||||
*/
|
||||
public CertificatesConfigBuilder mTlsEnabled(boolean mTlsEnabled) {
|
||||
this.mTlsEnabled = mTlsEnabled;
|
||||
return this;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ import org.keycloak.crypto.def.DefaultCryptoProvider;
|
|||
import org.apache.http.conn.ssl.TrustAllStrategy;
|
||||
import org.apache.http.ssl.SSLContextBuilder;
|
||||
|
||||
/**
|
||||
* Utilities for Keycloak server and clients to obtain keystore and truststores with the managed certificates
|
||||
*/
|
||||
public class ManagedCertificates {
|
||||
|
||||
private final static Path KEYSTORES_DIR = Path.of(System.getProperty("java.io.tmpdir"));
|
||||
|
|
@ -72,35 +75,68 @@ public class ManagedCertificates {
|
|||
clientSslContext = tlsEnabled ? createClientSSLContext() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The path of the generated Keycloak server keystore containing the private certificate for the Keycloak server
|
||||
*
|
||||
* @return path to keystore
|
||||
*/
|
||||
public String getServerKeyStorePath() {
|
||||
return tlsEnabled ? serverKeystorePath.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The password used for the keystore
|
||||
*
|
||||
* @return keystore password
|
||||
*/
|
||||
public String getServerKeyStorePassword() {
|
||||
return tlsEnabled ? STORE_PASSWORD : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The path of the generated Keycloak server truststore containing public certificates for clients
|
||||
* @return
|
||||
*/
|
||||
public String getServerTrustStorePath() {
|
||||
return mTlsEnabled ? serverTruststorePath.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The password used for the truststore
|
||||
*
|
||||
* @return truststore password
|
||||
*/
|
||||
public String getServerTrustStorePassword() {
|
||||
return mTlsEnabled ? STORE_PASSWORD : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SSL context configured with the client truststore
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public SSLContext getClientSSLContext() {
|
||||
return clientSslContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if TLS is enabled
|
||||
*
|
||||
* @return <code>true</code> if TLS is enabled
|
||||
*/
|
||||
public boolean isTlsEnabled() {
|
||||
return tlsEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if mTLS is enabled
|
||||
*
|
||||
* @return <code>true</code> if mTLS is enabled
|
||||
*/
|
||||
public boolean isMTlsEnabled() {
|
||||
return mTlsEnabled;
|
||||
}
|
||||
|
||||
|
||||
private SSLContext createClientSSLContext() {
|
||||
try {
|
||||
SSLContextBuilder sslContextBuilder = SSLContextBuilder.create()
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ public abstract class ManagedTestResource {
|
|||
return dirty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marking the resource as dirty will result in the test framework re-creating the resource after the test
|
||||
* has executed
|
||||
*/
|
||||
public void dirty() {
|
||||
this.dirty = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package org.keycloak.testframework.realm;
|
||||
|
||||
/**
|
||||
* Declarative configuration for managed clients
|
||||
*/
|
||||
public interface ClientConfig {
|
||||
|
||||
ClientConfigBuilder configure(ClientConfigBuilder client);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ import org.keycloak.admin.client.resource.ClientResource;
|
|||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.testframework.injection.ManagedTestResource;
|
||||
|
||||
/**
|
||||
* Utilities to work with managed clients
|
||||
*/
|
||||
public class ManagedClient extends ManagedTestResource {
|
||||
|
||||
private final ClientRepresentation createdRepresentation;
|
||||
|
|
@ -16,22 +19,46 @@ public class ManagedClient extends ManagedTestResource {
|
|||
this.clientResource = clientResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* The UUID of the client
|
||||
* @return client UUID
|
||||
*/
|
||||
public String getId() {
|
||||
return createdRepresentation.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* The clientId of the client
|
||||
* @return client clientId
|
||||
*/
|
||||
public String getClientId() {
|
||||
return createdRepresentation.getClientId();
|
||||
}
|
||||
|
||||
/**
|
||||
* The client secret if set
|
||||
* @return client secret
|
||||
*/
|
||||
public String getSecret() {
|
||||
return createdRepresentation.getSecret();
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin client resource for the client to view or update the configuration of the client. Updates should in general
|
||||
* not be done directly through the client resource as it will leave the client in a unexpected state for sub-sequent
|
||||
* tests
|
||||
*
|
||||
* @return client resource
|
||||
*/
|
||||
public ClientResource admin() {
|
||||
return clientResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the client within a test with automatic reset to the original configuration after the test has completed
|
||||
*
|
||||
* @param updates the update to the client
|
||||
*/
|
||||
public void updateWithCleanup(ManagedClient.ClientUpdate... updates) {
|
||||
ClientRepresentation rep = admin().toRepresentation();
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ public class ManagedClientCleanup {
|
|||
|
||||
private final List<ClientCleanup> cleanupTasks = new LinkedList<>();
|
||||
|
||||
/**
|
||||
* Add a cleanup to be done for the client after the test is completed
|
||||
*
|
||||
* @param clientCleanup the required cleanup
|
||||
* @return
|
||||
*/
|
||||
public ManagedClientCleanup add(ClientCleanup clientCleanup) {
|
||||
this.cleanupTasks.add(clientCleanup);
|
||||
return this;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ import org.keycloak.testframework.util.ApiUtil;
|
|||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
||||
/**
|
||||
* Utilities to work with managed realms
|
||||
*/
|
||||
public class ManagedRealm extends ManagedTestResource {
|
||||
|
||||
private final String baseUrl;
|
||||
|
|
@ -29,10 +32,20 @@ public class ManagedRealm extends ManagedTestResource {
|
|||
this.realmResource = realmResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* The base URL of the realm (for example <code>http://localhost:8080/realms/myrealm</code>)
|
||||
*
|
||||
* @return the realm base URL
|
||||
*/
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* The UUID of the realm
|
||||
*
|
||||
* @return realm UUID
|
||||
*/
|
||||
public String getId() {
|
||||
if (realmId == null && createdRepresentation.getId() != null) {
|
||||
realmId = createdRepresentation.getId();
|
||||
|
|
@ -42,18 +55,40 @@ public class ManagedRealm extends ManagedTestResource {
|
|||
return realmId;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the realm
|
||||
*
|
||||
* @return realm name
|
||||
*/
|
||||
public String getName() {
|
||||
return createdRepresentation.getRealm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin realm resource for the realm to view or update the configuration of the realm. Updates should in general
|
||||
* not be done directly through the realm resource as it will leave the realm in an unexpected state for sub-sequent
|
||||
* tests
|
||||
*
|
||||
* @return realm resource
|
||||
*/
|
||||
public RealmResource admin() {
|
||||
return realmResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* The representation used to create the realm
|
||||
*
|
||||
* @return realm representation
|
||||
*/
|
||||
public RealmRepresentation getCreatedRepresentation() {
|
||||
return createdRepresentation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the realm within a test with automatic reset to the original configuration after the test has completed
|
||||
*
|
||||
* @param updates the updates to the realm
|
||||
*/
|
||||
public void updateWithCleanup(RealmUpdate... updates) {
|
||||
RealmRepresentation rep = admin().toRepresentation();
|
||||
cleanup().resetToOriginalRepresentation(rep);
|
||||
|
|
@ -66,12 +101,23 @@ public class ManagedRealm extends ManagedTestResource {
|
|||
admin().update(configBuilder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a user to the realm, which is automatically removed once the test is completed
|
||||
*
|
||||
* @param user the user to add
|
||||
*/
|
||||
public void addUser(UserConfigBuilder user) {
|
||||
UserRepresentation rep = user.build();
|
||||
String id = ApiUtil.getCreatedId(realmResource.users().create(rep));
|
||||
cleanup().add(r -> r.users().get(id).remove());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a user within the realm, which is automatically reset once the test is completed
|
||||
*
|
||||
* @param username the username of the user to update
|
||||
* @param update the update to perform on the user
|
||||
*/
|
||||
public void updateUser(String username, UserConfigBuilder.UserUpdate update) {
|
||||
List<UserRepresentation> result = realmResource.users().search(username);
|
||||
Assertions.assertEquals(1, result.size());
|
||||
|
|
@ -84,6 +130,12 @@ public class ManagedRealm extends ManagedTestResource {
|
|||
cleanup().add(r -> r.users().get(original.getId()).update(original));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an identity provider within the realm, which is automatically reset once the test is completed
|
||||
*
|
||||
* @param alias the alias of the identity provider to update
|
||||
* @param update the update to perform on the identity provider
|
||||
*/
|
||||
public void updateIdentityProvider(String alias, IdentityProviderUpdate update) {
|
||||
IdentityProviderResource resource = realmResource.identityProviders().get(alias);
|
||||
|
||||
|
|
@ -95,6 +147,12 @@ public class ManagedRealm extends ManagedTestResource {
|
|||
cleanup().add(r -> r.identityProviders().get(alias).update(original));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a component within the realm, which is automatically reset once the test is completed
|
||||
*
|
||||
* @param id the id of the component to update
|
||||
* @param update the update to perform on the component
|
||||
*/
|
||||
public void updateComponent(String id, ComponentUpdate update) {
|
||||
ComponentResource componentResource = realmResource.components().component(id);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ public class ManagedRealmCleanup {
|
|||
|
||||
private final List<RealmCleanup> cleanupTasks = new LinkedList<>();
|
||||
|
||||
/**
|
||||
* Add a cleanup task to perform on the realm once the test has completed
|
||||
*
|
||||
* @param realmCleanup the cleanup to perform on the realm
|
||||
* @return
|
||||
*/
|
||||
public ManagedRealmCleanup add(RealmCleanup realmCleanup) {
|
||||
this.cleanupTasks.add(realmCleanup);
|
||||
return this;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package org.keycloak.testframework.realm;
|
||||
|
||||
/**
|
||||
* Declarative configuration for managed realms
|
||||
*/
|
||||
public interface RealmConfig {
|
||||
|
||||
RealmConfigBuilder configure(RealmConfigBuilder realm);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package org.keycloak.testframework.realm;
|
||||
|
||||
/**
|
||||
* Declarative configuration for managed users
|
||||
*/
|
||||
public interface UserConfig {
|
||||
|
||||
UserConfigBuilder configure(UserConfigBuilder user);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package org.keycloak.testframework.server;
|
||||
|
||||
/**
|
||||
* Declarative configuration for the managed Keycloak server
|
||||
*/
|
||||
public interface KeycloakServerConfig {
|
||||
|
||||
KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder config);
|
||||
|
|
|
|||
|
|
@ -38,21 +38,48 @@ public class KeycloakServerConfigBuilder {
|
|||
return new KeycloakServerConfigBuilder("start-dev");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the client id and secret to use for bootstrapping configuration
|
||||
*
|
||||
* @param clientId the client id
|
||||
* @param clientSecret the client secret
|
||||
* @return
|
||||
*/
|
||||
public KeycloakServerConfigBuilder bootstrapAdminClient(String clientId, String clientSecret) {
|
||||
return option("bootstrap-admin-client-id", clientId)
|
||||
.option("bootstrap-admin-client-secret", clientSecret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the username and password to use for bootstrapping configuration
|
||||
*
|
||||
* @param username the username
|
||||
* @param password the secret
|
||||
* @return
|
||||
*/
|
||||
public KeycloakServerConfigBuilder bootstrapAdminUser(String username, String password) {
|
||||
return option("bootstrap-admin-username", username)
|
||||
.option("bootstrap-admin-password", password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure if local caches or clustered caches should be used. Using local caches results in a faster startup
|
||||
* time
|
||||
*
|
||||
* @param cacheType
|
||||
* @return
|
||||
*/
|
||||
public KeycloakServerConfigBuilder cache(CacheType cacheType) {
|
||||
this.cacheType = cacheType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a managed external Infinispan server
|
||||
*
|
||||
* @param enabled
|
||||
* @return
|
||||
*/
|
||||
public KeycloakServerConfigBuilder externalInfinispanEnabled(boolean enabled) {
|
||||
if (enabled) {
|
||||
this.externalInfinispan = true;
|
||||
|
|
@ -68,35 +95,82 @@ public class KeycloakServerConfigBuilder {
|
|||
return this.externalInfinispan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure logging
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public LogBuilder log() {
|
||||
return log;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the specified features. In most cases used to enable features that are not enabled by default
|
||||
*
|
||||
* @param features the features to enable
|
||||
* @return
|
||||
*/
|
||||
public KeycloakServerConfigBuilder features(Profile.Feature... features) {
|
||||
this.features.addAll(toFeatureStrings(features));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the specified features. In most cases used to disable features that are enabled by default
|
||||
*
|
||||
* @param features the features to disable
|
||||
* @return
|
||||
*/
|
||||
public KeycloakServerConfigBuilder featuresDisabled(Profile.Feature... features) {
|
||||
this.featuresDisabled.addAll(toFeatureStrings(features));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set multiple CLI options
|
||||
*
|
||||
* @param options
|
||||
* @return
|
||||
*/
|
||||
public KeycloakServerConfigBuilder options(Map<String, String> options) {
|
||||
this.options.putAll(options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the specified CLI option
|
||||
*
|
||||
* @param key the key of the option
|
||||
* @param value the value of the option
|
||||
* @return
|
||||
*/
|
||||
public KeycloakServerConfigBuilder option(String key, String value) {
|
||||
options.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an SPI configuration option
|
||||
*
|
||||
* @param spi the name of the SPI
|
||||
* @param provider the name of the provider
|
||||
* @param key the name of the option
|
||||
* @param value the value to set
|
||||
* @return
|
||||
*/
|
||||
public KeycloakServerConfigBuilder spiOption(String spi, String provider, String key, String value) {
|
||||
options.put(String.format(SPI_OPTION, spi, provider, key), value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deploy a dependency to the server by specifying the Maven groupId and artifactId. The version is resolved from
|
||||
* the project pom files
|
||||
*
|
||||
* @param groupId the Maven groupId of the dependency
|
||||
* @param artifactId the Maven artifactId of the dependency
|
||||
* @return
|
||||
*/
|
||||
public KeycloakServerConfigBuilder dependency(String groupId, String artifactId) {
|
||||
dependencies.add(new DependencyBuilder().setGroupId(groupId).setArtifactId(artifactId).build());
|
||||
return this;
|
||||
|
|
|
|||
|
|
@ -16,38 +16,84 @@ public class KeycloakUrls {
|
|||
this.managementBaseUrl = managementBaseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* The base string representation of the URL of the Keycloak server (for example <code>http://localhost:8080</code>)
|
||||
*
|
||||
* @return the server base URL as a string
|
||||
*/
|
||||
public String getBase() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The URL of the Keycloak server (for example <code>http://localhost:8080</code>)
|
||||
*
|
||||
* @return the server base URL
|
||||
*/
|
||||
public URL getBaseUrl() {
|
||||
return toUrl(getBase());
|
||||
}
|
||||
|
||||
/**
|
||||
* The string representation of the base URL of the master realm
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getMasterRealm() {
|
||||
return baseUrl + "/realms/master";
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL of the master realm
|
||||
*
|
||||
* @return master realm URL
|
||||
*/
|
||||
public URL getMasterRealmUrl() {
|
||||
return toUrl(getMasterRealm());
|
||||
}
|
||||
|
||||
/**
|
||||
* The string representation of the URL of Admin endpoints
|
||||
*
|
||||
* @return admin URL as a string
|
||||
*/
|
||||
public String getAdmin() {
|
||||
return baseUrl + "/admin";
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL of Admin endpoints
|
||||
*
|
||||
* @return admin URL
|
||||
*/
|
||||
public URL getAdminUrl() {
|
||||
return toUrl(getAdmin());
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder to resolve paths from the Keycloak server base URL
|
||||
*
|
||||
* @return base URL builder
|
||||
*/
|
||||
public KeycloakUriBuilder getBaseBuilder() {
|
||||
return toBuilder(getBase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder to resolve paths from the admin URL
|
||||
*
|
||||
* @return admin URL builder
|
||||
*/
|
||||
public KeycloakUriBuilder getAdminBuilder() {
|
||||
return toBuilder(getAdmin());
|
||||
}
|
||||
|
||||
/**
|
||||
* String representation of the URL of the metrics endpoint
|
||||
*
|
||||
* @return metrics endpoint
|
||||
*/
|
||||
public String getMetric() {
|
||||
return managementBaseUrl + "/metrics";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,20 @@ import jakarta.ws.rs.core.Response;
|
|||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
||||
/**
|
||||
* Utilities for the Keycloak Java Admin client
|
||||
*/
|
||||
public class ApiUtil {
|
||||
|
||||
/**
|
||||
* Several POST endpoints in Keycloak Admin API does not return the created resource in the response; but rather
|
||||
* returns a location header instead, making it harder to get the generated ID of a newly created resource. This
|
||||
* method parses the location header and returns the ID of the created resource, as well as closing the JAX-RS
|
||||
* response.
|
||||
*
|
||||
* @param response the response from a POST request, for example creating a new user in a realm
|
||||
* @return the ID of the created resource, for example the UUID of a new user
|
||||
*/
|
||||
public static String getCreatedId(Response response) {
|
||||
try (response) {
|
||||
Assertions.assertEquals(201, response.getStatus());
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ import com.icegreen.greenmail.user.TokenValidator;
|
|||
import com.icegreen.greenmail.util.GreenMail;
|
||||
import com.icegreen.greenmail.util.ServerSetup;
|
||||
|
||||
/**
|
||||
* Retrieve emails sent by the Keycloak server. Received emails are reset when a test is executed, which means
|
||||
* only emails sent during a test was executed are returned.
|
||||
*/
|
||||
public class MailServer extends ManagedTestResource {
|
||||
|
||||
private final GreenMail greenMail;
|
||||
|
|
@ -36,19 +40,41 @@ public class MailServer extends ManagedTestResource {
|
|||
((com.icegreen.greenmail.user.UserImpl)user).setTokenValidator(validator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all received emails
|
||||
*
|
||||
* @return list of received emails
|
||||
*/
|
||||
public MimeMessage[] getReceivedMessages() {
|
||||
return greenMail.getReceivedMessages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the last received email
|
||||
*
|
||||
* @return the last received email
|
||||
*/
|
||||
public MimeMessage getLastReceivedMessage() {
|
||||
MimeMessage[] receivedMessages = greenMail.getReceivedMessages();
|
||||
return receivedMessages != null && receivedMessages.length > 0 ? receivedMessages[receivedMessages.length - 1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the specified time to receive the specified number of emails
|
||||
*
|
||||
* @param timeout the time to wait for emails to be received
|
||||
* @param emailCount the number of emails to wait for
|
||||
* @return
|
||||
*/
|
||||
public boolean waitForIncomingEmail(long timeout, int emailCount) {
|
||||
return greenMail.waitForIncomingEmail(timeout, emailCount);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param emailCount
|
||||
* @return
|
||||
*/
|
||||
public boolean waitForIncomingEmail(int emailCount) {
|
||||
return greenMail.waitForIncomingEmail(emailCount);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Injects a {@link org.keycloak.testframework.mail.MailServer} to receive emails sent by the Keycloak server
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectMailServer { }
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ import org.keycloak.testsuite.util.oauth.OAuthClientConfig;
|
|||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.openqa.selenium.support.PageFactory;
|
||||
|
||||
/**
|
||||
* OAuth client to send OAuth request and handle callbacks
|
||||
*/
|
||||
public class OAuthClient extends AbstractOAuthClient<OAuthClient> {
|
||||
|
||||
private final ManagedWebDriver managedWebDriver;
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ import com.sun.net.httpserver.HttpServer;
|
|||
|
||||
import static org.keycloak.common.crypto.CryptoConstants.EC_KEY_SECP256R1;
|
||||
|
||||
/**
|
||||
* Mock identity provider that can be used to test various brokering flows
|
||||
*/
|
||||
public class OAuthIdentityProvider {
|
||||
|
||||
private final HttpServer httpServer;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ package org.keycloak.testframework.oauth;
|
|||
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
|
||||
/**
|
||||
* Mock OAuth client exposed on an HTTP server so Keycloak can send callbacks to the client
|
||||
*/
|
||||
public class TestApp {
|
||||
|
||||
public static final String OAUTH_CALLBACK_PATH = "/callback/oauth";
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ import org.keycloak.testframework.injection.LifeCycle;
|
|||
import org.keycloak.testframework.oauth.DefaultOAuthClientConfiguration;
|
||||
import org.keycloak.testframework.realm.ClientConfig;
|
||||
|
||||
/**
|
||||
* Injects a {@link org.keycloak.testframework.oauth.OAuthClient} that can be used to send OAuth request to Keycloak
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectOAuthClient {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ import org.keycloak.testframework.injection.LifeCycle;
|
|||
import org.keycloak.testframework.oauth.DefaultOAuthIdentityProviderConfig;
|
||||
import org.keycloak.testframework.oauth.OAuthIdentityProviderConfig;
|
||||
|
||||
/**
|
||||
* Injects a {@link org.keycloak.testframework.oauth.OAuthIdentityProvider} that can be used to mock an identity
|
||||
* provider
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectOAuthIdentityProvider {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Injects a {@link org.keycloak.testframework.oauth.TestApp} that can be used to mock an OAuth client
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectTestApp {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ import java.lang.annotation.Target;
|
|||
|
||||
import org.keycloak.testframework.injection.LifeCycle;
|
||||
|
||||
/**
|
||||
* Injects a {@link RunOnServerClient} to execute code within the Keycloak server. Classes are serialized and sent
|
||||
* to the Keycloak server when needed
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectRunOnServer {
|
||||
|
|
|
|||
|
|
@ -31,10 +31,26 @@ public class RunOnServerClient {
|
|||
this.executionId = executionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve some value from the Keycloak server using the specified wrapper
|
||||
*
|
||||
* @param wrapper the wrapper containing the code and return type
|
||||
* @return the value
|
||||
* @param <T> the return type
|
||||
* @throws RunOnServerException
|
||||
*/
|
||||
public <T> T fetch(FetchOnServerWrapper<T> wrapper) throws RunOnServerException {
|
||||
return fetch(wrapper.getRunOnServer(), wrapper.getResultClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve some value from the Keycloak server using the specified function
|
||||
* @param function the function to execute
|
||||
* @param clazz the return type
|
||||
* @return the value
|
||||
* @param <T> the return type
|
||||
* @throws RunOnServerException
|
||||
*/
|
||||
public <T> T fetch(FetchOnServer function, Class<T> clazz) throws RunOnServerException {
|
||||
try {
|
||||
String s = fetchString(function);
|
||||
|
|
@ -44,6 +60,12 @@ public class RunOnServerClient {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a string value from the Keycloak server using the specified function
|
||||
* @param function the function to execute
|
||||
* @return the value
|
||||
* @throws RunOnServerException
|
||||
*/
|
||||
public String fetchString(FetchOnServer function) throws RunOnServerException {
|
||||
String encoded = SerializationUtil.encode(function);
|
||||
|
||||
|
|
@ -60,6 +82,12 @@ public class RunOnServerClient {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute code on the Keycloak server, including assertions to verify values on the server side
|
||||
*
|
||||
* @param function the function to execute
|
||||
* @throws RunOnServerException
|
||||
*/
|
||||
public void run(RunOnServer function) throws RunOnServerException {
|
||||
String encoded = SerializationUtil.encode(function);
|
||||
|
||||
|
|
@ -74,7 +102,7 @@ public class RunOnServerClient {
|
|||
}
|
||||
}
|
||||
|
||||
public String runOnServer(String encoded) throws RunOnServerException {
|
||||
private String runOnServer(String encoded) throws RunOnServerException {
|
||||
try {
|
||||
HttpPost request = new HttpPost(url + "?executionId=" + executionId);
|
||||
request.setHeader("Content-type", "text/plain;charset=utf-8");
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ import java.lang.annotation.Target;
|
|||
|
||||
import org.keycloak.testframework.injection.LifeCycle;
|
||||
|
||||
/**
|
||||
* Injects a {@link TimeOffSet} to change the timeoffset on the Keycloak server
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectTimeOffSet {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,12 @@ public class TimeOffSet {
|
|||
currentOffset = initOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timeoffset on the Keycloak server
|
||||
*
|
||||
* @param offset the timeoffset
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public void set(int offset) throws RuntimeException {
|
||||
currentOffset = offset;
|
||||
|
||||
|
|
@ -57,6 +63,11 @@ public class TimeOffSet {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrive the current time offset
|
||||
*
|
||||
* @return the time offset
|
||||
*/
|
||||
public int get() {
|
||||
return currentOffset;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Injects an implementation of {@link org.keycloak.testframework.ui.page.AbstractPage} to interact with HTML pages
|
||||
* published by the Keycloak server
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectPage {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Inject a {@link org.keycloak.testframework.ui.webdriver.ManagedWebDriver} to interact directly with the web driver.
|
||||
* When possible it is recommended to use pages instead of directly accessing the web driver.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface InjectWebDriver { }
|
||||
|
|
|
|||
Loading…
Reference in a new issue