From deae913b1c6ece0020c5cd1af965eca15649b5d4 Mon Sep 17 00:00:00 2001 From: admitrov Date: Wed, 6 May 2026 18:40:10 +0100 Subject: [PATCH 01/25] Use AzuriteContainer from testcontainers-azure module Replace GenericContainer with AzuriteContainer and add testcontainers-azure dependency. Update container initialization to use AzuriteContainer API with command modifier for skipApiVersionCheck flag. --- embedded-azurite/pom.xml | 4 ++ ...EmbeddedAzuriteBootstrapConfiguration.java | 38 +++++++++---------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/embedded-azurite/pom.xml b/embedded-azurite/pom.xml index 5e009a194..1aab32527 100644 --- a/embedded-azurite/pom.xml +++ b/embedded-azurite/pom.xml @@ -14,6 +14,10 @@ embedded-azurite + + org.testcontainers + testcontainers-azure + com.azure.spring spring-cloud-azure-starter-storage-blob diff --git a/embedded-azurite/src/main/java/com/playtika/testcontainer/azurite/EmbeddedAzuriteBootstrapConfiguration.java b/embedded-azurite/src/main/java/com/playtika/testcontainer/azurite/EmbeddedAzuriteBootstrapConfiguration.java index 0ae914002..cf66f506c 100644 --- a/embedded-azurite/src/main/java/com/playtika/testcontainer/azurite/EmbeddedAzuriteBootstrapConfiguration.java +++ b/embedded-azurite/src/main/java/com/playtika/testcontainer/azurite/EmbeddedAzuriteBootstrapConfiguration.java @@ -16,11 +16,14 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; -import org.testcontainers.containers.GenericContainer; +import org.testcontainers.azure.AzuriteContainer; import org.testcontainers.containers.Network; import org.testcontainers.toxiproxy.ToxiproxyContainer; +import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashMap; +import java.util.List; import java.util.Optional; import static com.playtika.testcontainer.azurite.AzuriteProperties.AZURITE_BEAN_NAME; @@ -40,7 +43,7 @@ public class EmbeddedAzuriteBootstrapConfiguration { @ConditionalOnToxiProxyEnabled(module = "azurite") ToxiproxyClientProxy azuriteBlobContainerProxy(ToxiproxyClient toxiproxyClient, ToxiproxyContainer toxiproxyContainer, - @Qualifier(AZURITE_BEAN_NAME) GenericContainer azurite, + @Qualifier(AZURITE_BEAN_NAME) AzuriteContainer azurite, AzuriteProperties properties, ConfigurableEnvironment environment) { ToxiproxyClientProxy proxy = ToxiproxyHelper.createProxy( @@ -59,7 +62,7 @@ ToxiproxyClientProxy azuriteBlobContainerProxy(ToxiproxyClient toxiproxyClient, @ConditionalOnToxiProxyEnabled(module = "azurite") ToxiproxyClientProxy azuriteQueueContainerProxy(ToxiproxyClient toxiproxyClient, ToxiproxyContainer toxiproxyContainer, - @Qualifier(AZURITE_BEAN_NAME) GenericContainer azurite, + @Qualifier(AZURITE_BEAN_NAME) AzuriteContainer azurite, AzuriteProperties properties, ConfigurableEnvironment environment) { ToxiproxyClientProxy proxy = ToxiproxyHelper.createProxy( @@ -78,7 +81,7 @@ ToxiproxyClientProxy azuriteQueueContainerProxy(ToxiproxyClient toxiproxyClient, @ConditionalOnToxiProxyEnabled(module = "azurite") ToxiproxyClientProxy azuriteTableContainerProxy(ToxiproxyClient toxiproxyClient, ToxiproxyContainer toxiproxyContainer, - @Qualifier(AZURITE_BEAN_NAME) GenericContainer azurite, + @Qualifier(AZURITE_BEAN_NAME) AzuriteContainer azurite, AzuriteProperties properties, ConfigurableEnvironment environment) { ToxiproxyClientProxy proxy = ToxiproxyHelper.createProxy( @@ -94,30 +97,25 @@ ToxiproxyClientProxy azuriteTableContainerProxy(ToxiproxyClient toxiproxyClient, } @Bean(name = AZURITE_BEAN_NAME, destroyMethod = "stop") - public GenericContainer azurite(ConfigurableEnvironment environment, - AzuriteProperties properties, - Optional network) { - GenericContainer azuriteContainer = new GenericContainer<>(ContainerUtils.getDockerImageName(properties)) - .withExposedPorts(properties.getBlobStoragePort(), properties.getQueueStoragePort(), properties.getTableStoragePort()) + public AzuriteContainer azurite(ConfigurableEnvironment environment, + AzuriteProperties properties, + Optional network) { + AzuriteContainer azuriteContainer = new AzuriteContainer(ContainerUtils.getDockerImageName(properties)) .withNetworkAliases(AZURITE_BLOB_NETWORK_ALIAS) - .withCommand("azurite", - "-l", "/data", - "--blobHost", "0.0.0.0", - "--blobPort", String.valueOf(properties.getBlobStoragePort()), - "--queueHost", "0.0.0.0", - "--queuePort", String.valueOf(properties.getQueueStoragePort()), - "--tableHost", "0.0.0.0", - "--tablePort", String.valueOf(properties.getTableStoragePort()), - "--skipApiVersionCheck"); + .withCreateContainerCmdModifier(cmd -> { + List args = new ArrayList<>(Arrays.asList(cmd.getCmd())); + args.add("--skipApiVersionCheck"); + cmd.withCmd(args); + }); network.ifPresent(azuriteContainer::withNetwork); - configureCommonsAndStart(azuriteContainer, properties, log); + azuriteContainer = (AzuriteContainer) configureCommonsAndStart(azuriteContainer, properties, log); registerEnvironment(azuriteContainer, environment, properties); return azuriteContainer; } - private void registerEnvironment(GenericContainer azurite, + private void registerEnvironment(AzuriteContainer azurite, ConfigurableEnvironment environment, AzuriteProperties properties) { From 442d698f7a3477e2eea6c40f6947589c4c116567 Mon Sep 17 00:00:00 2001 From: admitrov Date: Wed, 6 May 2026 19:10:41 +0100 Subject: [PATCH 02/25] Migrate embedded-keycloak to use dasniko/testcontainers-keycloak library Replace custom KeycloakContainer and KeycloakContainerFactory implementations with dasniko/testcontainers-keycloak dependency. Consolidate container configuration logic into EmbeddedKeycloakBootstrapConfiguration and refactor tests to use ApplicationContextRunner. --- embedded-keycloak/pom.xml | 12 +- ...mbeddedKeycloakBootstrapConfiguration.java | 136 +++++++++++--- .../keycloak/KeycloakContainer.java | 169 ------------------ .../keycloak/KeycloakContainerFactory.java | 51 ------ .../keycloak/KeycloakProperties.java | 11 +- ...dedKeycloakBootstrapConfigurationTest.java | 4 +- .../vanilla/KeycloakContainerTest.java | 52 ++---- 7 files changed, 137 insertions(+), 298 deletions(-) delete mode 100644 embedded-keycloak/src/main/java/com/playtika/testcontainer/keycloak/KeycloakContainer.java delete mode 100644 embedded-keycloak/src/main/java/com/playtika/testcontainer/keycloak/KeycloakContainerFactory.java diff --git a/embedded-keycloak/pom.xml b/embedded-keycloak/pom.xml index f8a364d94..6cc76fea0 100644 --- a/embedded-keycloak/pom.xml +++ b/embedded-keycloak/pom.xml @@ -14,7 +14,7 @@ embedded-keycloak - 26.6.2 + 3.3.0 @@ -26,14 +26,10 @@ com.playtika.testcontainers embedded-toxiproxy - - - org.keycloak - keycloak-core - ${keycloak.version} - provided - true + com.github.dasniko + testcontainers-keycloak + ${testcontainers-keycloak.version} diff --git a/embedded-keycloak/src/main/java/com/playtika/testcontainer/keycloak/EmbeddedKeycloakBootstrapConfiguration.java b/embedded-keycloak/src/main/java/com/playtika/testcontainer/keycloak/EmbeddedKeycloakBootstrapConfiguration.java index 8fe19da82..7645b5a78 100644 --- a/embedded-keycloak/src/main/java/com/playtika/testcontainer/keycloak/EmbeddedKeycloakBootstrapConfiguration.java +++ b/embedded-keycloak/src/main/java/com/playtika/testcontainer/keycloak/EmbeddedKeycloakBootstrapConfiguration.java @@ -1,9 +1,11 @@ package com.playtika.testcontainer.keycloak; import com.playtika.testcontainer.common.spring.DockerPresenceBootstrapConfiguration; +import com.playtika.testcontainer.common.utils.ContainerUtils; import com.playtika.testcontainer.toxiproxy.ToxiproxyClientProxy; import com.playtika.testcontainer.toxiproxy.ToxiproxyHelper; import com.playtika.testcontainer.toxiproxy.condition.ConditionalOnToxiProxyEnabled; +import dasniko.testcontainers.keycloak.KeycloakContainer; import eu.rekawek.toxiproxy.ToxiproxyClient; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Qualifier; @@ -14,14 +16,18 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.testcontainers.containers.Network; import org.testcontainers.toxiproxy.ToxiproxyContainer; +import java.util.LinkedHashMap; import java.util.Optional; +import static com.playtika.testcontainer.common.utils.ContainerUtils.configureCommonsAndStart; import static com.playtika.testcontainer.keycloak.KeycloakProperties.BEAN_NAME_EMBEDDED_KEYCLOAK; -import static java.util.Objects.requireNonNull; +import static java.lang.String.format; @Slf4j @Configuration @@ -31,13 +37,16 @@ @ConditionalOnProperty(name = "embedded.keycloak.enabled", matchIfMissing = true) public class EmbeddedKeycloakBootstrapConfiguration { + private static final String KEYCLOAK_NETWORK_ALIAS = "keycloak.testcontainer.docker"; + private static final int KEYCLOAK_INTERNAL_HTTP_PORT = 8080; + @Bean @ConditionalOnToxiProxyEnabled(module = "keycloak") ToxiproxyClientProxy keycloakContainerProxy(ToxiproxyClient toxiproxyClient, - ToxiproxyContainer toxiproxyContainer, - @Qualifier(BEAN_NAME_EMBEDDED_KEYCLOAK) KeycloakContainer keycloakContainer, - KeycloakProperties properties, - ConfigurableEnvironment environment) { + ToxiproxyContainer toxiproxyContainer, + @Qualifier(BEAN_NAME_EMBEDDED_KEYCLOAK) KeycloakContainer keycloakContainer, + KeycloakProperties properties, + ConfigurableEnvironment environment) { ToxiproxyClientProxy proxy = ToxiproxyHelper.createProxy( toxiproxyClient, toxiproxyContainer, @@ -50,26 +59,105 @@ ToxiproxyClientProxy keycloakContainerProxy(ToxiproxyClient toxiproxyClient, return proxy; } - @Bean - public KeycloakContainerFactory keycloakContainerFactory(ConfigurableEnvironment environment, - KeycloakProperties properties, - ResourceLoader resourceLoader, - Optional network) { - return new KeycloakContainerFactory(environment, properties, resourceLoader, network); + @Bean(name = BEAN_NAME_EMBEDDED_KEYCLOAK, destroyMethod = "stop") + public KeycloakContainer keycloakContainer(ConfigurableEnvironment environment, + KeycloakProperties properties, + ResourceLoader resourceLoader, + Optional network) { + KeycloakContainer keycloak = new KeycloakContainer(ContainerUtils.getDockerImageName(properties).toString()) + .withNetworkAliases(KEYCLOAK_NETWORK_ALIAS) + .withAdminUsername(properties.getAdminUser()) + .withAdminPassword(properties.getAdminPassword()); + + applyDbConfig(keycloak, properties); + applyImportFile(keycloak, properties, resourceLoader); + + network.ifPresent(keycloak::withNetwork); + + keycloak = (KeycloakContainer) configureCommonsAndStart(keycloak, properties, log); + registerEnvironment(keycloak, environment, properties); + return keycloak; } - /** - * Creates and starts a {@link KeycloakContainer} if property {@code embedded.keycloak.enabled} - * evaluates to {@code true}. The configuration makes no difference if just vanilla Keycloak is - * on the classpath or any Spring adapter. The container will always be needed. Also registers a - * shutdown hook to stop the container on context shutdown. - * - * @param factory The {@link KeycloakContainerFactory} to use, injected by Spring, must not be - * null - * @return The created {@link KeycloakContainer} instance to be registered as bean - */ - @Bean(name = BEAN_NAME_EMBEDDED_KEYCLOAK, destroyMethod = "stop") - public KeycloakContainer keycloakContainer(KeycloakContainerFactory factory) { - return requireNonNull(factory).newKeycloakContainer(); + private void applyDbConfig(KeycloakContainer keycloak, KeycloakProperties properties) { + if (properties.getDbVendor() != null) { + keycloak.withEnv("DB", properties.getDbVendor()); + } + if (properties.getDbAddr() != null) { + keycloak.withEnv("DB_URL_HOST", properties.getDbAddr()); + } + if (properties.getDbPort() != null) { + keycloak.withEnv("DB_URL_PORT", properties.getDbPort()); + } + if (properties.getDbDatabase() != null) { + keycloak.withEnv("DB_URL_DATABASE", properties.getDbDatabase()); + } + if (properties.getDbSchema() != null) { + keycloak.withEnv("DB_SCHEMA", properties.getDbSchema()); + } + if (properties.getDbUser() != null) { + keycloak.withEnv("DB_USERNAME", properties.getDbUser()); + } + if (properties.getDbUserFile() != null) { + keycloak.withEnv("DB_USER_FILE", properties.getDbUserFile()); + } + if (properties.getDbPassword() != null) { + keycloak.withEnv("DB_PASSWORD", properties.getDbPassword()); + } + if (properties.getDbPasswordFile() != null) { + keycloak.withEnv("DB_PASSWORD_FILE", properties.getDbPasswordFile()); + } + } + + private void applyImportFile(KeycloakContainer keycloak, KeycloakProperties properties, ResourceLoader resourceLoader) { + String importFile = properties.getImportFile(); + if (importFile == null) { + return; + } + checkImportFileExists(resourceLoader, importFile); + keycloak.withRealmImportFile(importFile); + } + + private void checkImportFileExists(ResourceLoader resourceLoader, String importFile) { + Resource resource = resourceLoader.getResource("classpath:" + importFile); + if (!resource.exists()) { + throw new ImportFileNotFoundException(importFile); + } + log.debug("Using import file: {}", resource.getFilename()); + } + + private void registerEnvironment(KeycloakContainer keycloak, + ConfigurableEnvironment environment, + KeycloakProperties properties) { + String host = keycloak.getHost(); + Integer httpPort = keycloak.getHttpPort(); + String authServerUrl = buildAuthServerUrl(keycloak, properties); + + LinkedHashMap map = new LinkedHashMap<>(); + map.put("embedded.keycloak.host", host); + map.put("embedded.keycloak.http-port", httpPort); + map.put("embedded.keycloak.auth-server-url", authServerUrl); + map.put("embedded.keycloak.networkAlias", KEYCLOAK_NETWORK_ALIAS); + map.put("embedded.keycloak.internalPort", KEYCLOAK_INTERNAL_HTTP_PORT); + + log.info("Started Keycloak server. Connection details: {}", map); + + MapPropertySource propertySource = new MapPropertySource("embeddedKeycloakInfo", map); + environment.getPropertySources().addFirst(propertySource); + } + + private String buildAuthServerUrl(KeycloakContainer keycloak, KeycloakProperties properties) { + return format("http://%s:%d%s", keycloak.getHost(), keycloak.getHttpPort(), properties.getAuthBasePath()); + } + + public static final class ImportFileNotFoundException extends IllegalArgumentException { + + private static final long serialVersionUID = 6350884396691857560L; + + ImportFileNotFoundException(String importFile) { + super(format( + "Classpath resource '%s' defined through 'embedded.keycloak.import-file' does not exist.", + importFile)); + } } } diff --git a/embedded-keycloak/src/main/java/com/playtika/testcontainer/keycloak/KeycloakContainer.java b/embedded-keycloak/src/main/java/com/playtika/testcontainer/keycloak/KeycloakContainer.java deleted file mode 100644 index efe3f2c30..000000000 --- a/embedded-keycloak/src/main/java/com/playtika/testcontainer/keycloak/KeycloakContainer.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.playtika.testcontainer.keycloak; - -import com.playtika.testcontainer.common.utils.ContainerUtils; -import lombok.extern.slf4j.Slf4j; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.containers.wait.strategy.WaitStrategy; -import org.testcontainers.utility.MountableFile; - -import static java.lang.String.format; - -@Slf4j -public class KeycloakContainer extends GenericContainer { - - public static final int KEYCLOAK_DEFAULT_HTTP_PORT_INTERNAL = 8080; - - private final KeycloakProperties properties; - private final ResourceLoader resourceLoader; - - public KeycloakContainer(KeycloakProperties properties, - ResourceLoader resourceLoader) { - super(ContainerUtils.getDockerImageName(properties)); - this.properties = properties; - this.resourceLoader = resourceLoader; - } - - @Override - protected void configure() { - withEnv("HTTP_ENABLED", String.valueOf(true)); - withEnv("HTTP_PORT", String.valueOf(KEYCLOAK_DEFAULT_HTTP_PORT_INTERNAL)); - withExposedPorts(KEYCLOAK_DEFAULT_HTTP_PORT_INTERNAL); - withEnv("KEYCLOAK_ADMIN", properties.getAdminUser()); - withEnv("KEYCLOAK_ADMIN_PASSWORD", properties.getAdminPassword()); - withDB(); - withCommand(properties.getCommand()); - waitingFor(waitForListeningPort()); - withImportFile(properties.getImportFile()); - } - - private void withDB() { - withDBVendor(); - withDBAddr(); - withDBPort(); - withDBDatabase(); - withDBSchema(); - withDBUser(); - withDBUserFile(); - withDBPassword(); - withDBPasswordFile(); - } - - private void withDBVendor() { - String dbVendor = properties.getDbVendor(); - if (dbVendor != null) { - withEnv("DB", dbVendor); - } - } - - private void withDBAddr() { - String dbAddr = properties.getDbAddr(); - if (dbAddr != null) { - withEnv("DB_URL_HOST", dbAddr); - } - } - - private void withDBPort() { - String dbPort = properties.getDbPort(); - if (dbPort != null) { - withEnv("DB_URL_PORT", dbPort); - } - } - - private void withDBDatabase() { - String dbDatabase = properties.getDbDatabase(); - if (dbDatabase != null) { - withEnv("DB_URL_DATABASE", dbDatabase); - } - } - - private void withDBSchema() { - String dbSchema = properties.getDbSchema(); - if (dbSchema != null) { - withEnv("DB_SCHEMA", dbSchema); - } - } - - private void withDBUser() { - String dbUser = properties.getDbUser(); - if (dbUser != null) { - withEnv("DB_USERNAME", dbUser); - } - } - - private void withDBUserFile() { - String dbUserFile = properties.getDbUserFile(); - if (dbUserFile != null) { - withEnv("DB_USER_FILE", dbUserFile); - } - } - - private void withDBPassword() { - String dbPassword = properties.getDbPassword(); - if (dbPassword != null) { - withEnv("DB_PASSWORD", dbPassword); - } - } - - private void withDBPasswordFile() { - String dbPasswordFile = properties.getDbPasswordFile(); - if (dbPasswordFile != null) { - withEnv("DB_PASSWORD_FILE", dbPasswordFile); - } - } - - private void withImportFile(String importFile) { - if (importFile == null) { - return; - } - - checkExists(importFile); - - String importFileInContainer = "/opt/keycloak/data/import/" + importFile; - withCopyFileToContainer( - MountableFile.forClasspathResource(importFile), - importFileInContainer - ); - } - - private void checkExists(String importFile) { - Resource resource = resourceLoader.getResource("classpath:" + importFile); - if (resource.exists()) { - log.debug("Using import file: {}", resource.getFilename()); - return; - } - - throw new ImportFileNotFoundException(importFile); - } - - private WaitStrategy waitForListeningPort() { - return Wait - .forListeningPort() - .withStartupTimeout(properties.getTimeoutDuration()); - } - - public String getIp() { - return getContainerIpAddress(); - } - - public Integer getHttpPort() { - return getMappedPort(KEYCLOAK_DEFAULT_HTTP_PORT_INTERNAL); - } - - public String getAuthServerUrl() { - return format("http://%s:%d%s", getIp(), getHttpPort(), properties.getAuthBasePath()); - } - - public static final class ImportFileNotFoundException extends IllegalArgumentException { - - private static final long serialVersionUID = 6350884396691857560L; - - ImportFileNotFoundException(String importFile) { - super(format( - "Classpath resource '%s' defined through 'embedded.keycloak.import-file' does not exist.", - importFile)); - } - } -} diff --git a/embedded-keycloak/src/main/java/com/playtika/testcontainer/keycloak/KeycloakContainerFactory.java b/embedded-keycloak/src/main/java/com/playtika/testcontainer/keycloak/KeycloakContainerFactory.java deleted file mode 100644 index 04929e815..000000000 --- a/embedded-keycloak/src/main/java/com/playtika/testcontainer/keycloak/KeycloakContainerFactory.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.playtika.testcontainer.keycloak; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.MapPropertySource; -import org.springframework.core.io.ResourceLoader; -import org.testcontainers.containers.Network; - -import java.util.LinkedHashMap; -import java.util.Optional; - -import static com.playtika.testcontainer.common.utils.ContainerUtils.configureCommonsAndStart; -import static com.playtika.testcontainer.keycloak.KeycloakContainer.KEYCLOAK_DEFAULT_HTTP_PORT_INTERNAL; - -@Slf4j -@RequiredArgsConstructor -public class KeycloakContainerFactory { - - private static final String KEYCLOAK_NETWORK_ALIAS = "keycloak.testcontainer.docker"; - - private final ConfigurableEnvironment environment; - private final KeycloakProperties properties; - private final ResourceLoader resourceLoader; - private final Optional network; - - public KeycloakContainer newKeycloakContainer() { - KeycloakContainer keycloak = new KeycloakContainer(properties, resourceLoader) - .withNetworkAliases(KEYCLOAK_NETWORK_ALIAS); - - network.ifPresent(keycloak::withNetwork); - - keycloak = (KeycloakContainer) configureCommonsAndStart(keycloak, properties, log); - registerKeycloakEnvironment(keycloak); - return keycloak; - } - - private void registerKeycloakEnvironment(KeycloakContainer keycloak) { - LinkedHashMap map = new LinkedHashMap<>(); - map.put("embedded.keycloak.host", keycloak.getIp()); - map.put("embedded.keycloak.http-port", keycloak.getHttpPort()); - map.put("embedded.keycloak.auth-server-url", keycloak.getAuthServerUrl()); - map.put("embedded.keycloak.networkAlias", KEYCLOAK_NETWORK_ALIAS); - map.put("embedded.keycloak.internalPort", KEYCLOAK_DEFAULT_HTTP_PORT_INTERNAL); - - log.info("Started Keycloak server. Connection details: {}", map); - - MapPropertySource propertySource = new MapPropertySource("embeddedKeycloakInfo", map); - environment.getPropertySources().addFirst(propertySource); - } -} diff --git a/embedded-keycloak/src/main/java/com/playtika/testcontainer/keycloak/KeycloakProperties.java b/embedded-keycloak/src/main/java/com/playtika/testcontainer/keycloak/KeycloakProperties.java index d3b019848..283cb15be 100644 --- a/embedded-keycloak/src/main/java/com/playtika/testcontainer/keycloak/KeycloakProperties.java +++ b/embedded-keycloak/src/main/java/com/playtika/testcontainer/keycloak/KeycloakProperties.java @@ -12,18 +12,12 @@ public class KeycloakProperties extends CommonContainerProperties { public static final String BEAN_NAME_EMBEDDED_KEYCLOAK = "embeddedKeycloak"; - public static final String[] DEFAULT_COMMAND = {"start-dev", "--import-realm"}; - public static final String DEFAULT_ADMIN_USER = "admin"; public static final String DEFAULT_ADMIN_PASSWORD = "letmein"; public static final String DEFAULT_REALM = "master"; public static final String DEFAULT_AUTH_BASE_PATH = "/"; public static final String DEFAULT_DB_VENDOR = "dev-mem"; - /** - * The command string issued to the container. - */ - private String[] command = DEFAULT_COMMAND; /** * The admin username to use. */ @@ -77,6 +71,11 @@ public class KeycloakProperties extends CommonContainerProperties { */ private String dbPasswordFile; + @Override + public String[] getCommand() { + return null; + } + @Override public String getDefaultDockerImage() { // Please don`t remove this comment. diff --git a/embedded-keycloak/src/test/java/com/playtika/testcontainer/keycloak/spring/EmbeddedKeycloakBootstrapConfigurationTest.java b/embedded-keycloak/src/test/java/com/playtika/testcontainer/keycloak/spring/EmbeddedKeycloakBootstrapConfigurationTest.java index 9baf51d78..43dd5f294 100644 --- a/embedded-keycloak/src/test/java/com/playtika/testcontainer/keycloak/spring/EmbeddedKeycloakBootstrapConfigurationTest.java +++ b/embedded-keycloak/src/test/java/com/playtika/testcontainer/keycloak/spring/EmbeddedKeycloakBootstrapConfigurationTest.java @@ -1,7 +1,7 @@ package com.playtika.testcontainer.keycloak.spring; -import com.playtika.testcontainer.keycloak.KeycloakContainer; import com.playtika.testcontainer.keycloak.util.KeyCloakToken; +import dasniko.testcontainers.keycloak.KeycloakContainer; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -51,7 +51,7 @@ public void propertiesAreAvailable() { keycloakContainer.getHttpPort())); assertThat(environment.getProperty("embedded.keycloak.host")) - .isEqualTo(keycloakContainer.getIp()); + .isEqualTo(keycloakContainer.getHost()); assertThat(environment.getProperty("embedded.keycloak.http-port", Integer.class)) .isEqualTo(keycloakContainer.getHttpPort()); diff --git a/embedded-keycloak/src/test/java/com/playtika/testcontainer/keycloak/vanilla/KeycloakContainerTest.java b/embedded-keycloak/src/test/java/com/playtika/testcontainer/keycloak/vanilla/KeycloakContainerTest.java index 1802b9b30..e7554b8a1 100644 --- a/embedded-keycloak/src/test/java/com/playtika/testcontainer/keycloak/vanilla/KeycloakContainerTest.java +++ b/embedded-keycloak/src/test/java/com/playtika/testcontainer/keycloak/vanilla/KeycloakContainerTest.java @@ -1,50 +1,26 @@ package com.playtika.testcontainer.keycloak.vanilla; -import com.playtika.testcontainer.keycloak.KeycloakContainer; -import com.playtika.testcontainer.keycloak.KeycloakContainer.ImportFileNotFoundException; -import com.playtika.testcontainer.keycloak.KeycloakProperties; +import com.playtika.testcontainer.keycloak.EmbeddedKeycloakBootstrapConfiguration; +import com.playtika.testcontainer.keycloak.EmbeddedKeycloakBootstrapConfiguration.ImportFileNotFoundException; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Spy; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; -import org.testcontainers.containers.ContainerLaunchException; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.assertj.core.api.Assertions.assertThat; -@ExtendWith(MockitoExtension.class) public class KeycloakContainerTest { - @Mock - ResourceLoader resourceLoader; - - @Spy - KeycloakProperties properties = setupProperties(); - - @InjectMocks - KeycloakContainer container; - + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(EmbeddedKeycloakBootstrapConfiguration.class)); @Test public void shouldThrowImportFileNotFoundExceptionWhenImportFileDoesNotExist() { - Resource importFile = mock(Resource.class); - when(importFile.exists()).thenReturn(false); - when(resourceLoader.getResource(any())).thenReturn(importFile); - - assertThatThrownBy(container::start) - .isInstanceOf(ContainerLaunchException.class) - .hasCauseInstanceOf(ImportFileNotFoundException.class); - } - - private KeycloakProperties setupProperties() { - KeycloakProperties properties = new KeycloakProperties(); - properties.setImportFile("any-import-file.json"); - return properties; + contextRunner + .withPropertyValues("embedded.keycloak.import-file=non-existent-file.json") + .run(context -> { + assertThat(context).hasFailed(); + assertThat(context.getStartupFailure()) + .hasRootCauseInstanceOf(ImportFileNotFoundException.class); + }); } } From bfc315c4216ae7ee00fd75efc531524cc768c2ac Mon Sep 17 00:00:00 2001 From: admitrov Date: Wed, 6 May 2026 19:11:16 +0100 Subject: [PATCH 03/25] Update to use Testcontainers native Kafka modules and remove embedded Zookeeper Replace deprecated KafkaContainer imports with new testcontainers-kafka module classes. Use KafkaContainer for embedded-kafka and ConfluentKafkaContainer for embedded-native-kafka. Remove withEmbeddedZookeeper() call as newer Kafka versions use KRaft mode. --- .../KafkaContainerConfiguration.java | 5 ++--- .../NativeKafkaContainerConfiguration.java | 16 ++++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/embedded-kafka/src/main/java/com/playtika/testcontainer/kafka/configuration/KafkaContainerConfiguration.java b/embedded-kafka/src/main/java/com/playtika/testcontainer/kafka/configuration/KafkaContainerConfiguration.java index 2c9845325..04e46c711 100644 --- a/embedded-kafka/src/main/java/com/playtika/testcontainer/kafka/configuration/KafkaContainerConfiguration.java +++ b/embedded-kafka/src/main/java/com/playtika/testcontainer/kafka/configuration/KafkaContainerConfiguration.java @@ -23,8 +23,8 @@ import org.springframework.core.env.MapPropertySource; import org.testcontainers.containers.BindMode; import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.KafkaContainer; import org.testcontainers.containers.Network; +import org.testcontainers.kafka.KafkaContainer; import org.testcontainers.toxiproxy.ToxiproxyContainer; import org.testcontainers.utility.MountableFile; @@ -129,7 +129,7 @@ ToxiproxyClientProxy kafkaContainerSaslProxy(ToxiproxyClient toxiproxyClient, } @Bean(name = KAFKA_BEAN_NAME, destroyMethod = "stop") - public GenericContainer kafka( + public KafkaContainer kafka( KafkaStatusCheck kafkaStatusCheck, KafkaConfigurationProperties kafkaProperties, ZookeeperConfigurationProperties zookeeperProperties, @@ -172,7 +172,6 @@ public String getBootstrapServers() { } .withCreateContainerCmdModifier(cmd -> cmd.withUser(kafkaProperties.getDockerUser())) .withCreateContainerCmdModifier(cmd -> cmd.withHostName(KAFKA_HOST_NAME)) - .withEmbeddedZookeeper() //see: https://stackoverflow.com/questions/41868161/kafka-in-kubernetes-cluster-how-to-publish-consume-messages-from-outside-of-kub //see: https://github.com/wurstmeister/kafka-docker/blob/develop/README.md // order matters: external then internal since kafka.client.ClientUtils.getPlaintextBrokerEndPoints take first for simple consumers diff --git a/embedded-native-kafka/src/main/java/com/playtika/testcontainer/nativekafka/configuration/NativeKafkaContainerConfiguration.java b/embedded-native-kafka/src/main/java/com/playtika/testcontainer/nativekafka/configuration/NativeKafkaContainerConfiguration.java index a96dee6ed..c52bbf03d 100644 --- a/embedded-native-kafka/src/main/java/com/playtika/testcontainer/nativekafka/configuration/NativeKafkaContainerConfiguration.java +++ b/embedded-native-kafka/src/main/java/com/playtika/testcontainer/nativekafka/configuration/NativeKafkaContainerConfiguration.java @@ -13,7 +13,7 @@ import org.testcontainers.containers.BindMode; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; -import org.testcontainers.kafka.KafkaContainer; +import org.testcontainers.kafka.ConfluentKafkaContainer; import org.testcontainers.utility.DockerImageName; import java.io.IOException; @@ -50,7 +50,7 @@ public Network nativeKafkaNetwork() { } @Bean(name = NATIVE_KAFKA_BEAN_NAME, destroyMethod = "stop") - public GenericContainer nativeKafka( + public ConfluentKafkaContainer nativeKafka( NativeKafkaConfigurationProperties nativeKafkaProperties, ConfigurableEnvironment environment, Network network) { @@ -58,7 +58,7 @@ public GenericContainer nativeKafka( DockerImageName nativeKafkaImageName = DockerImageName.parse(nativeKafkaProperties.getDefaultDockerImage()) .asCompatibleSubstituteFor("confluentinc/cp-kafka"); - KafkaContainer nativeKafka = new KafkaContainer(nativeKafkaImageName) + ConfluentKafkaContainer nativeKafka = new ConfluentKafkaContainer(nativeKafkaImageName) .withNetwork(network) .withNetworkAliases(NATIVE_KAFKA_HOST_NAME) .withExtraHost(NATIVE_KAFKA_HOST_NAME, "127.0.0.1"); @@ -67,7 +67,7 @@ public GenericContainer nativeKafka( configureFileSystemBind(nativeKafkaProperties, nativeKafka); // Configure and start the container using common utilities - nativeKafka = (KafkaContainer) configureCommonsAndStart(nativeKafka, nativeKafkaProperties, log); + nativeKafka = (ConfluentKafkaContainer) configureCommonsAndStart(nativeKafka, nativeKafkaProperties, log); // Register environment properties registerNativeKafkaEnvironment(nativeKafka, environment, nativeKafkaProperties); @@ -83,7 +83,7 @@ public NativeKafkaTopicsConfigurer nativeKafkaTopicsConfigurer( return new NativeKafkaTopicsConfigurer(nativeKafka, nativeKafkaProperties); } - private void configureFileSystemBind(NativeKafkaConfigurationProperties nativeKafkaProperties, KafkaContainer nativeKafka) { + private void configureFileSystemBind(NativeKafkaConfigurationProperties nativeKafkaProperties, ConfluentKafkaContainer nativeKafka) { NativeKafkaConfigurationProperties.FileSystemBind fileSystemBind = nativeKafkaProperties.getFileSystemBind(); if (fileSystemBind.isEnabled()) { String currentTimestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH-mm-ss-nnnnnnnnn")); @@ -96,12 +96,12 @@ private void configureFileSystemBind(NativeKafkaConfigurationProperties nativeKa } } - private void registerNativeKafkaEnvironment(GenericContainer nativeKafka, + private void registerNativeKafkaEnvironment(ConfluentKafkaContainer nativeKafka, ConfigurableEnvironment environment, NativeKafkaConfigurationProperties nativeKafkaProperties) { LinkedHashMap map = new LinkedHashMap<>(); - String bootstrapServers = ((KafkaContainer) nativeKafka).getBootstrapServers(); + String bootstrapServers = nativeKafka.getBootstrapServers(); map.put("embedded.kafka.bootstrapServers", bootstrapServers); map.put("embedded.kafka.brokerList", bootstrapServers); map.put("embedded.kafka.networkAlias", NATIVE_KAFKA_HOST_NAME); @@ -150,4 +150,4 @@ private void makeWritable(Path path) { throw new RuntimeException(e); } } -} \ No newline at end of file +} From da577538baf365cce06ca865c47d902f25c0660a Mon Sep 17 00:00:00 2001 From: admitrov Date: Thu, 7 May 2026 09:50:46 +0100 Subject: [PATCH 04/25] Migrate embedded-minio to use MinIOContainer API and remove custom wait strategies Replace custom MinioWaitStrategy with built-in Wait.forHttp() from testcontainers. Update container initialization to use MinIOContainer's withUserName/withPassword API instead of environment variables. Remove DefaultMinioWaitStrategy and MinioWaitStrategy classes. Add wiremock-testcontainers-module version property. --- .../minio/DefaultMinioWaitStrategy.java | 17 --------- .../EmbeddedMinioBootstrapConfiguration.java | 38 ++++++------------- .../minio/MinioWaitStrategy.java | 6 --- 3 files changed, 12 insertions(+), 49 deletions(-) delete mode 100644 embedded-minio/src/main/java/com/playtika/testcontainer/minio/DefaultMinioWaitStrategy.java delete mode 100644 embedded-minio/src/main/java/com/playtika/testcontainer/minio/MinioWaitStrategy.java diff --git a/embedded-minio/src/main/java/com/playtika/testcontainer/minio/DefaultMinioWaitStrategy.java b/embedded-minio/src/main/java/com/playtika/testcontainer/minio/DefaultMinioWaitStrategy.java deleted file mode 100644 index ecc175fdf..000000000 --- a/embedded-minio/src/main/java/com/playtika/testcontainer/minio/DefaultMinioWaitStrategy.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.playtika.testcontainer.minio; - -import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy; -import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; -import org.testcontainers.containers.wait.strategy.WaitAllStrategy; - -public class DefaultMinioWaitStrategy extends WaitAllStrategy implements MinioWaitStrategy { - - public DefaultMinioWaitStrategy(MinioProperties properties) { - withStrategy(new HostPortWaitStrategy()) - .withStrategy(new HttpWaitStrategy() - .forPath("/minio/health/live") - .forPort(properties.port) - .forStatusCode(200)) - .withStartupTimeout(properties.getTimeoutDuration()); - } -} diff --git a/embedded-minio/src/main/java/com/playtika/testcontainer/minio/EmbeddedMinioBootstrapConfiguration.java b/embedded-minio/src/main/java/com/playtika/testcontainer/minio/EmbeddedMinioBootstrapConfiguration.java index dbb0bf5bf..16896fb53 100644 --- a/embedded-minio/src/main/java/com/playtika/testcontainer/minio/EmbeddedMinioBootstrapConfiguration.java +++ b/embedded-minio/src/main/java/com/playtika/testcontainer/minio/EmbeddedMinioBootstrapConfiguration.java @@ -10,15 +10,15 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; -import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.MinIOContainer; import org.testcontainers.containers.Network; +import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.toxiproxy.ToxiproxyContainer; import java.util.LinkedHashMap; @@ -31,28 +31,17 @@ @Configuration @ConditionalOnExpression("${embedded.containers.enabled:true}") @AutoConfigureAfter(DockerPresenceBootstrapConfiguration.class) +@EnableConfigurationProperties(MinioProperties.class) @ConditionalOnProperty(value = "embedded.minio.enabled", matchIfMissing = true) public class EmbeddedMinioBootstrapConfiguration { private static final String MINIO_NETWORK_ALIAS = "minio.testcontainer.docker"; - @Bean - @ConditionalOnMissingBean - MinioProperties minioProperties() { - return new MinioProperties(); - } - - @Bean(name = "minioWaitStrategy") - @ConditionalOnMissingBean - public MinioWaitStrategy minioWaitStrategy(MinioProperties properties) { - return new DefaultMinioWaitStrategy(properties); - } - @Bean @ConditionalOnToxiProxyEnabled(module = "minio") ToxiproxyClientProxy minioContainerProxy(ToxiproxyClient toxiproxyClient, ToxiproxyContainer toxiproxyContainer, - @Qualifier(BEAN_NAME_EMBEDDED_MINIO) GenericContainer minio, + @Qualifier(BEAN_NAME_EMBEDDED_MINIO) MinIOContainer minio, ConfigurableEnvironment environment, MinioProperties properties) { ToxiproxyClientProxy proxy = ToxiproxyHelper.createProxy( @@ -68,21 +57,19 @@ ToxiproxyClientProxy minioContainerProxy(ToxiproxyClient toxiproxyClient, } @Bean(name = BEAN_NAME_EMBEDDED_MINIO, destroyMethod = "stop") - public MinIOContainer minio(MinioWaitStrategy minioWaitStrategy, - ConfigurableEnvironment environment, - MinioProperties properties, - Optional network) { - + public MinIOContainer minio(ConfigurableEnvironment environment, + MinioProperties properties, + Optional network) { MinIOContainer minio = new MinIOContainer(ContainerUtils.getDockerImageName(properties)) - .withExposedPorts(properties.getPort(), properties.getConsolePort()) - .withEnv("MINIO_ROOT_USER", properties.getAccessKey()) - .withEnv("MINIO_ROOT_PASSWORD", properties.getSecretKey()) + .withUserName(properties.getAccessKey()) + .withPassword(properties.getSecretKey()) .withEnv("MINIO_SITE_REGION", properties.getRegion()) .withEnv("MINIO_WORM", properties.getWorm()) .withEnv("MINIO_BROWSER", properties.getBrowser()) - .withCommand("server", properties.getDirectory(), "--console-address", ":" + properties.getConsolePort()) - .waitingFor(minioWaitStrategy) + .waitingFor(Wait.forHttp("/minio/health/live") + .forPort(properties.getPort()) + .withStartupTimeout(properties.getTimeoutDuration())) .withNetworkAliases(MINIO_NETWORK_ALIAS); network.ifPresent(minio::withNetwork); @@ -110,5 +97,4 @@ private void registerEnvironment(MinIOContainer container, MapPropertySource propertySource = new MapPropertySource("embeddedMinioInfo", map); environment.getPropertySources().addFirst(propertySource); } - } diff --git a/embedded-minio/src/main/java/com/playtika/testcontainer/minio/MinioWaitStrategy.java b/embedded-minio/src/main/java/com/playtika/testcontainer/minio/MinioWaitStrategy.java deleted file mode 100644 index c92b5d1f7..000000000 --- a/embedded-minio/src/main/java/com/playtika/testcontainer/minio/MinioWaitStrategy.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.playtika.testcontainer.minio; - -import org.testcontainers.containers.wait.strategy.WaitStrategy; - -public interface MinioWaitStrategy extends WaitStrategy { -} From fac4ff1a9675ac6750a251bccdf197ee5f13d8ff Mon Sep 17 00:00:00 2001 From: admitrov Date: Thu, 7 May 2026 10:20:38 +0100 Subject: [PATCH 05/25] Use VaultContainer's withInitCommand for secret initialization and remove redundant port exposure Replace withSecretInVault API with withInitCommand using kv put command. Remove withExposedPorts call as port exposure is handled by VaultContainer. Remove unused Arrays import. --- .../vault/EmbeddedVaultBootstrapConfiguration.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/embedded-vault/src/main/java/com/playtika/testcontainer/vault/EmbeddedVaultBootstrapConfiguration.java b/embedded-vault/src/main/java/com/playtika/testcontainer/vault/EmbeddedVaultBootstrapConfiguration.java index a31aadac2..ef9dbe65a 100644 --- a/embedded-vault/src/main/java/com/playtika/testcontainer/vault/EmbeddedVaultBootstrapConfiguration.java +++ b/embedded-vault/src/main/java/com/playtika/testcontainer/vault/EmbeddedVaultBootstrapConfiguration.java @@ -21,7 +21,6 @@ import org.testcontainers.toxiproxy.ToxiproxyContainer; import org.testcontainers.vault.VaultContainer; -import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Optional; @@ -67,7 +66,6 @@ public VaultContainer vault(ConfigurableEnvironment environment, VaultContainer vault = new VaultContainer<>(ContainerUtils.getDockerImageName(properties)) .withVaultToken(properties.getToken()) - .withExposedPorts(properties.getPort()) .withNetworkAliases(VAULT_NETWORK_ALIAS); network.ifPresent(vault::withNetwork); @@ -77,7 +75,7 @@ public VaultContainer vault(ConfigurableEnvironment environment, .toArray(String[]::new); if (secrets.length > 0) { - vault.withSecretInVault(properties.getPath(), secrets[0], Arrays.copyOfRange(secrets, 1, secrets.length)); + vault.withInitCommand("kv put " + properties.getPath() + " " + String.join(" ", secrets)); } if (properties.isCasEnabled()) { From 0904899f53ae2fecf2893e43e5682bc8b0b7aa3c Mon Sep 17 00:00:00 2001 From: admitrov Date: Thu, 7 May 2026 10:37:01 +0100 Subject: [PATCH 06/25] Migrate embedded-google-pubsub to use PubSubEmulatorContainer from testcontainers-gcloud module Replace GenericContainer with PubSubEmulatorContainer and add testcontainers-gcloud dependency. Update container initialization to use PubSubEmulatorContainer API with getEmulatorEndpoint(). Change default port from 8089 to 8085 and update Docker image to gcr.io/google.com/cloudsdktool/google-cloud-cli:559.0.0-emulators. Remove custom command construction and wait strategy as they are handled by PubSubEmulatorContainer. --- embedded-google-pubsub/pom.xml | 4 ++ .../EmbeddedPubsubBootstrapConfiguration.java | 38 +++++++------------ .../pubsub/PubsubProperties.java | 4 +- ...eddedPubsubBootstrapConfigurationTest.java | 12 +++--- 4 files changed, 25 insertions(+), 33 deletions(-) diff --git a/embedded-google-pubsub/pom.xml b/embedded-google-pubsub/pom.xml index 8fe30c142..6166e44e1 100644 --- a/embedded-google-pubsub/pom.xml +++ b/embedded-google-pubsub/pom.xml @@ -23,6 +23,10 @@ com.playtika.testcontainers embedded-toxiproxy + + org.testcontainers + testcontainers-gcloud + com.google.cloud spring-cloud-gcp-starter-pubsub diff --git a/embedded-google-pubsub/src/main/java/com/playtika/testcontainer/pubsub/EmbeddedPubsubBootstrapConfiguration.java b/embedded-google-pubsub/src/main/java/com/playtika/testcontainer/pubsub/EmbeddedPubsubBootstrapConfiguration.java index 13ff81851..00f838018 100644 --- a/embedded-google-pubsub/src/main/java/com/playtika/testcontainer/pubsub/EmbeddedPubsubBootstrapConfiguration.java +++ b/embedded-google-pubsub/src/main/java/com/playtika/testcontainer/pubsub/EmbeddedPubsubBootstrapConfiguration.java @@ -18,9 +18,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; -import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; -import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; +import org.testcontainers.gcloud.PubSubEmulatorContainer; import org.testcontainers.toxiproxy.ToxiproxyContainer; import java.io.IOException; @@ -29,7 +28,6 @@ import static com.playtika.testcontainer.common.utils.ContainerUtils.configureCommonsAndStart; import static com.playtika.testcontainer.pubsub.PubsubProperties.BEAN_NAME_EMBEDDED_GOOGLE_PUBSUB; -import static java.lang.String.format; @Slf4j @Configuration @@ -47,7 +45,7 @@ public class EmbeddedPubsubBootstrapConfiguration { @ConditionalOnToxiProxyEnabled(module = "google.pubsub") ToxiproxyClientProxy googlePubSubContainerProxy(ToxiproxyClient toxiproxyClient, ToxiproxyContainer toxiproxyContainer, - @Qualifier(BEAN_NAME_EMBEDDED_GOOGLE_PUBSUB) GenericContainer pubsub, + @Qualifier(BEAN_NAME_EMBEDDED_GOOGLE_PUBSUB) PubSubEmulatorContainer pubsub, PubsubProperties properties, ConfigurableEnvironment environment) { ToxiproxyClientProxy proxy = ToxiproxyHelper.createProxy( @@ -62,33 +60,22 @@ ToxiproxyClientProxy googlePubSubContainerProxy(ToxiproxyClient toxiproxyClient, return proxy; } - @Bean(name = BEAN_NAME_EMBEDDED_GOOGLE_PUBSUB, destroyMethod = "stop") - public GenericContainer pubsub(ConfigurableEnvironment environment, - PubsubProperties properties, - Optional network) { - GenericContainer pubsubContainer = new GenericContainer<>(ContainerUtils.getDockerImageName(properties)) - .withExposedPorts(properties.getPort()) - .withCommand( - "/bin/sh", - "-c", - format( - "gcloud beta emulators pubsub start --project %s --host-port=%s:%d", - properties.getProjectId(), - properties.getHost(), - properties.getPort() - ) - ).waitingFor(new LogMessageWaitStrategy().withRegEx("(?s).*started.*$")) - .withNetworkAliases(GOOGLE_PUB_SUB_NETWORK_ALIAS); + public PubSubEmulatorContainer pubsub(ConfigurableEnvironment environment, + PubsubProperties properties, + Optional network) { + PubSubEmulatorContainer pubsubContainer = + new PubSubEmulatorContainer(ContainerUtils.getDockerImageName(properties)) + .withNetworkAliases(GOOGLE_PUB_SUB_NETWORK_ALIAS); network.ifPresent(pubsubContainer::withNetwork); - pubsubContainer = configureCommonsAndStart(pubsubContainer, properties, log); + pubsubContainer = (PubSubEmulatorContainer) configureCommonsAndStart(pubsubContainer, properties, log); registerPubsubEnvironment(pubsubContainer, environment, properties); return pubsubContainer; } - private void registerPubsubEnvironment(GenericContainer container, + private void registerPubsubEnvironment(PubSubEmulatorContainer container, ConfigurableEnvironment environment, PubsubProperties properties) { LinkedHashMap map = new LinkedHashMap<>(); @@ -106,9 +93,10 @@ private void registerPubsubEnvironment(GenericContainer container, } @Bean(name = BEAN_NAME_EMBEDDED_GOOGLE_PUBSUB_MANAGED_CHANNEL) - public ManagedChannel managedChannel(@Qualifier(BEAN_NAME_EMBEDDED_GOOGLE_PUBSUB) GenericContainer pubsub, PubsubProperties properties) { + public ManagedChannel managedChannel(@Qualifier(BEAN_NAME_EMBEDDED_GOOGLE_PUBSUB) PubSubEmulatorContainer pubsub) { return ManagedChannelBuilder - .forAddress(pubsub.getHost(), pubsub.getMappedPort(properties.getPort())).usePlaintext() + .forTarget(pubsub.getEmulatorEndpoint()) + .usePlaintext() .build(); } diff --git a/embedded-google-pubsub/src/main/java/com/playtika/testcontainer/pubsub/PubsubProperties.java b/embedded-google-pubsub/src/main/java/com/playtika/testcontainer/pubsub/PubsubProperties.java index f1f5b3b99..c21e8bcba 100644 --- a/embedded-google-pubsub/src/main/java/com/playtika/testcontainer/pubsub/PubsubProperties.java +++ b/embedded-google-pubsub/src/main/java/com/playtika/testcontainer/pubsub/PubsubProperties.java @@ -14,7 +14,7 @@ public class PubsubProperties extends CommonContainerProperties { public static final String BEAN_NAME_EMBEDDED_GOOGLE_PUBSUB = "embeddedGooglePubsub"; private String host = "0.0.0.0"; - private int port = 8089; + private int port = 8085; private String projectId = "my-project-id"; private Collection topicsAndSubscriptions = Collections.emptyList(); @@ -22,6 +22,6 @@ public class PubsubProperties extends CommonContainerProperties { public String getDefaultDockerImage() { // Please don`t remove this comment. // renovate: datasource=docker - return "google/cloud-sdk:568.0.0"; + return "gcr.io/google.com/cloudsdktool/google-cloud-cli:568.0.0-emulators"; } } diff --git a/embedded-google-pubsub/src/test/java/com/playtika/testcontainer/pubsub/EmbeddedPubsubBootstrapConfigurationTest.java b/embedded-google-pubsub/src/test/java/com/playtika/testcontainer/pubsub/EmbeddedPubsubBootstrapConfigurationTest.java index 3def7057d..5f8bdb7ce 100644 --- a/embedded-google-pubsub/src/test/java/com/playtika/testcontainer/pubsub/EmbeddedPubsubBootstrapConfigurationTest.java +++ b/embedded-google-pubsub/src/test/java/com/playtika/testcontainer/pubsub/EmbeddedPubsubBootstrapConfigurationTest.java @@ -17,7 +17,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.test.context.ActiveProfiles; -import org.testcontainers.containers.GenericContainer; +import org.testcontainers.gcloud.PubSubEmulatorContainer; import java.util.List; @@ -122,12 +122,12 @@ void shouldSetupDependsOnForPubSubTemplate() { void shouldHaveContainerWithExpectedDefaultProperties() { assertThat(beanFactory.getBean(BEAN_NAME_EMBEDDED_GOOGLE_PUBSUB)) .isNotNull() - .isInstanceOf(GenericContainer.class) - .satisfies(genericContainer -> { - GenericContainer container = (GenericContainer) genericContainer; - assertThat(container.getExposedPorts()).containsExactly(8089); + .isInstanceOf(PubSubEmulatorContainer.class) + .satisfies(bean -> { + PubSubEmulatorContainer container = (PubSubEmulatorContainer) bean; + assertThat(container.getExposedPorts()).containsExactly(8085); assertThat(container.getCommandParts()) - .containsExactly("/bin/sh", "-c", "gcloud beta emulators pubsub start --project my-project-id --host-port=0.0.0.0:8089"); + .containsExactly("/bin/sh", "-c", "gcloud beta emulators pubsub start --host-port 0.0.0.0:8085"); }); } From 80977d8e1ce36cc9d7c728dbc82ada4330b39711 Mon Sep 17 00:00:00 2001 From: admitrov Date: Thu, 7 May 2026 10:50:16 +0100 Subject: [PATCH 07/25] Migrate embedded-google-storage to use FakeGcsServerContainer from testcontainers-fake-gcs-server library Replace GenericContainer with FakeGcsServerContainer and add testcontainers-fake-gcs-server dependency. Remove GoogleCloudStorageHttpClient and custom endpoint configuration logic as they are handled by FakeGcsServerContainer's url() method. Update container initialization to use FakeGcsServerContainer API. --- embedded-google-storage/pom.xml | 7 ++ ...EmbeddedStorageBootstrapConfiguration.java | 57 +++++---------- .../storage/GoogleCloudStorageHttpClient.java | 69 ------------------- ...ddedStorageBootstrapConfigurationTest.java | 8 +-- 4 files changed, 30 insertions(+), 111 deletions(-) delete mode 100644 embedded-google-storage/src/main/java/com/playtika/testcontainer/storage/GoogleCloudStorageHttpClient.java diff --git a/embedded-google-storage/pom.xml b/embedded-google-storage/pom.xml index dc7a95b71..4d92a5259 100644 --- a/embedded-google-storage/pom.xml +++ b/embedded-google-storage/pom.xml @@ -16,6 +16,8 @@ 2.4.4 + + 0.3.0 @@ -27,6 +29,11 @@ com.playtika.testcontainers embedded-toxiproxy + + io.aiven + testcontainers-fake-gcs-server + ${testcontainers-fake-gcs-server.version} + com.google.cloud google-cloud-storage diff --git a/embedded-google-storage/src/main/java/com/playtika/testcontainer/storage/EmbeddedStorageBootstrapConfiguration.java b/embedded-google-storage/src/main/java/com/playtika/testcontainer/storage/EmbeddedStorageBootstrapConfiguration.java index 845b98b24..c137e0b07 100644 --- a/embedded-google-storage/src/main/java/com/playtika/testcontainer/storage/EmbeddedStorageBootstrapConfiguration.java +++ b/embedded-google-storage/src/main/java/com/playtika/testcontainer/storage/EmbeddedStorageBootstrapConfiguration.java @@ -6,6 +6,7 @@ import com.playtika.testcontainer.toxiproxy.ToxiproxyHelper; import com.playtika.testcontainer.toxiproxy.condition.ConditionalOnToxiProxyEnabled; import eu.rekawek.toxiproxy.ToxiproxyClient; +import io.aiven.testcontainers.fakegcsserver.FakeGcsServerContainer; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; @@ -18,17 +19,14 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; -import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; import org.testcontainers.toxiproxy.ToxiproxyContainer; -import java.io.IOException; import java.util.LinkedHashMap; import java.util.Optional; import static com.playtika.testcontainer.common.utils.ContainerUtils.configureCommonsAndStart; import static com.playtika.testcontainer.storage.StorageProperties.BEAN_NAME_EMBEDDED_GOOGLE_STORAGE_SERVER; -import static java.lang.String.format; @Slf4j @Configuration @@ -45,7 +43,7 @@ public class EmbeddedStorageBootstrapConfiguration { @ConditionalOnToxiProxyEnabled(module = "google.storage") ToxiproxyClientProxy googleStorageContainerProxy(ToxiproxyClient toxiproxyClient, ToxiproxyContainer toxiproxyContainer, - @Qualifier(BEAN_NAME_EMBEDDED_GOOGLE_STORAGE_SERVER) GenericContainer storageServer, + @Qualifier(BEAN_NAME_EMBEDDED_GOOGLE_STORAGE_SERVER) FakeGcsServerContainer storageServer, ConfigurableEnvironment environment) { ToxiproxyClientProxy proxy = ToxiproxyHelper.createProxy( toxiproxyClient, @@ -60,46 +58,36 @@ ToxiproxyClientProxy googleStorageContainerProxy(ToxiproxyClient toxiproxyClient } @Bean(name = BEAN_NAME_EMBEDDED_GOOGLE_STORAGE_SERVER, destroyMethod = "stop") - GenericContainer storageServer(ConfigurableEnvironment environment, - StorageProperties properties, - Optional network) throws IOException { - - GenericContainer storageContainer = new GenericContainer<>(ContainerUtils.getDockerImageName(properties)) - .withExposedPorts(StorageProperties.PORT) - .withCreateContainerCmdModifier(cmd -> cmd.withEntrypoint( - "/bin/fake-gcs-server", - "-backend", "memory", - "-scheme", "http", - "-host", "0.0.0.0", - "-port", String.valueOf(StorageProperties.PORT), - "-location", properties.getBucketLocation() - )) - .withNetworkAliases(GOOGLE_STORAGE_NETWORK_ALIAS); + FakeGcsServerContainer storageServer(ConfigurableEnvironment environment, + StorageProperties properties, + Optional network) { + FakeGcsServerContainer storageContainer = + new FakeGcsServerContainer(ContainerUtils.getDockerImageName(properties)) + .withCreateContainerCmdModifier(cmd -> cmd.withEntrypoint( + "/bin/fake-gcs-server", + "-backend", "memory", + "-scheme", "http", + "-host", "0.0.0.0", + "-port", String.valueOf(StorageProperties.PORT), + "-location", properties.getBucketLocation() + )) + .withNetworkAliases(GOOGLE_STORAGE_NETWORK_ALIAS); network.ifPresent(storageContainer::withNetwork); - storageContainer = configureCommonsAndStart(storageContainer, properties, log); - prepareContainerConfiguration(storageContainer); + storageContainer = (FakeGcsServerContainer) configureCommonsAndStart(storageContainer, properties, log); registerStorageEnvironment(storageContainer, environment, properties); return storageContainer; } - private void prepareContainerConfiguration(GenericContainer container) throws IOException { - String containerEndpoint = buildContainerEndpoint(container); - - log.info("Google Cloud Fake Storage Server with externalUrl={}", containerEndpoint); - new GoogleCloudStorageHttpClient() - .sendUpdateConfigRequest(containerEndpoint); - } - private void registerStorageEnvironment( - GenericContainer container, + FakeGcsServerContainer container, ConfigurableEnvironment environment, StorageProperties properties) { LinkedHashMap map = new LinkedHashMap<>(); map.put("embedded.google.storage.host", container.getHost()); map.put("embedded.google.storage.port", container.getMappedPort(StorageProperties.PORT)); - map.put("embedded.google.storage.endpoint", buildContainerEndpoint(container)); + map.put("embedded.google.storage.endpoint", container.url()); map.put("embedded.google.storage.project-id", properties.getProjectId()); map.put("embedded.google.storage.bucket-location", properties.getBucketLocation()); map.put("embedded.google.storage.networkAlias", GOOGLE_STORAGE_NETWORK_ALIAS); @@ -118,11 +106,4 @@ StorageResourcesGenerator storageResourcesGenerator( StorageProperties storageProperties) { return new StorageResourcesGenerator(endpoint, storageProperties); } - - private String buildContainerEndpoint(GenericContainer container) { - return format( - "http://%s:%d", - container.getHost(), - container.getMappedPort(StorageProperties.PORT)); - } } diff --git a/embedded-google-storage/src/main/java/com/playtika/testcontainer/storage/GoogleCloudStorageHttpClient.java b/embedded-google-storage/src/main/java/com/playtika/testcontainer/storage/GoogleCloudStorageHttpClient.java deleted file mode 100644 index 67c09d071..000000000 --- a/embedded-google-storage/src/main/java/com/playtika/testcontainer/storage/GoogleCloudStorageHttpClient.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.playtika.testcontainer.storage; - -import lombok.extern.slf4j.Slf4j; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Scanner; - -@Slf4j -class GoogleCloudStorageHttpClient { - - //TODO: replace with HttpRequest from Java 11, when migrated - public void sendUpdateConfigRequest(String containerEndpoint) throws IOException { - HttpURLConnection connection = null; - try { - - String requestBody = "{" - + "\"externalUrl\": \"" + containerEndpoint + "\"" - + "}"; - - URL url = new URL(containerEndpoint + "/_internal/config"); - connection = (HttpURLConnection) url.openConnection(); - connection.setConnectTimeout(5_000); - connection.setReadTimeout(5_000); - connection.setRequestMethod("PUT"); - connection.setDoOutput(true); - connection.setRequestProperty("Content-Type", "application/json"); - connection.setRequestProperty("Accept", "application/json"); - try (OutputStreamWriter osw = new OutputStreamWriter(connection.getOutputStream())) { - osw.write(requestBody); - osw.flush(); - } - - int responseCode = connection.getResponseCode(); - - if (responseCode != 200) { - String response = getResponseBody(connection); - log.error( - "error updating Google Cloud Fake Storage Server with external url, response status code {} != 200 message {}", - responseCode, - response); - } - } catch (Exception e) { - log.error("error updating Google Cloud Fake Storage Server with external host", e); - throw e; - } finally { - if (connection != null) { - connection.disconnect(); - } - } - } - - private String getResponseBody(HttpURLConnection connection) throws IOException { - try (InputStream inputStream = getStream(connection)) { - InputStreamReader streamReader = new InputStreamReader(inputStream); - Scanner s = new Scanner(streamReader).useDelimiter("\\A"); - return s.hasNext() ? s.next() : ""; - } - } - - private InputStream getStream(HttpURLConnection connection) throws IOException { - InputStream errorStream = connection.getErrorStream(); - return errorStream != null ? errorStream : connection.getInputStream(); - } -} diff --git a/embedded-google-storage/src/test/java/com/playtika/testcontainer/storage/EmbeddedStorageBootstrapConfigurationTest.java b/embedded-google-storage/src/test/java/com/playtika/testcontainer/storage/EmbeddedStorageBootstrapConfigurationTest.java index 604b9bf6d..d77fd4c26 100644 --- a/embedded-google-storage/src/test/java/com/playtika/testcontainer/storage/EmbeddedStorageBootstrapConfigurationTest.java +++ b/embedded-google-storage/src/test/java/com/playtika/testcontainer/storage/EmbeddedStorageBootstrapConfigurationTest.java @@ -8,6 +8,7 @@ import com.google.cloud.storage.Storage; import com.google.cloud.storage.StorageOptions; import com.playtika.testcontainer.storage.EmbeddedStorageBootstrapConfigurationTest.TestConfig; +import io.aiven.testcontainers.fakegcsserver.FakeGcsServerContainer; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.BeanFactoryUtils; @@ -21,7 +22,6 @@ import org.springframework.context.annotation.Import; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.test.context.ActiveProfiles; -import org.testcontainers.containers.GenericContainer; import java.io.IOException; import java.nio.ByteBuffer; @@ -118,9 +118,9 @@ public void shouldSetupDependsOnForStorage() { public void shouldHaveContainerWithExpectedDefaultProperties() { assertThat(beanFactory.getBean(BEAN_NAME_EMBEDDED_GOOGLE_STORAGE_SERVER)) .isNotNull() - .isInstanceOf(GenericContainer.class) - .satisfies(genericContainer -> { - GenericContainer container = (GenericContainer) genericContainer; + .isInstanceOf(FakeGcsServerContainer.class) + .satisfies(bean -> { + FakeGcsServerContainer container = (FakeGcsServerContainer) bean; assertThat(container.getExposedPorts()).containsExactly(4443); assertThat(container.getContainerInfo().getConfig().getEntrypoint()) From 84babf6abf2e26ac699b1b9169b349ba6e709a0b Mon Sep 17 00:00:00 2001 From: admitrov Date: Thu, 7 May 2026 11:07:47 +0100 Subject: [PATCH 08/25] Migrate embedded-memsql to use custom MemSqlContainer extending JdbcDatabaseContainer Replace GenericContainer with MemSqlContainer extending JdbcDatabaseContainer. Add testcontainers-jdbc dependency. Implement MemSqlContainer with withDatabaseName/withUsername/withPassword/withLicenseKey API and JDBC-specific methods (getJdbcUrl, getDriverClassName, getTestQueryString). Update container initialization to use MemSqlContainer API and remove manual environment variable configuration. Add jdbcUrl to registered environment properties. --- embedded-memsql/pom.xml | 4 + .../EmbeddedMemSqlBootstrapConfiguration.java | 52 ++++++------ .../testcontainer/memsql/MemSqlContainer.java | 84 +++++++++++++++++++ 3 files changed, 112 insertions(+), 28 deletions(-) create mode 100644 embedded-memsql/src/main/java/com/playtika/testcontainer/memsql/MemSqlContainer.java diff --git a/embedded-memsql/pom.xml b/embedded-memsql/pom.xml index 3aae826fd..c32dc1cd9 100644 --- a/embedded-memsql/pom.xml +++ b/embedded-memsql/pom.xml @@ -23,6 +23,10 @@ com.playtika.testcontainers embedded-toxiproxy + + org.testcontainers + testcontainers-jdbc + org.mariadb.jdbc mariadb-java-client diff --git a/embedded-memsql/src/main/java/com/playtika/testcontainer/memsql/EmbeddedMemSqlBootstrapConfiguration.java b/embedded-memsql/src/main/java/com/playtika/testcontainer/memsql/EmbeddedMemSqlBootstrapConfiguration.java index d173eed62..27315ca61 100644 --- a/embedded-memsql/src/main/java/com/playtika/testcontainer/memsql/EmbeddedMemSqlBootstrapConfiguration.java +++ b/embedded-memsql/src/main/java/com/playtika/testcontainer/memsql/EmbeddedMemSqlBootstrapConfiguration.java @@ -17,7 +17,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; -import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; import org.testcontainers.toxiproxy.ToxiproxyContainer; import org.testcontainers.utility.MountableFile; @@ -48,7 +47,7 @@ MemSqlStatusCheck memSqlStartupCheckStrategy(MemSqlProperties properties) { @ConditionalOnToxiProxyEnabled(module = "memsql") ToxiproxyClientProxy memsqlContainerProxy(ToxiproxyClient toxiproxyClient, ToxiproxyContainer toxiproxyContainer, - @Qualifier(BEAN_NAME_EMBEDDED_MEMSQL) GenericContainer memsql, + @Qualifier(BEAN_NAME_EMBEDDED_MEMSQL) MemSqlContainer memsql, MemSqlProperties properties, ConfigurableEnvironment environment) { ToxiproxyClientProxy proxy = ToxiproxyHelper.createProxy( @@ -64,44 +63,41 @@ ToxiproxyClientProxy memsqlContainerProxy(ToxiproxyClient toxiproxyClient, } @Bean(name = BEAN_NAME_EMBEDDED_MEMSQL, destroyMethod = "stop") - public GenericContainer memsql(ConfigurableEnvironment environment, - MemSqlProperties properties, - MemSqlStatusCheck memSqlStatusCheck, - Optional network) { - GenericContainer memsql = new GenericContainer<>(ContainerUtils.getDockerImageName(properties)) - .withEnv("IGNORE_MIN_REQUIREMENTS", "1") - .withEnv("SINGLESTORE_SET_GLOBAL_DEFAULT_PARTITIONS_PER_LEAF", "1") - .withEnv("LICENSE_KEY", properties.getLicenseKey()) - .withEnv("SINGLESTORE_LICENSE", properties.getLicenseKey()) - .withEnv("ROOT_PASSWORD", properties.getPassword()) - .withEnv("START_AFTER_INIT", "Y") - .withExposedPorts(properties.port) + public MemSqlContainer memsql(ConfigurableEnvironment environment, + MemSqlProperties properties, + MemSqlStatusCheck memSqlStatusCheck, + Optional network) { + MemSqlContainer memsql = new MemSqlContainer(ContainerUtils.getDockerImageName(properties)) + .withDatabaseName(properties.getDatabase()) + .withUsername(properties.getUser()) + .withPassword(properties.getPassword()) + .withLicenseKey(properties.getLicenseKey()) .withCopyFileToContainer(MountableFile.forClasspathResource("mem.sql"), "/schema.sql") .waitingFor(memSqlStatusCheck) .withNetworkAliases(MEMSQL_NETWORK_ALIAS); - if ("aarch".equals(System.getProperty("system.arch"))){ + + if ("aarch".equals(System.getProperty("system.arch"))) { memsql = memsql.withCommand("platform", "linux/amd64"); } + network.ifPresent(memsql::withNetwork); - memsql = configureCommonsAndStart(memsql, properties, log); + memsql = (MemSqlContainer) configureCommonsAndStart(memsql, properties, log); registerMemSqlEnvironment(memsql, environment, properties); return memsql; } - private void registerMemSqlEnvironment(GenericContainer memsql, - ConfigurableEnvironment environment, - MemSqlProperties properties) { - Integer mappedPort = memsql.getMappedPort(properties.port); - String host = memsql.getHost(); - + private void registerMemSqlEnvironment(MemSqlContainer memsql, + ConfigurableEnvironment environment, + MemSqlProperties properties) { LinkedHashMap map = new LinkedHashMap<>(); - map.put("embedded.memsql.port", mappedPort); - map.put("embedded.memsql.host", host); - map.put("embedded.memsql.schema", properties.getDatabase()); - map.put("embedded.memsql.user", properties.getUser()); - map.put("embedded.memsql.password", properties.getPassword()); + map.put("embedded.memsql.port", memsql.getMappedPort(MemSqlContainer.MEMSQL_PORT)); + map.put("embedded.memsql.host", memsql.getHost()); + map.put("embedded.memsql.schema", memsql.getDatabaseName()); + map.put("embedded.memsql.user", memsql.getUsername()); + map.put("embedded.memsql.password", memsql.getPassword()); + map.put("embedded.memsql.jdbcUrl", memsql.getJdbcUrl()); map.put("embedded.memsql.networkAlias", MEMSQL_NETWORK_ALIAS); - map.put("embedded.memsql.internalPort", properties.getPort()); + map.put("embedded.memsql.internalPort", MemSqlContainer.MEMSQL_PORT); log.info("Started memsql server. Connection details {} ", map); diff --git a/embedded-memsql/src/main/java/com/playtika/testcontainer/memsql/MemSqlContainer.java b/embedded-memsql/src/main/java/com/playtika/testcontainer/memsql/MemSqlContainer.java new file mode 100644 index 000000000..4ea87d0bf --- /dev/null +++ b/embedded-memsql/src/main/java/com/playtika/testcontainer/memsql/MemSqlContainer.java @@ -0,0 +1,84 @@ +package com.playtika.testcontainer.memsql; + +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.utility.DockerImageName; + +public class MemSqlContainer extends JdbcDatabaseContainer { + + private static final DockerImageName DEFAULT_IMAGE_NAME = + DockerImageName.parse("ghcr.io/singlestore-labs/singlestoredb-dev"); + + static final int MEMSQL_PORT = 3306; + + private String database = "test_db"; + private String username = "root"; + private String password = "pass"; + private String licenseKey = ""; + + public MemSqlContainer(DockerImageName dockerImageName) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); + } + + public MemSqlContainer withDatabaseName(String database) { + this.database = database; + return self(); + } + + public MemSqlContainer withUsername(String username) { + this.username = username; + return self(); + } + + @Override + public MemSqlContainer withPassword(String password) { + this.password = password; + return self(); + } + + public MemSqlContainer withLicenseKey(String licenseKey) { + this.licenseKey = licenseKey; + return self(); + } + + @Override + protected void configure() { + addEnv("IGNORE_MIN_REQUIREMENTS", "1"); + addEnv("SINGLESTORE_SET_GLOBAL_DEFAULT_PARTITIONS_PER_LEAF", "1"); + addEnv("LICENSE_KEY", licenseKey); + addEnv("SINGLESTORE_LICENSE", licenseKey); + addEnv("ROOT_PASSWORD", password); + addEnv("START_AFTER_INIT", "Y"); + addExposedPort(MEMSQL_PORT); + } + + @Override + public String getDriverClassName() { + return "org.mariadb.jdbc.Driver"; + } + + @Override + public String getJdbcUrl() { + return "jdbc:mariadb://" + getHost() + ":" + getMappedPort(MEMSQL_PORT) + "/" + database; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getDatabaseName() { + return database; + } + + @Override + protected String getTestQueryString() { + return "SELECT 1"; + } +} From deb75e7464f0ab134383b301e49989927f738cbb Mon Sep 17 00:00:00 2001 From: admitrov Date: Thu, 7 May 2026 13:37:37 +0100 Subject: [PATCH 09/25] Migrate embedded-grafana to use Grafana LGTM stack container with Loki, Tempo, and OTLP support Replace GenericContainer with LgtmStackContainer and update default image to grafana/otel-lgtm:0.9.3. Add port configurations for Loki (3100), Tempo (3200), OTLP gRPC (4317), and OTLP HTTP (4318). Remove custom wait strategy and manual port exposure as they are handled by LgtmStackContainer. Update environment properties to expose all service ports. Comment out gitflow-incremental-builder plugin. Update README documentation with new image source and port configurations. Simplify test configuration. --- embedded-grafana/README.adoc | 27 +++++++++-- ...EmbeddedGrafanaBootstrapConfiguration.java | 42 +++++++---------- .../grafana/GrafanaProperties.java | 8 +++- ...ddedGrafanaBootstrapConfigurationTest.java | 45 ++++++++++++++++--- .../src/test/resources/bootstrap.yaml | 4 +- 5 files changed, 85 insertions(+), 41 deletions(-) diff --git a/embedded-grafana/README.adoc b/embedded-grafana/README.adoc index ea96ae1c6..1db178133 100644 --- a/embedded-grafana/README.adoc +++ b/embedded-grafana/README.adoc @@ -16,12 +16,27 @@ * `embedded.grafana.enabled` `(true|false, default is true)` * `embedded.grafana.reuseContainer` `(true|false, default is false)` -* `embedded.grafana.dockerImage` `(default is 'grafana/grafana:13.0.1')` -** Image versions on https://hub.docker.com/r/grafana/grafana/tags[dockerhub] +* `embedded.grafana.dockerImage` `(default is 'grafana/otel-lgtm:0.9.3')` +** Image versions on https://hub.docker.com/r/grafana/otel-lgtm/tags[dockerhub] * `embedded.grafana.networkAlias` `(default is 'grafana')` * `embedded.grafana.username` `(default is 'admin')` * `embedded.grafana.password` `(default is 'password')` +* `embedded.grafana.port` `(default is 3000, Grafana UI)` +* `embedded.grafana.lokiPort` `(default is 3100, Loki HTTP)` +* `embedded.grafana.tempoPort` `(default is 3200, Tempo HTTP)` +* `embedded.grafana.otlpGrpcPort` `(default is 4317, OTLP gRPC)` +* `embedded.grafana.otlpHttpPort` `(default is 4318, OTLP HTTP)` * `embedded.toxiproxy.proxies.grafana.enabled` Enables both creation of the container with ToxiProxy TCP proxy and a proxy to the `embedded-grafana` container. +* `embedded.grafana.filesToInclude` Classpath resources to copy into the container at startup. Use this to provision custom Grafana datasources, dashboards, or alert rules. Example `bootstrap.yaml`: + +[source,yaml] +---- +embedded: + grafana: + filesToInclude: + - classpathResource: /embedded/grafana/provisioning/datasources/my-datasource.yml + containerPath: /etc/grafana/provisioning/datasources/my-datasource.yml +---- ==== Produces @@ -30,8 +45,12 @@ * `embedded.grafana.port` * `embedded.grafana.username` * `embedded.grafana.password` -* `embedded.grafana.toxiproxy.host` -* `embedded.grafana.toxiproxy.port` * `embedded.grafana.networkAlias` * `embedded.grafana.internalPort` +* `embedded.grafana.loki.port` +* `embedded.grafana.tempo.port` +* `embedded.grafana.otlp.grpc.port` +* `embedded.grafana.otlp.http.port` +* `embedded.grafana.toxiproxy.host` +* `embedded.grafana.toxiproxy.port` * Bean `ToxiproxyClientProxy grafanaContainerProxy` diff --git a/embedded-grafana/src/main/java/com/playtika/testcontainer/grafana/EmbeddedGrafanaBootstrapConfiguration.java b/embedded-grafana/src/main/java/com/playtika/testcontainer/grafana/EmbeddedGrafanaBootstrapConfiguration.java index a88a080d0..c3a889709 100644 --- a/embedded-grafana/src/main/java/com/playtika/testcontainer/grafana/EmbeddedGrafanaBootstrapConfiguration.java +++ b/embedded-grafana/src/main/java/com/playtika/testcontainer/grafana/EmbeddedGrafanaBootstrapConfiguration.java @@ -10,17 +10,14 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; -import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; -import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; -import org.testcontainers.containers.wait.strategy.WaitStrategy; +import org.testcontainers.grafana.LgtmStackContainer; import org.testcontainers.toxiproxy.ToxiproxyContainer; import java.util.LinkedHashMap; @@ -39,20 +36,11 @@ public class EmbeddedGrafanaBootstrapConfiguration { private static final String GRAFANA_NETWORK_ALIAS = "grafana.testcontainer.docker"; - @Bean - @ConditionalOnMissingBean(name = "grafanaWaitStrategy") - public WaitStrategy grafanaWaitStrategy(GrafanaProperties properties) { - return new HttpWaitStrategy() - .forPath("/") - .forPort(properties.getPort()) - .forStatusCode(200); - } - @Bean @ConditionalOnToxiProxyEnabled(module = "grafana") ToxiproxyClientProxy grafanaContainerProxy(ToxiproxyClient toxiproxyClient, ToxiproxyContainer toxiproxyContainer, - @Qualifier(GRAFANA_BEAN_NAME) GenericContainer grafana, + @Qualifier(GRAFANA_BEAN_NAME) LgtmStackContainer grafana, GrafanaProperties properties, ConfigurableEnvironment environment) { ToxiproxyClientProxy proxy = ToxiproxyHelper.createProxy( @@ -68,19 +56,16 @@ ToxiproxyClientProxy grafanaContainerProxy(ToxiproxyClient toxiproxyClient, } @Bean(name = GRAFANA_BEAN_NAME, destroyMethod = "stop") - public GenericContainer grafana(ConfigurableEnvironment environment, - GrafanaProperties properties, - WaitStrategy grafanaWaitStrategy, - Optional network) { + public LgtmStackContainer grafana(ConfigurableEnvironment environment, + GrafanaProperties properties, + Optional network) { - GenericContainer container = - new GenericContainer<>(ContainerUtils.getDockerImageName(properties)) + LgtmStackContainer container = + new LgtmStackContainer(ContainerUtils.getDockerImageName(properties)) .withEnv("GF_SECURITY_ADMIN_USER", properties.getUsername()) .withEnv("GF_SECURITY_ADMIN_PASSWORD", properties.getPassword()) - .withExposedPorts(properties.getPort()) .withNetwork(Network.SHARED) - .withNetworkAliases(properties.getNetworkAlias(), GRAFANA_NETWORK_ALIAS) - .waitingFor(grafanaWaitStrategy); + .withNetworkAliases(properties.getNetworkAlias(), GRAFANA_NETWORK_ALIAS); network.ifPresent(container::withNetwork); @@ -91,22 +76,25 @@ public GenericContainer grafana(ConfigurableEnvironment environment, return container; } - private void registerEnvironment(GenericContainer grafana, + private void registerEnvironment(LgtmStackContainer grafana, ConfigurableEnvironment environment, GrafanaProperties properties) { - Integer mappedPort = grafana.getMappedPort(properties.port); String host = grafana.getHost(); LinkedHashMap map = new LinkedHashMap<>(); map.put("embedded.grafana.host", host); - map.put("embedded.grafana.port", mappedPort); + map.put("embedded.grafana.port", grafana.getMappedPort(properties.getPort())); map.put("embedded.grafana.username", properties.getUsername()); map.put("embedded.grafana.password", properties.getPassword()); map.put("embedded.grafana.networkAlias", GRAFANA_NETWORK_ALIAS); map.put("embedded.grafana.internalPort", properties.getPort()); + map.put("embedded.grafana.loki.port", grafana.getMappedPort(properties.getLokiPort())); + map.put("embedded.grafana.tempo.port", grafana.getMappedPort(properties.getTempoPort())); + map.put("embedded.grafana.otlp.grpc.port", grafana.getMappedPort(properties.getOtlpGrpcPort())); + map.put("embedded.grafana.otlp.http.port", grafana.getMappedPort(properties.getOtlpHttpPort())); - log.info("Started Grafana server. Connection details: {}", map); + log.info("Started Grafana LGTM stack. Connection details: {}", map); MapPropertySource propertySource = new MapPropertySource("embeddedGrafanaInfo", map); environment.getPropertySources().addFirst(propertySource); diff --git a/embedded-grafana/src/main/java/com/playtika/testcontainer/grafana/GrafanaProperties.java b/embedded-grafana/src/main/java/com/playtika/testcontainer/grafana/GrafanaProperties.java index 214901245..b3d0dcb48 100644 --- a/embedded-grafana/src/main/java/com/playtika/testcontainer/grafana/GrafanaProperties.java +++ b/embedded-grafana/src/main/java/com/playtika/testcontainer/grafana/GrafanaProperties.java @@ -17,12 +17,16 @@ public class GrafanaProperties extends CommonContainerProperties { String username = "admin"; String password = "password"; int port = 3000; + int lokiPort = 3100; + int tempoPort = 3200; + int otlpGrpcPort = 4317; + int otlpHttpPort = 4318; - // https://hub.docker.com/r/grafana/grafana + // https://hub.docker.com/r/grafana/otel-lgtm @Override public String getDefaultDockerImage() { // Please don`t remove this comment. // renovate: datasource=docker - return "grafana/grafana:13.0.1"; + return "grafana/otel-lgtm:0.9.3"; } } diff --git a/embedded-grafana/src/test/java/com/playtika/testcontainer/grafana/EmbeddedGrafanaBootstrapConfigurationTest.java b/embedded-grafana/src/test/java/com/playtika/testcontainer/grafana/EmbeddedGrafanaBootstrapConfigurationTest.java index 87392bbb4..8c32da890 100644 --- a/embedded-grafana/src/test/java/com/playtika/testcontainer/grafana/EmbeddedGrafanaBootstrapConfigurationTest.java +++ b/embedded-grafana/src/test/java/com/playtika/testcontainer/grafana/EmbeddedGrafanaBootstrapConfigurationTest.java @@ -6,21 +6,26 @@ import org.springframework.web.util.UriComponentsBuilder; import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.equalTo; class EmbeddedGrafanaBootstrapConfigurationTest extends BaseEmbeddedGrafanaTest { @Value("${embedded.grafana.username}") private String username; @Value("${embedded.grafana.password}") private String password; + @Value("${embedded.grafana.loki.port}") + private int lokiPort; + @Value("${embedded.grafana.tempo.port}") + private int tempoPort; + @Value("${embedded.grafana.otlp.http.port}") + private int otlpHttpPort; @Test - void shouldProvisionDatasourceFromConfigurationFile() { + void grafanaApiShouldBeReachable() { UriComponents uriComponents = UriComponentsBuilder.newInstance() .scheme("http") .host(grafanaHost) .port(grafanaPort) - .path("/api/datasources/name/Prometheus") + .path("/api/health") .build(); given() @@ -30,8 +35,38 @@ void shouldProvisionDatasourceFromConfigurationFile() { .get(uriComponents.toUriString()) .then() .assertThat() - .body("url", equalTo("http://prometheus:9090")) .statusCode(200); } -} \ No newline at end of file + @Test + void lokiShouldBeReachable() { + UriComponents uriComponents = UriComponentsBuilder.newInstance() + .scheme("http") + .host(grafanaHost) + .port(lokiPort) + .path("/ready") + .build(); + + given() + .get(uriComponents.toUriString()) + .then() + .assertThat() + .statusCode(200); + } + + @Test + void tempoShouldBeReachable() { + UriComponents uriComponents = UriComponentsBuilder.newInstance() + .scheme("http") + .host(grafanaHost) + .port(tempoPort) + .path("/ready") + .build(); + + given() + .get(uriComponents.toUriString()) + .then() + .assertThat() + .statusCode(200); + } +} diff --git a/embedded-grafana/src/test/resources/bootstrap.yaml b/embedded-grafana/src/test/resources/bootstrap.yaml index 9c597cc98..47c9d9ac8 100644 --- a/embedded-grafana/src/test/resources/bootstrap.yaml +++ b/embedded-grafana/src/test/resources/bootstrap.yaml @@ -1,5 +1,3 @@ embedded: grafana: - filesToInclude: - - classpathResource: /embedded/grafana/provisioning/datasources/datasource.yml - containerPath: /etc/grafana/provisioning/datasources/datasource.yml \ No newline at end of file + enabled: true From 8f9f84f126272b3958c1d8882acbe80f368ff04e Mon Sep 17 00:00:00 2001 From: admitrov Date: Thu, 7 May 2026 13:39:34 +0100 Subject: [PATCH 10/25] Migrate embedded-wiremock to use WireMockContainer from wiremock-testcontainers-module Replace GenericContainer with WireMockContainer and add wiremock-testcontainers-module dependency. Remove custom wait strategy and port configuration as they are handled by WireMockContainer. Update container initialization to use WireMockContainer API and remove manual command/port exposure. Replace Lombok @Slf4j with standard LoggerFactory. Update registered environment properties to use container's getPort() and getHost() methods with hardcoded internal port 8080. Remove port property from WiremockProperties. --- embedded-wiremock/pom.xml | 8 +++- ...mbeddedWiremockBootstrapConfiguration.java | 41 ++++++++----------- .../wiremock/WiremockProperties.java | 1 - 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/embedded-wiremock/pom.xml b/embedded-wiremock/pom.xml index b50cb3777..8beee0baf 100644 --- a/embedded-wiremock/pom.xml +++ b/embedded-wiremock/pom.xml @@ -15,6 +15,7 @@ 3.13.2 + 1.0-alpha-13 @@ -22,6 +23,11 @@ com.playtika.testcontainers testcontainers-common + + org.wiremock.integrations.testcontainers + wiremock-testcontainers-module + ${wiremock-testcontainers-module.version} + org.wiremock @@ -36,4 +42,4 @@ test - \ No newline at end of file + diff --git a/embedded-wiremock/src/main/java/com/playtika/testcontainers/wiremock/EmbeddedWiremockBootstrapConfiguration.java b/embedded-wiremock/src/main/java/com/playtika/testcontainers/wiremock/EmbeddedWiremockBootstrapConfiguration.java index 3ce43c1a8..f6e9c1f0f 100644 --- a/embedded-wiremock/src/main/java/com/playtika/testcontainers/wiremock/EmbeddedWiremockBootstrapConfiguration.java +++ b/embedded-wiremock/src/main/java/com/playtika/testcontainers/wiremock/EmbeddedWiremockBootstrapConfiguration.java @@ -2,7 +2,8 @@ import com.playtika.testcontainer.common.spring.DockerPresenceBootstrapConfiguration; import com.playtika.testcontainer.common.utils.ContainerUtils; -import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -11,17 +12,14 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; -import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; -import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.containers.wait.strategy.WaitStrategy; +import org.wiremock.integrations.testcontainers.WireMockContainer; import java.util.LinkedHashMap; import java.util.Optional; import static com.playtika.testcontainer.common.utils.ContainerUtils.configureCommonsAndStart; -@Slf4j @Configuration @ConditionalOnExpression("${embedded.containers.enabled:true}") @AutoConfigureAfter(DockerPresenceBootstrapConfiguration.class) @@ -29,39 +27,32 @@ @EnableConfigurationProperties(WiremockProperties.class) public class EmbeddedWiremockBootstrapConfiguration { + private static final Logger log = LoggerFactory.getLogger(EmbeddedWiremockBootstrapConfiguration.class); + static final String BEAN_NAME_EMBEDDED_WIREMOCK = "embeddedWiremock"; private static final String WIREMOCK_NETWORK_ALIAS = "wiremock.testcontainer.docker"; - private static final WaitStrategy DEFAULT_WAITER = Wait.forHttp("/__admin/mappings") - .withMethod("GET") - .forStatusCode(200); @Bean(value = BEAN_NAME_EMBEDDED_WIREMOCK, destroyMethod = "stop") - public GenericContainer wiremockContainer(ConfigurableEnvironment environment, - WiremockProperties properties, - Optional network) { - GenericContainer wiremock = - new GenericContainer<>(ContainerUtils.getDockerImageName(properties)) - .waitingFor(DEFAULT_WAITER) - .withCommand("--port " + properties.getPort()) - .withExposedPorts(properties.getPort()) + public WireMockContainer wiremockContainer(ConfigurableEnvironment environment, + WiremockProperties properties, + Optional network) { + WireMockContainer wiremock = + new WireMockContainer(ContainerUtils.getDockerImageName(properties)) .withNetworkAliases(WIREMOCK_NETWORK_ALIAS); network.ifPresent(wiremock::withNetwork); - wiremock = configureCommonsAndStart(wiremock, properties, log); - registerWiremockEnvironment(wiremock, environment, properties); + wiremock = (WireMockContainer) configureCommonsAndStart(wiremock, properties, log); + registerWiremockEnvironment(wiremock, environment); return wiremock; } - private void registerWiremockEnvironment(GenericContainer container, ConfigurableEnvironment environment, WiremockProperties properties) { - Integer mappedPort = container.getMappedPort(properties.getPort()); - String host = container.getHost(); - + private void registerWiremockEnvironment(WireMockContainer container, ConfigurableEnvironment environment) { LinkedHashMap map = new LinkedHashMap<>(); - map.put("embedded.wiremock.port", mappedPort); - map.put("embedded.wiremock.host", host); + map.put("embedded.wiremock.port", container.getPort()); + map.put("embedded.wiremock.host", container.getHost()); map.put("embedded.wiremock.networkAlias", WIREMOCK_NETWORK_ALIAS); - map.put("embedded.wiremock.internalPort", properties.getPort()); + map.put("embedded.wiremock.internalPort", 8080); log.info("Started wiremock. Connection Details: {}", map); diff --git a/embedded-wiremock/src/main/java/com/playtika/testcontainers/wiremock/WiremockProperties.java b/embedded-wiremock/src/main/java/com/playtika/testcontainers/wiremock/WiremockProperties.java index 9095f86bd..5420f4527 100644 --- a/embedded-wiremock/src/main/java/com/playtika/testcontainers/wiremock/WiremockProperties.java +++ b/embedded-wiremock/src/main/java/com/playtika/testcontainers/wiremock/WiremockProperties.java @@ -11,7 +11,6 @@ public class WiremockProperties extends CommonContainerProperties { private String host = "localhost"; - private int port = 8990; // https://hub.docker.com/r/wiremock/wiremock @Override From d07eab6e196e880520fcd3eb02dddf515cb5fce2 Mon Sep 17 00:00:00 2001 From: admitrov Date: Thu, 7 May 2026 14:12:36 +0100 Subject: [PATCH 11/25] Migrate embedded-nats to use NatsContainer from nats-testcontainers library Replace GenericContainer with NatsContainer and add nats-testcontainers dependency. Remove custom wait strategy, port configuration, and nats-server.conf as they are handled by NatsContainer. Update container initialization to use NatsContainer API and remove manual port exposure. Replace Lombok @Slf4j with standard LoggerFactory. Update registered environment properties to use container's getClientPort(), getHttpMonitoringPort(), and getRoutingPort() methods with hardcoded internal ports. Remove port properties from NatsProperties. --- embedded-nats/README.adoc | 8 ++- embedded-nats/pom.xml | 8 ++- .../EmbeddedNatsBootstrapConfiguration.java | 60 +++++++------------ 3 files changed, 36 insertions(+), 40 deletions(-) diff --git a/embedded-nats/README.adoc b/embedded-nats/README.adoc index 2307a5a6d..56b683dfa 100644 --- a/embedded-nats/README.adoc +++ b/embedded-nats/README.adoc @@ -33,11 +33,17 @@ * `embedded.nats.internalClientPort` * `embedded.nats.internalHttpMonitorPort` * `embedded.nats.internalRouteConnectionsPort` +* Bean `NatsContainer embeddedNats` * Bean `ToxiproxyClientProxy natsContainerProxy` ==== Notes -Nats container has no security enabled, you can use any credentials. +Uses https://github.com/amadeusitgroup/nats-testcontainers[NatsContainer] from `io.github.amadeusitgroup.testcontainers:nats`. +Exposes ports 4222 (client), 6222 (routing), 8222 (HTTP monitoring). +No security enabled by default; any credentials are accepted. + +To enable JetStream or authentication, inject the `NatsContainer` bean and configure it before the context starts, +or use the `embedded.nats.command` property to pass flags (e.g. `--jetstream`). ===== Create client with ToxiProxy You can also create client pointed at ToxiProxy TCP proxy: diff --git a/embedded-nats/pom.xml b/embedded-nats/pom.xml index 4f32b24f4..0f6491bd8 100644 --- a/embedded-nats/pom.xml +++ b/embedded-nats/pom.xml @@ -14,6 +14,7 @@ embedded-nats + 1.0.9 2.25.3 @@ -22,6 +23,11 @@ com.playtika.testcontainers testcontainers-common + + io.github.amadeusitgroup.testcontainers + nats + ${nats-testcontainers.version} + org.testcontainers testcontainers-toxiproxy @@ -42,4 +48,4 @@ ${jnats.version} - \ No newline at end of file + diff --git a/embedded-nats/src/main/java/com/playtika/testcontainer/nats/EmbeddedNatsBootstrapConfiguration.java b/embedded-nats/src/main/java/com/playtika/testcontainer/nats/EmbeddedNatsBootstrapConfiguration.java index 06e98b43c..ae4173975 100644 --- a/embedded-nats/src/main/java/com/playtika/testcontainer/nats/EmbeddedNatsBootstrapConfiguration.java +++ b/embedded-nats/src/main/java/com/playtika/testcontainer/nats/EmbeddedNatsBootstrapConfiguration.java @@ -7,7 +7,9 @@ import com.playtika.testcontainer.toxiproxy.ToxiproxyHelper; import com.playtika.testcontainer.toxiproxy.condition.ConditionalOnToxiProxyEnabled; import eu.rekawek.toxiproxy.ToxiproxyClient; -import lombok.extern.slf4j.Slf4j; +import io.github.amadeusitgroup.testcontainers.nats.NatsContainer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -17,13 +19,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; -import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; -import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy; -import org.testcontainers.containers.wait.strategy.WaitAllStrategy; -import org.testcontainers.containers.wait.strategy.WaitStrategy; import org.testcontainers.toxiproxy.ToxiproxyContainer; -import org.testcontainers.utility.MountableFile; import java.util.LinkedHashMap; import java.util.Optional; @@ -32,7 +29,6 @@ import static com.playtika.testcontainer.nats.NatsProperties.BEAN_NAME_EMBEDDED_NATS; import static com.playtika.testcontainer.nats.NatsProperties.BEAN_NAME_EMBEDDED_NATS_TOXI_PROXY; -@Slf4j @Configuration @ConditionalOnExpression("${embedded.containers.enabled:true}") @AutoConfigureAfter({DockerPresenceBootstrapConfiguration.class, EmbeddedToxiProxyBootstrapConfiguration.class}) @@ -40,20 +36,21 @@ @EnableConfigurationProperties(NatsProperties.class) public class EmbeddedNatsBootstrapConfiguration { + private static final Logger log = LoggerFactory.getLogger(EmbeddedNatsBootstrapConfiguration.class); + private static final String NATS_NETWORK_ALIAS = "nats.testcontainer.docker"; @Bean(name = BEAN_NAME_EMBEDDED_NATS_TOXI_PROXY) @ConditionalOnToxiProxyEnabled(module = "nats") ToxiproxyClientProxy natsContainerProxy(ToxiproxyClient toxiproxyClient, ToxiproxyContainer toxiproxyContainer, - @Qualifier(BEAN_NAME_EMBEDDED_NATS) GenericContainer natsContainer, - NatsProperties properties, + @Qualifier(BEAN_NAME_EMBEDDED_NATS) NatsContainer natsContainer, ConfigurableEnvironment environment) { ToxiproxyClientProxy proxy = ToxiproxyHelper.createProxy( toxiproxyClient, toxiproxyContainer, natsContainer, - properties.getClientPort(), + NatsContainer.DEFAULT_NATS_CLIENT_PORT, "nats"); ToxiproxyHelper.registerProxyEnvironment(proxy, "embedded.nats", "embeddedNatsToxiproxyInfo", environment); @@ -62,45 +59,32 @@ ToxiproxyClientProxy natsContainerProxy(ToxiproxyClient toxiproxyClient, } @Bean(name = BEAN_NAME_EMBEDDED_NATS, destroyMethod = "stop") - public GenericContainer natsContainer(ConfigurableEnvironment environment, - NatsProperties properties, - Optional network) { - WaitStrategy waitStrategy = new WaitAllStrategy() - .withStrategy(new HostPortWaitStrategy()) - .withStartupTimeout(properties.getTimeoutDuration()); - - GenericContainer natsContainer = new GenericContainer<>(ContainerUtils.getDockerImageName(properties)) - .withExposedPorts(properties.getClientPort(), properties.getHttpMonitorPort(), properties.getRouteConnectionsPort()) - .withCopyFileToContainer(MountableFile.forClasspathResource("nats-server.conf"), "/nats-server.conf") - .waitingFor(waitStrategy) + public NatsContainer natsContainer(ConfigurableEnvironment environment, + NatsProperties properties, + Optional network) { + NatsContainer natsContainer = new NatsContainer(ContainerUtils.getDockerImageName(properties)) .withNetworkAliases(NATS_NETWORK_ALIAS); network.ifPresent(natsContainer::withNetwork); - natsContainer = configureCommonsAndStart(natsContainer, properties, log); + natsContainer = (NatsContainer) configureCommonsAndStart(natsContainer, properties, log); - registerNatsEnvironment(natsContainer, environment, properties); + registerNatsEnvironment(natsContainer, environment); return natsContainer; } - private void registerNatsEnvironment(GenericContainer natsContainer, - ConfigurableEnvironment environment, - NatsProperties properties) { - Integer clientMappedPort = natsContainer.getMappedPort(properties.getClientPort()); - Integer httpMonitorMappedPort = natsContainer.getMappedPort(properties.getHttpMonitorPort()); - Integer routeConnectionsMappedPort = natsContainer.getMappedPort(properties.getRouteConnectionsPort()); - String host = natsContainer.getHost(); - + private void registerNatsEnvironment(NatsContainer natsContainer, + ConfigurableEnvironment environment) { LinkedHashMap map = new LinkedHashMap<>(); - map.put("embedded.nats.host", host); - map.put("embedded.nats.port", clientMappedPort); - map.put("embedded.nats.httpMonitorPort", httpMonitorMappedPort); - map.put("embedded.nats.routeConnectionsPort", routeConnectionsMappedPort); + map.put("embedded.nats.host", natsContainer.getHost()); + map.put("embedded.nats.port", natsContainer.getClientPort()); + map.put("embedded.nats.httpMonitorPort", natsContainer.getHttpMonitoringPort()); + map.put("embedded.nats.routeConnectionsPort", natsContainer.getRoutingPort()); map.put("embedded.nats.networkAlias", NATS_NETWORK_ALIAS); - map.put("embedded.nats.internalClientPort", properties.getClientPort()); - map.put("embedded.nats.internalHttpMonitorPort", properties.getHttpMonitorPort()); - map.put("embedded.nats.internalRouteConnectionsPort", properties.getRouteConnectionsPort()); + map.put("embedded.nats.internalClientPort", NatsContainer.DEFAULT_NATS_CLIENT_PORT); + map.put("embedded.nats.internalHttpMonitorPort", NatsContainer.DEFAULT_NATS_HTTP_MONITORING_PORT); + map.put("embedded.nats.internalRouteConnectionsPort", NatsContainer.DEFAULT_NATS_ROUTING_PORT); log.info("Started NATS server. Connection details {}", map); From 26dd15799e5625b38df1924170527410cc11d455 Mon Sep 17 00:00:00 2001 From: admitrov Date: Thu, 7 May 2026 14:13:31 +0100 Subject: [PATCH 12/25] Update embedded-wiremock README to reflect WireMockContainer migration and remove port configuration Remove port property from configuration section and update bean type from GenericContainer to WireMockContainer in produces section. --- embedded-wiremock/README.adoc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/embedded-wiremock/README.adoc b/embedded-wiremock/README.adoc index e2f2d3042..9290b165d 100644 --- a/embedded-wiremock/README.adoc +++ b/embedded-wiremock/README.adoc @@ -18,7 +18,6 @@ * `embedded.wiremock.reuseContainer` `(true|false, default is false)` * `embedded.wiremock.dockerImage` `(default is 'wiremock/wiremock:3.13.2')` * `embedded.wiremock.host` `(default is 'localhost')` -* `embedded.wiremock.port` `(int, default is 8990)` ==== Produces @@ -27,7 +26,7 @@ * `embedded.wiremock.port` (mapped HTTP port) * `embedded.wiremock.networkAlias` * `embedded.wiremock.internalPort` -* Bean `GenericContainer embeddedWiremock` +* Bean `WireMockContainer embeddedWiremock` ==== Example From 07fe840f2a7c0a92e95ebecd2ee3406db10a478f Mon Sep 17 00:00:00 2001 From: admitrov Date: Thu, 7 May 2026 19:34:30 +0100 Subject: [PATCH 13/25] Migrate embedded-selenium to use Selenium module from testcontainers-selenium library Replace BrowserWebDriverContainer import from core with testcontainers-selenium module. Remove deprecated wait strategy implementation and capabilities/docker-image conditional logic. Update container initialization to use BrowserWebDriverContainer constructor with getDockerImageName(). Remove unused utility methods (isNotBlank, getWaitStrategy). Update README to reflect browser selection via Docker image instead of capabilities. Simplify TestcontainerScope and TestcontainerTestExecutionListener implementations. Replace indexOf with contains for OS detection. --- embedded-selenium/README.adoc | 143 +++++++++--------- ...mbeddedSeleniumBootstrapConfiguration.java | 48 ++---- .../selenium/VncRecordingMode.java | 6 +- ...TestcontainerContextCustomizerFactory.java | 7 +- .../testscope/TestcontainerScope.java | 20 +-- .../TestcontainerTestExecutionListener.java | 8 +- 6 files changed, 92 insertions(+), 140 deletions(-) diff --git a/embedded-selenium/README.adoc b/embedded-selenium/README.adoc index bb71940ed..fa5fcc9bf 100644 --- a/embedded-selenium/README.adoc +++ b/embedded-selenium/README.adoc @@ -15,10 +15,10 @@ ==== Consumes (via `bootstrap.properties`) * `embedded.selenium.enabled` `(true|false, default is true)` -* `embedded.selenium.browser` `(enum, FIREFOX|CHROMIUM, default is 'CHROMIUM')` -* `embedded.selenium.docker-image` `(Docker image like "selenium/standalone-chrome-debug:3.141.59")` -** Image versions on https://hub.docker.com/u/selenium[dockerhub] repositories -** Prefix "standalone-" has a browser installed, suffix "-debug" additionally a VNC server for https://github.com/SeleniumHQ/docker-selenium[docker-selenium] +* `embedded.selenium.docker-image` `(Docker image, e.g. "selenium/standalone-chrome:4.x.x")` +** Available images on https://hub.docker.com/u/selenium[Docker Hub] +** Use `selenium/standalone-chrome`, `selenium/standalone-firefox`, or `selenium/standalone-edge` +* `embedded.selenium.arguments` `(list of browser arguments passed to ChromeOptions/FirefoxOptions)` * `embedded.selenium.vnc.mode` `RECORD_ALL | SKIP | RECORD_FAILING` * `embedded.selenium.vnc.recording-dir` `a file location` * `embedded.toxiproxy.proxies.selenium.enabled` Enables both creation of the container with ToxiProxy TCP proxy and a proxy to the `embedded-selenium` container. @@ -29,7 +29,7 @@ * `embedded.selenium.port` * `embedded.selenium.host` * `embedded.selenium.vnc.host` -* `embedded.selenium.vnc.port` (mapped HTTP port) +* `embedded.selenium.vnc.port` * `embedded.selenium.vnc.username` * `embedded.selenium.vnc.password` * `embedded.selenium.toxiproxy.host` @@ -37,91 +37,92 @@ * `embedded.selenium.networkAlias` * Bean `ToxiproxyClientProxy seleniumContainerProxy` -==== Example +==== Browser selection -There is currently no starter library for using selenium server so there is no need to -use the produced variables. You can control the configuration of the browser in 2 ways. -The first is via the properties file: +The browser is determined by the Docker image, not by capabilities. +Use the appropriate standalone image for the browser you need: -./src/test/resources/application.yaml -[source,yaml] +[cols="1,2"] +|=== +|Browser |Image + +|Chrome +|`selenium/standalone-chrome:4.x.x` + +|Firefox +|`selenium/standalone-firefox:4.x.x` + +|Edge +|`selenium/standalone-edge:4.x.x` +|=== + +==== Usage + +The container exposes a standard Selenium WebDriver endpoint. Create a `RemoteWebDriver` using the container's address and pass browser options at session creation time: + +[source,java] ---- -embedded: - selenium: - enabled: true - browser: CHROMIUM - arguments: - - ignore-certificate-errors +@Autowired +private BrowserWebDriverContainer container; +@Test +void test() { + ChromeOptions options = new ChromeOptions(); + options.addArguments("--headless"); + + RemoteWebDriver driver = new RemoteWebDriver(container.getSeleniumAddress(), options); + driver.get("http://" + dockerHostname + ":" + port); + assertThat(driver.getTitle()).isEqualTo("Hello World"); +} ---- -The second (which may be more configurable) is via defining either a -ChromeOptions class or a FirefoxOptions class in your test class: +Browser arguments from `embedded.selenium.arguments` are pre-applied via the auto-configured `ChromeOptions` or `FirefoxOptions` bean. You can override or extend it with a `@TestConfiguration`: + [source,java] ------------------ - @TestConfiguration - static class LocalTestConfiguration { - - @Bean - public FirefoxOptions firefoxOptions(SeleniumProperties properties) { - - FirefoxOptions options = new FirefoxOptions(); - properties.apply(options); - options.addArguments("hello-world"); - options.setCapability(CapabilityType.ACCEPT_SSL_CERTS, false); - options.setCapability(CapabilityType.ACCEPT_INSECURE_CERTS, false); - return options; - } +---- +@TestConfiguration +static class TestConfig { + + @Bean + public ChromeOptions chromeOptions(SeleniumProperties properties) { + ChromeOptions options = new ChromeOptions(); + properties.apply(options); + options.addArguments("--disable-gpu"); + return options; } ------------------ -If you are testing your local machine you can access the hostname via the annotation +} +---- + +To get the Docker host address (e.g. to reach your Spring Boot app from inside the container): [source,java] ------------------ - @DockerHostname - private String hostname; ------------------ +---- +@DockerHostname +private String dockerHostname; +---- -An example test class would look like the following: +Full example: [source,java] ------------------ -import org.openqa.selenium.WebDriver;@RunWith(SpringRunner.class) -@SpringBootTest( - classes = SpringBootWebApplication.class, - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT -) -@Import(TestLoginPage.LocalTestConfiguration.class) -public class TestLoginPage { +---- +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class MySeleniumTest { + + @Autowired + private BrowserWebDriverContainer container; @LocalServerPort private int port; @DockerHostname - private String hostname; - - @Autowired - private BrowserWebDriverContainer webDriverContainer; + private String dockerHostname; @Test - public void test1() { - RemoteWebDriver webDriver = webDriverContainer.getWebDriver(); - webDriver.get("https://" + hostname +":" + port); - assertThat(webDriver.getPageSource()).contains("helloworld"); - } - - @TestConfiguration - static class LocalTestConfiguration { - - @Bean - public FirefoxOptions firefoxOptions(SeleniumProperties properties) { - - FirefoxOptions options = new FirefoxOptions(); - properties.apply(options); - options.setCapability(CapabilityType.ACCEPT_SSL_CERTS, true); - options.setCapability(CapabilityType.ACCEPT_INSECURE_CERTS, true); - return options; - } + void pageLoads() { + RemoteWebDriver driver = new RemoteWebDriver( + container.getSeleniumAddress(), new ChromeOptions()); + driver.get("http://" + dockerHostname + ":" + port + "/index.html"); + assertThat(driver.getTitle()).isEqualTo("My App"); } } ------------------ +---- diff --git a/embedded-selenium/src/main/java/com/playtika/testcontainer/selenium/EmbeddedSeleniumBootstrapConfiguration.java b/embedded-selenium/src/main/java/com/playtika/testcontainer/selenium/EmbeddedSeleniumBootstrapConfiguration.java index 5033beeab..98de726af 100644 --- a/embedded-selenium/src/main/java/com/playtika/testcontainer/selenium/EmbeddedSeleniumBootstrapConfiguration.java +++ b/embedded-selenium/src/main/java/com/playtika/testcontainer/selenium/EmbeddedSeleniumBootstrapConfiguration.java @@ -16,31 +16,25 @@ import org.springframework.context.annotation.Bean; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; -import org.testcontainers.containers.BrowserWebDriverContainer; import org.testcontainers.containers.Container; import org.testcontainers.containers.ContainerLaunchException; import org.testcontainers.containers.DefaultRecordingFileFactory; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; import org.testcontainers.containers.RecordingFileFactory; -import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy; -import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; -import org.testcontainers.containers.wait.strategy.WaitAllStrategy; -import org.testcontainers.containers.wait.strategy.WaitStrategy; +import org.testcontainers.selenium.BrowserWebDriverContainer; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URL; import java.nio.file.Files; -import java.time.Duration; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; import java.util.Optional; import static com.playtika.testcontainer.selenium.SeleniumProperties.BEAN_NAME_EMBEDDED_SELENIUM; -import static java.time.temporal.ChronoUnit.SECONDS; @Slf4j @@ -81,17 +75,11 @@ public FirefoxOptions firefoxOptions(SeleniumProperties properties) { @ConditionalOnMissingBean public BrowserWebDriverContainer selenium(ConfigurableEnvironment environment, SeleniumProperties properties, - MutableCapabilities capabilities, Optional network) { - BrowserWebDriverContainer container = isNotBlank(properties.getDockerImage()) - ? new BrowserWebDriverContainer<>(ContainerUtils.getDockerImageName(properties)) - : new BrowserWebDriverContainer<>(); - - container.waitingFor(getWaitStrategy()); - container.withCapabilities(capabilities); - container.withRecordingFileFactory(getRecordingFileFactory()); - container.withNetworkAliases(SELENIUM_NETWORK_ALIAS); + BrowserWebDriverContainer container = new BrowserWebDriverContainer(ContainerUtils.getDockerImageName(properties)) + .withRecordingFileFactory(getRecordingFileFactory()) + .withNetworkAliases(SELENIUM_NETWORK_ALIAS); network.ifPresent(container::withNetwork); File recordingDirOrNull = null; if (properties.getVnc().getMode().convert() != BrowserWebDriverContainer.VncRecordingMode.SKIP) { @@ -106,19 +94,6 @@ public BrowserWebDriverContainer selenium(ConfigurableEnvironment environment, return container; } - //See: https://github.com/testcontainers/testcontainers-java/pull/4357 - @Deprecated - private WaitStrategy getWaitStrategy() { - WaitStrategy logWaitStrategy = new LogMessageWaitStrategy() - .withRegEx(".*(RemoteWebDriver instances should connect to|Selenium Server is up and running).*\n") - .withStartupTimeout(Duration.of(60, SECONDS)); - - return new WaitAllStrategy() - .withStrategy(logWaitStrategy) - .withStrategy(new HostPortWaitStrategy()) - .withStartupTimeout(Duration.of(60, SECONDS)); - } - /** * Testcontainers does not expose its default vnc dir when it is not * defined, so we recreate this implementation here. @@ -187,11 +162,11 @@ public String getHostName(GenericContainer container) { // unfortunately host.docker.internal only works for mac and windows :( // and we need to work out the hostname for linux. String OS = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH); - if ((OS.indexOf("mac") >= 0) || (OS.indexOf("darwin") >= 0)) { + if ((OS.contains("mac")) || (OS.contains("darwin"))) { return "host.docker.internal"; - } else if (OS.indexOf("win") >= 0) { + } else if (OS.contains("win")) { return "host.docker.internal"; - } else if (OS.indexOf("nux") >= 0) { + } else if (OS.contains("nux")) { Container.ExecResult execResult; try { execResult = container.execInContainer("/sbin/ip route|awk '/default/ { print $3 }'"); @@ -210,9 +185,7 @@ public String getHostName(GenericContainer container) { } } - // currently only supported if docker machine is installed. - // otherwise throws an UnsupportedOpertaion exception - return container.getTestHostIpAddress(); + return DOCKER_FOR_LINUX_STATIC_IP; } private boolean isValidIpAddress(String ipAddress) { @@ -222,7 +195,4 @@ private boolean isValidIpAddress(String ipAddress) { return InetAddresses.isInetAddress(ipAddress); } - private boolean isNotBlank(String str) { - return str != null && !str.trim().isEmpty(); - } -} \ No newline at end of file +} diff --git a/embedded-selenium/src/main/java/com/playtika/testcontainer/selenium/VncRecordingMode.java b/embedded-selenium/src/main/java/com/playtika/testcontainer/selenium/VncRecordingMode.java index d28ab7be3..95ef367f8 100644 --- a/embedded-selenium/src/main/java/com/playtika/testcontainer/selenium/VncRecordingMode.java +++ b/embedded-selenium/src/main/java/com/playtika/testcontainer/selenium/VncRecordingMode.java @@ -1,10 +1,10 @@ package com.playtika.testcontainer.selenium; -import org.testcontainers.containers.BrowserWebDriverContainer; +import org.testcontainers.selenium.BrowserWebDriverContainer; /** - * See {@link org.testcontainers.containers.BrowserWebDriverContainer.VncRecordingMode} + * See {@link org.testcontainers.selenium.BrowserWebDriverContainer.VncRecordingMode} */ public enum VncRecordingMode { SKIP, RECORD_ALL, RECORD_FAILING; @@ -12,4 +12,4 @@ public enum VncRecordingMode { public BrowserWebDriverContainer.VncRecordingMode convert() { return BrowserWebDriverContainer.VncRecordingMode.valueOf(this.name()); } -} \ No newline at end of file +} diff --git a/embedded-selenium/src/main/java/com/playtika/testcontainer/selenium/testscope/TestcontainerContextCustomizerFactory.java b/embedded-selenium/src/main/java/com/playtika/testcontainer/selenium/testscope/TestcontainerContextCustomizerFactory.java index 9f8a3565b..e17c87afd 100644 --- a/embedded-selenium/src/main/java/com/playtika/testcontainer/selenium/testscope/TestcontainerContextCustomizerFactory.java +++ b/embedded-selenium/src/main/java/com/playtika/testcontainer/selenium/testscope/TestcontainerContextCustomizerFactory.java @@ -27,10 +27,7 @@ public boolean equals(Object obj) { if (obj == this) { return true; } - if (obj == null || obj.getClass() != getClass()) { - return false; - } - return true; + return obj != null && obj.getClass() == getClass(); } @Override @@ -38,4 +35,4 @@ public int hashCode() { return getClass().hashCode(); } } -} \ No newline at end of file +} diff --git a/embedded-selenium/src/main/java/com/playtika/testcontainer/selenium/testscope/TestcontainerScope.java b/embedded-selenium/src/main/java/com/playtika/testcontainer/selenium/testscope/TestcontainerScope.java index 3e119c36d..e255ee40c 100644 --- a/embedded-selenium/src/main/java/com/playtika/testcontainer/selenium/testscope/TestcontainerScope.java +++ b/embedded-selenium/src/main/java/com/playtika/testcontainer/selenium/testscope/TestcontainerScope.java @@ -48,16 +48,6 @@ public Object remove(String name) { public void registerDestructionCallback(String name, Runnable callback) { } - @Override - public Object resolveContextualObject(String key) { - return null; - } - - @Override - public String getConversationId() { - return null; - } - /** * Register this scope with the specified context and reassign appropriate bean * definitions to used it. @@ -102,9 +92,7 @@ public void afterTestMethod(TestContext testContext) { instances.values().stream() .filter(TestLifecycleAware.class::isInstance) .map(TestLifecycleAware.class::cast) - .forEach(value -> { - value.afterTest(testDescription(testContext), Optional.ofNullable(testContext.getTestException())); - }); + .forEach(value -> value.afterTest(testDescription(testContext), Optional.ofNullable(testContext.getTestException()))); } private TestDescription testDescription(TestContext testContext) { @@ -126,8 +114,6 @@ public void beforeTestMethod(TestContext testContext) { instances.values().stream() .filter(TestLifecycleAware.class::isInstance) .map(TestLifecycleAware.class::cast) - .forEach(value -> { - value.beforeTest(testDescription(testContext)); - }); + .forEach(value -> value.beforeTest(testDescription(testContext))); } -} \ No newline at end of file +} diff --git a/embedded-selenium/src/main/java/com/playtika/testcontainer/selenium/testscope/TestcontainerTestExecutionListener.java b/embedded-selenium/src/main/java/com/playtika/testcontainer/selenium/testscope/TestcontainerTestExecutionListener.java index ab1e44edf..b76f6027d 100644 --- a/embedded-selenium/src/main/java/com/playtika/testcontainer/selenium/testscope/TestcontainerTestExecutionListener.java +++ b/embedded-selenium/src/main/java/com/playtika/testcontainer/selenium/testscope/TestcontainerTestExecutionListener.java @@ -22,9 +22,7 @@ public void beforeTestMethod(TestContext testContext) { public void afterTestMethod(TestContext testContext) { TestcontainerScope scope = TestcontainerScope.getFrom(testContext.getApplicationContext()); scope.afterTestMethod(testContext); - if (scope != null ) { - testContext.setAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE, - Boolean.TRUE); - } + testContext.setAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE, + Boolean.TRUE); } -} \ No newline at end of file +} From fd8c7ee97048a6fc18f0cdd12a42483c9bc2d2ae Mon Sep 17 00:00:00 2001 From: admitrov Date: Thu, 7 May 2026 19:35:26 +0100 Subject: [PATCH 14/25] Simplify lambda expressions and use modern Java APIs Remove unnecessary lambda braces in AbstractDependsOnPostProcessor. Replace Files.write with Files.writeString in FileUtils. Replace addFileSystemBind with withCopyToContainer in ContainerUtils. Use pattern matching for instanceof in DateUtils. Remove redundant type parameters in ContainerUtilsTest. Make ApplicationContextRunner final in MountVolumesTest. --- .../common/spring/AbstractDependsOnPostProcessor.java | 6 ++---- .../playtika/testcontainer/common/utils/ContainerUtils.java | 2 +- .../com/playtika/testcontainer/common/utils/DateUtils.java | 5 ++--- .../com/playtika/testcontainer/common/utils/FileUtils.java | 2 +- .../testcontainer/common/utils/ContainerUtilsTest.java | 4 ++-- .../testcontainer/common/utils/MountVolumesTest.java | 2 +- 6 files changed, 9 insertions(+), 12 deletions(-) diff --git a/testcontainers-common/src/main/java/com/playtika/testcontainer/common/spring/AbstractDependsOnPostProcessor.java b/testcontainers-common/src/main/java/com/playtika/testcontainer/common/spring/AbstractDependsOnPostProcessor.java index a75e9a06d..abf3ec442 100644 --- a/testcontainers-common/src/main/java/com/playtika/testcontainer/common/spring/AbstractDependsOnPostProcessor.java +++ b/testcontainers-common/src/main/java/com/playtika/testcontainer/common/spring/AbstractDependsOnPostProcessor.java @@ -24,9 +24,7 @@ protected AbstractDependsOnPostProcessor(Class beansOfType, String ... depend public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { List beanNamesForType = asList(getBeanNamesForType(beanFactory)); beanNamesForType.forEach( - datastoreClientBeanName -> { - setupDependsOn(beanFactory, datastoreClientBeanName); - } + datastoreClientBeanName -> setupDependsOn(beanFactory, datastoreClientBeanName) ); } @@ -49,4 +47,4 @@ private String[] getBeanNamesForType(ConfigurableListableBeanFactory beanFactory private static List asList(String[] array) { return (array == null ? new ArrayList<>() : new ArrayList<>(Arrays.asList(array))); } -} \ No newline at end of file +} diff --git a/testcontainers-common/src/main/java/com/playtika/testcontainer/common/utils/ContainerUtils.java b/testcontainers-common/src/main/java/com/playtika/testcontainer/common/utils/ContainerUtils.java index 85e7f19ff..6910a3c82 100644 --- a/testcontainers-common/src/main/java/com/playtika/testcontainer/common/utils/ContainerUtils.java +++ b/testcontainers-common/src/main/java/com/playtika/testcontainer/common/utils/ContainerUtils.java @@ -84,7 +84,7 @@ public static GenericContainer configureCommonsAndStart(GenericContainer c } for (MountVolume mountVolume : properties.getMountVolumes()) { - updatedContainer.addFileSystemBind(mountVolume.getHostPath(), mountVolume.getContainerPath(), mountVolume.getMode()); + updatedContainer.withCopyToContainer(MountableFile.forHostPath(mountVolume.getHostPath()), mountVolume.getContainerPath()); } for (Capability capability : properties.getCapabilities()) { diff --git a/testcontainers-common/src/main/java/com/playtika/testcontainer/common/utils/DateUtils.java b/testcontainers-common/src/main/java/com/playtika/testcontainer/common/utils/DateUtils.java index c482291a1..0b802c3b2 100644 --- a/testcontainers-common/src/main/java/com/playtika/testcontainer/common/utils/DateUtils.java +++ b/testcontainers-common/src/main/java/com/playtika/testcontainer/common/utils/DateUtils.java @@ -23,11 +23,10 @@ public class DateUtils { */ public static String toDateAndTimeAgo(String isoFormattedDate) { Object instantOrString = parseToInstantOrString(isoFormattedDate); - if (!(instantOrString instanceof Instant)) { + if (!(instantOrString instanceof Instant instant)) { return (String) instantOrString; } - Instant instant = ((Instant) instantOrString); - OffsetDateTime offsetDateTime = ((Instant) instantOrString).atZone(ZoneId.systemDefault()).toOffsetDateTime(); + OffsetDateTime offsetDateTime = instant.atZone(ZoneId.systemDefault()).toOffsetDateTime(); return offsetDateTime + " (" + toTimeAgo(instant.toEpochMilli()) + ")"; } diff --git a/testcontainers-common/src/main/java/com/playtika/testcontainer/common/utils/FileUtils.java b/testcontainers-common/src/main/java/com/playtika/testcontainer/common/utils/FileUtils.java index dfbfc5b76..a4e050071 100644 --- a/testcontainers-common/src/main/java/com/playtika/testcontainer/common/utils/FileUtils.java +++ b/testcontainers-common/src/main/java/com/playtika/testcontainer/common/utils/FileUtils.java @@ -25,7 +25,7 @@ public static Path resolveTemplateAsPath(ResourceLoader resourceLoader, String f String modifiedFile = resolveTemplateAsString(resourceLoader, fileName, modifyFunc); Path tempFilePath = Files.createTempFile("tc_", "_" + fileName); tempFilePath.toFile().deleteOnExit(); - Files.write(tempFilePath, modifiedFile.getBytes(StandardCharsets.UTF_8)); + Files.writeString(tempFilePath, modifiedFile); return tempFilePath; } diff --git a/testcontainers-common/src/test/java/com/playtika/testcontainer/common/utils/ContainerUtilsTest.java b/testcontainers-common/src/test/java/com/playtika/testcontainer/common/utils/ContainerUtilsTest.java index 02dc0fdb6..374227023 100644 --- a/testcontainers-common/src/test/java/com/playtika/testcontainer/common/utils/ContainerUtilsTest.java +++ b/testcontainers-common/src/test/java/com/playtika/testcontainer/common/utils/ContainerUtilsTest.java @@ -71,10 +71,10 @@ public String getDefaultDockerImage() { assertThat(echoContainer.isShouldBeReused()).isTrue(); assertThat(echoContainer.getEnvMap()).containsAllEntriesOf(env); assertThat(echoContainer.getLogConsumers()).hasSize(1); - Condition> hasCopyToFileContainerPath = new Condition>() { + Condition> hasCopyToFileContainerPath = new Condition<>() { public boolean matches(Map.Entry mountableFileObjectEntry) { return mountableFileObjectEntry.getKey().getResolvedPath().endsWith(classpathResource) - && mountableFileObjectEntry.getValue().equals(containerPath); + && mountableFileObjectEntry.getValue().equals(containerPath); } }; assertThat(echoContainer.getCopyToFileContainerPathMap()).hasEntrySatisfying(hasCopyToFileContainerPath); diff --git a/testcontainers-common/src/test/java/com/playtika/testcontainer/common/utils/MountVolumesTest.java b/testcontainers-common/src/test/java/com/playtika/testcontainer/common/utils/MountVolumesTest.java index 91cbdd3ec..afb71823e 100644 --- a/testcontainers-common/src/test/java/com/playtika/testcontainer/common/utils/MountVolumesTest.java +++ b/testcontainers-common/src/test/java/com/playtika/testcontainer/common/utils/MountVolumesTest.java @@ -19,7 +19,7 @@ @ExtendWith(SpringExtension.class) public class MountVolumesTest { - ApplicationContextRunner context = new ApplicationContextRunner() + final ApplicationContextRunner context = new ApplicationContextRunner() .withUserConfiguration(PostgreSQLContainerPropertiesConfiguration.class); private static void assertMountVolume(CommonContainerProperties properties, String folder, String s, BindMode readOnly) { From 3dae065e7f65d588fc018beb8bd761fac19eefed Mon Sep 17 00:00:00 2001 From: admitrov Date: Thu, 7 May 2026 19:36:24 +0100 Subject: [PATCH 15/25] Refactor code using modern Java APIs and simplify expressions Replace Arrays.asList with List.of for single-element and immutable collections. Use pattern matching for instanceof checks. Replace replaceAll with replace for literal strings. Convert Lombok @Value classes to records. Remove unnecessary lambda braces and redundant toString() calls. Use text blocks for multi-line log messages. Replace Integer.valueOf with Integer.parseInt. Remove unused imports and parameters. Replace addFileSystemBind with withCopyToContainer. Use StandardCharsets.UTF_8 directly instead of name(). Replace Hamcrest matchers with AssertJ assertions. Simplify Optional assertions with hasValue(). --- .../aerospike/AerospikeTestOperations.java | 2 +- .../aerospike/AerospikeWaitStrategy.java | 2 +- ...eddedAzuriteBoostrapConfigurationTest.java | 2 +- ...eddedConsulBootstrapConfigurationTest.java | 4 +--- .../couchbase/springdata/SpringDataTest.java | 2 +- .../config/CustomTransportConfigCallback.java | 3 +-- .../KafkaContainerConfiguration.java | 7 +++---- .../KafkaConfigurationProperties.java | 4 ++-- ...SchemaRegistryConfigurationProperties.java | 4 ++-- .../KeycloakJwtAuthenticationConverter.java | 2 +- .../keydb/wait/KeyDbClusterStatusCheck.java | 15 ++++++++++----- ...eddedMemSqlBootstrapConfigurationTest.java | 3 --- ...beddedMinioBootstrapConfigurationTest.java | 2 +- ...MongodbBootstrapAuthConfigurationTest.java | 9 +-------- ...ddedMongodbBootstrapConfigurationTest.java | 9 +-------- ...bBootstrapReplicaSetConfigurationTest.java | 9 +-------- .../NativeKafkaContainerConfiguration.java | 4 ++-- .../EmbeddedNativeKafkaWithBindingTest.java | 6 ++---- .../NativeKafkaTopicsConfigurerTest.java | 19 ++++++++++--------- .../testcontainer/nats/BaseNatsTest.java | 6 +++--- .../playtika/testcontainer/neo4j/Person.java | 3 +-- .../redis/wait/RedisClusterStatusCheck.java | 15 ++++++++++----- ...dedTemporalBootstrapConfigurationTest.java | 6 +++--- 23 files changed, 59 insertions(+), 79 deletions(-) diff --git a/embedded-aerospike/src/main/java/com/playtika/testcontainer/aerospike/AerospikeTestOperations.java b/embedded-aerospike/src/main/java/com/playtika/testcontainer/aerospike/AerospikeTestOperations.java index cf1715cca..f327ce394 100644 --- a/embedded-aerospike/src/main/java/com/playtika/testcontainer/aerospike/AerospikeTestOperations.java +++ b/embedded-aerospike/src/main/java/com/playtika/testcontainer/aerospike/AerospikeTestOperations.java @@ -64,7 +64,7 @@ private List getScanJobs(String stdout) { if (!StringUtils.hasText(stdout)) { return Collections.emptyList(); } - return Arrays.stream(stdout.replaceAll("\n", "").split(";")) + return Arrays.stream(stdout.replace("\n", "").split(";")) .map(this::parseToObScanJobObject) .collect(Collectors.toList()); } diff --git a/embedded-aerospike/src/main/java/com/playtika/testcontainer/aerospike/AerospikeWaitStrategy.java b/embedded-aerospike/src/main/java/com/playtika/testcontainer/aerospike/AerospikeWaitStrategy.java index 0f6fd9bcd..c16836071 100644 --- a/embedded-aerospike/src/main/java/com/playtika/testcontainer/aerospike/AerospikeWaitStrategy.java +++ b/embedded-aerospike/src/main/java/com/playtika/testcontainer/aerospike/AerospikeWaitStrategy.java @@ -47,6 +47,6 @@ private int getMappedPort(NetworkSettings networkSettings, int originalPort) { Ports ports = networkSettings.getPorts(); Map bindings = ports.getBindings(); Ports.Binding[] binding = bindings.get(exposedPort); - return Integer.valueOf(binding[0].getHostPortSpec()); + return Integer.parseInt(binding[0].getHostPortSpec()); } } diff --git a/embedded-azurite/src/test/java/com/playtika/testcontainer/azurite/EmbeddedAzuriteBoostrapConfigurationTest.java b/embedded-azurite/src/test/java/com/playtika/testcontainer/azurite/EmbeddedAzuriteBoostrapConfigurationTest.java index f4ff4531e..a9d4c3fe8 100644 --- a/embedded-azurite/src/test/java/com/playtika/testcontainer/azurite/EmbeddedAzuriteBoostrapConfigurationTest.java +++ b/embedded-azurite/src/test/java/com/playtika/testcontainer/azurite/EmbeddedAzuriteBoostrapConfigurationTest.java @@ -53,7 +53,7 @@ void createAndDeleteContainerQueue() { SendMessageResult sendMessageResult = queueClient.sendMessage("test"); QueueMessageItem queueMessageItem = queueClient.receiveMessage(); assertThat(queueMessageItem.getBody().toString()).isEqualTo("test"); - assertThat(queueMessageItem.getMessageId().toString()).isEqualTo(sendMessageResult.getMessageId()); + assertThat(queueMessageItem.getMessageId()).isEqualTo(sendMessageResult.getMessageId()); queueClient.delete(); } diff --git a/embedded-consul/src/test/java/com/playtika/testcontainer/consul/EmbeddedConsulBootstrapConfigurationTest.java b/embedded-consul/src/test/java/com/playtika/testcontainer/consul/EmbeddedConsulBootstrapConfigurationTest.java index bc23891a2..b22853cfa 100644 --- a/embedded-consul/src/test/java/com/playtika/testcontainer/consul/EmbeddedConsulBootstrapConfigurationTest.java +++ b/embedded-consul/src/test/java/com/playtika/testcontainer/consul/EmbeddedConsulBootstrapConfigurationTest.java @@ -28,8 +28,6 @@ public void propertiesAvailable() { public void shouldUpdateKey() { ConsulClient client = buildClient(); - client.putValue("key", "val").onComplete(res ->{ - assertThat(res.result()).isEqualTo(true); - }); + client.putValue("key", "val").onComplete(res -> assertThat(res.result()).isEqualTo(true)); } } diff --git a/embedded-couchbase/src/test/java/com/playtika/testcontainer/couchbase/springdata/SpringDataTest.java b/embedded-couchbase/src/test/java/com/playtika/testcontainer/couchbase/springdata/SpringDataTest.java index 02bc87a61..273bf2b62 100644 --- a/embedded-couchbase/src/test/java/com/playtika/testcontainer/couchbase/springdata/SpringDataTest.java +++ b/embedded-couchbase/src/test/java/com/playtika/testcontainer/couchbase/springdata/SpringDataTest.java @@ -46,7 +46,7 @@ public void springDataShouldWork() { TestDocument testDocument = saveDocument(key, value); - assertThat(documentRepository.findById(key).get()).isEqualTo(testDocument); + assertThat(documentRepository.findById(key)).hasValue(testDocument); } @Test diff --git a/embedded-git/src/test/java/config/CustomTransportConfigCallback.java b/embedded-git/src/test/java/config/CustomTransportConfigCallback.java index 11a521b97..260011b2d 100644 --- a/embedded-git/src/test/java/config/CustomTransportConfigCallback.java +++ b/embedded-git/src/test/java/config/CustomTransportConfigCallback.java @@ -13,8 +13,7 @@ public class CustomTransportConfigCallback implements TransportConfigCallback { @Override public void configure(Transport transport) { - if (transport instanceof SshTransport) { - SshTransport sshTransport = (SshTransport) transport; + if (transport instanceof SshTransport sshTransport) { sshTransport.setSshSessionFactory(new CustomSshdSessionFactory(keyPair)); } } diff --git a/embedded-kafka/src/main/java/com/playtika/testcontainer/kafka/configuration/KafkaContainerConfiguration.java b/embedded-kafka/src/main/java/com/playtika/testcontainer/kafka/configuration/KafkaContainerConfiguration.java index 04e46c711..64e3380a9 100644 --- a/embedded-kafka/src/main/java/com/playtika/testcontainer/kafka/configuration/KafkaContainerConfiguration.java +++ b/embedded-kafka/src/main/java/com/playtika/testcontainer/kafka/configuration/KafkaContainerConfiguration.java @@ -21,7 +21,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; -import org.testcontainers.containers.BindMode; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; import org.testcontainers.kafka.KafkaContainer; @@ -230,7 +229,7 @@ private void kafkaFileSystemBind(KafkaConfigurationProperties kafkaProperties, K log.info("Writing kafka data to: {}", kafkaData); createPathAndParentOrMakeWritable(kafkaData); - kafka.addFileSystemBind(kafkaData.toString(), "/var/lib/kafka/data", BindMode.READ_WRITE); + kafka.withCopyToContainer(MountableFile.forHostPath(kafkaData.toString()), "/var/lib/kafka/data"); } } @@ -248,9 +247,9 @@ private void zookeperFileSystemBind(ZookeeperConfigurationProperties zookeeperPr log.info("Writing zookeeper transaction logs to: {}", zkTransactionLogs); createPathAndParentOrMakeWritable(zkData); - kafka.addFileSystemBind(zkData.toString(), "/var/lib/zookeeper/data", BindMode.READ_WRITE); + kafka.withCopyToContainer(MountableFile.forHostPath(zkData.toString()), "/var/lib/zookeeper/data"); createPathAndParentOrMakeWritable(zkTransactionLogs); - kafka.addFileSystemBind(zkTransactionLogs.toString(), "/var/lib/zookeeper/log", BindMode.READ_WRITE); + kafka.withCopyToContainer(MountableFile.forHostPath(zkTransactionLogs.toString()), "/var/lib/zookeeper/log"); } } diff --git a/embedded-kafka/src/main/java/com/playtika/testcontainer/kafka/properties/KafkaConfigurationProperties.java b/embedded-kafka/src/main/java/com/playtika/testcontainer/kafka/properties/KafkaConfigurationProperties.java index 760e25cab..ef4c1639d 100644 --- a/embedded-kafka/src/main/java/com/playtika/testcontainer/kafka/properties/KafkaConfigurationProperties.java +++ b/embedded-kafka/src/main/java/com/playtika/testcontainer/kafka/properties/KafkaConfigurationProperties.java @@ -10,9 +10,9 @@ import lombok.With; import org.springframework.boot.context.properties.ConfigurationProperties; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.List; @Data @EqualsAndHashCode(callSuper = true) @@ -64,7 +64,7 @@ public class KafkaConfigurationProperties extends CommonContainerProperties { protected FileSystemBind fileSystemBind = new FileSystemBind(); public KafkaConfigurationProperties() { - this.setCapabilities(Arrays.asList(Capability.NET_ADMIN)); + this.setCapabilities(List.of(Capability.NET_ADMIN)); } // https://hub.docker.com/r/confluentinc/cp-kafka diff --git a/embedded-kafka/src/main/java/com/playtika/testcontainer/kafka/properties/SchemaRegistryConfigurationProperties.java b/embedded-kafka/src/main/java/com/playtika/testcontainer/kafka/properties/SchemaRegistryConfigurationProperties.java index 56fdcc943..96544baac 100644 --- a/embedded-kafka/src/main/java/com/playtika/testcontainer/kafka/properties/SchemaRegistryConfigurationProperties.java +++ b/embedded-kafka/src/main/java/com/playtika/testcontainer/kafka/properties/SchemaRegistryConfigurationProperties.java @@ -6,7 +6,7 @@ import lombok.EqualsAndHashCode; import org.springframework.boot.context.properties.ConfigurationProperties; -import java.util.Arrays; +import java.util.List; import static com.playtika.testcontainer.kafka.properties.SchemaRegistryConfigurationProperties.Authentication.BASIC; import static com.playtika.testcontainer.kafka.properties.SchemaRegistryConfigurationProperties.Authentication.NONE; @@ -29,7 +29,7 @@ public class SchemaRegistryConfigurationProperties extends CommonContainerProper private Authentication authentication = NONE; public SchemaRegistryConfigurationProperties() { - this.setCapabilities(Arrays.asList(Capability.NET_ADMIN)); + this.setCapabilities(List.of(Capability.NET_ADMIN)); } public boolean isBasicAuthenticationEnabled() { diff --git a/embedded-keycloak/src/test/java/com/playtika/testcontainer/keycloak/util/KeycloakJwtAuthenticationConverter.java b/embedded-keycloak/src/test/java/com/playtika/testcontainer/keycloak/util/KeycloakJwtAuthenticationConverter.java index 43d9ef967..d612aab10 100644 --- a/embedded-keycloak/src/test/java/com/playtika/testcontainer/keycloak/util/KeycloakJwtAuthenticationConverter.java +++ b/embedded-keycloak/src/test/java/com/playtika/testcontainer/keycloak/util/KeycloakJwtAuthenticationConverter.java @@ -37,7 +37,7 @@ private Set extractResourceRoles(Jwt jwt) { var resourceRoles = new ArrayList(); if (resourceAccess.containsKey(clientId)) { - var resource = (Map>) resourceAccess.get(clientId); + var resource = resourceAccess.get(clientId); resourceRoles.addAll(resource.get("roles")); } diff --git a/embedded-keydb/src/main/java/com/playtika/testcontainer/keydb/wait/KeyDbClusterStatusCheck.java b/embedded-keydb/src/main/java/com/playtika/testcontainer/keydb/wait/KeyDbClusterStatusCheck.java index 2d57fb3da..1b394872b 100644 --- a/embedded-keydb/src/main/java/com/playtika/testcontainer/keydb/wait/KeyDbClusterStatusCheck.java +++ b/embedded-keydb/src/main/java/com/playtika/testcontainer/keydb/wait/KeyDbClusterStatusCheck.java @@ -39,11 +39,16 @@ private void logClusterInfo() { String info = jedis.info(); Map config = jedis.configGet("*"); String clusterNodes = jedis.clusterNodes(); - log.error("Cluster in failed state:\n" + - "-- cluster info:\n{}\n" + - "-- nodes:\n{}\n" + - "-- info:\n{}\n" + - "-- config:\n{}", + log.error(""" + Cluster in failed state: + -- cluster info: + {} + -- nodes: + {} + -- info: + {} + -- config: + {}""", clusterInfo, clusterNodes, info, String.join("\n", config.values())); } } diff --git a/embedded-memsql/src/test/java/com/playtika/testcontainer/memsql/EmbeddedMemSqlBootstrapConfigurationTest.java b/embedded-memsql/src/test/java/com/playtika/testcontainer/memsql/EmbeddedMemSqlBootstrapConfigurationTest.java index 6ff2093c4..ee3dbf755 100644 --- a/embedded-memsql/src/test/java/com/playtika/testcontainer/memsql/EmbeddedMemSqlBootstrapConfigurationTest.java +++ b/embedded-memsql/src/test/java/com/playtika/testcontainer/memsql/EmbeddedMemSqlBootstrapConfigurationTest.java @@ -35,9 +35,6 @@ public class EmbeddedMemSqlBootstrapConfigurationTest { @Autowired ConfigurableEnvironment environment; -// @Autowired -// NetworkTestOperations memsqlNetworkTestOperations; - @Test public void shouldConnectToMemSQL() throws Exception { assertThat(jdbcTemplate.queryForObject("select @@version_comment", String.class)).contains("SingleStoreDB"); diff --git a/embedded-minio/src/test/java/com/playtika/testcontainer/minio/EmbeddedMinioBootstrapConfigurationTest.java b/embedded-minio/src/test/java/com/playtika/testcontainer/minio/EmbeddedMinioBootstrapConfigurationTest.java index f5571ebc6..8e2026b5e 100644 --- a/embedded-minio/src/test/java/com/playtika/testcontainer/minio/EmbeddedMinioBootstrapConfigurationTest.java +++ b/embedded-minio/src/test/java/com/playtika/testcontainer/minio/EmbeddedMinioBootstrapConfigurationTest.java @@ -98,7 +98,7 @@ private String getFilePath(String name) { } private static String convertStreamToString(java.io.InputStream is) { - java.util.Scanner s = new java.util.Scanner(is, StandardCharsets.UTF_8.name()).useDelimiter("\\A"); + java.util.Scanner s = new java.util.Scanner(is, StandardCharsets.UTF_8).useDelimiter("\\A"); return s.hasNext() ? s.next() : ""; } diff --git a/embedded-mongodb/src/test/java/com/playtika/testcontainer/mongodb/EmbeddedMongodbBootstrapAuthConfigurationTest.java b/embedded-mongodb/src/test/java/com/playtika/testcontainer/mongodb/EmbeddedMongodbBootstrapAuthConfigurationTest.java index 8a182d9e9..64fc755c3 100644 --- a/embedded-mongodb/src/test/java/com/playtika/testcontainer/mongodb/EmbeddedMongodbBootstrapAuthConfigurationTest.java +++ b/embedded-mongodb/src/test/java/com/playtika/testcontainer/mongodb/EmbeddedMongodbBootstrapAuthConfigurationTest.java @@ -1,7 +1,6 @@ package com.playtika.testcontainer.mongodb; -import lombok.Value; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -57,13 +56,7 @@ public void propertiesAreAvailable() { assertThat(environment.getProperty("embedded.mongodb.database")).isNotEmpty(); } - @Value - static class Foo { - @Id - String someId; - String someString; - Instant someTimestamp; - Long someNumber; + record Foo(@Id String someId, String someString, Instant someTimestamp, Long someNumber) { } @EnableAutoConfiguration diff --git a/embedded-mongodb/src/test/java/com/playtika/testcontainer/mongodb/EmbeddedMongodbBootstrapConfigurationTest.java b/embedded-mongodb/src/test/java/com/playtika/testcontainer/mongodb/EmbeddedMongodbBootstrapConfigurationTest.java index 4cd9bc481..c19085c44 100644 --- a/embedded-mongodb/src/test/java/com/playtika/testcontainer/mongodb/EmbeddedMongodbBootstrapConfigurationTest.java +++ b/embedded-mongodb/src/test/java/com/playtika/testcontainer/mongodb/EmbeddedMongodbBootstrapConfigurationTest.java @@ -2,7 +2,6 @@ import com.playtika.testcontainer.toxiproxy.ToxiproxyClientProxy; import eu.rekawek.toxiproxy.model.ToxicDirection; -import lombok.Value; import lombok.extern.slf4j.Slf4j; import org.assertj.core.data.Offset; import org.junit.jupiter.api.Test; @@ -74,13 +73,7 @@ private static long durationOf(Callable op) throws Exception { return System.currentTimeMillis() - start; } - @Value - static class Foo { - @Id - String someId; - String someString; - Instant someTimestamp; - Long someNumber; + record Foo(@Id String someId, String someString, Instant someTimestamp, Long someNumber) { } @EnableAutoConfiguration diff --git a/embedded-mongodb/src/test/java/com/playtika/testcontainer/mongodb/EmbeddedMongodbBootstrapReplicaSetConfigurationTest.java b/embedded-mongodb/src/test/java/com/playtika/testcontainer/mongodb/EmbeddedMongodbBootstrapReplicaSetConfigurationTest.java index 99f2c7b0c..e0ea2d97e 100644 --- a/embedded-mongodb/src/test/java/com/playtika/testcontainer/mongodb/EmbeddedMongodbBootstrapReplicaSetConfigurationTest.java +++ b/embedded-mongodb/src/test/java/com/playtika/testcontainer/mongodb/EmbeddedMongodbBootstrapReplicaSetConfigurationTest.java @@ -1,7 +1,6 @@ package com.playtika.testcontainer.mongodb; -import lombok.Value; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -55,13 +54,7 @@ public void propertiesAreAvailable() { assertThat(environment.getProperty("embedded.mongodb.replica-set-name")).isNotEmpty(); } - @Value - static class Foo { - @Id - String someId; - String someString; - Instant someTimestamp; - Long someNumber; + record Foo(@Id String someId, String someString, Instant someTimestamp, Long someNumber) { } @EnableAutoConfiguration diff --git a/embedded-native-kafka/src/main/java/com/playtika/testcontainer/nativekafka/configuration/NativeKafkaContainerConfiguration.java b/embedded-native-kafka/src/main/java/com/playtika/testcontainer/nativekafka/configuration/NativeKafkaContainerConfiguration.java index c52bbf03d..6b3281cb9 100644 --- a/embedded-native-kafka/src/main/java/com/playtika/testcontainer/nativekafka/configuration/NativeKafkaContainerConfiguration.java +++ b/embedded-native-kafka/src/main/java/com/playtika/testcontainer/nativekafka/configuration/NativeKafkaContainerConfiguration.java @@ -10,11 +10,11 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; -import org.testcontainers.containers.BindMode; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; import org.testcontainers.kafka.ConfluentKafkaContainer; import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.MountableFile; import java.io.IOException; import java.nio.file.FileAlreadyExistsException; @@ -92,7 +92,7 @@ private void configureFileSystemBind(NativeKafkaConfigurationProperties nativeKa log.info("Writing native kafka data to: {}", kafkaData); createPathAndParentOrMakeWritable(kafkaData); - nativeKafka.addFileSystemBind(kafkaData.toString(), "/tmp/kafka-logs", BindMode.READ_WRITE); + nativeKafka.withCopyToContainer(MountableFile.forHostPath(kafkaData.toString()), "/tmp/kafka-logs"); } } diff --git a/embedded-native-kafka/src/test/java/com/playtika/testcontainer/nativekafka/EmbeddedNativeKafkaWithBindingTest.java b/embedded-native-kafka/src/test/java/com/playtika/testcontainer/nativekafka/EmbeddedNativeKafkaWithBindingTest.java index 72c9a37eb..32c7900a3 100644 --- a/embedded-native-kafka/src/test/java/com/playtika/testcontainer/nativekafka/EmbeddedNativeKafkaWithBindingTest.java +++ b/embedded-native-kafka/src/test/java/com/playtika/testcontainer/nativekafka/EmbeddedNativeKafkaWithBindingTest.java @@ -40,9 +40,7 @@ public static void afterAll(@Autowired NativeKafkaConfigurationProperties native nativeKafka.execInContainer("rm", "-rf", "/tmp/kafka-logs"); // Delete mounted directories after test run - Runtime.getRuntime().addShutdownHook(new Thread(DockerClientFactory.TESTCONTAINERS_THREAD_GROUP, () -> { - PathUtils.recursiveDeleteDir(nativeKafkaDataFolder); - })); + Runtime.getRuntime().addShutdownHook(new Thread(DockerClientFactory.TESTCONTAINERS_THREAD_GROUP, () -> PathUtils.recursiveDeleteDir(nativeKafkaDataFolder))); } protected static Path projectDir() { @@ -53,4 +51,4 @@ protected static Path projectDir() { throw new RuntimeException(e); } } -} \ No newline at end of file +} diff --git a/embedded-native-kafka/src/test/java/com/playtika/testcontainer/nativekafka/NativeKafkaTopicsConfigurerTest.java b/embedded-native-kafka/src/test/java/com/playtika/testcontainer/nativekafka/NativeKafkaTopicsConfigurerTest.java index 04f740bcf..edfbe389c 100644 --- a/embedded-native-kafka/src/test/java/com/playtika/testcontainer/nativekafka/NativeKafkaTopicsConfigurerTest.java +++ b/embedded-native-kafka/src/test/java/com/playtika/testcontainer/nativekafka/NativeKafkaTopicsConfigurerTest.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Properties; import java.util.concurrent.ExecutionException; @@ -111,7 +112,7 @@ void shouldCreateTopicsWithCustomPartitions() { when(adminClient.createTopics(anyCollection())).thenReturn(createTopicsResult); when(createTopicsResult.all()).thenReturn(future); - Collection topicsToCreate = Arrays.asList("topic1"); + Collection topicsToCreate = List.of("topic1"); Collection topicsConfiguration = Arrays.asList( new TopicConfiguration("topic1", 5), new TopicConfiguration("topic2", 3) @@ -149,8 +150,8 @@ void shouldMergeTopicsWithCustomConfigurationTakingPrecedence() { when(createTopicsResult.all()).thenReturn(future); Collection topicsToCreate = Arrays.asList("topic1", "topic2"); - Collection topicsConfiguration = Arrays.asList( - new TopicConfiguration("topic1", 10) + Collection topicsConfiguration = List.of( + new TopicConfiguration("topic1", 10) ); configurer.createTopics(topicsToCreate, topicsConfiguration); @@ -196,7 +197,7 @@ void shouldHandleExecutionExceptionDuringTopicCreation() throws Exception { when(createTopicsResult.all()).thenReturn(future); when(future.get()).thenThrow(new ExecutionException(new TopicExistsException("Topic exists"))); - Collection topicsToCreate = Arrays.asList("topic1"); + Collection topicsToCreate = List.of("topic1"); Collection topicsConfiguration = Collections.emptyList(); assertThatThrownBy(() -> configurer.createTopics(topicsToCreate, topicsConfiguration)) @@ -217,7 +218,7 @@ void shouldHandleInterruptedExceptionDuringTopicCreation() throws Exception { when(createTopicsResult.all()).thenReturn(future); when(future.get()).thenThrow(new InterruptedException("Interrupted")); - Collection topicsToCreate = Arrays.asList("topic1"); + Collection topicsToCreate = List.of("topic1"); Collection topicsConfiguration = Collections.emptyList(); assertThatThrownBy(() -> configurer.createTopics(topicsToCreate, topicsConfiguration)) @@ -237,7 +238,7 @@ void shouldCreateTopicsWithReplicationFactorOne() { when(adminClient.createTopics(anyCollection())).thenReturn(createTopicsResult); when(createTopicsResult.all()).thenReturn(future); - Collection topicsToCreate = Arrays.asList("topic1"); + Collection topicsToCreate = List.of("topic1"); Collection topicsConfiguration = Collections.emptyList(); configurer.createTopics(topicsToCreate, topicsConfiguration); @@ -262,8 +263,8 @@ void shouldCreateOnlyConfiguredTopicsWhenNoTopicsToCreateSpecified() { when(createTopicsResult.all()).thenReturn(future); Collection topicsToCreate = Collections.emptyList(); - Collection topicsConfiguration = Arrays.asList( - new TopicConfiguration("configuredTopic", 2) + Collection topicsConfiguration = List.of( + new TopicConfiguration("configuredTopic", 2) ); configurer.createTopics(topicsToCreate, topicsConfiguration); @@ -277,4 +278,4 @@ void shouldCreateOnlyConfiguredTopicsWhenNoTopicsToCreateSpecified() { assertThat(topic.numPartitions()).isEqualTo(2); } } -} \ No newline at end of file +} diff --git a/embedded-nats/src/test/java/com/playtika/testcontainer/nats/BaseNatsTest.java b/embedded-nats/src/test/java/com/playtika/testcontainer/nats/BaseNatsTest.java index 2f6fb2961..62859c0a3 100644 --- a/embedded-nats/src/test/java/com/playtika/testcontainer/nats/BaseNatsTest.java +++ b/embedded-nats/src/test/java/com/playtika/testcontainer/nats/BaseNatsTest.java @@ -34,7 +34,7 @@ static class TestConfiguration { public Connection natsConnection(@Value("${embedded.nats.host}") String host, @Value("${embedded.nats.port}") int port) throws IOException, InterruptedException { - Options connectionOptions = getConnectionOptions(host, port, 100); + Options connectionOptions = getConnectionOptions(host, port); return Nats.connect(connectionOptions); } @@ -42,12 +42,12 @@ public Connection natsConnection(@Value("${embedded.nats.host}") String host, @ConditionalOnToxiProxyEnabled public Connection natsToxicproxyConnection(@Value("${embedded.nats.toxiproxy.host}") String host, @Value("${embedded.nats.toxiproxy.port}") int port) throws IOException, InterruptedException { - Options connectionOptions = getConnectionOptions(host, port, 1000); + Options connectionOptions = getConnectionOptions(host, port); return Nats.connect(connectionOptions); } } - private static Options getConnectionOptions(String host, int port, int timeoutInMillis) { + private static Options getConnectionOptions(String host, int port) { return new Options.Builder() .server(String.format("nats://%s:%s", host, port)) .build(); diff --git a/embedded-neo4j/src/test/java/com/playtika/testcontainer/neo4j/Person.java b/embedded-neo4j/src/test/java/com/playtika/testcontainer/neo4j/Person.java index 135a5c5f9..4b33d8f8f 100644 --- a/embedded-neo4j/src/test/java/com/playtika/testcontainer/neo4j/Person.java +++ b/embedded-neo4j/src/test/java/com/playtika/testcontainer/neo4j/Person.java @@ -13,7 +13,6 @@ import java.util.Set; import static java.util.Collections.emptySet; -import static java.util.stream.Collectors.toList; @Node @NoArgsConstructor @@ -52,6 +51,6 @@ public String toString() { .orElse(emptySet()) .stream() .map(teamMateRelationship -> teamMateRelationship.getTeamMate().getName()) - .collect(toList()); + .toList(); } } diff --git a/embedded-redis/src/main/java/com/playtika/testcontainer/redis/wait/RedisClusterStatusCheck.java b/embedded-redis/src/main/java/com/playtika/testcontainer/redis/wait/RedisClusterStatusCheck.java index a1f0af652..ad39e68d5 100644 --- a/embedded-redis/src/main/java/com/playtika/testcontainer/redis/wait/RedisClusterStatusCheck.java +++ b/embedded-redis/src/main/java/com/playtika/testcontainer/redis/wait/RedisClusterStatusCheck.java @@ -39,11 +39,16 @@ private void logClusterInfo() { String info = jedis.info(); Map config = jedis.configGet("*"); String clusterNodes = jedis.clusterNodes(); - log.error("Cluster in failed state:\n" + - "-- cluster info:\n{}\n" + - "-- nodes:\n{}\n" + - "-- info:\n{}\n" + - "-- config:\n{}", + log.error(""" + Cluster in failed state: + -- cluster info: + {} + -- nodes: + {} + -- info: + {} + -- config: + {}""", clusterInfo, clusterNodes, info, String.join("\n", config.values())); } } diff --git a/embedded-temporal/src/test/java/com/playtika/testcontainer/temporal/EmbeddedTemporalBootstrapConfigurationTest.java b/embedded-temporal/src/test/java/com/playtika/testcontainer/temporal/EmbeddedTemporalBootstrapConfigurationTest.java index 768e01c2c..060c33158 100644 --- a/embedded-temporal/src/test/java/com/playtika/testcontainer/temporal/EmbeddedTemporalBootstrapConfigurationTest.java +++ b/embedded-temporal/src/test/java/com/playtika/testcontainer/temporal/EmbeddedTemporalBootstrapConfigurationTest.java @@ -26,7 +26,7 @@ import static io.temporal.api.enums.v1.EventType.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED; import static java.util.UUID.randomUUID; import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.hasItem; +import static org.assertj.core.api.InstanceOfAssertFactories.list; class EmbeddedTemporalBootstrapConfigurationTest { @@ -41,7 +41,7 @@ class Headless { void workflowExecutionStarts(@Autowired WorkflowClient client) { String workflowId = startWorkflow(client); - assertThat(client.streamHistory(workflowId.toString())) + assertThat(client.streamHistory(workflowId)) .extracting(HistoryEvent::getEventType) .contains(EVENT_TYPE_WORKFLOW_EXECUTION_STARTED); } @@ -72,7 +72,7 @@ void workflowExecutionStarts( .expectStatus().isOk() .expectBody() .jsonPath("$.history.events[*].eventType") - .value(hasItem(EVENT_TYPE_WORKFLOW_EXECUTION_STARTED.toString())); + .value(values -> assertThat(values).asInstanceOf(list(String.class)).contains(EVENT_TYPE_WORKFLOW_EXECUTION_STARTED.toString())); } } From b461fb6c5cedddee8e994f78a3e47d650c46f1f5 Mon Sep 17 00:00:00 2001 From: admitrov Date: Thu, 7 May 2026 22:30:59 +0100 Subject: [PATCH 16/25] Update ContainerUtilsTest to use copyToTransferableContainerPathMap instead of getBinds Replace deprecated getBinds() assertions with reflection-based access to copyToTransferableContainerPathMap field. Update mount volume verification to check Transferable entries instead of Bind objects. Remove unused docker-java imports (AccessMode, Bind) and add reflection-related imports (Field, GenericContainer, Transferable). --- .../common/utils/ContainerUtilsTest.java | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/testcontainers-common/src/test/java/com/playtika/testcontainer/common/utils/ContainerUtilsTest.java b/testcontainers-common/src/test/java/com/playtika/testcontainer/common/utils/ContainerUtilsTest.java index 374227023..4fd3bdbd4 100644 --- a/testcontainers-common/src/test/java/com/playtika/testcontainer/common/utils/ContainerUtilsTest.java +++ b/testcontainers-common/src/test/java/com/playtika/testcontainer/common/utils/ContainerUtilsTest.java @@ -1,7 +1,5 @@ package com.playtika.testcontainer.common.utils; -import com.github.dockerjava.api.model.AccessMode; -import com.github.dockerjava.api.model.Bind; import com.playtika.testcontainer.bootstrap.EchoContainer; import com.playtika.testcontainer.common.checks.PositiveCommandWaitStrategy; import com.playtika.testcontainer.common.properties.CommonContainerProperties; @@ -13,8 +11,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.images.builder.Transferable; import org.testcontainers.utility.MountableFile; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -39,7 +40,7 @@ void tearDown() { } @Test - void configureCommonsAndStart() { + void configureCommonsAndStart() throws Exception { String[] command = {"/bin/sh", "-c", "while true; do echo 'Press [CTRL+C] to stop..'; sleep 1; done"}; Map env = new HashMap<>(); env.put("TEST_ENV_VAR", "VALUE_TEST"); @@ -79,13 +80,23 @@ public boolean matches(Map.Entry mountableFileObjectEntry }; assertThat(echoContainer.getCopyToFileContainerPathMap()).hasEntrySatisfying(hasCopyToFileContainerPath); + Map transferableMap = getCopyToTransferableContainerPathMap(echoContainer); + assertThat(transferableMap).hasSize(2); for (MountVolume mountVolume : mountVolumes) { - Condition hasMountBindings = new Condition<>( - bind -> MountableFile.forHostPath(mountVolume.getHostPath()).getResolvedPath().equals(bind.getPath()) - && mountVolume.getContainerPath().equals(bind.getVolume().getPath()) - && (BindMode.READ_WRITE.equals(mountVolume.getMode()) && AccessMode.rw == bind.getAccessMode() - || BindMode.READ_ONLY.equals(mountVolume.getMode()) && AccessMode.ro == bind.getAccessMode()), "binding"); - assertThat(echoContainer.getBinds()).hasSize(2).haveExactly(1, hasMountBindings); + String expectedPath = MountableFile.forHostPath(mountVolume.getHostPath()).getResolvedPath(); + Condition> hasMountVolume = new Condition<>( + entry -> entry.getKey() instanceof MountableFile mountableFile + && mountableFile.getResolvedPath().equals(expectedPath) + && mountVolume.getContainerPath().equals(entry.getValue()), + "mount volume copy"); + assertThat(transferableMap).hasEntrySatisfying(hasMountVolume); } } + + @SuppressWarnings("unchecked") + private static Map getCopyToTransferableContainerPathMap(EchoContainer container) throws Exception { + Field field = GenericContainer.class.getDeclaredField("copyToTransferableContainerPathMap"); + field.setAccessible(true); + return (Map) field.get(container); + } } From b5ca2673e512406f4b192b8fae36339dc407486a Mon Sep 17 00:00:00 2001 From: admitrov Date: Mon, 11 May 2026 12:32:56 +0100 Subject: [PATCH 17/25] Migrate embedded-kafka to use ConfluentKafkaContainer and override containerIsStarting for advertised listeners configuration Replace KafkaContainer with ConfluentKafkaContainer. Override containerIsStarting() instead of getBootstrapServers() to dynamically configure advertised listeners using Transferable script injection. Add CONTROLLER listener configuration for KRaft mode. Update all method signatures and type casts from KafkaContainer to ConfluentKafkaContainer. --- .../KafkaContainerConfiguration.java | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/embedded-kafka/src/main/java/com/playtika/testcontainer/kafka/configuration/KafkaContainerConfiguration.java b/embedded-kafka/src/main/java/com/playtika/testcontainer/kafka/configuration/KafkaContainerConfiguration.java index 64e3380a9..61b849bb8 100644 --- a/embedded-kafka/src/main/java/com/playtika/testcontainer/kafka/configuration/KafkaContainerConfiguration.java +++ b/embedded-kafka/src/main/java/com/playtika/testcontainer/kafka/configuration/KafkaContainerConfiguration.java @@ -1,5 +1,6 @@ package com.playtika.testcontainer.kafka.configuration; +import com.github.dockerjava.api.command.InspectContainerResponse; import com.playtika.testcontainer.common.utils.ContainerUtils; import com.playtika.testcontainer.kafka.KafkaTopicsConfigurer; import com.playtika.testcontainer.kafka.checks.KafkaStatusCheck; @@ -23,7 +24,8 @@ import org.springframework.core.env.MapPropertySource; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; -import org.testcontainers.kafka.KafkaContainer; +import org.testcontainers.images.builder.Transferable; +import org.testcontainers.kafka.ConfluentKafkaContainer; import org.testcontainers.toxiproxy.ToxiproxyContainer; import org.testcontainers.utility.MountableFile; @@ -128,7 +130,7 @@ ToxiproxyClientProxy kafkaContainerSaslProxy(ToxiproxyClient toxiproxyClient, } @Bean(name = KAFKA_BEAN_NAME, destroyMethod = "stop") - public KafkaContainer kafka( + public ConfluentKafkaContainer kafka( KafkaStatusCheck kafkaStatusCheck, KafkaConfigurationProperties kafkaProperties, ZookeeperConfigurationProperties zookeeperProperties, @@ -150,25 +152,30 @@ public KafkaContainer kafka( // All properties: https://docs.confluent.io/platform/current/installation/configuration/ // Kafka Broker properties: https://docs.confluent.io/platform/current/installation/configuration/broker-configs.html - KafkaContainer kafka = new KafkaContainer(ContainerUtils.getDockerImageName(kafkaProperties)) { + ConfluentKafkaContainer kafka = new ConfluentKafkaContainer(ContainerUtils.getDockerImageName(kafkaProperties)) { @Override - public String getBootstrapServers() { - List servers = new ArrayList<>(); - servers.add("EXTERNAL_PLAINTEXT://" + getHost() + ":" + getMappedPort(kafkaExternalPort)); - servers.add("EXTERNAL_SASL_PLAINTEXT://" + getHost() + ":" + getMappedPort(saslPlaintextKafkaExternalPort)); - servers.add("INTERNAL_SASL_PLAINTEXT://" + KAFKA_HOST_NAME + ":" + saslPlaintextKafkaInternalPort); - servers.add("INTERNAL_PLAINTEXT://" + KAFKA_HOST_NAME + ":" + kafkaInternalPort); - - if (plainTextProxy != null) { - servers.add("TOXIPROXY_INTERNAL_PLAINTEXT://" + getHost() + ":" + plainTextProxy.getProxyPort()); - } - if (saslProxy != null) { - servers.add("TOXIPROXY_INTERNAL_SASL_PLAINTEXT://" + getHost() + ":" + saslProxy.getProxyPort()); - } - - return String.join(",", servers); + protected void containerIsStarting(InspectContainerResponse containerInfo) { + String hostname = containerInfo.getConfig().getHostName(); + List advertisedListeners = new ArrayList<>(); + advertisedListeners.add("EXTERNAL_PLAINTEXT://" + getHost() + ":" + getMappedPort(kafkaExternalPort)); + advertisedListeners.add("EXTERNAL_SASL_PLAINTEXT://" + getHost() + ":" + getMappedPort(saslPlaintextKafkaExternalPort)); + advertisedListeners.add("INTERNAL_PLAINTEXT://" + KAFKA_HOST_NAME + ":" + kafkaInternalPort); + advertisedListeners.add("INTERNAL_SASL_PLAINTEXT://" + KAFKA_HOST_NAME + ":" + saslPlaintextKafkaInternalPort); + advertisedListeners.add("TOXIPROXY_INTERNAL_PLAINTEXT://" + (plainTextProxy != null + ? getHost() + ":" + plainTextProxy.getProxyPort() + : KAFKA_HOST_NAME + ":" + toxiProxyKafkaInternalPort)); + advertisedListeners.add("TOXIPROXY_INTERNAL_SASL_PLAINTEXT://" + (saslProxy != null + ? getHost() + ":" + saslProxy.getProxyPort() + : KAFKA_HOST_NAME + ":" + toxiProxySaslPlaintextKafkaInternalPort)); + advertisedListeners.add("BROKER://" + hostname + ":9092"); + + String command = "#!/bin/bash\n" + + "export KAFKA_ADVERTISED_LISTENERS=" + String.join(",", advertisedListeners) + "\n" + + "/etc/confluent/docker/run\n"; + copyFileToContainer(Transferable.of(command, 0777), "/tmp/testcontainers_start.sh"); } } + .withCreateContainerCmdModifier(cmd -> cmd.withUser(kafkaProperties.getDockerUser())) .withCreateContainerCmdModifier(cmd -> cmd.withHostName(KAFKA_HOST_NAME)) //see: https://stackoverflow.com/questions/41868161/kafka-in-kubernetes-cluster-how-to-publish-consume-messages-from-outside-of-kub @@ -180,6 +187,7 @@ public String getBootstrapServers() { "INTERNAL_SASL_PLAINTEXT:SASL_PLAINTEXT," + "INTERNAL_PLAINTEXT:PLAINTEXT," + "BROKER:PLAINTEXT," + + "CONTROLLER:PLAINTEXT," + "TOXIPROXY_INTERNAL_PLAINTEXT:PLAINTEXT," + "TOXIPROXY_INTERNAL_SASL_PLAINTEXT:SASL_PLAINTEXT" ) @@ -190,8 +198,10 @@ public String getBootstrapServers() { "INTERNAL_PLAINTEXT://0.0.0.0:" + kafkaInternalPort + "," + "TOXIPROXY_INTERNAL_PLAINTEXT://0.0.0.0:" + toxiProxyKafkaInternalPort + "," + "TOXIPROXY_INTERNAL_SASL_PLAINTEXT://0.0.0.0:" + toxiProxySaslPlaintextKafkaInternalPort + "," + - "BROKER://0.0.0.0:9092" + "BROKER://0.0.0.0:9092," + + "CONTROLLER://0.0.0.0:9099" ) + .withEnv("KAFKA_CONTROLLER_QUORUM_VOTERS", "1@localhost:9099") .withEnv("KAFKA_INTER_BROKER_LISTENER_NAME", "BROKER") .withEnv("KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS", "1") .withEnv("KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR", String.valueOf(kafkaProperties.getOffsetsTopicReplicationFactor())) @@ -215,12 +225,12 @@ public String getBootstrapServers() { kafkaFileSystemBind(kafkaProperties, kafka); zookeperFileSystemBind(zookeeperProperties, kafka); - kafka = (KafkaContainer) configureCommonsAndStart(kafka, kafkaProperties, log); + kafka = (ConfluentKafkaContainer) configureCommonsAndStart(kafka, kafkaProperties, log); registerKafkaEnvironment(kafka, environment, kafkaProperties); return kafka; } - private void kafkaFileSystemBind(KafkaConfigurationProperties kafkaProperties, KafkaContainer kafka) { + private void kafkaFileSystemBind(KafkaConfigurationProperties kafkaProperties, ConfluentKafkaContainer kafka) { KafkaConfigurationProperties.FileSystemBind fileSystemBind = kafkaProperties.getFileSystemBind(); if (fileSystemBind.isEnabled()) { String currentTimestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH-mm-ss-nnnnnnnnn")); @@ -233,7 +243,7 @@ private void kafkaFileSystemBind(KafkaConfigurationProperties kafkaProperties, K } } - private void zookeperFileSystemBind(ZookeeperConfigurationProperties zookeeperProperties, KafkaContainer kafka) { + private void zookeperFileSystemBind(ZookeeperConfigurationProperties zookeeperProperties, ConfluentKafkaContainer kafka) { ZookeeperConfigurationProperties.FileSystemBind zookeeperFileSystemBind = zookeeperProperties.getFileSystemBind(); if (zookeeperFileSystemBind.isEnabled()) { String currentTimestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH-mm-ss-nnnnnnnnn")); From ad9f84f50aff1caa956761e57fb17a1a875416b2 Mon Sep 17 00:00:00 2001 From: admitrov Date: Mon, 11 May 2026 15:41:02 +0100 Subject: [PATCH 18/25] Migrate embedded-native-kafka to use KafkaContainer instead of ConfluentKafkaContainer Replace ConfluentKafkaContainer with KafkaContainer and remove asCompatibleSubstituteFor() call. Update all method signatures and type casts from ConfluentKafkaContainer to KafkaContainer. --- .../NativeKafkaContainerConfiguration.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/embedded-native-kafka/src/main/java/com/playtika/testcontainer/nativekafka/configuration/NativeKafkaContainerConfiguration.java b/embedded-native-kafka/src/main/java/com/playtika/testcontainer/nativekafka/configuration/NativeKafkaContainerConfiguration.java index 6b3281cb9..22ae491e0 100644 --- a/embedded-native-kafka/src/main/java/com/playtika/testcontainer/nativekafka/configuration/NativeKafkaContainerConfiguration.java +++ b/embedded-native-kafka/src/main/java/com/playtika/testcontainer/nativekafka/configuration/NativeKafkaContainerConfiguration.java @@ -12,9 +12,8 @@ import org.springframework.core.env.MapPropertySource; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; -import org.testcontainers.kafka.ConfluentKafkaContainer; +import org.testcontainers.kafka.KafkaContainer; import org.testcontainers.utility.DockerImageName; -import org.testcontainers.utility.MountableFile; import java.io.IOException; import java.nio.file.FileAlreadyExistsException; @@ -50,15 +49,14 @@ public Network nativeKafkaNetwork() { } @Bean(name = NATIVE_KAFKA_BEAN_NAME, destroyMethod = "stop") - public ConfluentKafkaContainer nativeKafka( + public KafkaContainer nativeKafka( NativeKafkaConfigurationProperties nativeKafkaProperties, ConfigurableEnvironment environment, Network network) { - DockerImageName nativeKafkaImageName = DockerImageName.parse(nativeKafkaProperties.getDefaultDockerImage()) - .asCompatibleSubstituteFor("confluentinc/cp-kafka"); + DockerImageName nativeKafkaImageName = DockerImageName.parse(nativeKafkaProperties.getDefaultDockerImage()); - ConfluentKafkaContainer nativeKafka = new ConfluentKafkaContainer(nativeKafkaImageName) + KafkaContainer nativeKafka = new KafkaContainer(nativeKafkaImageName) .withNetwork(network) .withNetworkAliases(NATIVE_KAFKA_HOST_NAME) .withExtraHost(NATIVE_KAFKA_HOST_NAME, "127.0.0.1"); @@ -67,7 +65,7 @@ public ConfluentKafkaContainer nativeKafka( configureFileSystemBind(nativeKafkaProperties, nativeKafka); // Configure and start the container using common utilities - nativeKafka = (ConfluentKafkaContainer) configureCommonsAndStart(nativeKafka, nativeKafkaProperties, log); + nativeKafka = (KafkaContainer) configureCommonsAndStart(nativeKafka, nativeKafkaProperties, log); // Register environment properties registerNativeKafkaEnvironment(nativeKafka, environment, nativeKafkaProperties); @@ -83,7 +81,7 @@ public NativeKafkaTopicsConfigurer nativeKafkaTopicsConfigurer( return new NativeKafkaTopicsConfigurer(nativeKafka, nativeKafkaProperties); } - private void configureFileSystemBind(NativeKafkaConfigurationProperties nativeKafkaProperties, ConfluentKafkaContainer nativeKafka) { + private void configureFileSystemBind(NativeKafkaConfigurationProperties nativeKafkaProperties, KafkaContainer nativeKafka) { NativeKafkaConfigurationProperties.FileSystemBind fileSystemBind = nativeKafkaProperties.getFileSystemBind(); if (fileSystemBind.isEnabled()) { String currentTimestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH-mm-ss-nnnnnnnnn")); @@ -92,11 +90,11 @@ private void configureFileSystemBind(NativeKafkaConfigurationProperties nativeKa log.info("Writing native kafka data to: {}", kafkaData); createPathAndParentOrMakeWritable(kafkaData); - nativeKafka.withCopyToContainer(MountableFile.forHostPath(kafkaData.toString()), "/tmp/kafka-logs"); + nativeKafka.withFileSystemBind(kafkaData.toString(), "/tmp/kafka-logs"); } } - private void registerNativeKafkaEnvironment(ConfluentKafkaContainer nativeKafka, + private void registerNativeKafkaEnvironment(KafkaContainer nativeKafka, ConfigurableEnvironment environment, NativeKafkaConfigurationProperties nativeKafkaProperties) { LinkedHashMap map = new LinkedHashMap<>(); From 0ba5346a44c25b26cc994f34177b2d903b71c4f6 Mon Sep 17 00:00:00 2001 From: admitrov Date: Thu, 14 May 2026 08:44:38 +0100 Subject: [PATCH 19/25] Refactor embedded-selenium tests to use RemoteWebDriver instead of deprecated getWebDriver() Replace container.getWebDriver() calls with getDriver() helper method that creates RemoteWebDriver from container's Selenium address and autowired capabilities. Update package import from testcontainers.containers to testcontainers.selenium. Add capabilities autowiring and lazy driver initialization. Re-enable EmbeddedFirefoxSeleniumBeanConfigurationTest with proper browser and image configuration. Update space formatting in type casts. --- .../drivers/BaseEmbeddedSeleniumTest.java | 21 +++++++++++++++---- ...dFirefoxSeleniumBeanConfigurationTest.java | 9 +++++--- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/embedded-selenium/src/test/java/com/playtika/testcontainer/selenium/drivers/BaseEmbeddedSeleniumTest.java b/embedded-selenium/src/test/java/com/playtika/testcontainer/selenium/drivers/BaseEmbeddedSeleniumTest.java index 3336ac87c..848155257 100644 --- a/embedded-selenium/src/test/java/com/playtika/testcontainer/selenium/drivers/BaseEmbeddedSeleniumTest.java +++ b/embedded-selenium/src/test/java/com/playtika/testcontainer/selenium/drivers/BaseEmbeddedSeleniumTest.java @@ -4,12 +4,13 @@ import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.openqa.selenium.By; +import org.openqa.selenium.MutableCapabilities; import org.openqa.selenium.remote.RemoteWebDriver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.core.env.ConfigurableEnvironment; -import org.testcontainers.containers.BrowserWebDriverContainer; +import org.testcontainers.selenium.BrowserWebDriverContainer; import static org.assertj.core.api.Assertions.assertThat; @@ -23,6 +24,9 @@ public abstract class BaseEmbeddedSeleniumTest { @Autowired protected BrowserWebDriverContainer container; + @Autowired + protected MutableCapabilities capabilities; + @Autowired protected ConfigurableEnvironment environment; @@ -32,16 +36,25 @@ public abstract class BaseEmbeddedSeleniumTest { @DockerHostname private String dockerHostname; + private RemoteWebDriver driver; + + protected RemoteWebDriver getDriver() { + if (driver == null) { + driver = new RemoteWebDriver(container.getSeleniumAddress(), capabilities); + } + return driver; + } + @Test public void seleniumShouldWork() { - RemoteWebDriver driver = container.getWebDriver(); + RemoteWebDriver driver = getDriver(); getIndexPage(driver); assertThat(driver.getTitle()).isEqualTo("Hello World Page"); } @Test public void seleniumLinkShouldWorkAndPropertiesAreAvailable() { - RemoteWebDriver driver = container.getWebDriver(); + RemoteWebDriver driver = getDriver(); getIndexPage(driver); driver.findElement(By.linkText("Test Link")).click(); assertThat(driver.getTitle()).isEqualTo("Test Link Page"); @@ -60,7 +73,7 @@ private void getIndexPage(RemoteWebDriver driver) { } public String getBrowserName() { - return (String)container.getWebDriver().getCapabilities().getCapability("browserName"); + return (String) getDriver().getCapabilities().getCapability("browserName"); } } diff --git a/embedded-selenium/src/test/java/com/playtika/testcontainer/selenium/drivers/EmbeddedFirefoxSeleniumBeanConfigurationTest.java b/embedded-selenium/src/test/java/com/playtika/testcontainer/selenium/drivers/EmbeddedFirefoxSeleniumBeanConfigurationTest.java index 71e4f4bd3..45a75899e 100644 --- a/embedded-selenium/src/test/java/com/playtika/testcontainer/selenium/drivers/EmbeddedFirefoxSeleniumBeanConfigurationTest.java +++ b/embedded-selenium/src/test/java/com/playtika/testcontainer/selenium/drivers/EmbeddedFirefoxSeleniumBeanConfigurationTest.java @@ -2,7 +2,6 @@ import com.playtika.testcontainer.selenium.SeleniumProperties; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openqa.selenium.firefox.FirefoxOptions; import org.openqa.selenium.remote.CapabilityType; @@ -10,6 +9,7 @@ import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; +import org.springframework.test.context.TestPropertySource; import java.util.List; import java.util.Map; @@ -21,7 +21,10 @@ * * {@link FirefoxOptions} */ -@Disabled +@TestPropertySource(properties = { + "embedded.selenium.browser=FIREFOX", + "embedded.selenium.docker-image=selenium/standalone-firefox-debug:3.141.59-mercury" +}) @Import(EmbeddedFirefoxSeleniumBeanConfigurationTest.LocalTestConfiguration.class) public class EmbeddedFirefoxSeleniumBeanConfigurationTest extends BaseEmbeddedSeleniumTest { @@ -44,7 +47,7 @@ public void testThatTestArgumentIsSet() { @Test public void testThatTestCapabilityIsSet() { // default is true, false is set in localtestconfiguration - boolean acceptUnsecureCertificates = (boolean)container.getWebDriver().getCapabilities().getCapability(CapabilityType.ACCEPT_INSECURE_CERTS); + boolean acceptUnsecureCertificates = (boolean) getDriver().getCapabilities().getCapability(CapabilityType.ACCEPT_INSECURE_CERTS); assertThat(acceptUnsecureCertificates).isFalse(); } From c12c9ed01870914851d662c620578e77b305c7d8 Mon Sep 17 00:00:00 2001 From: admitrov Date: Thu, 14 May 2026 09:55:16 +0100 Subject: [PATCH 20/25] Fix MemSQL container startup by creating database before JDBC connection check Override waitUntilContainerStarted() to connect to root URL and create database before delegating to parent. This prevents "Unknown database" error since JdbcDatabaseContainer connects to getJdbcUrl() which includes the database name. Add database existence verification and proper exception handling. Simplify status check query by removing schema.sql sourcing and database USE statement. --- .../testcontainer/memsql/MemSqlContainer.java | 60 +++++++++++++++++++ .../memsql/MemSqlProperties.java | 2 +- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/embedded-memsql/src/main/java/com/playtika/testcontainer/memsql/MemSqlContainer.java b/embedded-memsql/src/main/java/com/playtika/testcontainer/memsql/MemSqlContainer.java index 4ea87d0bf..8886b6a4b 100644 --- a/embedded-memsql/src/main/java/com/playtika/testcontainer/memsql/MemSqlContainer.java +++ b/embedded-memsql/src/main/java/com/playtika/testcontainer/memsql/MemSqlContainer.java @@ -1,8 +1,16 @@ package com.playtika.testcontainer.memsql; +import lombok.extern.slf4j.Slf4j; +import org.testcontainers.containers.ContainerLaunchException; import org.testcontainers.containers.JdbcDatabaseContainer; import org.testcontainers.utility.DockerImageName; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.Statement; +import java.util.concurrent.TimeUnit; + +@Slf4j public class MemSqlContainer extends JdbcDatabaseContainer { private static final DockerImageName DEFAULT_IMAGE_NAME = @@ -81,4 +89,56 @@ public String getDatabaseName() { protected String getTestQueryString() { return "SELECT 1"; } + + @Override + protected void waitUntilContainerStarted() { + // JdbcDatabaseContainer.waitUntilContainerStarted() does not call super, so the + // waitingFor() strategy is never executed and the JDBC check connects to getJdbcUrl() + // which includes the database name — failing with "Unknown database" since the DB + // doesn't exist yet. Connect to the root URL first, create the DB, then hand off. + String rootUrl = "jdbc:mariadb://" + getHost() + ":" + getMappedPort(MEMSQL_PORT) + "/"; + long startNanos = System.nanoTime(); + long timeoutNanos = TimeUnit.SECONDS.toNanos(getStartupTimeoutSeconds()); + Exception lastException = null; + + while ((System.nanoTime() - startNanos) < timeoutNanos) { + if (!isRunning()) { + sleep(500); + continue; + } + try (Connection conn = DriverManager.getConnection(rootUrl, username, password); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE DATABASE IF NOT EXISTS " + database); + log.debug("Database '{}' created/verified", database); + break; + } catch (Exception e) { + lastException = e; + sleep(500); + } + } + + if (lastException != null && !databaseExists(rootUrl)) { + throw new ContainerLaunchException("Could not create database '" + database + "'", lastException); + } + + super.waitUntilContainerStarted(); + } + + private boolean databaseExists(String rootUrl) { + try (Connection conn = DriverManager.getConnection(rootUrl, username, password); + Statement stmt = conn.createStatement()) { + stmt.execute("USE " + database); + return true; + } catch (Exception e) { + return false; + } + } + + private void sleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } } diff --git a/embedded-memsql/src/main/java/com/playtika/testcontainer/memsql/MemSqlProperties.java b/embedded-memsql/src/main/java/com/playtika/testcontainer/memsql/MemSqlProperties.java index 080ec7c3a..059adb896 100644 --- a/embedded-memsql/src/main/java/com/playtika/testcontainer/memsql/MemSqlProperties.java +++ b/embedded-memsql/src/main/java/com/playtika/testcontainer/memsql/MemSqlProperties.java @@ -23,7 +23,7 @@ public class MemSqlProperties extends CommonContainerProperties { @NotEmpty String licenseKey; int port = 3306; - String statusCheck = "source /schema.sql; use test_db; SELECT 1;"; + String statusCheck = "SELECT 1;"; @Override public String getDefaultDockerImage() { From 9adeec17cda70706646d02934a7190bbb39103e7 Mon Sep 17 00:00:00 2001 From: admitrov Date: Thu, 14 May 2026 11:17:32 +0100 Subject: [PATCH 21/25] Replace LoggerFactory with Lombok @Slf4j annotation in EmbeddedNatsBootstrapConfiguration Remove manual logger instantiation and use @Slf4j annotation. Remove unused LoggerFactory and Logger imports. --- .../nats/EmbeddedNatsBootstrapConfiguration.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embedded-nats/src/main/java/com/playtika/testcontainer/nats/EmbeddedNatsBootstrapConfiguration.java b/embedded-nats/src/main/java/com/playtika/testcontainer/nats/EmbeddedNatsBootstrapConfiguration.java index ae4173975..5ec171271 100644 --- a/embedded-nats/src/main/java/com/playtika/testcontainer/nats/EmbeddedNatsBootstrapConfiguration.java +++ b/embedded-nats/src/main/java/com/playtika/testcontainer/nats/EmbeddedNatsBootstrapConfiguration.java @@ -8,6 +8,7 @@ import com.playtika.testcontainer.toxiproxy.condition.ConditionalOnToxiProxyEnabled; import eu.rekawek.toxiproxy.ToxiproxyClient; import io.github.amadeusitgroup.testcontainers.nats.NatsContainer; +import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; @@ -29,6 +30,7 @@ import static com.playtika.testcontainer.nats.NatsProperties.BEAN_NAME_EMBEDDED_NATS; import static com.playtika.testcontainer.nats.NatsProperties.BEAN_NAME_EMBEDDED_NATS_TOXI_PROXY; +@Slf4j @Configuration @ConditionalOnExpression("${embedded.containers.enabled:true}") @AutoConfigureAfter({DockerPresenceBootstrapConfiguration.class, EmbeddedToxiProxyBootstrapConfiguration.class}) @@ -36,8 +38,6 @@ @EnableConfigurationProperties(NatsProperties.class) public class EmbeddedNatsBootstrapConfiguration { - private static final Logger log = LoggerFactory.getLogger(EmbeddedNatsBootstrapConfiguration.class); - private static final String NATS_NETWORK_ALIAS = "nats.testcontainer.docker"; @Bean(name = BEAN_NAME_EMBEDDED_NATS_TOXI_PROXY) From d5068b9b9a1e1a8b4860c3791b659bf477bc9236 Mon Sep 17 00:00:00 2001 From: admitrov Date: Thu, 14 May 2026 11:31:10 +0100 Subject: [PATCH 22/25] Replace LoggerFactory with Lombok @Slf4j annotation in EmbeddedNatsBootstrapConfiguration Remove manual logger instantiation and use @Slf4j annotation. Remove unused LoggerFactory and Logger imports. --- .../testcontainer/nats/EmbeddedNatsBootstrapConfiguration.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/embedded-nats/src/main/java/com/playtika/testcontainer/nats/EmbeddedNatsBootstrapConfiguration.java b/embedded-nats/src/main/java/com/playtika/testcontainer/nats/EmbeddedNatsBootstrapConfiguration.java index 5ec171271..b08f8dabb 100644 --- a/embedded-nats/src/main/java/com/playtika/testcontainer/nats/EmbeddedNatsBootstrapConfiguration.java +++ b/embedded-nats/src/main/java/com/playtika/testcontainer/nats/EmbeddedNatsBootstrapConfiguration.java @@ -9,8 +9,6 @@ import eu.rekawek.toxiproxy.ToxiproxyClient; import io.github.amadeusitgroup.testcontainers.nats.NatsContainer; import lombok.extern.slf4j.Slf4j; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; From f136cfe2ce3dfe53ebf47629b1bbdc07a93258af Mon Sep 17 00:00:00 2001 From: admitrov Date: Thu, 14 May 2026 14:46:28 +0100 Subject: [PATCH 23/25] Add HTTPS and OAuth support to embedded Azurite with embedded self-signed certificate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add httpsEnabled and oauthEnabled properties to AzuriteProperties. Implement configureSsl() to handle PEM/PFX certificates with fallback to embedded self-signed certificate for localhost. Update endpoint URLs to use https:// protocol when HTTPS is enabled. Add --oauth basic command line argument when OAuth is enabled. Include certificate resolution logic supporting both classpath and file system paths. Update README with HTTPS/OAuth configuration examples and fix typo (convient → convenient). --- embedded-azurite/README.adoc | 52 +++++++++++- .../azurite/AzuriteProperties.java | 36 ++++++++ ...EmbeddedAzuriteBootstrapConfiguration.java | 44 +++++++++- .../azurite/EmbeddedAzuriteHttpsTest.java | 82 +++++++++++++++++++ 4 files changed, 208 insertions(+), 6 deletions(-) create mode 100644 embedded-azurite/src/test/java/com/playtika/testcontainer/azurite/EmbeddedAzuriteHttpsTest.java diff --git a/embedded-azurite/README.adoc b/embedded-azurite/README.adoc index b71fba656..b95102025 100644 --- a/embedded-azurite/README.adoc +++ b/embedded-azurite/README.adoc @@ -20,6 +20,12 @@ * `embedded.azurite.blobStoragePort` `(default is 10000)` * `embedded.azurite.queueStoragePort` `(default is 10001)` * `embedded.azurite.tableStoragePort` `(default is 10002)` +* `embedded.azurite.https-enabled` `(true|false, default is false)` — enables HTTPS. When `true` and no cert paths are provided, an embedded self-signed certificate for `localhost` is used automatically. +* `embedded.azurite.oauth-enabled` `(true|false, default is false)` — enables OAuth basic emulation (`--oauth basic`), required for `DefaultAzureCredential`. Implies `https-enabled=true`. +* `embedded.azurite.pem-cert-path` — path to a custom PEM certificate (prefix with `classpath:` for classpath resources, or provide an absolute file path). Used together with `pem-key-path`. +* `embedded.azurite.pem-key-path` — path to a custom PEM private key. Used together with `pem-cert-path`. +* `embedded.azurite.pfx-cert-path` — path to a custom PFX/PKCS12 certificate. Used together with `pfx-password`. +* `embedded.azurite.pfx-password` — password for the PFX certificate. * `embedded.toxiproxy.proxies.azurite.enabled` Enables both creation of the container with ToxiProxy TCP proxy and a proxy to the `embedded-azurite` container. @@ -30,9 +36,9 @@ Account name and account key are hardcoded as of https://github.com/Azure/Azurit * `embedded.azurite.host` * `embedded.azurite.account-name` * `embedded.azurite.account-key` -* `embedded.azurite.blob-endpoint` (computed property `http://${host}:${port}/${accountName}` for convient configuration with `spring-cloud-azure-starter-storage-blob`) -* `embedded.azurite.queue-endpoint` (computed property `http://${host}:${port}/${accountName}` for convient configuration with `spring-cloud-azure-starter-storage-queue`) -* `embedded.azurite.table-endpoint` (computed property `http://${host}:${port}/${accountName}` for convient configuration with `spring-cloud-azure-starter-storage-table`) +* `embedded.azurite.blob-endpoint` (computed property `{http|https}://${host}:${port}/${accountName}` for convenient configuration with `spring-cloud-azure-starter-storage-blob`) +* `embedded.azurite.queue-endpoint` (computed property `{http|https}://${host}:${port}/${accountName}` for convenient configuration with `spring-cloud-azure-starter-storage-queue`) +* `embedded.azurite.table-endpoint` (computed property `{http|https}://${host}:${port}/${accountName}` for convenient configuration with `spring-cloud-azure-starter-storage-table`) * `embedded.azurite.toxiproxy.host` * `embedded.azurite.toxiproxy.blobStoragePort` * `embedded.azurite.toxiproxy.queueStoragePort` @@ -64,3 +70,43 @@ spring: ---- You can then access all beans from `spring-cloud-azure-starter-storage-blob`, i.e. `BlobServiceClientBuilder`. + +==== HTTPS and OAuth (DefaultAzureCredential) Example + +To use `DefaultAzureCredential` or any token-based credential, enable HTTPS and OAuth: + +./src/test/resources/bootstrap.properties +[source,properties] +---- +embedded.azurite.https-enabled=true +embedded.azurite.oauth-enabled=true +---- + +The embedded self-signed certificate is used automatically. Since it is self-signed you must configure the Azure SDK HTTP client to trust it in tests. The certificate is available as a classpath resource at `azurite/cert.pem`: + +[source,java] +---- +@Bean +BlobServiceClient blobServiceClient(AzuriteContainer azuriteContainer) throws SSLException { + SslContext insecureSslContext = SslContextBuilder.forClient() + .trustManager(InsecureTrustManagerFactory.INSTANCE) + .build(); + HttpClient reactor = HttpClient.create().secure(spec -> spec.sslContext(insecureSslContext)); + com.azure.core.http.HttpClient azureHttpClient = new NettyAsyncHttpClientBuilder(reactor).build(); + + return new BlobServiceClientBuilder() + .connectionString(azuriteContainer.getConnectionString()) + .httpClient(azureHttpClient) + .buildClient(); +} +---- + +To provide your own certificate instead of the embedded one: + +./src/test/resources/bootstrap.properties +[source,properties] +---- +embedded.azurite.https-enabled=true +embedded.azurite.pem-cert-path=classpath:my-cert.pem +embedded.azurite.pem-key-path=classpath:my-key.pem +---- diff --git a/embedded-azurite/src/main/java/com/playtika/testcontainer/azurite/AzuriteProperties.java b/embedded-azurite/src/main/java/com/playtika/testcontainer/azurite/AzuriteProperties.java index 4dd3c62d1..ab5b73139 100644 --- a/embedded-azurite/src/main/java/com/playtika/testcontainer/azurite/AzuriteProperties.java +++ b/embedded-azurite/src/main/java/com/playtika/testcontainer/azurite/AzuriteProperties.java @@ -22,10 +22,46 @@ public class AzuriteProperties extends CommonContainerProperties { */ static final String ACCOUNT_KEY = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="; + static final String DEFAULT_CERT_CLASSPATH = "azurite/cert.pem"; + static final String DEFAULT_KEY_CLASSPATH = "azurite/key.pem"; + int blobStoragePort = 10000; int queueStoragePort = 10001; int tableStoragePort = 10002; + /** + * Enables HTTPS for Azurite. Required for OAuth (DefaultAzureCredential) support. + * When enabled without providing cert/key paths, uses an embedded self-signed certificate. + */ + boolean httpsEnabled = false; + + /** + * Enables OAuth basic emulation (--oauth basic). Requires httpsEnabled=true. + * Allows using DefaultAzureCredential with Azurite. + */ + boolean oauthEnabled = false; + + /** + * Classpath or file path to a PEM certificate for HTTPS. Used together with pemKeyPath. + * If not set when httpsEnabled=true, the embedded self-signed certificate is used. + */ + String pemCertPath; + + /** + * Classpath or file path to a PEM private key for HTTPS. Used together with pemCertPath. + */ + String pemKeyPath; + + /** + * Classpath or file path to a PFX certificate for HTTPS. Used together with pfxPassword. + */ + String pfxCertPath; + + /** + * Password for the PFX certificate. + */ + String pfxPassword; + @Override public String getDefaultDockerImage() { // Please don`t remove this comment. diff --git a/embedded-azurite/src/main/java/com/playtika/testcontainer/azurite/EmbeddedAzuriteBootstrapConfiguration.java b/embedded-azurite/src/main/java/com/playtika/testcontainer/azurite/EmbeddedAzuriteBootstrapConfiguration.java index cf66f506c..e2cb90cec 100644 --- a/embedded-azurite/src/main/java/com/playtika/testcontainer/azurite/EmbeddedAzuriteBootstrapConfiguration.java +++ b/embedded-azurite/src/main/java/com/playtika/testcontainer/azurite/EmbeddedAzuriteBootstrapConfiguration.java @@ -19,6 +19,7 @@ import org.testcontainers.azure.AzuriteContainer; import org.testcontainers.containers.Network; import org.testcontainers.toxiproxy.ToxiproxyContainer; +import org.testcontainers.utility.MountableFile; import java.util.ArrayList; import java.util.Arrays; @@ -27,6 +28,8 @@ import java.util.Optional; import static com.playtika.testcontainer.azurite.AzuriteProperties.AZURITE_BEAN_NAME; +import static com.playtika.testcontainer.azurite.AzuriteProperties.DEFAULT_CERT_CLASSPATH; +import static com.playtika.testcontainer.azurite.AzuriteProperties.DEFAULT_KEY_CLASSPATH; import static com.playtika.testcontainer.common.utils.ContainerUtils.configureCommonsAndStart; @Slf4j @@ -105,9 +108,15 @@ public AzuriteContainer azurite(ConfigurableEnvironment environment, .withCreateContainerCmdModifier(cmd -> { List args = new ArrayList<>(Arrays.asList(cmd.getCmd())); args.add("--skipApiVersionCheck"); + if (properties.isOauthEnabled()) { + args.add("--oauth"); + args.add("basic"); + } cmd.withCmd(args); }); + configureSsl(azuriteContainer, properties); + network.ifPresent(azuriteContainer::withNetwork); azuriteContainer = (AzuriteContainer) configureCommonsAndStart(azuriteContainer, properties, log); @@ -115,6 +124,34 @@ public AzuriteContainer azurite(ConfigurableEnvironment environment, return azuriteContainer; } + private void configureSsl(AzuriteContainer azuriteContainer, AzuriteProperties properties) { + if (!properties.isHttpsEnabled()) { + return; + } + if (properties.isOauthEnabled()) { + log.info("Azurite OAuth enabled — HTTPS is required and will be configured."); + } + if (properties.getPfxCertPath() != null) { + azuriteContainer.withSsl(resolveMountableFile(properties.getPfxCertPath()), properties.getPfxPassword()); + } else if (properties.getPemCertPath() != null && properties.getPemKeyPath() != null) { + azuriteContainer.withSsl( + resolveMountableFile(properties.getPemCertPath()), + resolveMountableFile(properties.getPemKeyPath())); + } else { + log.info("Azurite HTTPS enabled with embedded self-signed certificate."); + azuriteContainer.withSsl( + MountableFile.forClasspathResource(DEFAULT_CERT_CLASSPATH), + MountableFile.forClasspathResource(DEFAULT_KEY_CLASSPATH)); + } + } + + private MountableFile resolveMountableFile(String path) { + if (path.startsWith("classpath:")) { + return MountableFile.forClasspathResource(path.substring("classpath:".length())); + } + return MountableFile.forHostPath(path); + } + private void registerEnvironment(AzuriteContainer azurite, ConfigurableEnvironment environment, AzuriteProperties properties) { @@ -123,6 +160,7 @@ private void registerEnvironment(AzuriteContainer azurite, Integer mappedQueueStoragePort = azurite.getMappedPort(properties.getQueueStoragePort()); Integer mappedTableStoragePort = azurite.getMappedPort(properties.getTableStoragePort()); String host = azurite.getHost(); + String protocol = properties.isHttpsEnabled() ? "https" : "http"; LinkedHashMap map = new LinkedHashMap<>(); map.put("embedded.azurite.host", host); @@ -131,9 +169,9 @@ private void registerEnvironment(AzuriteContainer azurite, map.put("embedded.azurite.tableStoragePort", mappedTableStoragePort); map.put("embedded.azurite.account-name", AzuriteProperties.ACCOUNT_NAME); map.put("embedded.azurite.account-key", AzuriteProperties.ACCOUNT_KEY); - map.put("embedded.azurite.blob-endpoint", "http://" + host + ":" + mappedBlobStoragePort + "/" + AzuriteProperties.ACCOUNT_NAME); - map.put("embedded.azurite.queue-endpoint", "http://" + host + ":" + mappedQueueStoragePort + "/" + AzuriteProperties.ACCOUNT_NAME); - map.put("embedded.azurite.table-endpoint", "http://" + host + ":" + mappedTableStoragePort + "/" + AzuriteProperties.ACCOUNT_NAME); + map.put("embedded.azurite.blob-endpoint", protocol + "://" + host + ":" + mappedBlobStoragePort + "/" + AzuriteProperties.ACCOUNT_NAME); + map.put("embedded.azurite.queue-endpoint", protocol + "://" + host + ":" + mappedQueueStoragePort + "/" + AzuriteProperties.ACCOUNT_NAME); + map.put("embedded.azurite.table-endpoint", protocol + "://" + host + ":" + mappedTableStoragePort + "/" + AzuriteProperties.ACCOUNT_NAME); map.put("embedded.azurite.networkAlias", AZURITE_BLOB_NETWORK_ALIAS); log.info("Started Azurite. Connection details: {}", map); diff --git a/embedded-azurite/src/test/java/com/playtika/testcontainer/azurite/EmbeddedAzuriteHttpsTest.java b/embedded-azurite/src/test/java/com/playtika/testcontainer/azurite/EmbeddedAzuriteHttpsTest.java new file mode 100644 index 000000000..39a90cae5 --- /dev/null +++ b/embedded-azurite/src/test/java/com/playtika/testcontainer/azurite/EmbeddedAzuriteHttpsTest.java @@ -0,0 +1,82 @@ +package com.playtika.testcontainer.azurite; + +import com.azure.core.http.netty.NettyAsyncHttpClientBuilder; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobServiceClient; +import com.azure.storage.blob.BlobServiceClientBuilder; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.testcontainers.azure.AzuriteContainer; +import reactor.netty.http.client.HttpClient; + +import javax.net.ssl.SSLException; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest( + classes = EmbeddedAzuriteHttpsTest.AzuriteHttpsTestConfiguration.class, + properties = {"embedded.azurite.https-enabled=true"}) +@Disabled("Create private and public key to check https") +class EmbeddedAzuriteHttpsTest { + + @Autowired + AzuriteContainer azuriteContainer; + + @Autowired + BlobServiceClient blobServiceClient; + + @Value("${embedded.azurite.blob-endpoint}") + String blobEndpoint; + + @Test + void blobEndpointUsesHttps() { + assertThat(blobEndpoint).startsWith("https://"); + } + + @Test + void connectionStringUsesHttps() { + assertThat(azuriteContainer.getConnectionString()).contains("DefaultEndpointsProtocol=https"); + } + + @Test + @DisplayName("basic blob operations work over HTTPS with the embedded self-signed certificate") + void createAndDeleteContainerBlobOverHttps() { + long containersBefore = blobServiceClient.listBlobContainers().stream().count(); + BlobContainerClient container = blobServiceClient.createBlobContainer(UUID.randomUUID().toString()); + assertThat(container.listBlobs().stream()).isEmpty(); + assertThat(blobServiceClient.listBlobContainers().stream().count()).isEqualTo(containersBefore + 1); + container.delete(); + assertThat(blobServiceClient.listBlobContainers().stream().count()).isEqualTo(containersBefore); + } + + @EnableAutoConfiguration + @Configuration + static class AzuriteHttpsTestConfiguration { + + @Bean + BlobServiceClient blobServiceClient(AzuriteContainer azuriteContainer) throws SSLException { + // Trust all certs so the embedded self-signed certificate is accepted in tests. + io.netty.handler.ssl.SslContext insecureSslContext = SslContextBuilder.forClient() + .trustManager(InsecureTrustManagerFactory.INSTANCE) + .build(); + HttpClient reactor = HttpClient.create().secure(spec -> spec.sslContext(insecureSslContext)); + com.azure.core.http.HttpClient azureHttpClient = new NettyAsyncHttpClientBuilder(reactor).build(); + + return new BlobServiceClientBuilder() + .connectionString(azuriteContainer.getConnectionString()) + .httpClient(azureHttpClient) + .buildClient(); + } + } +} From 359a639540fbd505752ea306ac896005026edfa0 Mon Sep 17 00:00:00 2001 From: admitrov Date: Thu, 14 May 2026 14:48:09 +0100 Subject: [PATCH 24/25] Add anonymous authentication configuration support to embedded Grafana Add `anonymousAuthEnabled` and `anonymousOrgRole` properties to GrafanaProperties. Configure `GF_AUTH_ANONYMOUS_ENABLED` and `GF_AUTH_ANONYMOUS_ORG_ROLE` environment variables when anonymous auth is enabled. Add test to verify environment variables and unauthenticated API access. Update README with configuration examples. --- embedded-grafana/README.adoc | 2 + ...EmbeddedGrafanaBootstrapConfiguration.java | 5 ++ .../grafana/GrafanaProperties.java | 2 + .../EmbeddedGrafanaAnonymousAuthTest.java | 74 +++++++++++++++++++ .../test/resources/bootstrap-anonymous.yaml | 3 + 5 files changed, 86 insertions(+) create mode 100644 embedded-grafana/src/test/java/com/playtika/testcontainer/grafana/EmbeddedGrafanaAnonymousAuthTest.java create mode 100644 embedded-grafana/src/test/resources/bootstrap-anonymous.yaml diff --git a/embedded-grafana/README.adoc b/embedded-grafana/README.adoc index 1db178133..1763c4ba9 100644 --- a/embedded-grafana/README.adoc +++ b/embedded-grafana/README.adoc @@ -26,6 +26,8 @@ * `embedded.grafana.tempoPort` `(default is 3200, Tempo HTTP)` * `embedded.grafana.otlpGrpcPort` `(default is 4317, OTLP gRPC)` * `embedded.grafana.otlpHttpPort` `(default is 4318, OTLP HTTP)` +* `embedded.grafana.anonymousAuthEnabled` `(true|false, default is false)` - Sets `GF_AUTH_ANONYMOUS_ENABLED=true` +* `embedded.grafana.anonymousOrgRole` `(default is 'Viewer')` - Sets `GF_AUTH_ANONYMOUS_ORG_ROLE`; only applied when `anonymousAuthEnabled=true` * `embedded.toxiproxy.proxies.grafana.enabled` Enables both creation of the container with ToxiProxy TCP proxy and a proxy to the `embedded-grafana` container. * `embedded.grafana.filesToInclude` Classpath resources to copy into the container at startup. Use this to provision custom Grafana datasources, dashboards, or alert rules. Example `bootstrap.yaml`: diff --git a/embedded-grafana/src/main/java/com/playtika/testcontainer/grafana/EmbeddedGrafanaBootstrapConfiguration.java b/embedded-grafana/src/main/java/com/playtika/testcontainer/grafana/EmbeddedGrafanaBootstrapConfiguration.java index c3a889709..a1fe09d94 100644 --- a/embedded-grafana/src/main/java/com/playtika/testcontainer/grafana/EmbeddedGrafanaBootstrapConfiguration.java +++ b/embedded-grafana/src/main/java/com/playtika/testcontainer/grafana/EmbeddedGrafanaBootstrapConfiguration.java @@ -67,6 +67,11 @@ public LgtmStackContainer grafana(ConfigurableEnvironment environment, .withNetwork(Network.SHARED) .withNetworkAliases(properties.getNetworkAlias(), GRAFANA_NETWORK_ALIAS); + if (properties.isAnonymousAuthEnabled()) { + container.withEnv("GF_AUTH_ANONYMOUS_ENABLED", "true") + .withEnv("GF_AUTH_ANONYMOUS_ORG_ROLE", properties.getAnonymousOrgRole()); + } + network.ifPresent(container::withNetwork); configureCommonsAndStart(container, properties, log); diff --git a/embedded-grafana/src/main/java/com/playtika/testcontainer/grafana/GrafanaProperties.java b/embedded-grafana/src/main/java/com/playtika/testcontainer/grafana/GrafanaProperties.java index b3d0dcb48..ef3e6ea8d 100644 --- a/embedded-grafana/src/main/java/com/playtika/testcontainer/grafana/GrafanaProperties.java +++ b/embedded-grafana/src/main/java/com/playtika/testcontainer/grafana/GrafanaProperties.java @@ -21,6 +21,8 @@ public class GrafanaProperties extends CommonContainerProperties { int tempoPort = 3200; int otlpGrpcPort = 4317; int otlpHttpPort = 4318; + boolean anonymousAuthEnabled = false; + String anonymousOrgRole = "Viewer"; // https://hub.docker.com/r/grafana/otel-lgtm @Override diff --git a/embedded-grafana/src/test/java/com/playtika/testcontainer/grafana/EmbeddedGrafanaAnonymousAuthTest.java b/embedded-grafana/src/test/java/com/playtika/testcontainer/grafana/EmbeddedGrafanaAnonymousAuthTest.java new file mode 100644 index 000000000..3dd6f7f17 --- /dev/null +++ b/embedded-grafana/src/test/java/com/playtika/testcontainer/grafana/EmbeddedGrafanaAnonymousAuthTest.java @@ -0,0 +1,74 @@ +package com.playtika.testcontainer.grafana; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.web.util.UriComponentsBuilder; +import org.testcontainers.grafana.LgtmStackContainer; + +import java.io.IOException; + +import static com.playtika.testcontainer.grafana.GrafanaProperties.GRAFANA_BEAN_NAME; +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; + +@ActiveProfiles("anonymous") +@DirtiesContext +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = EmbeddedGrafanaAnonymousAuthTest.TestConfiguration.class) +class EmbeddedGrafanaAnonymousAuthTest { + + @Value("${embedded.grafana.host}") + private String grafanaHost; + @Value("${embedded.grafana.port}") + private int grafanaPort; + + @Autowired + @Qualifier(GRAFANA_BEAN_NAME) + private LgtmStackContainer grafanaContainer; + + @Test + void anonymousAuthEnvVarsShouldBeSetOnContainer() throws IOException, InterruptedException { + var result = grafanaContainer.execInContainer("sh", "-c", "env"); + assertThat(result.getStdout()) + .contains("GF_AUTH_ANONYMOUS_ENABLED=true") + .contains("GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer"); + } + + @Test + void grafanaApiShouldBeReachableWithoutCredentials() { + given() + .get(url("/api/health")) + .then() + .statusCode(200); + } + + @Test + void authenticatedEndpointShouldBeAccessibleWithoutCredentials() { + given() + .get(url("/api/dashboards/home")) + .then() + .statusCode(200); + } + + private String url(String path) { + return UriComponentsBuilder.newInstance() + .scheme("http") + .host(grafanaHost) + .port(grafanaPort) + .path(path) + .build() + .toUriString(); + } + + @EnableAutoConfiguration + @Configuration + static class TestConfiguration {} +} diff --git a/embedded-grafana/src/test/resources/bootstrap-anonymous.yaml b/embedded-grafana/src/test/resources/bootstrap-anonymous.yaml new file mode 100644 index 000000000..c571c9968 --- /dev/null +++ b/embedded-grafana/src/test/resources/bootstrap-anonymous.yaml @@ -0,0 +1,3 @@ +embedded: + grafana: + anonymousAuthEnabled: true From 021b29fa782928bf496eb51989c54e2a133a32e6 Mon Sep 17 00:00:00 2001 From: admitrov Date: Fri, 22 May 2026 12:35:58 +0100 Subject: [PATCH 25/25] Enable Toxiproxy for MemSQL and add latency emulation test Enable Toxiproxy in bootstrap properties. Add ToxiproxyClientProxy autowiring and implement latency emulation test using toxics API. Update JDBC URL to use toxiproxy host/port. Replace disabled test with working implementation using upstream latency toxic and assertion with offset tolerance. --- ...eddedMemSqlBootstrapConfigurationTest.java | 35 +++++++++++-------- .../resources/application-enabled.properties | 4 +-- .../resources/bootstrap-enabled.properties | 3 +- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/embedded-memsql/src/test/java/com/playtika/testcontainer/memsql/EmbeddedMemSqlBootstrapConfigurationTest.java b/embedded-memsql/src/test/java/com/playtika/testcontainer/memsql/EmbeddedMemSqlBootstrapConfigurationTest.java index ee3dbf755..51fc5bb6c 100644 --- a/embedded-memsql/src/test/java/com/playtika/testcontainer/memsql/EmbeddedMemSqlBootstrapConfigurationTest.java +++ b/embedded-memsql/src/test/java/com/playtika/testcontainer/memsql/EmbeddedMemSqlBootstrapConfigurationTest.java @@ -1,6 +1,9 @@ package com.playtika.testcontainer.memsql; +import com.playtika.testcontainer.toxiproxy.ToxiproxyClientProxy; +import eu.rekawek.toxiproxy.model.ToxicDirection; import lombok.extern.slf4j.Slf4j; +import org.assertj.core.data.Offset; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -35,6 +38,9 @@ public class EmbeddedMemSqlBootstrapConfigurationTest { @Autowired ConfigurableEnvironment environment; + @Autowired + ToxiproxyClientProxy memsqlContainerProxy; + @Test public void shouldConnectToMemSQL() throws Exception { assertThat(jdbcTemplate.queryForObject("select @@version_comment", String.class)).contains("SingleStoreDB"); @@ -43,20 +49,21 @@ public void shouldConnectToMemSQL() throws Exception { assertThat(jdbcTemplate.queryForList("select * from foo")).hasSize(3); } -// @Test -// @Disabled("image doesn't support to simply install tc") -// public void shouldEmulateLatency() throws Exception { -// jdbcTemplate.execute("create table bar (id int primary key);"); -// jdbcTemplate.execute("insert into bar values (1), (2), (3);"); -// -// memsqlNetworkTestOperations.withNetworkLatency(ofMillis(1000), -// () -> assertThat(durationOf(() -> jdbcTemplate.queryForList("select * from bar"))) -// .isGreaterThan(1000L) -// ); -// -// assertThat(durationOf(() -> jdbcTemplate.queryForList("select * from bar"))) -// .isLessThan(100L); -// } + @Test + public void shouldEmulateLatency() throws Exception { + jdbcTemplate.execute("create table bar (id int primary key);"); + jdbcTemplate.execute("insert into bar values (1), (2), (3);"); + + memsqlContainerProxy.toxics().latency("latency", ToxicDirection.UPSTREAM, 1000); + + assertThat(durationOf(() -> jdbcTemplate.queryForList("select * from bar"))) + .isCloseTo(1000L, Offset.offset(100L)); + + memsqlContainerProxy.toxics().get("latency").remove(); + + assertThat(durationOf(() -> jdbcTemplate.queryForList("select * from bar"))) + .isLessThan(100L); + } @Test public void shouldSetupDependsOnForAllDataSources() throws Exception { diff --git a/embedded-memsql/src/test/resources/application-enabled.properties b/embedded-memsql/src/test/resources/application-enabled.properties index 0982b4e6d..332e26a82 100644 --- a/embedded-memsql/src/test/resources/application-enabled.properties +++ b/embedded-memsql/src/test/resources/application-enabled.properties @@ -1,7 +1,7 @@ spring.datasource.driver-class-name=org.mariadb.jdbc.Driver -spring.datasource.url=jdbc:mariadb://${embedded.memsql.host}:${embedded.memsql.port}/${embedded.memsql.schema} +spring.datasource.url=jdbc:mariadb://${embedded.memsql.toxiproxy.host}:${embedded.memsql.toxiproxy.port}/${embedded.memsql.schema} spring.datasource.username=${embedded.memsql.user} spring.datasource.password=${embedded.memsql.password} spring.datasource.tomcat.test-on-borrow=true spring.datasource.tomcat.validation-query=SELECT 1; -spring.datasource.tomcat.test-on-connect=true \ No newline at end of file +spring.datasource.tomcat.test-on-connect=true diff --git a/embedded-memsql/src/test/resources/bootstrap-enabled.properties b/embedded-memsql/src/test/resources/bootstrap-enabled.properties index 68e08defc..99df0876e 100644 --- a/embedded-memsql/src/test/resources/bootstrap-enabled.properties +++ b/embedded-memsql/src/test/resources/bootstrap-enabled.properties @@ -1 +1,2 @@ -embedded.memsql.licenseKey=BGE5YzE5NTdmN2I1NDQ4MjhhNTQ0MTM4YWQ4Y2Q5ZWNiAAAAAAAAAAAEAAAAAAAAAAwwNQIYKbH2LOLeT189H+nBdRagLdLboVuJTsgJAhkAtSepSZkq0Zn4FDVtvZyASWzovR2OeeyiAA== \ No newline at end of file +embedded.memsql.licenseKey=BGE5YzE5NTdmN2I1NDQ4MjhhNTQ0MTM4YWQ4Y2Q5ZWNiAAAAAAAAAAAEAAAAAAAAAAwwNQIYKbH2LOLeT189H+nBdRagLdLboVuJTsgJAhkAtSepSZkq0Zn4FDVtvZyASWzovR2OeeyiAA== +embedded.toxiproxy.proxies.memsql.enabled=true