From 53b386970a74bb5995d8864b8824907a627bfa97 Mon Sep 17 00:00:00 2001 From: taole33 Date: Sat, 6 Dec 2025 10:10:46 +0900 Subject: [PATCH 1/7] feat(module/mongodb): Add support for initialization scripts Running initialization scripts in MongoDB causes the container to restart, which previously required manual WaitStrategy adjustment. This commit adds 'withInitScript(String)' to automatically handle the script copy and adjust the WaitStrategy to expect two startup log messages. Fixes #3066 --- .../containers/MongoDBContainer.java | 22 +++++++++++++++ .../mongodb/MongoDBInitScriptTest.java | 27 +++++++++++++++++++ modules/mongodb/src/test/resources/init.js | 1 + 3 files changed, 50 insertions(+) create mode 100644 modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBInitScriptTest.java create mode 100644 modules/mongodb/src/test/resources/init.js diff --git a/modules/mongodb/src/main/java/org/testcontainers/containers/MongoDBContainer.java b/modules/mongodb/src/main/java/org/testcontainers/containers/MongoDBContainer.java index d30f45739c0..83b57fcb4f8 100644 --- a/modules/mongodb/src/main/java/org/testcontainers/containers/MongoDBContainer.java +++ b/modules/mongodb/src/main/java/org/testcontainers/containers/MongoDBContainer.java @@ -223,4 +223,26 @@ void withSharding() { setEntrypoint("sh"); } } + + /** + * Executes a MongoDB initialization script from the classpath during startup. + *

+ * The script will be copied to {@code /docker-entrypoint-initdb.d/init.js}. + * This method also adjusts the {@link org.testcontainers.containers.wait.strategy.WaitStrategy} + * to expect the "waiting for connections" log message twice, as the execution of an init script + * causes MongoDB to restart. + * + * @param scriptPath the path to the init script file on the classpath + * @return this container instance + */ + public MongoDBContainer withInitScript(String scriptPath) { + withCopyFileToContainer( + MountableFile.forClasspathResource(scriptPath), + "/docker-entrypoint-initdb.d/init.js" + ); + + this.waitStrategy = Wait.forLogMessage("(?i).*waiting for connections.*", 2); + + return this; + } } diff --git a/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBInitScriptTest.java b/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBInitScriptTest.java new file mode 100644 index 00000000000..34ff0e728ec --- /dev/null +++ b/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBInitScriptTest.java @@ -0,0 +1,27 @@ +package org.testcontainers.containers; + +import org.junit.jupiter.api.Test; +import org.testcontainers.utility.MountableFile; + +import java.time.Duration; + +import static org.assertj.core.api.Assertions.assertThat; + +class MongoDBInitScriptTest { + + @Test + void testWithInitScript() { + // Start the container using try-with-resources to ensure it closes automatically + try (MongoDBContainer mongoDB = new MongoDBContainer("mongo:4.0.10") + // Configure the init script. This triggers a restart inside the container, + // so the container must wait for the second "waiting for connections" log message. + .withInitScript("init.js") + .withStartupTimeout(Duration.ofSeconds(30))) { + + mongoDB.start(); + + // Assert that the container started successfully + assertThat(mongoDB.isRunning()).isTrue(); + } + } +} \ No newline at end of file diff --git a/modules/mongodb/src/test/resources/init.js b/modules/mongodb/src/test/resources/init.js new file mode 100644 index 00000000000..fe29f7f7d2f --- /dev/null +++ b/modules/mongodb/src/test/resources/init.js @@ -0,0 +1 @@ +db.createCollection("test_collection"); \ No newline at end of file From e2adfb18f4d1f3a396d680820244e32cb6eef3d6 Mon Sep 17 00:00:00 2001 From: taole33 Date: Sat, 6 Dec 2025 10:20:47 +0900 Subject: [PATCH 2/7] style: Apply code formatting --- .../containers/MongoDBContainer.java | 5 +---- .../mongodb/MongoDBInitScriptTest.java | 16 ++++++++-------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/modules/mongodb/src/main/java/org/testcontainers/containers/MongoDBContainer.java b/modules/mongodb/src/main/java/org/testcontainers/containers/MongoDBContainer.java index 83b57fcb4f8..43c80c45cc7 100644 --- a/modules/mongodb/src/main/java/org/testcontainers/containers/MongoDBContainer.java +++ b/modules/mongodb/src/main/java/org/testcontainers/containers/MongoDBContainer.java @@ -236,10 +236,7 @@ void withSharding() { * @return this container instance */ public MongoDBContainer withInitScript(String scriptPath) { - withCopyFileToContainer( - MountableFile.forClasspathResource(scriptPath), - "/docker-entrypoint-initdb.d/init.js" - ); + withCopyFileToContainer(MountableFile.forClasspathResource(scriptPath), "/docker-entrypoint-initdb.d/init.js"); this.waitStrategy = Wait.forLogMessage("(?i).*waiting for connections.*", 2); diff --git a/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBInitScriptTest.java b/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBInitScriptTest.java index 34ff0e728ec..cc051b9e439 100644 --- a/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBInitScriptTest.java +++ b/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBInitScriptTest.java @@ -1,7 +1,6 @@ package org.testcontainers.containers; import org.junit.jupiter.api.Test; -import org.testcontainers.utility.MountableFile; import java.time.Duration; @@ -12,16 +11,17 @@ class MongoDBInitScriptTest { @Test void testWithInitScript() { // Start the container using try-with-resources to ensure it closes automatically - try (MongoDBContainer mongoDB = new MongoDBContainer("mongo:4.0.10") - // Configure the init script. This triggers a restart inside the container, - // so the container must wait for the second "waiting for connections" log message. - .withInitScript("init.js") - .withStartupTimeout(Duration.ofSeconds(30))) { - + try ( + MongoDBContainer mongoDB = new MongoDBContainer("mongo:4.0.10") + // Configure the init script. This triggers a restart inside the container, + // so the container must wait for the second "waiting for connections" log message. + .withInitScript("init.js") + .withStartupTimeout(Duration.ofSeconds(30)) + ) { mongoDB.start(); // Assert that the container started successfully assertThat(mongoDB.isRunning()).isTrue(); } } -} \ No newline at end of file +} From e81a2aca4850474e9e4fd1aa1f13f98e3ec689c1 Mon Sep 17 00:00:00 2001 From: taole33 Date: Wed, 10 Dec 2025 08:24:36 +0900 Subject: [PATCH 3/7] fix(module/mongodb):move to org.testcontainers.mongodb.MongoDBContainer --- .../containers/MongoDBContainer.java | 19 ------------- .../mongodb/MongoDBContainer.java | 19 +++++++++++++ .../mongodb/MongoDBContainerTest.java | 15 +++++++++++ .../mongodb/MongoDBInitScriptTest.java | 27 ------------------- 4 files changed, 34 insertions(+), 46 deletions(-) delete mode 100644 modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBInitScriptTest.java diff --git a/modules/mongodb/src/main/java/org/testcontainers/containers/MongoDBContainer.java b/modules/mongodb/src/main/java/org/testcontainers/containers/MongoDBContainer.java index 43c80c45cc7..d30f45739c0 100644 --- a/modules/mongodb/src/main/java/org/testcontainers/containers/MongoDBContainer.java +++ b/modules/mongodb/src/main/java/org/testcontainers/containers/MongoDBContainer.java @@ -223,23 +223,4 @@ void withSharding() { setEntrypoint("sh"); } } - - /** - * Executes a MongoDB initialization script from the classpath during startup. - *

- * The script will be copied to {@code /docker-entrypoint-initdb.d/init.js}. - * This method also adjusts the {@link org.testcontainers.containers.wait.strategy.WaitStrategy} - * to expect the "waiting for connections" log message twice, as the execution of an init script - * causes MongoDB to restart. - * - * @param scriptPath the path to the init script file on the classpath - * @return this container instance - */ - public MongoDBContainer withInitScript(String scriptPath) { - withCopyFileToContainer(MountableFile.forClasspathResource(scriptPath), "/docker-entrypoint-initdb.d/init.js"); - - this.waitStrategy = Wait.forLogMessage("(?i).*waiting for connections.*", 2); - - return this; - } } diff --git a/modules/mongodb/src/main/java/org/testcontainers/mongodb/MongoDBContainer.java b/modules/mongodb/src/main/java/org/testcontainers/mongodb/MongoDBContainer.java index c61be6675ba..4836544c5b8 100644 --- a/modules/mongodb/src/main/java/org/testcontainers/mongodb/MongoDBContainer.java +++ b/modules/mongodb/src/main/java/org/testcontainers/mongodb/MongoDBContainer.java @@ -204,4 +204,23 @@ public String getReplicaSetUrl(String databaseName) { } return getConnectionString() + "/" + databaseName; } + + /** + * Executes a MongoDB initialization script from the classpath during startup. + *

+ * The script will be copied to {@code /docker-entrypoint-initdb.d/init.js}. + * This method also adjusts the {@link org.testcontainers.containers.wait.strategy.WaitStrategy} + * to expect the "waiting for connections" log message twice, as the execution of an init script + * causes MongoDB to restart. + * + * @param scriptPath the path to the init script file on the classpath + * @return this container instance + */ + public MongoDBContainer withInitScript(String scriptPath) { + withCopyFileToContainer(MountableFile.forClasspathResource(scriptPath), "/docker-entrypoint-initdb.d/init.js"); + + this.waitStrategy = Wait.forLogMessage("(?i).*waiting for connections.*", 2); + + return this; + } } diff --git a/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java b/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java index 816243d769e..46c7c993d0a 100644 --- a/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java +++ b/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java @@ -2,6 +2,8 @@ import org.junit.jupiter.api.Test; +import java.time.Duration; + import static org.assertj.core.api.Assertions.assertThat; class MongoDBContainerTest extends AbstractMongo { @@ -38,4 +40,17 @@ void shouldTestDatabaseName() { assertThat(mongoDBContainer.getReplicaSetUrl(databaseName)).endsWith(databaseName); } } + + @Test + void testWithInitScript() { + try ( + MongoDBContainer mongoDB = new MongoDBContainer("mongo:4.0.10") + .withInitScript("init.js") + .withStartupTimeout(Duration.ofSeconds(30)) + ) { + mongoDB.start(); + + assertThat(mongoDB.isRunning()).isTrue(); + } + } } diff --git a/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBInitScriptTest.java b/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBInitScriptTest.java deleted file mode 100644 index cc051b9e439..00000000000 --- a/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBInitScriptTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.testcontainers.containers; - -import org.junit.jupiter.api.Test; - -import java.time.Duration; - -import static org.assertj.core.api.Assertions.assertThat; - -class MongoDBInitScriptTest { - - @Test - void testWithInitScript() { - // Start the container using try-with-resources to ensure it closes automatically - try ( - MongoDBContainer mongoDB = new MongoDBContainer("mongo:4.0.10") - // Configure the init script. This triggers a restart inside the container, - // so the container must wait for the second "waiting for connections" log message. - .withInitScript("init.js") - .withStartupTimeout(Duration.ofSeconds(30)) - ) { - mongoDB.start(); - - // Assert that the container started successfully - assertThat(mongoDB.isRunning()).isTrue(); - } - } -} From 35b753bf84b20069bfbc9534b4c62610f0fd80f3 Mon Sep 17 00:00:00 2001 From: taole33 Date: Thu, 11 Dec 2025 21:39:28 +0900 Subject: [PATCH 4/7] Fix withInitScript compatibility with ReplicaSet and Sharding modes --- .../mongodb/MongoDBContainer.java | 65 ++++++++++++++--- .../mongodb/MongoDBContainerTest.java | 69 ++++++++++++++++++- .../src/test/resources/initEdgeCase.js | 16 +++++ 3 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 modules/mongodb/src/test/resources/initEdgeCase.js diff --git a/modules/mongodb/src/main/java/org/testcontainers/mongodb/MongoDBContainer.java b/modules/mongodb/src/main/java/org/testcontainers/mongodb/MongoDBContainer.java index 4836544c5b8..ec22bc72539 100644 --- a/modules/mongodb/src/main/java/org/testcontainers/mongodb/MongoDBContainer.java +++ b/modules/mongodb/src/main/java/org/testcontainers/mongodb/MongoDBContainer.java @@ -41,10 +41,16 @@ public class MongoDBContainer extends GenericContainer { private static final String STARTER_SCRIPT = "/testcontainers_start.sh"; + private static final String SCRIPT_DESTINATION_DEFAULT = "/docker-entrypoint-initdb.d/init.js"; + + private static final String SCRIPT_DESTINATION_MANUAL = "/tmp/init.js"; + private boolean shardingEnabled; private boolean rsEnabled; + private String initScriptPath; + public MongoDBContainer(@NonNull String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } @@ -68,6 +74,26 @@ protected void containerIsStarted(InspectContainerResponse containerInfo, boolea if (this.rsEnabled) { initReplicaSet(reused); } + + boolean isClusterMode = this.shardingEnabled || this.rsEnabled; + + if (isClusterMode && this.initScriptPath != null) { + executeInitScriptInContainer(); + } + } + + @Override + protected void configure() { + super.configure(); + boolean isClusterMode = this.shardingEnabled || this.rsEnabled; + if (this.initScriptPath != null) { + String destination = isClusterMode ? SCRIPT_DESTINATION_MANUAL : SCRIPT_DESTINATION_DEFAULT; + withCopyFileToContainer(MountableFile.forClasspathResource(this.initScriptPath), destination); + } + + if (this.initScriptPath != null && !isClusterMode) { + this.waitStrategy = Wait.forLogMessage("(?i).*waiting for connections.*", 2); + } } private String[] buildMongoEvalCommand(String command) { @@ -208,19 +234,42 @@ public String getReplicaSetUrl(String databaseName) { /** * Executes a MongoDB initialization script from the classpath during startup. *

- * The script will be copied to {@code /docker-entrypoint-initdb.d/init.js}. - * This method also adjusts the {@link org.testcontainers.containers.wait.strategy.WaitStrategy} - * to expect the "waiting for connections" log message twice, as the execution of an init script - * causes MongoDB to restart. + * In standalone mode, the script will be copied to {@code /docker-entrypoint-initdb.d/init.js}, + * and the {@link org.testcontainers.containers.wait.strategy.WaitStrategy} is adjusted + * to expect the "waiting for connections" log message twice. + *

+ * In Replica Set or Sharding mode, the script is copied to a temporary location and executed + * manually after the cluster is initialized. * * @param scriptPath the path to the init script file on the classpath * @return this container instance */ public MongoDBContainer withInitScript(String scriptPath) { - withCopyFileToContainer(MountableFile.forClasspathResource(scriptPath), "/docker-entrypoint-initdb.d/init.js"); - - this.waitStrategy = Wait.forLogMessage("(?i).*waiting for connections.*", 2); - + this.initScriptPath = scriptPath; return this; } + + @SneakyThrows + private void executeInitScriptInContainer() { + String cmd = + "mongosh " + + MONGODB_DATABASE_NAME_DEFAULT + + " " + + SCRIPT_DESTINATION_MANUAL + + " || mongo " + + MONGODB_DATABASE_NAME_DEFAULT + + " " + + SCRIPT_DESTINATION_MANUAL; + + ExecResult result = execInContainer("sh", "-c", cmd); + if (result.getExitCode() != CONTAINER_EXIT_CODE_OK) { + throw new IllegalStateException( + String.format( + "Failed to execute init script.\nStdout: %s\nStderr: %s", + result.getStdout(), + result.getStderr() + ) + ); + } + } } diff --git a/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java b/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java index 46c7c993d0a..ec38937cbc2 100644 --- a/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java +++ b/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java @@ -42,15 +42,80 @@ void shouldTestDatabaseName() { } @Test - void testWithInitScript() { + void shouldExecuteInitScript() { try ( MongoDBContainer mongoDB = new MongoDBContainer("mongo:4.0.10") .withInitScript("init.js") .withStartupTimeout(Duration.ofSeconds(30)) ) { mongoDB.start(); - assertThat(mongoDB.isRunning()).isTrue(); } } + + @Test + void shouldExecuteInitScriptWithEdgeCases() { + try ( + MongoDBContainer mongoDB = new MongoDBContainer("mongo:4.0.10") + .withInitScript("initEdgeCase.js") + .withEnv("LANG", "C.UTF-8") + .withEnv("LC_ALL", "C.UTF-8") + .withStartupTimeout(Duration.ofSeconds(30)) + ) { + mongoDB.start(); + + try ( + com.mongodb.client.MongoClient client = com.mongodb.client.MongoClients.create( + mongoDB.getReplicaSetUrl() + ) + ) { + String expectedComplexName = "test_col_\"_with_specials_!@#%^&*()"; + String expectedJapaneseName = "日本語 コレクション テスト"; + + assertThat(client.getDatabase("test").listCollectionNames()) + .as("Check if init script created the collections with special chars and Japanese") + .contains(expectedComplexName, expectedJapaneseName); + } + } + } + + @Test + void shouldExecuteInitScriptWithReplicaSet() { + try (MongoDBContainer mongo = new MongoDBContainer("mongo:7.0.0").withInitScript("init.js").withReplicaSet()) { + mongo.start(); + assertInitScriptExecuted(mongo); + } + } + + @Test + void shouldExecuteInitScriptWithReplicaSetConfiguredFirst() { + try (MongoDBContainer mongo = new MongoDBContainer("mongo:7.0.0").withReplicaSet().withInitScript("init.js")) { + mongo.start(); + assertInitScriptExecuted(mongo); + } + } + + @Test + void shouldExecuteInitScriptWithSharding() { + try (MongoDBContainer mongo = new MongoDBContainer("mongo:7.0.0").withInitScript("init.js").withSharding()) { + mongo.start(); + assertInitScriptExecuted(mongo); + } + } + + @Test + void shouldExecuteInitScriptWithShardingConfiguredFirst() { + try (MongoDBContainer mongo = new MongoDBContainer("mongo:7.0.0").withSharding().withInitScript("init.js")) { + mongo.start(); + assertInitScriptExecuted(mongo); + } + } + + private void assertInitScriptExecuted(MongoDBContainer mongo) { + try (com.mongodb.client.MongoClient client = com.mongodb.client.MongoClients.create(mongo.getReplicaSetUrl())) { + assertThat(client.getDatabase("test").listCollectionNames()) + .as("Check if init.js created the collection") + .contains("test_collection"); + } + } } diff --git a/modules/mongodb/src/test/resources/initEdgeCase.js b/modules/mongodb/src/test/resources/initEdgeCase.js new file mode 100644 index 00000000000..40b7de35384 --- /dev/null +++ b/modules/mongodb/src/test/resources/initEdgeCase.js @@ -0,0 +1,16 @@ +var complexCollectionName = 'test_col_"_with_specials_!@#%^&*()'; + +db.createCollection(complexCollectionName); + +var japaneseCollectionName = "日本語 コレクション テスト"; + +db.createCollection(japaneseCollectionName); + +db.getCollection(complexCollectionName).insertOne({ + "_id": 1, + "key_with_quotes": "This is a \"double quoted\" string", + "key_with_json_chars": "{ } [ ] : ,", + "description": "特殊記号を含むコレクションへの挿入テスト" +}); + +print("Initialization completed: " + complexCollectionName); \ No newline at end of file From 488b5fab86c0991633fc22d57219dd6dda47b868 Mon Sep 17 00:00:00 2001 From: taole33 Date: Thu, 11 Dec 2025 21:56:23 +0900 Subject: [PATCH 5/7] Fix edgecases test --- .../mongodb/MongoDBContainerTest.java | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java b/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java index ec38937cbc2..f179724acbb 100644 --- a/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java +++ b/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java @@ -72,9 +72,29 @@ void shouldExecuteInitScriptWithEdgeCases() { String expectedComplexName = "test_col_\"_with_specials_!@#%^&*()"; String expectedJapaneseName = "日本語 コレクション テスト"; - assertThat(client.getDatabase("test").listCollectionNames()) - .as("Check if init script created the collections with special chars and Japanese") - .contains(expectedComplexName, expectedJapaneseName); + com.mongodb.client.MongoDatabase database = client.getDatabase("test"); + + assertThat(database.listCollectionNames()).contains(expectedComplexName, expectedJapaneseName); + + com.mongodb.client.MongoCollection collection = database.getCollection( + expectedComplexName + ); + + org.bson.Document doc = collection.find(new org.bson.Document("_id", 1)).first(); + + assertThat(doc).as("Document with _id=1 should exist").isNotNull(); + + assertThat(doc.getString("key_with_quotes")) + .as("Double quotes should be preserved correctly") + .isEqualTo("This is a \"double quoted\" string"); + + assertThat(doc.getString("key_with_json_chars")) + .as("JSON special chars should be treated as plain text") + .isEqualTo("{ } [ ] : ,"); + + assertThat(doc.getString("description")) + .as("Japanese text should be preserved correctly") + .isEqualTo("特殊記号を含むコレクションへの挿入テスト"); } } } From b7ca5315a532df58c354e357f1da97001249f852 Mon Sep 17 00:00:00 2001 From: taole33 Date: Tue, 16 Dec 2025 10:59:27 +0900 Subject: [PATCH 6/7] fix: Address review comments --- .../mongodb/MongoDBContainer.java | 53 ++++++++++++++++++- .../mongodb/MongoDBContainerTest.java | 42 ++++----------- ...EdgeCase.js => initEdgeCase!@#%^& *'().js} | 6 +-- 3 files changed, 63 insertions(+), 38 deletions(-) rename modules/mongodb/src/test/resources/{initEdgeCase.js => initEdgeCase!@#%^& *'().js} (63%) diff --git a/modules/mongodb/src/main/java/org/testcontainers/mongodb/MongoDBContainer.java b/modules/mongodb/src/main/java/org/testcontainers/mongodb/MongoDBContainer.java index ec22bc72539..ca08c0d7223 100644 --- a/modules/mongodb/src/main/java/org/testcontainers/mongodb/MongoDBContainer.java +++ b/modules/mongodb/src/main/java/org/testcontainers/mongodb/MongoDBContainer.java @@ -6,10 +6,16 @@ import lombok.extern.slf4j.Slf4j; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.builder.Transferable; import org.testcontainers.utility.DockerImageName; import org.testcontainers.utility.MountableFile; import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Enumeration; /** * Testcontainers implementation for MongoDB. @@ -82,13 +88,56 @@ protected void containerIsStarted(InspectContainerResponse containerInfo, boolea } } + /** + * Configures the container. + *

+ * This method handles the transfer of the initialization script to the container. + * Unlike standard file copying mechanisms, this implementation explicitly reads the script content as bytes + * and uses {@link org.testcontainers.images.builder.Transferable} to copy it. + *

+ * This approach is necessary to strictly support filenames containing special characters + * (e.g., "#", spaces, etc.) on the classpath. Standard resource loading methods may misinterpret + * these characters (e.g., treating "#" as a URL fragment), causing resolution failures. + * By manually resolving the file path and transferring the raw bytes, we ensure the script + * is correctly deployed regardless of its filename complexity. + */ @Override protected void configure() { super.configure(); boolean isClusterMode = this.shardingEnabled || this.rsEnabled; if (this.initScriptPath != null) { - String destination = isClusterMode ? SCRIPT_DESTINATION_MANUAL : SCRIPT_DESTINATION_DEFAULT; - withCopyFileToContainer(MountableFile.forClasspathResource(this.initScriptPath), destination); + try { + Path scriptPath = Paths.get(this.initScriptPath); + String fileName = scriptPath.getFileName().toString(); + Path parentDir = scriptPath.getParent(); + String resourceDir = (parentDir == null) ? "" : parentDir.toString(); + + Enumeration resources = this.getClass().getClassLoader().getResources(resourceDir); + byte[] fileContent = null; + + while (resources.hasMoreElements()) { + URL dirUrl = resources.nextElement(); + + if ("file".equals(dirUrl.getProtocol())) { + Path dirPath = Paths.get(dirUrl.toURI()); + Path candidatePath = dirPath.resolve(fileName); + + if (Files.exists(candidatePath) && !Files.isDirectory(candidatePath)) { + fileContent = Files.readAllBytes(candidatePath); + break; + } + } + } + + if (fileContent == null) { + throw new RuntimeException("Could not find init script on classpath: " + this.initScriptPath); + } + + String destination = isClusterMode ? SCRIPT_DESTINATION_MANUAL : SCRIPT_DESTINATION_DEFAULT; + withCopyToContainer(Transferable.of(fileContent, 0777), destination); + } catch (Exception e) { + throw new RuntimeException("Failed to read or transfer init script: " + this.initScriptPath, e); + } } if (this.initScriptPath != null && !isClusterMode) { diff --git a/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java b/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java index f179724acbb..52b4e34c22f 100644 --- a/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java +++ b/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java @@ -57,9 +57,7 @@ void shouldExecuteInitScript() { void shouldExecuteInitScriptWithEdgeCases() { try ( MongoDBContainer mongoDB = new MongoDBContainer("mongo:4.0.10") - .withInitScript("initEdgeCase.js") - .withEnv("LANG", "C.UTF-8") - .withEnv("LC_ALL", "C.UTF-8") + .withInitScript("initEdgeCase!@#%^& *'().js") .withStartupTimeout(Duration.ofSeconds(30)) ) { mongoDB.start(); @@ -70,11 +68,12 @@ void shouldExecuteInitScriptWithEdgeCases() { ) ) { String expectedComplexName = "test_col_\"_with_specials_!@#%^&*()"; - String expectedJapaneseName = "日本語 コレクション テスト"; + String expectedCollectionWithSpecialChars = "col with spaces & symbols !@#"; com.mongodb.client.MongoDatabase database = client.getDatabase("test"); - assertThat(database.listCollectionNames()).contains(expectedComplexName, expectedJapaneseName); + assertThat(database.listCollectionNames()) + .contains(expectedComplexName, expectedCollectionWithSpecialChars); com.mongodb.client.MongoCollection collection = database.getCollection( expectedComplexName @@ -82,19 +81,14 @@ void shouldExecuteInitScriptWithEdgeCases() { org.bson.Document doc = collection.find(new org.bson.Document("_id", 1)).first(); - assertThat(doc).as("Document with _id=1 should exist").isNotNull(); + assertThat(doc).isNotNull(); - assertThat(doc.getString("key_with_quotes")) - .as("Double quotes should be preserved correctly") - .isEqualTo("This is a \"double quoted\" string"); + assertThat(doc.getString("key_with_quotes")).isEqualTo("This is a \"double quoted\" string"); - assertThat(doc.getString("key_with_json_chars")) - .as("JSON special chars should be treated as plain text") - .isEqualTo("{ } [ ] : ,"); + assertThat(doc.getString("key_with_json_chars")).isEqualTo("{ } [ ] : ,"); assertThat(doc.getString("description")) - .as("Japanese text should be preserved correctly") - .isEqualTo("特殊記号を含むコレクションへの挿入テスト"); + .isEqualTo("Insertion test for collection with special symbols"); } } } @@ -107,14 +101,6 @@ void shouldExecuteInitScriptWithReplicaSet() { } } - @Test - void shouldExecuteInitScriptWithReplicaSetConfiguredFirst() { - try (MongoDBContainer mongo = new MongoDBContainer("mongo:7.0.0").withReplicaSet().withInitScript("init.js")) { - mongo.start(); - assertInitScriptExecuted(mongo); - } - } - @Test void shouldExecuteInitScriptWithSharding() { try (MongoDBContainer mongo = new MongoDBContainer("mongo:7.0.0").withInitScript("init.js").withSharding()) { @@ -123,19 +109,9 @@ void shouldExecuteInitScriptWithSharding() { } } - @Test - void shouldExecuteInitScriptWithShardingConfiguredFirst() { - try (MongoDBContainer mongo = new MongoDBContainer("mongo:7.0.0").withSharding().withInitScript("init.js")) { - mongo.start(); - assertInitScriptExecuted(mongo); - } - } - private void assertInitScriptExecuted(MongoDBContainer mongo) { try (com.mongodb.client.MongoClient client = com.mongodb.client.MongoClients.create(mongo.getReplicaSetUrl())) { - assertThat(client.getDatabase("test").listCollectionNames()) - .as("Check if init.js created the collection") - .contains("test_collection"); + assertThat(client.getDatabase("test").listCollectionNames()).contains("test_collection"); } } } diff --git a/modules/mongodb/src/test/resources/initEdgeCase.js b/modules/mongodb/src/test/resources/initEdgeCase!@#%^& *'().js similarity index 63% rename from modules/mongodb/src/test/resources/initEdgeCase.js rename to modules/mongodb/src/test/resources/initEdgeCase!@#%^& *'().js index 40b7de35384..21f8a7658d0 100644 --- a/modules/mongodb/src/test/resources/initEdgeCase.js +++ b/modules/mongodb/src/test/resources/initEdgeCase!@#%^& *'().js @@ -2,15 +2,15 @@ var complexCollectionName = 'test_col_"_with_specials_!@#%^&*()'; db.createCollection(complexCollectionName); -var japaneseCollectionName = "日本語 コレクション テスト"; +var collectionWithSpecialChars = "col with spaces & symbols !@#"; -db.createCollection(japaneseCollectionName); +db.createCollection(collectionWithSpecialChars); db.getCollection(complexCollectionName).insertOne({ "_id": 1, "key_with_quotes": "This is a \"double quoted\" string", "key_with_json_chars": "{ } [ ] : ,", - "description": "特殊記号を含むコレクションへの挿入テスト" + "description": "Insertion test for collection with special symbols" }); print("Initialization completed: " + complexCollectionName); \ No newline at end of file From d868b952674af76747f96d9b555498b1b3ff0153 Mon Sep 17 00:00:00 2001 From: taole33 Date: Tue, 16 Dec 2025 11:25:37 +0900 Subject: [PATCH 7/7] fix: Address review comments --- .../mongodb/MongoDBContainerTest.java | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java b/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java index 52b4e34c22f..4f23a060b1a 100644 --- a/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java +++ b/modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java @@ -2,8 +2,6 @@ import org.junit.jupiter.api.Test; -import java.time.Duration; - import static org.assertj.core.api.Assertions.assertThat; class MongoDBContainerTest extends AbstractMongo { @@ -43,22 +41,16 @@ void shouldTestDatabaseName() { @Test void shouldExecuteInitScript() { - try ( - MongoDBContainer mongoDB = new MongoDBContainer("mongo:4.0.10") - .withInitScript("init.js") - .withStartupTimeout(Duration.ofSeconds(30)) - ) { + try (MongoDBContainer mongoDB = new MongoDBContainer("mongo:4.0.10").withInitScript("init.js")) { mongoDB.start(); - assertThat(mongoDB.isRunning()).isTrue(); + assertInitScriptExecuted(mongoDB); } } @Test void shouldExecuteInitScriptWithEdgeCases() { try ( - MongoDBContainer mongoDB = new MongoDBContainer("mongo:4.0.10") - .withInitScript("initEdgeCase!@#%^& *'().js") - .withStartupTimeout(Duration.ofSeconds(30)) + MongoDBContainer mongoDB = new MongoDBContainer("mongo:4.0.10").withInitScript("initEdgeCase!@#%^& *'().js") ) { mongoDB.start();