Skip to content

Commit 35b753b

Browse files
committed
Fix withInitScript compatibility with ReplicaSet and Sharding modes
1 parent e81a2ac commit 35b753b

File tree

3 files changed

+140
-10
lines changed

3 files changed

+140
-10
lines changed

modules/mongodb/src/main/java/org/testcontainers/mongodb/MongoDBContainer.java

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,16 @@ public class MongoDBContainer extends GenericContainer<MongoDBContainer> {
4141

4242
private static final String STARTER_SCRIPT = "/testcontainers_start.sh";
4343

44+
private static final String SCRIPT_DESTINATION_DEFAULT = "/docker-entrypoint-initdb.d/init.js";
45+
46+
private static final String SCRIPT_DESTINATION_MANUAL = "/tmp/init.js";
47+
4448
private boolean shardingEnabled;
4549

4650
private boolean rsEnabled;
4751

52+
private String initScriptPath;
53+
4854
public MongoDBContainer(@NonNull String dockerImageName) {
4955
this(DockerImageName.parse(dockerImageName));
5056
}
@@ -68,6 +74,26 @@ protected void containerIsStarted(InspectContainerResponse containerInfo, boolea
6874
if (this.rsEnabled) {
6975
initReplicaSet(reused);
7076
}
77+
78+
boolean isClusterMode = this.shardingEnabled || this.rsEnabled;
79+
80+
if (isClusterMode && this.initScriptPath != null) {
81+
executeInitScriptInContainer();
82+
}
83+
}
84+
85+
@Override
86+
protected void configure() {
87+
super.configure();
88+
boolean isClusterMode = this.shardingEnabled || this.rsEnabled;
89+
if (this.initScriptPath != null) {
90+
String destination = isClusterMode ? SCRIPT_DESTINATION_MANUAL : SCRIPT_DESTINATION_DEFAULT;
91+
withCopyFileToContainer(MountableFile.forClasspathResource(this.initScriptPath), destination);
92+
}
93+
94+
if (this.initScriptPath != null && !isClusterMode) {
95+
this.waitStrategy = Wait.forLogMessage("(?i).*waiting for connections.*", 2);
96+
}
7197
}
7298

7399
private String[] buildMongoEvalCommand(String command) {
@@ -208,19 +234,42 @@ public String getReplicaSetUrl(String databaseName) {
208234
/**
209235
* Executes a MongoDB initialization script from the classpath during startup.
210236
* <p>
211-
* The script will be copied to {@code /docker-entrypoint-initdb.d/init.js}.
212-
* This method also adjusts the {@link org.testcontainers.containers.wait.strategy.WaitStrategy}
213-
* to expect the "waiting for connections" log message twice, as the execution of an init script
214-
* causes MongoDB to restart.
237+
* In standalone mode, the script will be copied to {@code /docker-entrypoint-initdb.d/init.js},
238+
* and the {@link org.testcontainers.containers.wait.strategy.WaitStrategy} is adjusted
239+
* to expect the "waiting for connections" log message twice.
240+
* <p>
241+
* In Replica Set or Sharding mode, the script is copied to a temporary location and executed
242+
* manually after the cluster is initialized.
215243
*
216244
* @param scriptPath the path to the init script file on the classpath
217245
* @return this container instance
218246
*/
219247
public MongoDBContainer withInitScript(String scriptPath) {
220-
withCopyFileToContainer(MountableFile.forClasspathResource(scriptPath), "/docker-entrypoint-initdb.d/init.js");
221-
222-
this.waitStrategy = Wait.forLogMessage("(?i).*waiting for connections.*", 2);
223-
248+
this.initScriptPath = scriptPath;
224249
return this;
225250
}
251+
252+
@SneakyThrows
253+
private void executeInitScriptInContainer() {
254+
String cmd =
255+
"mongosh " +
256+
MONGODB_DATABASE_NAME_DEFAULT +
257+
" " +
258+
SCRIPT_DESTINATION_MANUAL +
259+
" || mongo " +
260+
MONGODB_DATABASE_NAME_DEFAULT +
261+
" " +
262+
SCRIPT_DESTINATION_MANUAL;
263+
264+
ExecResult result = execInContainer("sh", "-c", cmd);
265+
if (result.getExitCode() != CONTAINER_EXIT_CODE_OK) {
266+
throw new IllegalStateException(
267+
String.format(
268+
"Failed to execute init script.\nStdout: %s\nStderr: %s",
269+
result.getStdout(),
270+
result.getStderr()
271+
)
272+
);
273+
}
274+
}
226275
}

modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,80 @@ void shouldTestDatabaseName() {
4242
}
4343

4444
@Test
45-
void testWithInitScript() {
45+
void shouldExecuteInitScript() {
4646
try (
4747
MongoDBContainer mongoDB = new MongoDBContainer("mongo:4.0.10")
4848
.withInitScript("init.js")
4949
.withStartupTimeout(Duration.ofSeconds(30))
5050
) {
5151
mongoDB.start();
52-
5352
assertThat(mongoDB.isRunning()).isTrue();
5453
}
5554
}
55+
56+
@Test
57+
void shouldExecuteInitScriptWithEdgeCases() {
58+
try (
59+
MongoDBContainer mongoDB = new MongoDBContainer("mongo:4.0.10")
60+
.withInitScript("initEdgeCase.js")
61+
.withEnv("LANG", "C.UTF-8")
62+
.withEnv("LC_ALL", "C.UTF-8")
63+
.withStartupTimeout(Duration.ofSeconds(30))
64+
) {
65+
mongoDB.start();
66+
67+
try (
68+
com.mongodb.client.MongoClient client = com.mongodb.client.MongoClients.create(
69+
mongoDB.getReplicaSetUrl()
70+
)
71+
) {
72+
String expectedComplexName = "test_col_\"_with_specials_!@#%^&*()";
73+
String expectedJapaneseName = "日本語 コレクション テスト";
74+
75+
assertThat(client.getDatabase("test").listCollectionNames())
76+
.as("Check if init script created the collections with special chars and Japanese")
77+
.contains(expectedComplexName, expectedJapaneseName);
78+
}
79+
}
80+
}
81+
82+
@Test
83+
void shouldExecuteInitScriptWithReplicaSet() {
84+
try (MongoDBContainer mongo = new MongoDBContainer("mongo:7.0.0").withInitScript("init.js").withReplicaSet()) {
85+
mongo.start();
86+
assertInitScriptExecuted(mongo);
87+
}
88+
}
89+
90+
@Test
91+
void shouldExecuteInitScriptWithReplicaSetConfiguredFirst() {
92+
try (MongoDBContainer mongo = new MongoDBContainer("mongo:7.0.0").withReplicaSet().withInitScript("init.js")) {
93+
mongo.start();
94+
assertInitScriptExecuted(mongo);
95+
}
96+
}
97+
98+
@Test
99+
void shouldExecuteInitScriptWithSharding() {
100+
try (MongoDBContainer mongo = new MongoDBContainer("mongo:7.0.0").withInitScript("init.js").withSharding()) {
101+
mongo.start();
102+
assertInitScriptExecuted(mongo);
103+
}
104+
}
105+
106+
@Test
107+
void shouldExecuteInitScriptWithShardingConfiguredFirst() {
108+
try (MongoDBContainer mongo = new MongoDBContainer("mongo:7.0.0").withSharding().withInitScript("init.js")) {
109+
mongo.start();
110+
assertInitScriptExecuted(mongo);
111+
}
112+
}
113+
114+
private void assertInitScriptExecuted(MongoDBContainer mongo) {
115+
try (com.mongodb.client.MongoClient client = com.mongodb.client.MongoClients.create(mongo.getReplicaSetUrl())) {
116+
assertThat(client.getDatabase("test").listCollectionNames())
117+
.as("Check if init.js created the collection")
118+
.contains("test_collection");
119+
}
120+
}
56121
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
var complexCollectionName = 'test_col_"_with_specials_!@#%^&*()';
2+
3+
db.createCollection(complexCollectionName);
4+
5+
var japaneseCollectionName = "日本語 コレクション テスト";
6+
7+
db.createCollection(japaneseCollectionName);
8+
9+
db.getCollection(complexCollectionName).insertOne({
10+
"_id": 1,
11+
"key_with_quotes": "This is a \"double quoted\" string",
12+
"key_with_json_chars": "{ } [ ] : ,",
13+
"description": "特殊記号を含むコレクションへの挿入テスト"
14+
});
15+
16+
print("Initialization completed: " + complexCollectionName);

0 commit comments

Comments
 (0)