From 31854b546eb77b156ce4f7367790207a94e09362 Mon Sep 17 00:00:00 2001 From: digital88 Date: Thu, 10 Jul 2025 21:46:13 +0300 Subject: [PATCH 01/10] mongodb-container-2 --- package-lock.json | 1 + packages/modules/mongodb/package.json | 3 +- .../mongodb/src/mongodb-container-2.test.ts | 33 ++++ .../mongodb/src/mongodb-container-2.ts | 142 ++++++++++++++++++ 4 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 packages/modules/mongodb/src/mongodb-container-2.test.ts create mode 100644 packages/modules/mongodb/src/mongodb-container-2.ts diff --git a/package-lock.json b/package-lock.json index c18943564..11aa4f135 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21534,6 +21534,7 @@ "version": "11.2.1", "license": "MIT", "dependencies": { + "compare-versions": "^6.1.1", "testcontainers": "^11.2.1" }, "devDependencies": { diff --git a/packages/modules/mongodb/package.json b/packages/modules/mongodb/package.json index 50df925c3..f36a1242b 100644 --- a/packages/modules/mongodb/package.json +++ b/packages/modules/mongodb/package.json @@ -32,6 +32,7 @@ "mongoose": "^8.16.1" }, "dependencies": { - "testcontainers": "^11.2.1" + "testcontainers": "^11.2.1", + "compare-versions": "^6.1.1" } } diff --git a/packages/modules/mongodb/src/mongodb-container-2.test.ts b/packages/modules/mongodb/src/mongodb-container-2.test.ts new file mode 100644 index 000000000..c577fe1f1 --- /dev/null +++ b/packages/modules/mongodb/src/mongodb-container-2.test.ts @@ -0,0 +1,33 @@ +import mongoose from "mongoose"; +import { MongoDBContainer2, StartedMongoDBContainer2 } from "./mongodb-container-2"; + +describe("MongodbContainer", { timeout: 240_000 }, () => { + test.for([ + ["mongo:5.0", "true"], + ["mongo:8.0", "true"], + ["mongo:5.0", "false"], + ["mongo:8.0", "false"], + ])("should connect to %s (credentials: %s)", async ([image, creds]) => { + const mongodbContainer = await getContainer(image, creds === "true"); + await runTest(mongodbContainer); + }); +}); + +async function getContainer(image: string, auth: boolean) { + if (auth) return new MongoDBContainer2(image).withUsername("mongo_user").withPassword("mongo_password").start(); + return new MongoDBContainer2(image).start(); +} + +async function runTest(mongodbContainer: StartedMongoDBContainer2) { + const mongo = await mongoose.connect(mongodbContainer.getConnectionString(), { directConnection: true }); + expect(mongo.connection.readyState).toBe(1); + const result = await mongo.connection.collection("testcontainers").insertOne({ title: "testcontainers" }); + const id = result.insertedId.toString(); + expect(id).not.toBeNull(); + expect(id).not.toBe(""); + const rsStatus = await mongo.connection.db?.admin().replSetGetStatus(); + expect(rsStatus).toBeDefined(); + expect(rsStatus?.set).toBe("rs0"); + await mongo.disconnect(); + await mongodbContainer.stop(); +} diff --git a/packages/modules/mongodb/src/mongodb-container-2.ts b/packages/modules/mongodb/src/mongodb-container-2.ts new file mode 100644 index 000000000..1701bf7b9 --- /dev/null +++ b/packages/modules/mongodb/src/mongodb-container-2.ts @@ -0,0 +1,142 @@ +import { AbstractStartedContainer, ExecResult, GenericContainer, StartedTestContainer, Wait } from "testcontainers"; + +const MONGODB_PORT = 27017; + +export class MongoDBContainer2 extends GenericContainer { + private username = ""; + private password = ""; + private database = "testcontainers"; + + constructor(image: string) { + super(image); + this.withExposedPorts(MONGODB_PORT) + .withWaitStrategy(Wait.forLogMessage(/.*waiting for connections.*/i)) + .withStartupTimeout(120_000); + } + + public withUsername(username: string): this { + if (username === "") throw new Error("Username should not be empty."); + this.username = username; + return this; + } + + public withPassword(password: string): this { + if (password === "") throw new Error("Password should not be empty."); + this.password = password; + return this; + } + + public withDatabase(database: string): this { + if (database === "") throw new Error("Database should not be empty."); + this.database = database; + return this; + } + + public override async start(): Promise { + if (this.authEnabled()) { + this.withEnvironment({ + MONGO_INITDB_ROOT_USERNAME: this.username, + MONGO_INITDB_ROOT_PASSWORD: this.password, + MONGO_INITDB_DATABASE: this.database, + }) + .withCopyContentToContainer([ + { + content: "1111111111", + mode: 0o400, + target: "/data/db/key.txt", + }, + ]) + .withCommand(["--replSet", "rs0", "--keyFile", "/data/db/key.txt"]); + } else { + this.withCommand(["--replSet", "rs0"]); + } + return new StartedMongoDBContainer2(await super.start(), this.username, this.password, this.database); + } + + protected override async containerStarted(container: StartedTestContainer): Promise { + await this.executeMongoEvalCommand(container, this.buildMongoRsInitCommand()); + await this.executeMongoEvalCommand(container, this.buildMongoWaitCommand()); + } + + private async executeMongoEvalCommand(container: StartedTestContainer, command: string) { + let totalElapsed = 0; + const waitInterval = 1000; + const timeout = 60 * waitInterval; + while (totalElapsed < timeout) { + const execResult = await container.exec(this.buildMongoEvalCommand(command)); + const output = execResult.output?.trimEnd(); + if ( + output?.endsWith("MongoServerError: Authentication failed.") || + output?.includes("MongoNetworkError: connect ECONNREFUSED") + ) { + await new Promise((resolve) => setTimeout(resolve, waitInterval)); + totalElapsed += waitInterval; + } else { + this.checkMongoNodeExitCode(execResult); + break; + } + } + } + + private buildMongoEvalCommand(command: string) { + return ["mongosh", "-u", this.username, "-p", this.password, "--eval", command]; + } + + private checkMongoNodeExitCode(execResult: ExecResult) { + const { exitCode, output } = execResult; + if (execResult.exitCode !== 0) { + throw new Error(`Error running mongo command. Exit code ${exitCode}: ${output}`); + } + } + + private buildMongoRsInitCommand() { + return ` + while (true) { + try { + rs.initiate(); + break; + } + catch { + sleep(1000); + } + } + `; + } + + private buildMongoWaitCommand() { + return ` + while (true) { + try { + rs.status(); + break; + } + catch { + sleep(1000); + } + } + `; + } + + private authEnabled() { + return this.username && this.password; + } +} + +export class StartedMongoDBContainer2 extends AbstractStartedContainer { + private readonly username: string = ""; + private readonly password: string = ""; + private readonly database: string = ""; + + constructor(startedTestContainer: StartedTestContainer, username: string, password: string, database: string) { + super(startedTestContainer); + this.username = username; + this.password = password; + this.database = database; + } + + public getConnectionString(): string { + if (this.username && this.password) + return `mongodb://${this.username}:${this.password}@${this.getHost()}:${this.getMappedPort(MONGODB_PORT)}/${this.database}?authSource=admin`; + return `mongodb://${this.getHost()}:${this.getMappedPort(MONGODB_PORT)}/${this.database}`; + } +} From 7f47befefcc84d591bd6517319356f6ac935a366 Mon Sep 17 00:00:00 2001 From: digital88 Date: Thu, 17 Jul 2025 20:52:08 +0300 Subject: [PATCH 02/10] fixed rs with credentials --- package-lock.json | 206 +++++++++--------- packages/modules/mongodb/package.json | 2 +- .../mongodb/src/mongodb-container-2.test.ts | 33 --- .../mongodb/src/mongodb-container-2.ts | 142 ------------ .../mongodb/src/mongodb-container.test.ts | 24 ++ .../modules/mongodb/src/mongodb-container.ts | 97 +++++---- 6 files changed, 189 insertions(+), 315 deletions(-) delete mode 100644 packages/modules/mongodb/src/mongodb-container-2.test.ts delete mode 100644 packages/modules/mongodb/src/mongodb-container-2.ts diff --git a/package-lock.json b/package-lock.json index 11aa4f135..88f38eb6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15163,106 +15163,6 @@ "whatwg-url": "^14.1.0 || ^13.0.0" } }, - "node_modules/mongoose": { - "version": "8.16.1", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.1.tgz", - "integrity": "sha512-Q+0TC+KLdY4SYE+u9gk9pdW1tWu/pl0jusyEkMGTgBoAbvwQdfy4f9IM8dmvCwb/blSfp7IfLkob7v76x6ZGpQ==", - "dev": true, - "dependencies": { - "bson": "^6.10.4", - "kareem": "2.6.3", - "mongodb": "~6.17.0", - "mpath": "0.9.0", - "mquery": "5.0.0", - "ms": "2.1.3", - "sift": "17.1.3" - }, - "engines": { - "node": ">=16.20.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mongoose" - } - }, - "node_modules/mongoose/node_modules/gaxios": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", - "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mongoose/node_modules/gcp-metadata": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", - "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "gaxios": "^5.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mongoose/node_modules/mongodb": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz", - "integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==", - "dev": true, - "dependencies": { - "@mongodb-js/saslprep": "^1.1.9", - "bson": "^6.10.4", - "mongodb-connection-string-url": "^3.0.0" - }, - "engines": { - "node": ">=16.20.1" - }, - "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", - "gcp-metadata": "^5.2.0", - "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", - "socks": "^2.7.1" - }, - "peerDependenciesMeta": { - "@aws-sdk/credential-providers": { - "optional": true - }, - "@mongodb-js/zstd": { - "optional": true - }, - "gcp-metadata": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "snappy": { - "optional": true - }, - "socks": { - "optional": true - } - } - }, "node_modules/mpath": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", @@ -21538,7 +21438,111 @@ "testcontainers": "^11.2.1" }, "devDependencies": { - "mongoose": "^8.16.1" + "mongoose": "8.15.0" + } + }, + "packages/modules/mongodb/node_modules/gaxios": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", + "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + }, + "engines": { + "node": ">=12" + } + }, + "packages/modules/mongodb/node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "packages/modules/mongodb/node_modules/mongodb": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.16.0.tgz", + "integrity": "sha512-D1PNcdT0y4Grhou5Zi/qgipZOYeWrhLEpk33n3nm6LGtz61jvO88WlrWCK/bigMjpnOdAUKKQwsGIl0NtWMyYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.3", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "packages/modules/mongodb/node_modules/mongoose": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.15.0.tgz", + "integrity": "sha512-WFKsY1q12ScGabnZWUB9c/QzZmz/ESorrV27OembB7Gz6rrh9m3GA4Srsv1uvW1s9AHO5DeZ6DdUTyF9zyNERQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bson": "^6.10.3", + "kareem": "2.6.3", + "mongodb": "~6.16.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" } }, "packages/modules/mssqlserver": { diff --git a/packages/modules/mongodb/package.json b/packages/modules/mongodb/package.json index f36a1242b..7c007f078 100644 --- a/packages/modules/mongodb/package.json +++ b/packages/modules/mongodb/package.json @@ -29,7 +29,7 @@ "build": "tsc --project tsconfig.build.json" }, "devDependencies": { - "mongoose": "^8.16.1" + "mongoose": "8.15.0" }, "dependencies": { "testcontainers": "^11.2.1", diff --git a/packages/modules/mongodb/src/mongodb-container-2.test.ts b/packages/modules/mongodb/src/mongodb-container-2.test.ts deleted file mode 100644 index c577fe1f1..000000000 --- a/packages/modules/mongodb/src/mongodb-container-2.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import mongoose from "mongoose"; -import { MongoDBContainer2, StartedMongoDBContainer2 } from "./mongodb-container-2"; - -describe("MongodbContainer", { timeout: 240_000 }, () => { - test.for([ - ["mongo:5.0", "true"], - ["mongo:8.0", "true"], - ["mongo:5.0", "false"], - ["mongo:8.0", "false"], - ])("should connect to %s (credentials: %s)", async ([image, creds]) => { - const mongodbContainer = await getContainer(image, creds === "true"); - await runTest(mongodbContainer); - }); -}); - -async function getContainer(image: string, auth: boolean) { - if (auth) return new MongoDBContainer2(image).withUsername("mongo_user").withPassword("mongo_password").start(); - return new MongoDBContainer2(image).start(); -} - -async function runTest(mongodbContainer: StartedMongoDBContainer2) { - const mongo = await mongoose.connect(mongodbContainer.getConnectionString(), { directConnection: true }); - expect(mongo.connection.readyState).toBe(1); - const result = await mongo.connection.collection("testcontainers").insertOne({ title: "testcontainers" }); - const id = result.insertedId.toString(); - expect(id).not.toBeNull(); - expect(id).not.toBe(""); - const rsStatus = await mongo.connection.db?.admin().replSetGetStatus(); - expect(rsStatus).toBeDefined(); - expect(rsStatus?.set).toBe("rs0"); - await mongo.disconnect(); - await mongodbContainer.stop(); -} diff --git a/packages/modules/mongodb/src/mongodb-container-2.ts b/packages/modules/mongodb/src/mongodb-container-2.ts deleted file mode 100644 index 1701bf7b9..000000000 --- a/packages/modules/mongodb/src/mongodb-container-2.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { AbstractStartedContainer, ExecResult, GenericContainer, StartedTestContainer, Wait } from "testcontainers"; - -const MONGODB_PORT = 27017; - -export class MongoDBContainer2 extends GenericContainer { - private username = ""; - private password = ""; - private database = "testcontainers"; - - constructor(image: string) { - super(image); - this.withExposedPorts(MONGODB_PORT) - .withWaitStrategy(Wait.forLogMessage(/.*waiting for connections.*/i)) - .withStartupTimeout(120_000); - } - - public withUsername(username: string): this { - if (username === "") throw new Error("Username should not be empty."); - this.username = username; - return this; - } - - public withPassword(password: string): this { - if (password === "") throw new Error("Password should not be empty."); - this.password = password; - return this; - } - - public withDatabase(database: string): this { - if (database === "") throw new Error("Database should not be empty."); - this.database = database; - return this; - } - - public override async start(): Promise { - if (this.authEnabled()) { - this.withEnvironment({ - MONGO_INITDB_ROOT_USERNAME: this.username, - MONGO_INITDB_ROOT_PASSWORD: this.password, - MONGO_INITDB_DATABASE: this.database, - }) - .withCopyContentToContainer([ - { - content: "1111111111", - mode: 0o400, - target: "/data/db/key.txt", - }, - ]) - .withCommand(["--replSet", "rs0", "--keyFile", "/data/db/key.txt"]); - } else { - this.withCommand(["--replSet", "rs0"]); - } - return new StartedMongoDBContainer2(await super.start(), this.username, this.password, this.database); - } - - protected override async containerStarted(container: StartedTestContainer): Promise { - await this.executeMongoEvalCommand(container, this.buildMongoRsInitCommand()); - await this.executeMongoEvalCommand(container, this.buildMongoWaitCommand()); - } - - private async executeMongoEvalCommand(container: StartedTestContainer, command: string) { - let totalElapsed = 0; - const waitInterval = 1000; - const timeout = 60 * waitInterval; - while (totalElapsed < timeout) { - const execResult = await container.exec(this.buildMongoEvalCommand(command)); - const output = execResult.output?.trimEnd(); - if ( - output?.endsWith("MongoServerError: Authentication failed.") || - output?.includes("MongoNetworkError: connect ECONNREFUSED") - ) { - await new Promise((resolve) => setTimeout(resolve, waitInterval)); - totalElapsed += waitInterval; - } else { - this.checkMongoNodeExitCode(execResult); - break; - } - } - } - - private buildMongoEvalCommand(command: string) { - return ["mongosh", "-u", this.username, "-p", this.password, "--eval", command]; - } - - private checkMongoNodeExitCode(execResult: ExecResult) { - const { exitCode, output } = execResult; - if (execResult.exitCode !== 0) { - throw new Error(`Error running mongo command. Exit code ${exitCode}: ${output}`); - } - } - - private buildMongoRsInitCommand() { - return ` - while (true) { - try { - rs.initiate(); - break; - } - catch { - sleep(1000); - } - } - `; - } - - private buildMongoWaitCommand() { - return ` - while (true) { - try { - rs.status(); - break; - } - catch { - sleep(1000); - } - } - `; - } - - private authEnabled() { - return this.username && this.password; - } -} - -export class StartedMongoDBContainer2 extends AbstractStartedContainer { - private readonly username: string = ""; - private readonly password: string = ""; - private readonly database: string = ""; - - constructor(startedTestContainer: StartedTestContainer, username: string, password: string, database: string) { - super(startedTestContainer); - this.username = username; - this.password = password; - this.database = database; - } - - public getConnectionString(): string { - if (this.username && this.password) - return `mongodb://${this.username}:${this.password}@${this.getHost()}:${this.getMappedPort(MONGODB_PORT)}/${this.database}?authSource=admin`; - return `mongodb://${this.getHost()}:${this.getMappedPort(MONGODB_PORT)}/${this.database}`; - } -} diff --git a/packages/modules/mongodb/src/mongodb-container.test.ts b/packages/modules/mongodb/src/mongodb-container.test.ts index b038a64dd..d62ef9e02 100644 --- a/packages/modules/mongodb/src/mongodb-container.test.ts +++ b/packages/modules/mongodb/src/mongodb-container.test.ts @@ -65,3 +65,27 @@ describe("MongodbContainer", { timeout: 240_000 }, () => { }); // } }); + +describe("MongodbContainer connect with credentials", { timeout: 240_000 }, () => { + it.for([["mongo:4.0.1"], ["mongo:5.0"], ["mongo:8.0"]])("should connect to %s with credentials", async ([image]) => { + const mongodbContainer = await new MongoDBContainer(image) + .withUsername("mongo_user") + .withPassword("mongo_password") + .start(); + const connection = mongoose.createConnection(mongodbContainer.getConnectionString(), { + directConnection: true, + }); + try { + const result = await connection.collection("testcontainers").insertOne({ title: "testcontainers" }); + const id = result.insertedId.toString(); + expect(id).not.toBeNull(); + expect(id).not.toBe(""); + const rsStatus = await connection.db?.admin().replSetGetStatus(); + expect(rsStatus).toBeDefined(); + expect(rsStatus?.set).toBe("rs0"); + } finally { + await connection.close(); + await mongodbContainer.stop(); + } + }); +}); diff --git a/packages/modules/mongodb/src/mongodb-container.ts b/packages/modules/mongodb/src/mongodb-container.ts index 3634d844d..bf7e73619 100644 --- a/packages/modules/mongodb/src/mongodb-container.ts +++ b/packages/modules/mongodb/src/mongodb-container.ts @@ -1,68 +1,89 @@ -import { AbstractStartedContainer, ExecResult, GenericContainer, StartedTestContainer, Wait } from "testcontainers"; +import { satisfies } from "compare-versions"; +import { AbstractStartedContainer, GenericContainer, StartedTestContainer, Wait } from "testcontainers"; const MONGODB_PORT = 27017; export class MongoDBContainer extends GenericContainer { + private username = ""; + private password = ""; + constructor(image: string) { super(image); - this.withExposedPorts(MONGODB_PORT) - .withCommand(["--replSet", "rs0"]) - .withWaitStrategy(Wait.forLogMessage(/.*waiting for connections.*/i)) - .withStartupTimeout(120_000); - } - - public override async start(): Promise { - return new StartedMongoDBContainer(await super.start()); + this.withExposedPorts(MONGODB_PORT).withStartupTimeout(120_000); } - protected override async containerStarted(startedTestContainer: StartedTestContainer): Promise { - await this.initReplicaSet(startedTestContainer); + public withUsername(username: string): this { + if (username === "") throw new Error("Username should not be empty."); + this.username = username; + return this; } - private async initReplicaSet(startedTestContainer: StartedTestContainer) { - await this.executeMongoEvalCommand(startedTestContainer, "rs.initiate();"); - await this.executeMongoEvalCommand(startedTestContainer, this.buildMongoWaitCommand()); + public withPassword(password: string): this { + if (password === "") throw new Error("Password should not be empty."); + this.password = password; + return this; } - private async executeMongoEvalCommand(startedTestContainer: StartedTestContainer, command: string) { - const execResult = await startedTestContainer.exec(this.buildMongoEvalCommand(command)); - this.checkMongoNodeExitCode(execResult); + public override async start(): Promise { + const cmdArgs = ["--replSet", "rs0", "--bind_ip_all"]; + this.withHealthCheck({ + test: ["CMD", ...this.buildMongoEvalCommand(this.buildMongoWaitCommand())], + startPeriod: 1_000, + timeout: 60_000, + retries: 10, + interval: 2_000, + }).withWaitStrategy(Wait.forHealthCheck()); + if (this.authEnabled()) { + cmdArgs.push("--keyFile", "/data/db/key.txt"); + this.withEnvironment({ + MONGO_INITDB_ROOT_USERNAME: this.username, + MONGO_INITDB_ROOT_PASSWORD: this.password, + }) + .withCopyContentToContainer([ + { + content: "1111111111", + mode: 0o400, + target: "/data/db/key.txt", + }, + ]) + .withCommand(cmdArgs); + } else { + this.withCommand(cmdArgs); + } + return new StartedMongoDBContainer(await super.start(), this.username, this.password); } private buildMongoEvalCommand(command: string) { - return [this.getMongoCmdBasedOnImageTag(), "--eval", command]; + const useMongosh = satisfies(this.imageName.tag, ">=5.0.0"); + const args = []; + if (useMongosh) args.push("mongosh"); + else args.push("mongo", "admin"); + args.push("-u", this.username, "-p", this.password, "--eval", command); + return args; } - private getMongoCmdBasedOnImageTag() { - return parseInt(this.imageName.tag[0]) >= 5 ? "mongosh" : "mongo"; - } - - private checkMongoNodeExitCode(execResult: ExecResult) { - const { exitCode, output } = execResult; - if (execResult.exitCode !== 0) { - throw new Error(`Error running mongo command. Exit code ${exitCode}: ${output}`); - } + private buildMongoWaitCommand() { + return `rs.initiate(); while (db.runCommand({isMaster: 1}).ismaster==false) { sleep(1000); } `; } - private buildMongoWaitCommand() { - return ` - var attempt = 0; - while(db.runCommand({isMaster: 1}).ismaster==false) { - if (attempt > 60) { - quit(1); - } - print(attempt); sleep(100); attempt++; - } - `; + private authEnabled() { + return this.username && this.password; } } export class StartedMongoDBContainer extends AbstractStartedContainer { - constructor(startedTestContainer: StartedTestContainer) { + private readonly username: string = ""; + private readonly password: string = ""; + + constructor(startedTestContainer: StartedTestContainer, username: string, password: string) { super(startedTestContainer); + this.username = username; + this.password = password; } public getConnectionString(): string { + if (this.username && this.password) + return `mongodb://${this.username}:${this.password}@${this.getHost()}:${this.getMappedPort(MONGODB_PORT)}?authSource=admin`; return `mongodb://${this.getHost()}:${this.getMappedPort(MONGODB_PORT)}`; } } From cd717aaa0f778811d6c6b8584dfc7232b8773f1f Mon Sep 17 00:00:00 2001 From: digital88 Date: Fri, 18 Jul 2025 21:24:52 +0300 Subject: [PATCH 03/10] fix tests in podman --- .../mongodb/src/mongodb-container.test.ts | 2 +- .../modules/mongodb/src/mongodb-container.ts | 24 ++++++++----------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/modules/mongodb/src/mongodb-container.test.ts b/packages/modules/mongodb/src/mongodb-container.test.ts index d62ef9e02..9167f2866 100644 --- a/packages/modules/mongodb/src/mongodb-container.test.ts +++ b/packages/modules/mongodb/src/mongodb-container.test.ts @@ -67,7 +67,7 @@ describe("MongodbContainer", { timeout: 240_000 }, () => { }); describe("MongodbContainer connect with credentials", { timeout: 240_000 }, () => { - it.for([["mongo:4.0.1"], ["mongo:5.0"], ["mongo:8.0"]])("should connect to %s with credentials", async ([image]) => { + it.for([["mongo:4.0.1"], ["mongo:8.0"]])("should connect to %s with credentials", async ([image]) => { const mongodbContainer = await new MongoDBContainer(image) .withUsername("mongo_user") .withPassword("mongo_password") diff --git a/packages/modules/mongodb/src/mongodb-container.ts b/packages/modules/mongodb/src/mongodb-container.ts index bf7e73619..5eae39297 100644 --- a/packages/modules/mongodb/src/mongodb-container.ts +++ b/packages/modules/mongodb/src/mongodb-container.ts @@ -27,13 +27,12 @@ export class MongoDBContainer extends GenericContainer { public override async start(): Promise { const cmdArgs = ["--replSet", "rs0", "--bind_ip_all"]; this.withHealthCheck({ - test: ["CMD", ...this.buildMongoEvalCommand(this.buildMongoWaitCommand())], - startPeriod: 1_000, - timeout: 60_000, - retries: 10, - interval: 2_000, + test: ["CMD-SHELL", this.buildMongoEvalCommand(this.initRsAndWait())], + interval: 250, + timeout: 60000, + retries: 1000, }).withWaitStrategy(Wait.forHealthCheck()); - if (this.authEnabled()) { + if (this.username && this.password) { cmdArgs.push("--keyFile", "/data/db/key.txt"); this.withEnvironment({ MONGO_INITDB_ROOT_USERNAME: this.username, @@ -58,16 +57,13 @@ export class MongoDBContainer extends GenericContainer { const args = []; if (useMongosh) args.push("mongosh"); else args.push("mongo", "admin"); - args.push("-u", this.username, "-p", this.password, "--eval", command); - return args; + if (this.username && this.password) args.push("-u", this.username, "-p", this.password); + args.push("--host", "localhost", "--quiet", "--eval", command); + return args.join(" "); } - private buildMongoWaitCommand() { - return `rs.initiate(); while (db.runCommand({isMaster: 1}).ismaster==false) { sleep(1000); } `; - } - - private authEnabled() { - return this.username && this.password; + private initRsAndWait() { + return `'try { rs.initiate(); } catch (e){} while (db.runCommand({isMaster: 1}).ismaster==false) { sleep(100); }'`; } } From 78a2e851c18286b27463d7154821b176139e214a Mon Sep 17 00:00:00 2001 From: digital88 Date: Tue, 22 Jul 2025 20:27:20 +0300 Subject: [PATCH 04/10] fixes after review --- package-lock.json | 2 +- packages/modules/mongodb/package.json | 2 +- .../mongodb/src/mongodb-container.test.ts | 4 +- .../modules/mongodb/src/mongodb-container.ts | 45 ++++++++++--------- 4 files changed, 29 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index d68c35d6d..88aaa7d71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22571,7 +22571,7 @@ "testcontainers": "^11.3.1" }, "devDependencies": { - "mongoose": "8.15.0" + "mongoose": "^8.16.4" } }, "packages/modules/mongodb/node_modules/gaxios": { diff --git a/packages/modules/mongodb/package.json b/packages/modules/mongodb/package.json index e6bbdffe5..36ca3d588 100644 --- a/packages/modules/mongodb/package.json +++ b/packages/modules/mongodb/package.json @@ -29,7 +29,7 @@ "build": "tsc --project tsconfig.build.json" }, "devDependencies": { - "mongoose": "8.15.0" + "mongoose": "^8.16.4" }, "dependencies": { "testcontainers": "^11.3.1", diff --git a/packages/modules/mongodb/src/mongodb-container.test.ts b/packages/modules/mongodb/src/mongodb-container.test.ts index 0a29acd1c..930365b9a 100644 --- a/packages/modules/mongodb/src/mongodb-container.test.ts +++ b/packages/modules/mongodb/src/mongodb-container.test.ts @@ -65,8 +65,8 @@ describe("MongodbContainer", { timeout: 240_000 }, () => { }); describe("MongodbContainer connect with credentials", { timeout: 240_000 }, () => { - it.for([["mongo:4.0.1"], ["mongo:8.0"]])("should connect to %s with credentials", async ([image]) => { - const mongodbContainer = await new MongoDBContainer(image) + it("should connect to %s with credentials", async () => { + const mongodbContainer = await new MongoDBContainer("mongo:8.0") .withUsername("mongo_user") .withPassword("mongo_password") .start(); diff --git a/packages/modules/mongodb/src/mongodb-container.ts b/packages/modules/mongodb/src/mongodb-container.ts index 5eae39297..47db093ca 100644 --- a/packages/modules/mongodb/src/mongodb-container.ts +++ b/packages/modules/mongodb/src/mongodb-container.ts @@ -4,34 +4,29 @@ import { AbstractStartedContainer, GenericContainer, StartedTestContainer, Wait const MONGODB_PORT = 27017; export class MongoDBContainer extends GenericContainer { - private username = ""; - private password = ""; + private username: string | undefined; + private password: string | undefined; constructor(image: string) { super(image); - this.withExposedPorts(MONGODB_PORT).withStartupTimeout(120_000); + this.withExposedPorts(MONGODB_PORT).withWaitStrategy(Wait.forHealthCheck()).withStartupTimeout(120_000); } public withUsername(username: string): this { - if (username === "") throw new Error("Username should not be empty."); + if (!username) throw new Error("Username should not be empty."); this.username = username; return this; } public withPassword(password: string): this { - if (password === "") throw new Error("Password should not be empty."); + if (!password) throw new Error("Password should not be empty."); this.password = password; return this; } public override async start(): Promise { - const cmdArgs = ["--replSet", "rs0", "--bind_ip_all"]; - this.withHealthCheck({ - test: ["CMD-SHELL", this.buildMongoEvalCommand(this.initRsAndWait())], - interval: 250, - timeout: 60000, - retries: 1000, - }).withWaitStrategy(Wait.forHealthCheck()); + const cmdArgs = ["--replSet", "rs0"]; + if (!this.healthCheck) this.withWaitForRsHealthCheck(); if (this.username && this.password) { cmdArgs.push("--keyFile", "/data/db/key.txt"); this.withEnvironment({ @@ -52,26 +47,36 @@ export class MongoDBContainer extends GenericContainer { return new StartedMongoDBContainer(await super.start(), this.username, this.password); } + private withWaitForRsHealthCheck(): this { + return this.withHealthCheck({ + test: [ + "CMD-SHELL", + this.buildMongoEvalCommand( + `'try { rs.initiate(); } catch (e){} while (db.runCommand({isMaster: 1}).ismaster==false) { sleep(100); }'` + ), + ], + interval: 250, + timeout: 60000, + retries: 1000, + }); + } + private buildMongoEvalCommand(command: string) { const useMongosh = satisfies(this.imageName.tag, ">=5.0.0"); const args = []; if (useMongosh) args.push("mongosh"); else args.push("mongo", "admin"); if (this.username && this.password) args.push("-u", this.username, "-p", this.password); - args.push("--host", "localhost", "--quiet", "--eval", command); + args.push("--quiet", "--eval", command); return args.join(" "); } - - private initRsAndWait() { - return `'try { rs.initiate(); } catch (e){} while (db.runCommand({isMaster: 1}).ismaster==false) { sleep(100); }'`; - } } export class StartedMongoDBContainer extends AbstractStartedContainer { - private readonly username: string = ""; - private readonly password: string = ""; + private readonly username: string | undefined; + private readonly password: string | undefined; - constructor(startedTestContainer: StartedTestContainer, username: string, password: string) { + constructor(startedTestContainer: StartedTestContainer, username: string | undefined, password: string | undefined) { super(startedTestContainer); this.username = username; this.password = password; From 098d2fb13bcca85a9de1d6c47b7656410a721c74 Mon Sep 17 00:00:00 2001 From: digital88 Date: Tue, 22 Jul 2025 20:34:28 +0300 Subject: [PATCH 05/10] TC patch version up --- package-lock.json | 2 +- packages/modules/mongodb/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 88aaa7d71..7cf5e1fbc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22568,7 +22568,7 @@ "license": "MIT", "dependencies": { "compare-versions": "^6.1.1", - "testcontainers": "^11.3.1" + "testcontainers": "^11.3.2" }, "devDependencies": { "mongoose": "^8.16.4" diff --git a/packages/modules/mongodb/package.json b/packages/modules/mongodb/package.json index 36ca3d588..1112b682b 100644 --- a/packages/modules/mongodb/package.json +++ b/packages/modules/mongodb/package.json @@ -32,7 +32,7 @@ "mongoose": "^8.16.4" }, "dependencies": { - "testcontainers": "^11.3.1", + "testcontainers": "^11.3.2", "compare-versions": "^6.1.1" } } From 4dd3ee4d28d7757237dce9872e007dc45cf04034 Mon Sep 17 00:00:00 2001 From: digital88 Date: Wed, 23 Jul 2025 10:22:40 +0300 Subject: [PATCH 06/10] fixed lockfile and test --- package-lock.json | 208 +++++++++--------- .../mongodb/src/mongodb-container.test.ts | 6 +- 2 files changed, 106 insertions(+), 108 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7cf5e1fbc..b55f4f57d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15991,6 +15991,110 @@ "whatwg-url": "^14.1.0 || ^13.0.0" } }, + "node_modules/mongoose": { + "version": "8.16.4", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.4.tgz", + "integrity": "sha512-jslgdQ8pY2vcNSKPv3Dbi5ogo/NT8zcvf6kPDyD8Sdsjsa1at3AFAF0F5PT+jySPGSPbvlNaQ49nT9h+Kx2UDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bson": "^6.10.4", + "kareem": "2.6.3", + "mongodb": "~6.17.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/node_modules/gaxios": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", + "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongoose/node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongoose/node_modules/mongodb": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz", + "integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, "node_modules/mpath": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", @@ -22574,110 +22678,6 @@ "mongoose": "^8.16.4" } }, - "packages/modules/mongodb/node_modules/gaxios": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", - "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9" - }, - "engines": { - "node": ">=12" - } - }, - "packages/modules/mongodb/node_modules/gcp-metadata": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", - "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "gaxios": "^5.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "packages/modules/mongodb/node_modules/mongodb": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.16.0.tgz", - "integrity": "sha512-D1PNcdT0y4Grhou5Zi/qgipZOYeWrhLEpk33n3nm6LGtz61jvO88WlrWCK/bigMjpnOdAUKKQwsGIl0NtWMyYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@mongodb-js/saslprep": "^1.1.9", - "bson": "^6.10.3", - "mongodb-connection-string-url": "^3.0.0" - }, - "engines": { - "node": ">=16.20.1" - }, - "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", - "gcp-metadata": "^5.2.0", - "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", - "socks": "^2.7.1" - }, - "peerDependenciesMeta": { - "@aws-sdk/credential-providers": { - "optional": true - }, - "@mongodb-js/zstd": { - "optional": true - }, - "gcp-metadata": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "snappy": { - "optional": true - }, - "socks": { - "optional": true - } - } - }, - "packages/modules/mongodb/node_modules/mongoose": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.15.0.tgz", - "integrity": "sha512-WFKsY1q12ScGabnZWUB9c/QzZmz/ESorrV27OembB7Gz6rrh9m3GA4Srsv1uvW1s9AHO5DeZ6DdUTyF9zyNERQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bson": "^6.10.3", - "kareem": "2.6.3", - "mongodb": "~6.16.0", - "mpath": "0.9.0", - "mquery": "5.0.0", - "ms": "2.1.3", - "sift": "17.1.3" - }, - "engines": { - "node": ">=16.20.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mongoose" - } - }, "packages/modules/mssqlserver": { "name": "@testcontainers/mssqlserver", "version": "11.3.2", diff --git a/packages/modules/mongodb/src/mongodb-container.test.ts b/packages/modules/mongodb/src/mongodb-container.test.ts index 930365b9a..86a9be69d 100644 --- a/packages/modules/mongodb/src/mongodb-container.test.ts +++ b/packages/modules/mongodb/src/mongodb-container.test.ts @@ -62,11 +62,9 @@ describe("MongodbContainer", { timeout: 240_000 }, () => { await mongoose.disconnect(); }); // } -}); -describe("MongodbContainer connect with credentials", { timeout: 240_000 }, () => { - it("should connect to %s with credentials", async () => { - const mongodbContainer = await new MongoDBContainer("mongo:8.0") + it("should connect with credentials", async () => { + const mongodbContainer = await new MongoDBContainer(IMAGE) .withUsername("mongo_user") .withPassword("mongo_password") .start(); From 747377b7149508b8b4850a17f7b06e60cac97fc6 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Wed, 23 Jul 2025 10:33:18 +0100 Subject: [PATCH 07/10] Refactor tests + update docs --- docs/modules/mongodb.md | 6 +- .../mongodb/src/mongodb-container.test.ts | 82 +++++-------------- 2 files changed, 22 insertions(+), 66 deletions(-) diff --git a/docs/modules/mongodb.md b/docs/modules/mongodb.md index 5820e1bc0..5c12961c0 100644 --- a/docs/modules/mongodb.md +++ b/docs/modules/mongodb.md @@ -11,9 +11,5 @@ npm install @testcontainers/mongodb --save-dev ## Examples -[Mongo 4.0.x:](../../packages/modules/mongodb/src/mongodb-container.test.ts) inside_block:connect4 - - - -[MongoDB 6.0.x:](../../packages/modules/mongodb/src/mongodb-container.test.ts) inside_block:connect6 +[Connect and execute query:](../../packages/modules/mongodb/src/mongodb-container.test.ts) inside_block:connect diff --git a/packages/modules/mongodb/src/mongodb-container.test.ts b/packages/modules/mongodb/src/mongodb-container.test.ts index 86a9be69d..617211b14 100644 --- a/packages/modules/mongodb/src/mongodb-container.test.ts +++ b/packages/modules/mongodb/src/mongodb-container.test.ts @@ -4,18 +4,13 @@ import { MongoDBContainer } from "./mongodb-container"; const IMAGE = getImage(__dirname); -describe("MongodbContainer", { timeout: 240_000 }, () => { - // connect4 { - it("should work using default version 4.0.1", async () => { - await using mongodbContainer = await new MongoDBContainer(IMAGE).start(); +describe("MongoDBContainer", { timeout: 240_000 }, () => { + it.each([IMAGE, "mongo:6.0.25", "mongo:4.4.29"])("should work with %s", async (image) => { + // connect { + await using mongodbContainer = await new MongoDBContainer(image).start(); // directConnection: true is required as the testcontainer is created as a MongoDB Replica Set. const db = mongoose.createConnection(mongodbContainer.getConnectionString(), { directConnection: true }); - - // You can also add the default connection flag as a query parameter - // const connectionString = `${mongodbContainer.getConnectionString()}?directConnection=true`; - // const db = mongoose.createConnection(connectionString); - const fooCollection = db.collection("foo"); const obj = { value: 1 }; @@ -24,64 +19,29 @@ describe("MongodbContainer", { timeout: 240_000 }, () => { await fooCollection.insertOne(obj); }); - expect( - await fooCollection.findOne({ - value: 1, - }) - ).toEqual(obj); + const result = await fooCollection.findOne({ value: 1 }); + expect(result).toEqual(obj); - await mongoose.disconnect(); + await db.close(); + // } }); - // } - - // connect6 { - it("should work using version 6.0.1", async () => { - await using mongodbContainer = await new MongoDBContainer("mongo:6.0.1").start(); - - // directConnection: true is required as the testcontainer is created as a MongoDB Replica Set. - const db = mongoose.createConnection(mongodbContainer.getConnectionString(), { directConnection: true }); - - // You can also add the default connection flag as a query parameter - // const connectionString = `${mongodbContainer.getConnectionString()}?directConnection=true`; - // const db = mongoose.createConnection(connectionString); - - const fooCollection = db.collection("foo"); - const obj = { value: 1 }; - - const session = await db.startSession(); - await session.withTransaction(async () => { - await fooCollection.insertOne(obj); - }); - - expect( - await fooCollection.findOne({ - value: 1, - }) - ).toEqual(obj); - - await mongoose.disconnect(); - }); - // } it("should connect with credentials", async () => { - const mongodbContainer = await new MongoDBContainer(IMAGE) + await using mongodbContainer = await new MongoDBContainer(IMAGE) .withUsername("mongo_user") .withPassword("mongo_password") .start(); - const connection = mongoose.createConnection(mongodbContainer.getConnectionString(), { - directConnection: true, - }); - try { - const result = await connection.collection("testcontainers").insertOne({ title: "testcontainers" }); - const id = result.insertedId.toString(); - expect(id).not.toBeNull(); - expect(id).not.toBe(""); - const rsStatus = await connection.db?.admin().replSetGetStatus(); - expect(rsStatus).toBeDefined(); - expect(rsStatus?.set).toBe("rs0"); - } finally { - await connection.close(); - await mongodbContainer.stop(); - } + + const db = mongoose.createConnection(mongodbContainer.getConnectionString(), { directConnection: true }); + + const result = await db.collection("testcontainers").insertOne({ title: "testcontainers" }); + const id = result.insertedId.toString(); + expect(id).not.toBeNull(); + expect(id).not.toBe(""); + const rsStatus = await db.db?.admin().replSetGetStatus(); + expect(rsStatus).toBeDefined(); + expect(rsStatus?.set).toBe("rs0"); + + await db.close(); }); }); From 9d0a117409f2b3553ad4d5a8ee1be91e919ee898 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Wed, 23 Jul 2025 10:41:51 +0100 Subject: [PATCH 08/10] Refactor tests + update docs --- docs/modules/mongodb.md | 6 +++++- .../mongodb/src/mongodb-container.test.ts | 19 +++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/docs/modules/mongodb.md b/docs/modules/mongodb.md index 5c12961c0..38a3089e0 100644 --- a/docs/modules/mongodb.md +++ b/docs/modules/mongodb.md @@ -11,5 +11,9 @@ npm install @testcontainers/mongodb --save-dev ## Examples -[Connect and execute query:](../../packages/modules/mongodb/src/mongodb-container.test.ts) inside_block:connect +[Connect:](../../packages/modules/mongodb/src/mongodb-container.test.ts) inside_block:connect + + + +[Connect with credentials:](../../packages/modules/mongodb/src/mongodb-container.test.ts) inside_block:connectWithCredentials diff --git a/packages/modules/mongodb/src/mongodb-container.test.ts b/packages/modules/mongodb/src/mongodb-container.test.ts index 617211b14..562de6bf4 100644 --- a/packages/modules/mongodb/src/mongodb-container.test.ts +++ b/packages/modules/mongodb/src/mongodb-container.test.ts @@ -5,27 +5,26 @@ import { MongoDBContainer } from "./mongodb-container"; const IMAGE = getImage(__dirname); describe("MongoDBContainer", { timeout: 240_000 }, () => { + // connect { it.each([IMAGE, "mongo:6.0.25", "mongo:4.4.29"])("should work with %s", async (image) => { - // connect { await using mongodbContainer = await new MongoDBContainer(image).start(); - // directConnection: true is required as the testcontainer is created as a MongoDB Replica Set. + // directConnection: true is required as the container is created as a MongoDB Replica Set. const db = mongoose.createConnection(mongodbContainer.getConnectionString(), { directConnection: true }); const fooCollection = db.collection("foo"); const obj = { value: 1 }; const session = await db.startSession(); - await session.withTransaction(async () => { - await fooCollection.insertOne(obj); - }); + await session.withTransaction(async () => await fooCollection.insertOne(obj)); const result = await fooCollection.findOne({ value: 1 }); expect(result).toEqual(obj); await db.close(); - // } }); + // } + // connectWithCredentials { it("should connect with credentials", async () => { await using mongodbContainer = await new MongoDBContainer(IMAGE) .withUsername("mongo_user") @@ -35,13 +34,13 @@ describe("MongoDBContainer", { timeout: 240_000 }, () => { const db = mongoose.createConnection(mongodbContainer.getConnectionString(), { directConnection: true }); const result = await db.collection("testcontainers").insertOne({ title: "testcontainers" }); - const id = result.insertedId.toString(); - expect(id).not.toBeNull(); - expect(id).not.toBe(""); + const resultId = result.insertedId.toString(); + expect(resultId).toBeTruthy(); + const rsStatus = await db.db?.admin().replSetGetStatus(); - expect(rsStatus).toBeDefined(); expect(rsStatus?.set).toBe("rs0"); await db.close(); }); + // } }); From 4317a39b972a3177b59c48038d71d96c9e5cdf8f Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Wed, 23 Jul 2025 10:43:37 +0100 Subject: [PATCH 09/10] Fix docs --- docs/modules/mongodb.md | 4 ++-- packages/modules/mongodb/src/mongodb-container.test.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/modules/mongodb.md b/docs/modules/mongodb.md index 38a3089e0..2e0a4eefa 100644 --- a/docs/modules/mongodb.md +++ b/docs/modules/mongodb.md @@ -11,9 +11,9 @@ npm install @testcontainers/mongodb --save-dev ## Examples -[Connect:](../../packages/modules/mongodb/src/mongodb-container.test.ts) inside_block:connect +[Connect:](../../packages/modules/mongodb/src/mongodb-container.test.ts) inside_block:mongoConnect -[Connect with credentials:](../../packages/modules/mongodb/src/mongodb-container.test.ts) inside_block:connectWithCredentials +[Connect with credentials:](../../packages/modules/mongodb/src/mongodb-container.test.ts) inside_block:mongoConnectWithCredentials diff --git a/packages/modules/mongodb/src/mongodb-container.test.ts b/packages/modules/mongodb/src/mongodb-container.test.ts index 562de6bf4..5a6ed5062 100644 --- a/packages/modules/mongodb/src/mongodb-container.test.ts +++ b/packages/modules/mongodb/src/mongodb-container.test.ts @@ -5,8 +5,8 @@ import { MongoDBContainer } from "./mongodb-container"; const IMAGE = getImage(__dirname); describe("MongoDBContainer", { timeout: 240_000 }, () => { - // connect { it.each([IMAGE, "mongo:6.0.25", "mongo:4.4.29"])("should work with %s", async (image) => { + // mongoConnect { await using mongodbContainer = await new MongoDBContainer(image).start(); // directConnection: true is required as the container is created as a MongoDB Replica Set. @@ -21,11 +21,11 @@ describe("MongoDBContainer", { timeout: 240_000 }, () => { expect(result).toEqual(obj); await db.close(); + // } }); - // } - // connectWithCredentials { it("should connect with credentials", async () => { + // mongoConnectWithCredentials { await using mongodbContainer = await new MongoDBContainer(IMAGE) .withUsername("mongo_user") .withPassword("mongo_password") @@ -41,6 +41,6 @@ describe("MongoDBContainer", { timeout: 240_000 }, () => { expect(rsStatus?.set).toBe("rs0"); await db.close(); + // } }); - // } }); From 20086b13528602f8837ea71ace1570a88e3ca2e7 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Wed, 23 Jul 2025 10:57:57 +0100 Subject: [PATCH 10/10] Fix docs --- docs/modules/mongodb.md | 4 ++-- mkdocs.yml | 2 +- packages/modules/mongodb/src/mongodb-container.test.ts | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/modules/mongodb.md b/docs/modules/mongodb.md index 2e0a4eefa..586173048 100644 --- a/docs/modules/mongodb.md +++ b/docs/modules/mongodb.md @@ -11,9 +11,9 @@ npm install @testcontainers/mongodb --save-dev ## Examples -[Connect:](../../packages/modules/mongodb/src/mongodb-container.test.ts) inside_block:mongoConnect +[Connect:](../../packages/modules/mongodb/src/mongodb-container.test.ts) inside_block:connectMongo -[Connect with credentials:](../../packages/modules/mongodb/src/mongodb-container.test.ts) inside_block:mongoConnectWithCredentials +[Connect with credentials:](../../packages/modules/mongodb/src/mongodb-container.test.ts) inside_block:connectWithCredentials diff --git a/mkdocs.yml b/mkdocs.yml index 1f71adc38..f83c3a2b0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -29,7 +29,7 @@ markdown_extensions: - pymdownx.details - pymdownx.superfences - pymdownx.tabbed: - alternate_style: true + alternate_style: false - toc: permalink: true diff --git a/packages/modules/mongodb/src/mongodb-container.test.ts b/packages/modules/mongodb/src/mongodb-container.test.ts index 5a6ed5062..a74750460 100644 --- a/packages/modules/mongodb/src/mongodb-container.test.ts +++ b/packages/modules/mongodb/src/mongodb-container.test.ts @@ -6,10 +6,9 @@ const IMAGE = getImage(__dirname); describe("MongoDBContainer", { timeout: 240_000 }, () => { it.each([IMAGE, "mongo:6.0.25", "mongo:4.4.29"])("should work with %s", async (image) => { - // mongoConnect { + // connectMongo { await using mongodbContainer = await new MongoDBContainer(image).start(); - // directConnection: true is required as the container is created as a MongoDB Replica Set. const db = mongoose.createConnection(mongodbContainer.getConnectionString(), { directConnection: true }); const fooCollection = db.collection("foo"); const obj = { value: 1 }; @@ -25,7 +24,7 @@ describe("MongoDBContainer", { timeout: 240_000 }, () => { }); it("should connect with credentials", async () => { - // mongoConnectWithCredentials { + // connectWithCredentials { await using mongodbContainer = await new MongoDBContainer(IMAGE) .withUsername("mongo_user") .withPassword("mongo_password")