From 1038321f9a936f029b7c964bf3728d98bd213217 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Mon, 12 Oct 2026 19:51:26 +0100 Subject: [PATCH 01/17] Test Valkey, Redis, NATS, etcd and CouchDB against their oldest supported versions --- .../couchdb/src/couchdb-container.test.ts | 7 +- .../modules/etcd/src/etcd-container.test.ts | 24 ++++- .../modules/nats/src/nats-container.test.ts | 13 +-- .../modules/redis/src/redis-container.test.ts | 98 +++++++++++-------- .../valkey/src/valkey-container.test.ts | 34 +++++-- 5 files changed, 116 insertions(+), 60 deletions(-) diff --git a/packages/modules/couchdb/src/couchdb-container.test.ts b/packages/modules/couchdb/src/couchdb-container.test.ts index 2a46976d0..e49f47ab6 100644 --- a/packages/modules/couchdb/src/couchdb-container.test.ts +++ b/packages/modules/couchdb/src/couchdb-container.test.ts @@ -4,16 +4,17 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { CouchDBContainer } from "./couchdb-container"; const IMAGE = getImage(__dirname); +const OLDEST_IMAGE = "couchdb:3.4.3"; type User = { username: string; email: string; }; -describe("CouchDBContainer", { timeout: 240_000 }, () => { +describe.each([IMAGE, OLDEST_IMAGE])("CouchDBContainer (%s)", { timeout: 240_000 }, (image) => { it("should write and read a collection", async () => { // startContainer { - await using container = await new CouchDBContainer(IMAGE).start(); + await using container = await new CouchDBContainer(image).start(); const client = nano({ url: container.getUrl(), @@ -37,7 +38,7 @@ describe("CouchDBContainer", { timeout: 240_000 }, () => { it("should use custom credentials", async () => { // customCredentials { - await using container = await new CouchDBContainer(IMAGE).withUsername("admin").withPassword("foo").start(); + await using container = await new CouchDBContainer(image).withUsername("admin").withPassword("foo").start(); const client = nano({ url: container.getUrl(), diff --git a/packages/modules/etcd/src/etcd-container.test.ts b/packages/modules/etcd/src/etcd-container.test.ts index dd28a7ce5..bfa70d0fe 100644 --- a/packages/modules/etcd/src/etcd-container.test.ts +++ b/packages/modules/etcd/src/etcd-container.test.ts @@ -3,11 +3,12 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { EtcdContainer } from "./etcd-container"; const IMAGE = getImage(__dirname); +const OLDEST_IMAGE = "quay.io/coreos/etcd:v3.5.34"; -describe("EtcdContainer", { timeout: 180_000 }, () => { +describe.each([IMAGE, OLDEST_IMAGE])("EtcdContainer (%s)", { timeout: 180_000 }, (image) => { it("should connect and perform read/write operations", async () => { // readWrite { - await using container = await new EtcdContainer(IMAGE).start(); + await using container = await new EtcdContainer(image).start(); const client = new Etcd3({ hosts: container.getClientEndpoint(), @@ -24,7 +25,7 @@ describe("EtcdContainer", { timeout: 180_000 }, () => { it("should subscribe to key changes", async () => { // etcdSubscribe { - await using container = await new EtcdContainer(IMAGE).start(); + await using container = await new EtcdContainer(image).start(); const client = new Etcd3({ hosts: container.getClientEndpoint(), @@ -42,3 +43,20 @@ describe("EtcdContainer", { timeout: 180_000 }, () => { // } }); }); + +describe("EtcdContainer", { timeout: 180_000 }, () => { + it.each(["quay.io/coreos/etcd:v3.6.15"])( + "should connect and perform read/write operations with %s", + async (image) => { + await using container = await new EtcdContainer(image).start(); + + const client = new Etcd3({ + hosts: container.getClientEndpoint(), + }); + + await client.put("foo").value("bar"); + + expect(await client.get("foo").string()).toEqual("bar"); + } + ); +}); diff --git a/packages/modules/nats/src/nats-container.test.ts b/packages/modules/nats/src/nats-container.test.ts index 39800661c..d5ef23449 100644 --- a/packages/modules/nats/src/nats-container.test.ts +++ b/packages/modules/nats/src/nats-container.test.ts @@ -4,8 +4,9 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { NatsContainer } from "./nats-container"; const IMAGE = getImage(__dirname); +const OLDEST_IMAGE = "nats:2.14.7-alpine"; -describe("NatsContainer", { timeout: 180_000 }, () => { +describe.each([IMAGE, OLDEST_IMAGE])("NatsContainer (%s)", { timeout: 180_000 }, (image) => { it("should subscribe and receive one published message", async () => { // natsPubsub { const SUBJECT = "HELLO"; @@ -13,7 +14,7 @@ describe("NatsContainer", { timeout: 180_000 }, () => { const TE = new TextEncoder(); const TD = new TextDecoder(); - await using container = await new NatsContainer(IMAGE).start(); + await using container = await new NatsContainer(image).start(); const nc = await connect(container.getConnectionOptions()); const sub = nc.subscribe(SUBJECT); @@ -36,7 +37,7 @@ describe("NatsContainer", { timeout: 180_000 }, () => { it("should start with alternative username and password ", async () => { // natsCredentials { - await using container = await new NatsContainer(IMAGE).withUsername("George").withPass("1234").start(); + await using container = await new NatsContainer(image).withUsername("George").withPass("1234").start(); // } const nc = await connect(container.getConnectionOptions()); @@ -48,7 +49,7 @@ describe("NatsContainer", { timeout: 180_000 }, () => { it("should start with JetStream ", async () => { // natsJetstream { - await using container = await new NatsContainer(IMAGE).withJetStream().start(); + await using container = await new NatsContainer(image).withJetStream().start(); // } const nc = await connect(container.getConnectionOptions()); @@ -60,7 +61,7 @@ describe("NatsContainer", { timeout: 180_000 }, () => { }); it("should fail without JetStream ", async () => { - await using container = await new NatsContainer(IMAGE).start(); + await using container = await new NatsContainer(image).start(); const nc = await connect(container.getConnectionOptions()); @@ -79,7 +80,7 @@ describe("NatsContainer", { timeout: 180_000 }, () => { // for the complete list of available arguments see: // See Command Line Options section inside [NATS docker image documentation](https://hub.docker.com/_/nats) async function outputVersionAndExit() { - await using container = await new NatsContainer(IMAGE).withArg("version", "").start(); + await using container = await new NatsContainer(image).withArg("version", "").start(); await connect(container.getConnectionOptions()); } diff --git a/packages/modules/redis/src/redis-container.test.ts b/packages/modules/redis/src/redis-container.test.ts index 640e523ea..78c8fc412 100644 --- a/packages/modules/redis/src/redis-container.test.ts +++ b/packages/modules/redis/src/redis-container.test.ts @@ -5,12 +5,13 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { RedisContainer } from "./redis-container"; const IMAGE = getImage(__dirname); +const OLDEST_IMAGE = "redis:6.2.24"; const REDISSTACK_IMAGE = getImage(__dirname, 1); -describe("RedisContainer", { timeout: 240_000 }, () => { +describe.each([IMAGE, OLDEST_IMAGE])("RedisContainer (%s)", { timeout: 240_000 }, (image) => { it("should connect and execute set-get", async () => { // redisStartContainer { - await using container = await new RedisContainer(IMAGE).start(); + await using container = await new RedisContainer(image).start(); const client = createClient({ url: container.getConnectionUrl() }); await client.connect(); @@ -23,7 +24,7 @@ describe("RedisContainer", { timeout: 240_000 }, () => { }); it("should connect with password and execute set-get", async () => { - await using container = await new RedisContainer(IMAGE).withPassword("test").start(); + await using container = await new RedisContainer(image).withPassword("test").start(); const client = createClient({ url: container.getConnectionUrl() }); await client.connect(); @@ -38,7 +39,7 @@ describe("RedisContainer", { timeout: 240_000 }, () => { // persistentData { const sourcePath = fs.mkdtempSync("redis-"); - await using container = await new RedisContainer(IMAGE).withPassword("test").withPersistence(sourcePath).start(); + await using container = await new RedisContainer(image).withPassword("test").withPersistence(sourcePath).start(); let client = createClient({ url: container.getConnectionUrl() }); await client.connect(); @@ -58,7 +59,7 @@ describe("RedisContainer", { timeout: 240_000 }, () => { it("should load initial data and can read it", async () => { // withPredefinedData { - await using container = await new RedisContainer(IMAGE) + await using container = await new RedisContainer(image) .withPassword("test") .withInitialData(path.join(__dirname, "initData.redis")) .start(); @@ -78,7 +79,7 @@ describe("RedisContainer", { timeout: 240_000 }, () => { }); it("should load initial data once", async () => { - await using container = await new RedisContainer(IMAGE) + await using container = await new RedisContainer(image) .withInitialData(path.join(__dirname, "initCount.redis")) .start(); @@ -94,7 +95,7 @@ describe("RedisContainer", { timeout: 240_000 }, () => { // redisStartWithCredentials { const password = "testPassword"; - await using container = await new RedisContainer(IMAGE).withPassword(password).start(); + await using container = await new RedisContainer(image).withPassword(password).start(); expect(container.getConnectionUrl()).toEqual(`redis://:${password}@${container.getHost()}:${container.getPort()}`); // } @@ -110,7 +111,7 @@ describe("RedisContainer", { timeout: 240_000 }, () => { it("should execute container cmd and return the result", async () => { // executeCommand { - await using container = await new RedisContainer(IMAGE).start(); + await using container = await new RedisContainer(image).start(); const queryResult = await container.executeCliCmd("info", ["clients"]); @@ -118,23 +119,8 @@ describe("RedisContainer", { timeout: 240_000 }, () => { // } }); - it("should start with redis-stack-server and json module", async () => { - // startWithRedisStack { - await using container = await new RedisContainer(REDISSTACK_IMAGE).withPassword("testPassword").start(); - - const client = createClient({ url: container.getConnectionUrl() }); - await client.connect(); - - await client.json.set("key", "$", { name: "test" }); - const result = await client.json.get("key"); - expect(result).toEqual({ name: "test" }); - - client.destroy(); - // } - }); - it("should start redis with custom command", async () => { - const container = new RedisContainer(IMAGE).withCommand(["redis-server", "--loglevel", "verbose"]); + const container = new RedisContainer(image).withCommand(["redis-server", "--loglevel", "verbose"]); await using startedContainer = await container.start(); // @ts-expect-error - accessing private property for testing @@ -149,12 +135,22 @@ describe("RedisContainer", { timeout: 240_000 }, () => { client.destroy(); }); - it("should start redis-stack with custom env", async () => { - const container = new RedisContainer(REDISSTACK_IMAGE).withEnvironment({ REDIS_ARGS: "--loglevel verbose" }); + it("should start redis with custom command and keeping default args", async () => { + const sourcePath = fs.mkdtempSync("redis-"); + + const container = new RedisContainer(image) + .withCommand(["redis-server", "--loglevel", "verbose"]) + .withPersistence(sourcePath); await using startedContainer = await container.start(); // @ts-expect-error - accessing private property for testing - expect(container.createOpts.Env).toEqual(["REDIS_ARGS=--loglevel verbose"]); + expect(container.createOpts.Cmd).toEqual([ + "redis-server", + "--loglevel", + "verbose", + "--save 1 1 ", + "--appendonly yes", + ]); const client = createClient({ url: startedContainer.getConnectionUrl() }); await client.connect(); @@ -163,24 +159,47 @@ describe("RedisContainer", { timeout: 240_000 }, () => { expect(await client.get("key")).toBe("val"); client.destroy(); + fs.rmSync(sourcePath, { force: true, recursive: true }); }); +}); - it("should start redis with custom command and keeping default args", async () => { - const sourcePath = fs.mkdtempSync("redis-"); +describe("RedisContainer", { timeout: 240_000 }, () => { + it.each(["redis:7.2.16", "redis:7.4.11", "redis:8.2.10"])( + "should connect and execute set-get with %s", + async (image) => { + await using container = await new RedisContainer(image).start(); - const container = new RedisContainer(IMAGE) - .withCommand(["redis-server", "--loglevel", "verbose"]) - .withPersistence(sourcePath); + const client = createClient({ url: container.getConnectionUrl() }); + await client.connect(); + + await client.set("key", "val"); + expect(await client.get("key")).toBe("val"); + + client.destroy(); + } + ); + + it("should start with redis-stack-server and json module", async () => { + // startWithRedisStack { + await using container = await new RedisContainer(REDISSTACK_IMAGE).withPassword("testPassword").start(); + + const client = createClient({ url: container.getConnectionUrl() }); + await client.connect(); + + await client.json.set("key", "$", { name: "test" }); + const result = await client.json.get("key"); + expect(result).toEqual({ name: "test" }); + + client.destroy(); + // } + }); + + it("should start redis-stack with custom env", async () => { + const container = new RedisContainer(REDISSTACK_IMAGE).withEnvironment({ REDIS_ARGS: "--loglevel verbose" }); await using startedContainer = await container.start(); // @ts-expect-error - accessing private property for testing - expect(container.createOpts.Cmd).toEqual([ - "redis-server", - "--loglevel", - "verbose", - "--save 1 1 ", - "--appendonly yes", - ]); + expect(container.createOpts.Env).toEqual(["REDIS_ARGS=--loglevel verbose"]); const client = createClient({ url: startedContainer.getConnectionUrl() }); await client.connect(); @@ -189,7 +208,6 @@ describe("RedisContainer", { timeout: 240_000 }, () => { expect(await client.get("key")).toBe("val"); client.destroy(); - fs.rmSync(sourcePath, { force: true, recursive: true }); }); it("should start redis-stack with custom env and keeping default args", async () => { diff --git a/packages/modules/valkey/src/valkey-container.test.ts b/packages/modules/valkey/src/valkey-container.test.ts index ff82f5fa2..9fd58206a 100644 --- a/packages/modules/valkey/src/valkey-container.test.ts +++ b/packages/modules/valkey/src/valkey-container.test.ts @@ -5,11 +5,12 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { ValkeyContainer } from "./valkey-container"; const IMAGE = getImage(__dirname); +const OLDEST_IMAGE = "valkey/valkey:7.2.14"; -describe("ValkeyContainer", { timeout: 240_000 }, () => { +describe.each([IMAGE, OLDEST_IMAGE])("ValkeyContainer (%s)", { timeout: 240_000 }, (image) => { it("should connect and execute set-get", async () => { // valkeyStartContainer { - await using container = await new ValkeyContainer(IMAGE).start(); + await using container = await new ValkeyContainer(image).start(); const client = createClient({ url: container.getConnectionUrl() }); await client.connect(); @@ -22,7 +23,7 @@ describe("ValkeyContainer", { timeout: 240_000 }, () => { }); it("should connect with password and execute set-get", async () => { - await using container = await new ValkeyContainer(IMAGE).withPassword("test").start(); + await using container = await new ValkeyContainer(image).withPassword("test").start(); const client = createClient({ url: container.getConnectionUrl() }); await client.connect(); @@ -37,7 +38,7 @@ describe("ValkeyContainer", { timeout: 240_000 }, () => { // valkeyWithPersistentData { const sourcePath = fs.mkdtempSync("valkey-"); - await using container = await new ValkeyContainer(IMAGE).withPassword("test").withPersistence(sourcePath).start(); + await using container = await new ValkeyContainer(image).withPassword("test").withPersistence(sourcePath).start(); let client = createClient({ url: container.getConnectionUrl() }); await client.connect(); @@ -58,7 +59,7 @@ describe("ValkeyContainer", { timeout: 240_000 }, () => { it("should load initial data and can read it", async () => { // valkeyWithPredefinedData { - await using container = await new ValkeyContainer(IMAGE) + await using container = await new ValkeyContainer(image) .withPassword("test") .withInitialData(path.join(__dirname, "initData.valkey")) .start(); @@ -82,7 +83,7 @@ describe("ValkeyContainer", { timeout: 240_000 }, () => { const username = "testUser"; const password = "testPassword"; - await using container = await new ValkeyContainer(IMAGE).withUsername(username).withPassword(password).start(); + await using container = await new ValkeyContainer(image).withUsername(username).withPassword(password).start(); expect(container.getConnectionUrl()).toEqual( `redis://${username}:${password}@${container.getHost()}:${container.getPort()}` @@ -102,7 +103,7 @@ describe("ValkeyContainer", { timeout: 240_000 }, () => { // valkeyWithPassword { const password = "testPassword"; - await using container = await new ValkeyContainer(IMAGE).withPassword(password).start(); + await using container = await new ValkeyContainer(image).withPassword(password).start(); expect(container.getConnectionUrl()).toEqual(`redis://:${password}@${container.getHost()}:${container.getPort()}`); // } @@ -118,7 +119,7 @@ describe("ValkeyContainer", { timeout: 240_000 }, () => { it("should execute container cmd and return the result", async () => { // valkeyExecuteCommand { - await using container = await new ValkeyContainer(IMAGE).start(); + await using container = await new ValkeyContainer(image).start(); const queryResult = await container.executeCliCmd("info", ["clients"]); @@ -126,3 +127,20 @@ describe("ValkeyContainer", { timeout: 240_000 }, () => { // } }); }); + +describe("ValkeyContainer", { timeout: 240_000 }, () => { + it.each(["valkey/valkey:8.0.11", "valkey/valkey:8.1.10", "valkey/valkey:9.0.6"])( + "should connect and execute set-get with %s", + async (image) => { + await using container = await new ValkeyContainer(image).start(); + + const client = createClient({ url: container.getConnectionUrl() }); + await client.connect(); + + await client.set("key", "val"); + expect(await client.get("key")).toBe("val"); + + client.destroy(); + } + ); +}); From 34bf6817469431a502bd6261ee10691741fc4107 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Mon, 12 Oct 2026 20:03:49 +0100 Subject: [PATCH 02/17] Test Elasticsearch, OpenSearch and MongoDB against their supported versions --- .../src/elasticsearch-container.test.ts | 15 ++++++------ .../mongodb/src/mongodb-container.test.ts | 24 ++++++++++++++++--- .../src/opensearch-container.test.ts | 16 ++++++------- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/packages/modules/elasticsearch/src/elasticsearch-container.test.ts b/packages/modules/elasticsearch/src/elasticsearch-container.test.ts index 886365a0e..1ae909acc 100644 --- a/packages/modules/elasticsearch/src/elasticsearch-container.test.ts +++ b/packages/modules/elasticsearch/src/elasticsearch-container.test.ts @@ -3,10 +3,10 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { ElasticsearchContainer } from "./elasticsearch-container"; const IMAGE = getImage(__dirname); -const images = ["elasticsearch:7.17.28", "elasticsearch:8.18.1", IMAGE]; +const OLDEST_IMAGE = "elasticsearch:8.19.23"; -describe("ElasticsearchContainer", { timeout: 180_000 }, () => { - it.each(images)("should create an index with %s", async (image) => { +describe.each([IMAGE, OLDEST_IMAGE])("ElasticsearchContainer (%s)", { timeout: 180_000 }, (image) => { + it("should create an index", async () => { // createIndex { await using container = await new ElasticsearchContainer(image).start(); @@ -21,9 +21,10 @@ describe("ElasticsearchContainer", { timeout: 180_000 }, () => { // } }); - it("should index a document", async () => { + // The 9.x client sends a compatible-with=9 media type with request bodies, which Elasticsearch 8 rejects. + it.skipIf(image === OLDEST_IMAGE)("should index a document", async () => { // indexDocument { - await using container = await new ElasticsearchContainer(IMAGE).start(); + await using container = await new ElasticsearchContainer(image).start(); const client = new Client({ node: container.getHttpUrl(), @@ -46,7 +47,7 @@ describe("ElasticsearchContainer", { timeout: 180_000 }, () => { it("should set custom password", async () => { // withPassword { - await using container = await new ElasticsearchContainer(IMAGE).withPassword("testPassword").start(); + await using container = await new ElasticsearchContainer(image).withPassword("testPassword").start(); const client = new Client({ node: container.getHttpUrl(), @@ -60,7 +61,7 @@ describe("ElasticsearchContainer", { timeout: 180_000 }, () => { }); it("should work with restarted container", async () => { - await using container = await new ElasticsearchContainer(IMAGE).start(); + await using container = await new ElasticsearchContainer(image).start(); await container.restart(); const client = new Client({ diff --git a/packages/modules/mongodb/src/mongodb-container.test.ts b/packages/modules/mongodb/src/mongodb-container.test.ts index d44864e9e..477d112f6 100644 --- a/packages/modules/mongodb/src/mongodb-container.test.ts +++ b/packages/modules/mongodb/src/mongodb-container.test.ts @@ -3,9 +3,10 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { MongoDBContainer } from "./mongodb-container"; const IMAGE = getImage(__dirname); +const OLDEST_IMAGE = "mongo:7.0.43"; -describe("MongoDBContainer", { timeout: 240_000 }, () => { - it.each([IMAGE, "mongo:6.0.25", "mongo:4.4.29"])("should work with %s", async (image) => { +describe.each([IMAGE, OLDEST_IMAGE])("MongoDBContainer (%s)", { timeout: 240_000 }, (image) => { + it("should connect", async () => { // connectMongo { await using container = await new MongoDBContainer(image).start(); @@ -24,7 +25,7 @@ describe("MongoDBContainer", { timeout: 240_000 }, () => { it("should connect with credentials", async () => { // connectWithCredentials { - await using container = await new MongoDBContainer(IMAGE) + await using container = await new MongoDBContainer(image) .withUsername("customUsername") .withPassword("customPassword") .start(); @@ -42,3 +43,20 @@ describe("MongoDBContainer", { timeout: 240_000 }, () => { await db.close(); }); }); + +describe("MongoDBContainer", { timeout: 240_000 }, () => { + it.each(["mongo:8.0.32", "mongo:8.3.11"])("should connect with %s", async (image) => { + await using container = await new MongoDBContainer(image).start(); + + const db = await mongoose.createConnection(container.getConnectionString(), { directConnection: true }).asPromise(); + + const obj = { value: 1 }; + const collection = db.collection("test"); + await collection.insertOne(obj); + + const result = await collection.findOne({ value: 1 }); + expect(result).toEqual(obj); + + await db.close(); + }); +}); diff --git a/packages/modules/opensearch/src/opensearch-container.test.ts b/packages/modules/opensearch/src/opensearch-container.test.ts index 2e7d57eaf..84802940e 100644 --- a/packages/modules/opensearch/src/opensearch-container.test.ts +++ b/packages/modules/opensearch/src/opensearch-container.test.ts @@ -3,10 +3,10 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { OpenSearchContainer } from "./opensearch-container"; const IMAGE = getImage(__dirname); -const images = ["opensearchproject/opensearch:2.19.2", IMAGE]; +const OLDEST_IMAGE = "opensearchproject/opensearch:2.19.6"; -describe("OpenSearchContainer", { timeout: 180_000 }, () => { - it.each(images)("should create an index with %s", async (image) => { +describe.each([IMAGE, OLDEST_IMAGE])("OpenSearchContainer (%s)", { timeout: 180_000 }, (image) => { + it("should create an index", async () => { // opensearchCreateIndex { await using container = await new OpenSearchContainer(image).start(); @@ -30,7 +30,7 @@ describe("OpenSearchContainer", { timeout: 180_000 }, () => { it("should index a document", async () => { // opensearchIndexDocument { - await using container = await new OpenSearchContainer(IMAGE).start(); + await using container = await new OpenSearchContainer(image).start(); const client = new Client({ node: container.getHttpUrl(), @@ -57,7 +57,7 @@ describe("OpenSearchContainer", { timeout: 180_000 }, () => { }); it("should work with restarted container", async () => { - await using container = await new OpenSearchContainer(IMAGE).start(); + await using container = await new OpenSearchContainer(image).start(); await container.restart(); const client = new Client({ @@ -78,12 +78,12 @@ describe("OpenSearchContainer", { timeout: 180_000 }, () => { }); it("should throw when given an invalid password", () => { - expect(() => new OpenSearchContainer(IMAGE).withPassword("weakpwd")).toThrowError(/Password "weakpwd" is too weak/); + expect(() => new OpenSearchContainer(image).withPassword("weakpwd")).toThrowError(/Password "weakpwd" is too weak/); }); it("should set custom password", async () => { // opensearchCustomPassword { - await using container = await new OpenSearchContainer(IMAGE).withPassword("Str0ng!Passw0rd2025").start(); + await using container = await new OpenSearchContainer(image).withPassword("Str0ng!Passw0rd2025").start(); // } const client = new Client({ @@ -105,7 +105,7 @@ describe("OpenSearchContainer", { timeout: 180_000 }, () => { it("should be reachable with security disabled", async () => { // opensearchDisableSecurity { - await using container = await new OpenSearchContainer(IMAGE).withSecurityEnabled(false).start(); + await using container = await new OpenSearchContainer(image).withSecurityEnabled(false).start(); const client = new Client({ node: container.getHttpUrl(), From 9e2c74d79fcfc380e4fe7a84423ec703864b564b Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 10:20:13 +0100 Subject: [PATCH 03/17] Drop support for MongoDB below 5.0 and Kafka KRaft below Confluent Platform 7.4 --- package-lock.json | 1 - .../kafka/src/kafka-container-7.test.ts | 6 +++--- packages/modules/kafka/src/kafka-container.ts | 17 ++--------------- packages/modules/mongodb/package.json | 1 - .../modules/mongodb/src/mongodb-container.ts | 18 ++---------------- 5 files changed, 7 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index 907a9af01..928c8e2f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15710,7 +15710,6 @@ "version": "12.2.0", "license": "MIT", "dependencies": { - "compare-versions": "^6.1.1", "testcontainers": "^12.2.0" }, "devDependencies": { diff --git a/packages/modules/kafka/src/kafka-container-7.test.ts b/packages/modules/kafka/src/kafka-container-7.test.ts index cedffc668..fdf1ab5ac 100644 --- a/packages/modules/kafka/src/kafka-container-7.test.ts +++ b/packages/modules/kafka/src/kafka-container-7.test.ts @@ -222,9 +222,9 @@ describe("KafkaContainer", { timeout: 240_000 }, () => { await assertMessageProducedAndConsumed(container); }); - it("should throw an error when using kraft and and confluence platfom below 7.0.0", async () => { - expect(() => new KafkaContainer("confluentinc/cp-kafka:6.2.14").withKraft()).toThrow( - "Provided Confluent Platform's version 6.2.14 is not supported in Kraft mode (must be 7.0.0 or above)" + it("should throw an error when using kraft and and confluence platfom below 7.4.0", async () => { + expect(() => new KafkaContainer("confluentinc/cp-kafka:7.3.15").withKraft()).toThrow( + "Provided Confluent Platform's version 7.3.15 is not supported in Kraft mode (must be 7.4.0 or above)" ); }); diff --git a/packages/modules/kafka/src/kafka-container.ts b/packages/modules/kafka/src/kafka-container.ts index 5c6ff485f..f6e3ae576 100644 --- a/packages/modules/kafka/src/kafka-container.ts +++ b/packages/modules/kafka/src/kafka-container.ts @@ -22,8 +22,7 @@ const DEFAULT_CLUSTER_ID = "4L6g3nShT-eMCtK--X86sw"; const STARTER_SCRIPT = "/tmp/testcontainers_start.sh"; const WAIT_FOR_SCRIPT_MESSAGE = "Waiting for script..."; -// https://docs.confluent.io/platform/7.0.0/release-notes/index.html#ak-raft-kraft -const MIN_KRAFT_VERSION = "7.0.0"; +const MIN_KRAFT_VERSION = "7.4.0"; const MIN_KRAFT_SASL_VERSION = "7.5.0"; export interface SaslSslListenerOptions { @@ -168,7 +167,7 @@ export class KafkaContainer extends GenericContainer { // exporting KAFKA_ADVERTISED_LISTENERS with the container hostname command += `export KAFKA_ADVERTISED_LISTENERS=${advertisedListeners}\n`; - if (this.mode !== KafkaMode.KRAFT || satisfies(this.imageName.tag, "<7.4.0")) { + if (this.mode !== KafkaMode.KRAFT) { // Optimization: skip the checks command += "echo '' > /etc/confluent/docker/ensure \n"; } @@ -176,9 +175,6 @@ export class KafkaContainer extends GenericContainer { if (this.saslSslConfig) { command += this.commandKraftCreateUser(this.saslSslConfig); } - if (satisfies(this.imageName.tag, "<7.4.0")) { - command += this.commandKraft(); - } } else if (this.mode === KafkaMode.EMBEDDED_ZOOKEEPER) { command += this.commandZookeeper(); } @@ -291,15 +287,6 @@ export class KafkaContainer extends GenericContainer { ); } - private commandKraft(): string { - let command = "sed -i '/KAFKA_ZOOKEEPER_CONNECT/d' /etc/confluent/docker/configure\n"; - command += - "echo 'kafka-storage format --ignore-formatted " + - `-t "${this.environment.CLUSTER_ID}" ` + - "-c /etc/kafka/kafka.properties' >> /etc/confluent/docker/configure\n"; - return command; - } - private commandZookeeper(): string { let command = `echo 'clientPort=${DEFAULT_ZOOKEEPER_PORT}' > zookeeper.properties\n`; command += "echo 'dataDir=/var/lib/zookeeper/data' >> zookeeper.properties\n"; diff --git a/packages/modules/mongodb/package.json b/packages/modules/mongodb/package.json index 2b632862f..894126c22 100644 --- a/packages/modules/mongodb/package.json +++ b/packages/modules/mongodb/package.json @@ -32,7 +32,6 @@ "mongoose": "^9.10.4" }, "dependencies": { - "compare-versions": "^6.1.1", "testcontainers": "^12.2.0" } } diff --git a/packages/modules/mongodb/src/mongodb-container.ts b/packages/modules/mongodb/src/mongodb-container.ts index 7b66ff344..656533764 100644 --- a/packages/modules/mongodb/src/mongodb-container.ts +++ b/packages/modules/mongodb/src/mongodb-container.ts @@ -1,4 +1,3 @@ -import { satisfies } from "compare-versions"; import { AbstractStartedContainer, GenericContainer, type StartedTestContainer, Wait } from "testcontainers"; const MONGODB_PORT = 27017; @@ -57,27 +56,14 @@ export class MongoDBContainer extends GenericContainer { } private buildMongoEvalCommand(command: string) { - const args = []; - if (this.isV5OrLater()) args.push("mongosh"); - else args.push("mongo", "admin"); + const args = ["mongosh"]; if (this.username && this.password) args.push("-u", this.username, "-p", this.password); args.push("--quiet", "--eval", command); return args.join(" "); } private getRsInitCmd() { - if (this.isV5OrLater()) - return `'try { rs.status(); } catch (e) { rs.initiate(); } while (db.runCommand({isMaster: 1}).ismaster==false) { sleep(100); }'`; - else - return `'try { rs.initiate(); } catch (e) {} while (db.runCommand({isMaster: 1}).ismaster==false) { sleep(100); }'`; - } - - private isV5OrLater() { - try { - return satisfies(this.imageName.tag, ">=5.0.0"); - } catch { - return false; - } + return `'try { rs.status(); } catch (e) { rs.initiate(); } while (db.runCommand({isMaster: 1}).ismaster==false) { sleep(100); }'`; } } From 44a737eb0ba7c2bd047e4e0075d17c26db4c5fc4 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 10:30:29 +0100 Subject: [PATCH 04/17] Remove the Kafka version guards and the Neo4j 4 plugin settings --- .../kafka/src/kafka-container-7.test.ts | 30 ------------------- packages/modules/kafka/src/kafka-container.ts | 21 ------------- packages/modules/neo4j/src/neo4j-container.ts | 3 +- 3 files changed, 1 insertion(+), 53 deletions(-) diff --git a/packages/modules/kafka/src/kafka-container-7.test.ts b/packages/modules/kafka/src/kafka-container-7.test.ts index fdf1ab5ac..73765c2f9 100644 --- a/packages/modules/kafka/src/kafka-container-7.test.ts +++ b/packages/modules/kafka/src/kafka-container-7.test.ts @@ -222,40 +222,10 @@ describe("KafkaContainer", { timeout: 240_000 }, () => { await assertMessageProducedAndConsumed(container); }); - it("should throw an error when using kraft and and confluence platfom below 7.4.0", async () => { - expect(() => new KafkaContainer("confluentinc/cp-kafka:7.3.15").withKraft()).toThrow( - "Provided Confluent Platform's version 7.3.15 is not supported in Kraft mode (must be 7.4.0 or above)" - ); - }); - it("should connect using kraft and custom network", async () => { await using network = await new Network().start(); await using container = await new KafkaContainer(IMAGE).withKraft().withNetwork(network).start(); await assertMessageProducedAndConsumed(container); }); - - it("should throw an error when using kraft wit sasl and confluence platfom below 7.5.0", async () => { - const kafkaContainer = new KafkaContainer("confluentinc/cp-kafka:7.4.0").withKraft().withSaslSslListener({ - port: 9094, - sasl: { - mechanism: "SCRAM-SHA-512", - user: { - name: "app-user", - password: "userPassword", - }, - }, - keystore: { - content: "fake", - passphrase: "serverKeystorePassword", - }, - truststore: { - content: "fake", - passphrase: "serverTruststorePassword", - }, - }); - await expect(() => kafkaContainer.start()).rejects.toThrow( - "Provided Confluent Platform's version 7.4.0 is not supported in Kraft mode with sasl (must be 7.5.0 or above)" - ); - }); }); diff --git a/packages/modules/kafka/src/kafka-container.ts b/packages/modules/kafka/src/kafka-container.ts index f6e3ae576..60c7c2755 100644 --- a/packages/modules/kafka/src/kafka-container.ts +++ b/packages/modules/kafka/src/kafka-container.ts @@ -22,9 +22,6 @@ const DEFAULT_CLUSTER_ID = "4L6g3nShT-eMCtK--X86sw"; const STARTER_SCRIPT = "/tmp/testcontainers_start.sh"; const WAIT_FOR_SCRIPT_MESSAGE = "Waiting for script..."; -const MIN_KRAFT_VERSION = "7.4.0"; -const MIN_KRAFT_SASL_VERSION = "7.5.0"; - export interface SaslSslListenerOptions { sasl: SaslOptions; port: number; @@ -92,7 +89,6 @@ export class KafkaContainer extends GenericContainer { } public withKraft(): this { - this.verifyMinKraftVersion(); this.mode = KafkaMode.KRAFT; this.zooKeeperHost = undefined; this.zooKeeperPort = undefined; @@ -146,15 +142,6 @@ export class KafkaContainer extends GenericContainer { } public override async start(): Promise { - if ( - this.mode === KafkaMode.KRAFT && - this.saslSslConfig && - satisfies(this.imageName.tag, `<${MIN_KRAFT_SASL_VERSION}`) - ) { - throw new Error( - `Provided Confluent Platform's version ${this.imageName.tag} is not supported in Kraft mode with sasl (must be ${MIN_KRAFT_SASL_VERSION} or above)` - ); - } return new StartedKafkaContainer(await super.start()); } @@ -270,14 +257,6 @@ export class KafkaContainer extends GenericContainer { } } - private verifyMinKraftVersion() { - if (satisfies(this.imageName.tag, `<${MIN_KRAFT_VERSION}`)) { - throw new Error( - `Provided Confluent Platform's version ${this.imageName.tag} is not supported in Kraft mode (must be ${MIN_KRAFT_VERSION} or above)` - ); - } - } - private commandKraftCreateUser(saslOptions: SaslSslListenerOptions): string { return ( "echo 'kafka-storage format --ignore-formatted " + diff --git a/packages/modules/neo4j/src/neo4j-container.ts b/packages/modules/neo4j/src/neo4j-container.ts index 8567215a2..037be614e 100755 --- a/packages/modules/neo4j/src/neo4j-container.ts +++ b/packages/modules/neo4j/src/neo4j-container.ts @@ -71,10 +71,9 @@ export class Neo4jContainer extends GenericContainer { .join(","); this.withEnvironment({ - NEO4JLABS_PLUGINS: JSON.stringify(this.plugins), // Older variant for older images. NEO4J_PLUGINS: JSON.stringify(this.plugins), NEO4J_dbms_security_procedures_unrestricted: whitelists, - NEO4J_dbms_security_procedures_whitelist: whitelists, + NEO4J_dbms_security_procedures_allowlist: whitelists, }); } return new StartedNeo4jContainer(await super.start(), this.password); From 4416c14958d92f19451c093093003b4d5ecee59a Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 10:35:28 +0100 Subject: [PATCH 05/17] Loop the suites over a list of every supported image --- .../couchdb/src/couchdb-container.test.ts | 5 ++-- .../modules/etcd/src/etcd-container.test.ts | 22 ++------------ .../modules/nats/src/nats-container.test.ts | 5 ++-- .../modules/redis/src/redis-container.test.ts | 20 ++----------- .../valkey/src/valkey-container.test.ts | 30 ++++++------------- 5 files changed, 17 insertions(+), 65 deletions(-) diff --git a/packages/modules/couchdb/src/couchdb-container.test.ts b/packages/modules/couchdb/src/couchdb-container.test.ts index e49f47ab6..8a4ea8a16 100644 --- a/packages/modules/couchdb/src/couchdb-container.test.ts +++ b/packages/modules/couchdb/src/couchdb-container.test.ts @@ -3,15 +3,14 @@ import { expect } from "vitest"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { CouchDBContainer } from "./couchdb-container"; -const IMAGE = getImage(__dirname); -const OLDEST_IMAGE = "couchdb:3.4.3"; +const IMAGES = ["couchdb:3.4.3", getImage(__dirname)]; type User = { username: string; email: string; }; -describe.each([IMAGE, OLDEST_IMAGE])("CouchDBContainer (%s)", { timeout: 240_000 }, (image) => { +describe.each(IMAGES)("CouchDBContainer (%s)", { timeout: 240_000 }, (image) => { it("should write and read a collection", async () => { // startContainer { await using container = await new CouchDBContainer(image).start(); diff --git a/packages/modules/etcd/src/etcd-container.test.ts b/packages/modules/etcd/src/etcd-container.test.ts index bfa70d0fe..84644548b 100644 --- a/packages/modules/etcd/src/etcd-container.test.ts +++ b/packages/modules/etcd/src/etcd-container.test.ts @@ -2,10 +2,9 @@ import { Etcd3 } from "etcd3"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { EtcdContainer } from "./etcd-container"; -const IMAGE = getImage(__dirname); -const OLDEST_IMAGE = "quay.io/coreos/etcd:v3.5.34"; +const IMAGES = ["quay.io/coreos/etcd:v3.5.34", "quay.io/coreos/etcd:v3.6.15", getImage(__dirname)]; -describe.each([IMAGE, OLDEST_IMAGE])("EtcdContainer (%s)", { timeout: 180_000 }, (image) => { +describe.each(IMAGES)("EtcdContainer (%s)", { timeout: 180_000 }, (image) => { it("should connect and perform read/write operations", async () => { // readWrite { await using container = await new EtcdContainer(image).start(); @@ -43,20 +42,3 @@ describe.each([IMAGE, OLDEST_IMAGE])("EtcdContainer (%s)", { timeout: 180_000 }, // } }); }); - -describe("EtcdContainer", { timeout: 180_000 }, () => { - it.each(["quay.io/coreos/etcd:v3.6.15"])( - "should connect and perform read/write operations with %s", - async (image) => { - await using container = await new EtcdContainer(image).start(); - - const client = new Etcd3({ - hosts: container.getClientEndpoint(), - }); - - await client.put("foo").value("bar"); - - expect(await client.get("foo").string()).toEqual("bar"); - } - ); -}); diff --git a/packages/modules/nats/src/nats-container.test.ts b/packages/modules/nats/src/nats-container.test.ts index d5ef23449..db465a759 100644 --- a/packages/modules/nats/src/nats-container.test.ts +++ b/packages/modules/nats/src/nats-container.test.ts @@ -3,10 +3,9 @@ import { connect } from "@nats-io/transport-node"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { NatsContainer } from "./nats-container"; -const IMAGE = getImage(__dirname); -const OLDEST_IMAGE = "nats:2.14.7-alpine"; +const IMAGES = ["nats:2.14.7-alpine", getImage(__dirname)]; -describe.each([IMAGE, OLDEST_IMAGE])("NatsContainer (%s)", { timeout: 180_000 }, (image) => { +describe.each(IMAGES)("NatsContainer (%s)", { timeout: 180_000 }, (image) => { it("should subscribe and receive one published message", async () => { // natsPubsub { const SUBJECT = "HELLO"; diff --git a/packages/modules/redis/src/redis-container.test.ts b/packages/modules/redis/src/redis-container.test.ts index 78c8fc412..19650494a 100644 --- a/packages/modules/redis/src/redis-container.test.ts +++ b/packages/modules/redis/src/redis-container.test.ts @@ -4,11 +4,10 @@ import { createClient } from "redis"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { RedisContainer } from "./redis-container"; -const IMAGE = getImage(__dirname); -const OLDEST_IMAGE = "redis:6.2.24"; +const IMAGES = ["redis:6.2.24", "redis:7.2.16", "redis:7.4.11", "redis:8.2.10", getImage(__dirname)]; const REDISSTACK_IMAGE = getImage(__dirname, 1); -describe.each([IMAGE, OLDEST_IMAGE])("RedisContainer (%s)", { timeout: 240_000 }, (image) => { +describe.each(IMAGES)("RedisContainer (%s)", { timeout: 240_000 }, (image) => { it("should connect and execute set-get", async () => { // redisStartContainer { await using container = await new RedisContainer(image).start(); @@ -164,21 +163,6 @@ describe.each([IMAGE, OLDEST_IMAGE])("RedisContainer (%s)", { timeout: 240_000 } }); describe("RedisContainer", { timeout: 240_000 }, () => { - it.each(["redis:7.2.16", "redis:7.4.11", "redis:8.2.10"])( - "should connect and execute set-get with %s", - async (image) => { - await using container = await new RedisContainer(image).start(); - - const client = createClient({ url: container.getConnectionUrl() }); - await client.connect(); - - await client.set("key", "val"); - expect(await client.get("key")).toBe("val"); - - client.destroy(); - } - ); - it("should start with redis-stack-server and json module", async () => { // startWithRedisStack { await using container = await new RedisContainer(REDISSTACK_IMAGE).withPassword("testPassword").start(); diff --git a/packages/modules/valkey/src/valkey-container.test.ts b/packages/modules/valkey/src/valkey-container.test.ts index 9fd58206a..8135479a9 100644 --- a/packages/modules/valkey/src/valkey-container.test.ts +++ b/packages/modules/valkey/src/valkey-container.test.ts @@ -4,10 +4,15 @@ import { createClient } from "redis"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { ValkeyContainer } from "./valkey-container"; -const IMAGE = getImage(__dirname); -const OLDEST_IMAGE = "valkey/valkey:7.2.14"; - -describe.each([IMAGE, OLDEST_IMAGE])("ValkeyContainer (%s)", { timeout: 240_000 }, (image) => { +const IMAGES = [ + "valkey/valkey:7.2.14", + "valkey/valkey:8.0.11", + "valkey/valkey:8.1.10", + "valkey/valkey:9.0.6", + getImage(__dirname), +]; + +describe.each(IMAGES)("ValkeyContainer (%s)", { timeout: 240_000 }, (image) => { it("should connect and execute set-get", async () => { // valkeyStartContainer { await using container = await new ValkeyContainer(image).start(); @@ -127,20 +132,3 @@ describe.each([IMAGE, OLDEST_IMAGE])("ValkeyContainer (%s)", { timeout: 240_000 // } }); }); - -describe("ValkeyContainer", { timeout: 240_000 }, () => { - it.each(["valkey/valkey:8.0.11", "valkey/valkey:8.1.10", "valkey/valkey:9.0.6"])( - "should connect and execute set-get with %s", - async (image) => { - await using container = await new ValkeyContainer(image).start(); - - const client = createClient({ url: container.getConnectionUrl() }); - await client.connect(); - - await client.set("key", "val"); - expect(await client.get("key")).toBe("val"); - - client.destroy(); - } - ); -}); From 267736295219acd0b0c0d65193daaf2804a73b8a Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 10:41:24 +0100 Subject: [PATCH 06/17] Loop the suites over a list of every supported image --- .../src/elasticsearch-container.test.ts | 7 +++--- .../mongodb/src/mongodb-container.test.ts | 22 ++----------------- .../src/opensearch-container.test.ts | 5 ++--- 3 files changed, 7 insertions(+), 27 deletions(-) diff --git a/packages/modules/elasticsearch/src/elasticsearch-container.test.ts b/packages/modules/elasticsearch/src/elasticsearch-container.test.ts index 1ae909acc..03979d232 100644 --- a/packages/modules/elasticsearch/src/elasticsearch-container.test.ts +++ b/packages/modules/elasticsearch/src/elasticsearch-container.test.ts @@ -2,10 +2,9 @@ import { Client } from "@elastic/elasticsearch"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { ElasticsearchContainer } from "./elasticsearch-container"; -const IMAGE = getImage(__dirname); -const OLDEST_IMAGE = "elasticsearch:8.19.23"; +const IMAGES = ["elasticsearch:8.19.23", getImage(__dirname)]; -describe.each([IMAGE, OLDEST_IMAGE])("ElasticsearchContainer (%s)", { timeout: 180_000 }, (image) => { +describe.each(IMAGES)("ElasticsearchContainer (%s)", { timeout: 180_000 }, (image) => { it("should create an index", async () => { // createIndex { await using container = await new ElasticsearchContainer(image).start(); @@ -22,7 +21,7 @@ describe.each([IMAGE, OLDEST_IMAGE])("ElasticsearchContainer (%s)", { timeout: 1 }); // The 9.x client sends a compatible-with=9 media type with request bodies, which Elasticsearch 8 rejects. - it.skipIf(image === OLDEST_IMAGE)("should index a document", async () => { + it.skipIf(image.startsWith("elasticsearch:8."))("should index a document", async () => { // indexDocument { await using container = await new ElasticsearchContainer(image).start(); diff --git a/packages/modules/mongodb/src/mongodb-container.test.ts b/packages/modules/mongodb/src/mongodb-container.test.ts index 477d112f6..893733a43 100644 --- a/packages/modules/mongodb/src/mongodb-container.test.ts +++ b/packages/modules/mongodb/src/mongodb-container.test.ts @@ -2,10 +2,9 @@ import mongoose from "mongoose"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { MongoDBContainer } from "./mongodb-container"; -const IMAGE = getImage(__dirname); -const OLDEST_IMAGE = "mongo:7.0.43"; +const IMAGES = ["mongo:7.0.43", "mongo:8.0.32", "mongo:8.3.11", getImage(__dirname)]; -describe.each([IMAGE, OLDEST_IMAGE])("MongoDBContainer (%s)", { timeout: 240_000 }, (image) => { +describe.each(IMAGES)("MongoDBContainer (%s)", { timeout: 240_000 }, (image) => { it("should connect", async () => { // connectMongo { await using container = await new MongoDBContainer(image).start(); @@ -43,20 +42,3 @@ describe.each([IMAGE, OLDEST_IMAGE])("MongoDBContainer (%s)", { timeout: 240_000 await db.close(); }); }); - -describe("MongoDBContainer", { timeout: 240_000 }, () => { - it.each(["mongo:8.0.32", "mongo:8.3.11"])("should connect with %s", async (image) => { - await using container = await new MongoDBContainer(image).start(); - - const db = await mongoose.createConnection(container.getConnectionString(), { directConnection: true }).asPromise(); - - const obj = { value: 1 }; - const collection = db.collection("test"); - await collection.insertOne(obj); - - const result = await collection.findOne({ value: 1 }); - expect(result).toEqual(obj); - - await db.close(); - }); -}); diff --git a/packages/modules/opensearch/src/opensearch-container.test.ts b/packages/modules/opensearch/src/opensearch-container.test.ts index 84802940e..ff4d4dfda 100644 --- a/packages/modules/opensearch/src/opensearch-container.test.ts +++ b/packages/modules/opensearch/src/opensearch-container.test.ts @@ -2,10 +2,9 @@ import { Client } from "@opensearch-project/opensearch"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { OpenSearchContainer } from "./opensearch-container"; -const IMAGE = getImage(__dirname); -const OLDEST_IMAGE = "opensearchproject/opensearch:2.19.6"; +const IMAGES = ["opensearchproject/opensearch:2.19.6", getImage(__dirname)]; -describe.each([IMAGE, OLDEST_IMAGE])("OpenSearchContainer (%s)", { timeout: 180_000 }, (image) => { +describe.each(IMAGES)("OpenSearchContainer (%s)", { timeout: 180_000 }, (image) => { it("should create an index", async () => { // opensearchCreateIndex { await using container = await new OpenSearchContainer(image).start(); From 1b3f8c04bbcc61c5be4b4361358c19644fedaf57 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 11:11:51 +0100 Subject: [PATCH 07/17] List one image per supported major version --- .../modules/couchdb/src/couchdb-container.test.ts | 8 ++++---- packages/modules/etcd/src/etcd-container.test.ts | 8 ++++---- packages/modules/nats/src/nats-container.test.ts | 14 +++++++------- packages/modules/redis/src/redis-container.test.ts | 2 +- .../modules/valkey/src/valkey-container.test.ts | 8 +------- 5 files changed, 17 insertions(+), 23 deletions(-) diff --git a/packages/modules/couchdb/src/couchdb-container.test.ts b/packages/modules/couchdb/src/couchdb-container.test.ts index 8a4ea8a16..2a46976d0 100644 --- a/packages/modules/couchdb/src/couchdb-container.test.ts +++ b/packages/modules/couchdb/src/couchdb-container.test.ts @@ -3,17 +3,17 @@ import { expect } from "vitest"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { CouchDBContainer } from "./couchdb-container"; -const IMAGES = ["couchdb:3.4.3", getImage(__dirname)]; +const IMAGE = getImage(__dirname); type User = { username: string; email: string; }; -describe.each(IMAGES)("CouchDBContainer (%s)", { timeout: 240_000 }, (image) => { +describe("CouchDBContainer", { timeout: 240_000 }, () => { it("should write and read a collection", async () => { // startContainer { - await using container = await new CouchDBContainer(image).start(); + await using container = await new CouchDBContainer(IMAGE).start(); const client = nano({ url: container.getUrl(), @@ -37,7 +37,7 @@ describe.each(IMAGES)("CouchDBContainer (%s)", { timeout: 240_000 }, (image) => it("should use custom credentials", async () => { // customCredentials { - await using container = await new CouchDBContainer(image).withUsername("admin").withPassword("foo").start(); + await using container = await new CouchDBContainer(IMAGE).withUsername("admin").withPassword("foo").start(); const client = nano({ url: container.getUrl(), diff --git a/packages/modules/etcd/src/etcd-container.test.ts b/packages/modules/etcd/src/etcd-container.test.ts index 84644548b..dd28a7ce5 100644 --- a/packages/modules/etcd/src/etcd-container.test.ts +++ b/packages/modules/etcd/src/etcd-container.test.ts @@ -2,12 +2,12 @@ import { Etcd3 } from "etcd3"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { EtcdContainer } from "./etcd-container"; -const IMAGES = ["quay.io/coreos/etcd:v3.5.34", "quay.io/coreos/etcd:v3.6.15", getImage(__dirname)]; +const IMAGE = getImage(__dirname); -describe.each(IMAGES)("EtcdContainer (%s)", { timeout: 180_000 }, (image) => { +describe("EtcdContainer", { timeout: 180_000 }, () => { it("should connect and perform read/write operations", async () => { // readWrite { - await using container = await new EtcdContainer(image).start(); + await using container = await new EtcdContainer(IMAGE).start(); const client = new Etcd3({ hosts: container.getClientEndpoint(), @@ -24,7 +24,7 @@ describe.each(IMAGES)("EtcdContainer (%s)", { timeout: 180_000 }, (image) => { it("should subscribe to key changes", async () => { // etcdSubscribe { - await using container = await new EtcdContainer(image).start(); + await using container = await new EtcdContainer(IMAGE).start(); const client = new Etcd3({ hosts: container.getClientEndpoint(), diff --git a/packages/modules/nats/src/nats-container.test.ts b/packages/modules/nats/src/nats-container.test.ts index db465a759..39800661c 100644 --- a/packages/modules/nats/src/nats-container.test.ts +++ b/packages/modules/nats/src/nats-container.test.ts @@ -3,9 +3,9 @@ import { connect } from "@nats-io/transport-node"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { NatsContainer } from "./nats-container"; -const IMAGES = ["nats:2.14.7-alpine", getImage(__dirname)]; +const IMAGE = getImage(__dirname); -describe.each(IMAGES)("NatsContainer (%s)", { timeout: 180_000 }, (image) => { +describe("NatsContainer", { timeout: 180_000 }, () => { it("should subscribe and receive one published message", async () => { // natsPubsub { const SUBJECT = "HELLO"; @@ -13,7 +13,7 @@ describe.each(IMAGES)("NatsContainer (%s)", { timeout: 180_000 }, (image) => { const TE = new TextEncoder(); const TD = new TextDecoder(); - await using container = await new NatsContainer(image).start(); + await using container = await new NatsContainer(IMAGE).start(); const nc = await connect(container.getConnectionOptions()); const sub = nc.subscribe(SUBJECT); @@ -36,7 +36,7 @@ describe.each(IMAGES)("NatsContainer (%s)", { timeout: 180_000 }, (image) => { it("should start with alternative username and password ", async () => { // natsCredentials { - await using container = await new NatsContainer(image).withUsername("George").withPass("1234").start(); + await using container = await new NatsContainer(IMAGE).withUsername("George").withPass("1234").start(); // } const nc = await connect(container.getConnectionOptions()); @@ -48,7 +48,7 @@ describe.each(IMAGES)("NatsContainer (%s)", { timeout: 180_000 }, (image) => { it("should start with JetStream ", async () => { // natsJetstream { - await using container = await new NatsContainer(image).withJetStream().start(); + await using container = await new NatsContainer(IMAGE).withJetStream().start(); // } const nc = await connect(container.getConnectionOptions()); @@ -60,7 +60,7 @@ describe.each(IMAGES)("NatsContainer (%s)", { timeout: 180_000 }, (image) => { }); it("should fail without JetStream ", async () => { - await using container = await new NatsContainer(image).start(); + await using container = await new NatsContainer(IMAGE).start(); const nc = await connect(container.getConnectionOptions()); @@ -79,7 +79,7 @@ describe.each(IMAGES)("NatsContainer (%s)", { timeout: 180_000 }, (image) => { // for the complete list of available arguments see: // See Command Line Options section inside [NATS docker image documentation](https://hub.docker.com/_/nats) async function outputVersionAndExit() { - await using container = await new NatsContainer(image).withArg("version", "").start(); + await using container = await new NatsContainer(IMAGE).withArg("version", "").start(); await connect(container.getConnectionOptions()); } diff --git a/packages/modules/redis/src/redis-container.test.ts b/packages/modules/redis/src/redis-container.test.ts index 19650494a..83b1b16cf 100644 --- a/packages/modules/redis/src/redis-container.test.ts +++ b/packages/modules/redis/src/redis-container.test.ts @@ -4,7 +4,7 @@ import { createClient } from "redis"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { RedisContainer } from "./redis-container"; -const IMAGES = ["redis:6.2.24", "redis:7.2.16", "redis:7.4.11", "redis:8.2.10", getImage(__dirname)]; +const IMAGES = ["redis:6.2.24", "redis:7.4.11", getImage(__dirname)]; const REDISSTACK_IMAGE = getImage(__dirname, 1); describe.each(IMAGES)("RedisContainer (%s)", { timeout: 240_000 }, (image) => { diff --git a/packages/modules/valkey/src/valkey-container.test.ts b/packages/modules/valkey/src/valkey-container.test.ts index 8135479a9..0ddd88a42 100644 --- a/packages/modules/valkey/src/valkey-container.test.ts +++ b/packages/modules/valkey/src/valkey-container.test.ts @@ -4,13 +4,7 @@ import { createClient } from "redis"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { ValkeyContainer } from "./valkey-container"; -const IMAGES = [ - "valkey/valkey:7.2.14", - "valkey/valkey:8.0.11", - "valkey/valkey:8.1.10", - "valkey/valkey:9.0.6", - getImage(__dirname), -]; +const IMAGES = ["valkey/valkey:7.2.14", "valkey/valkey:8.1.10", getImage(__dirname)]; describe.each(IMAGES)("ValkeyContainer (%s)", { timeout: 240_000 }, (image) => { it("should connect and execute set-get", async () => { From cf3237694ad77c5352259f7259e54f6ea08ddc9a Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 11:12:14 +0100 Subject: [PATCH 08/17] List one image per supported major version --- packages/modules/mongodb/src/mongodb-container.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/mongodb/src/mongodb-container.test.ts b/packages/modules/mongodb/src/mongodb-container.test.ts index 893733a43..e66773c2b 100644 --- a/packages/modules/mongodb/src/mongodb-container.test.ts +++ b/packages/modules/mongodb/src/mongodb-container.test.ts @@ -2,7 +2,7 @@ import mongoose from "mongoose"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { MongoDBContainer } from "./mongodb-container"; -const IMAGES = ["mongo:7.0.43", "mongo:8.0.32", "mongo:8.3.11", getImage(__dirname)]; +const IMAGES = ["mongo:7.0.43", "mongo:8.3.11", getImage(__dirname)]; describe.each(IMAGES)("MongoDBContainer (%s)", { timeout: 240_000 }, (image) => { it("should connect", async () => { From a48b73fd0a7614ed20092b8f4ed93e228cf26724 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 12:44:30 +0100 Subject: [PATCH 09/17] Run the images one after another and each image's tests concurrently --- .../src/elasticsearch-container.test.ts | 10 +++++----- .../modules/mongodb/src/mongodb-container.test.ts | 6 +++--- .../opensearch/src/opensearch-container.test.ts | 14 +++++++------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/modules/elasticsearch/src/elasticsearch-container.test.ts b/packages/modules/elasticsearch/src/elasticsearch-container.test.ts index 03979d232..b93e916b1 100644 --- a/packages/modules/elasticsearch/src/elasticsearch-container.test.ts +++ b/packages/modules/elasticsearch/src/elasticsearch-container.test.ts @@ -4,8 +4,8 @@ import { ElasticsearchContainer } from "./elasticsearch-container"; const IMAGES = ["elasticsearch:8.19.23", getImage(__dirname)]; -describe.each(IMAGES)("ElasticsearchContainer (%s)", { timeout: 180_000 }, (image) => { - it("should create an index", async () => { +describe.each(IMAGES)("ElasticsearchContainer (%s)", { concurrent: false, timeout: 180_000 }, (image) => { + it.concurrent("should create an index", async () => { // createIndex { await using container = await new ElasticsearchContainer(image).start(); @@ -21,7 +21,7 @@ describe.each(IMAGES)("ElasticsearchContainer (%s)", { timeout: 180_000 }, (imag }); // The 9.x client sends a compatible-with=9 media type with request bodies, which Elasticsearch 8 rejects. - it.skipIf(image.startsWith("elasticsearch:8."))("should index a document", async () => { + it.skipIf(image.startsWith("elasticsearch:8.")).concurrent("should index a document", async () => { // indexDocument { await using container = await new ElasticsearchContainer(image).start(); @@ -44,7 +44,7 @@ describe.each(IMAGES)("ElasticsearchContainer (%s)", { timeout: 180_000 }, (imag // } }); - it("should set custom password", async () => { + it.concurrent("should set custom password", async () => { // withPassword { await using container = await new ElasticsearchContainer(image).withPassword("testPassword").start(); @@ -59,7 +59,7 @@ describe.each(IMAGES)("ElasticsearchContainer (%s)", { timeout: 180_000 }, (imag expect(await client.indices.exists({ index: "people" })).toBe(true); }); - it("should work with restarted container", async () => { + it.concurrent("should work with restarted container", async () => { await using container = await new ElasticsearchContainer(image).start(); await container.restart(); diff --git a/packages/modules/mongodb/src/mongodb-container.test.ts b/packages/modules/mongodb/src/mongodb-container.test.ts index e66773c2b..67cbe39d2 100644 --- a/packages/modules/mongodb/src/mongodb-container.test.ts +++ b/packages/modules/mongodb/src/mongodb-container.test.ts @@ -4,8 +4,8 @@ import { MongoDBContainer } from "./mongodb-container"; const IMAGES = ["mongo:7.0.43", "mongo:8.3.11", getImage(__dirname)]; -describe.each(IMAGES)("MongoDBContainer (%s)", { timeout: 240_000 }, (image) => { - it("should connect", async () => { +describe.each(IMAGES)("MongoDBContainer (%s)", { concurrent: false, timeout: 240_000 }, (image) => { + it.concurrent("should connect", async () => { // connectMongo { await using container = await new MongoDBContainer(image).start(); @@ -22,7 +22,7 @@ describe.each(IMAGES)("MongoDBContainer (%s)", { timeout: 240_000 }, (image) => // } }); - it("should connect with credentials", async () => { + it.concurrent("should connect with credentials", async () => { // connectWithCredentials { await using container = await new MongoDBContainer(image) .withUsername("customUsername") diff --git a/packages/modules/opensearch/src/opensearch-container.test.ts b/packages/modules/opensearch/src/opensearch-container.test.ts index ff4d4dfda..fe909e64f 100644 --- a/packages/modules/opensearch/src/opensearch-container.test.ts +++ b/packages/modules/opensearch/src/opensearch-container.test.ts @@ -4,8 +4,8 @@ import { OpenSearchContainer } from "./opensearch-container"; const IMAGES = ["opensearchproject/opensearch:2.19.6", getImage(__dirname)]; -describe.each(IMAGES)("OpenSearchContainer (%s)", { timeout: 180_000 }, (image) => { - it("should create an index", async () => { +describe.each(IMAGES)("OpenSearchContainer (%s)", { concurrent: false, timeout: 180_000 }, (image) => { + it.concurrent("should create an index", async () => { // opensearchCreateIndex { await using container = await new OpenSearchContainer(image).start(); @@ -27,7 +27,7 @@ describe.each(IMAGES)("OpenSearchContainer (%s)", { timeout: 180_000 }, (image) // } }); - it("should index a document", async () => { + it.concurrent("should index a document", async () => { // opensearchIndexDocument { await using container = await new OpenSearchContainer(image).start(); @@ -55,7 +55,7 @@ describe.each(IMAGES)("OpenSearchContainer (%s)", { timeout: 180_000 }, (image) // } }); - it("should work with restarted container", async () => { + it.concurrent("should work with restarted container", async () => { await using container = await new OpenSearchContainer(image).start(); await container.restart(); @@ -76,11 +76,11 @@ describe.each(IMAGES)("OpenSearchContainer (%s)", { timeout: 180_000 }, (image) expect(existsResponse.body).toBe(true); }); - it("should throw when given an invalid password", () => { + it.concurrent("should throw when given an invalid password", () => { expect(() => new OpenSearchContainer(image).withPassword("weakpwd")).toThrowError(/Password "weakpwd" is too weak/); }); - it("should set custom password", async () => { + it.concurrent("should set custom password", async () => { // opensearchCustomPassword { await using container = await new OpenSearchContainer(image).withPassword("Str0ng!Passw0rd2025").start(); // } @@ -102,7 +102,7 @@ describe.each(IMAGES)("OpenSearchContainer (%s)", { timeout: 180_000 }, (image) expect(body).toBe(true); }); - it("should be reachable with security disabled", async () => { + it.concurrent("should be reachable with security disabled", async () => { // opensearchDisableSecurity { await using container = await new OpenSearchContainer(image).withSecurityEnabled(false).start(); From 03d42c5563c654ea32da1c372eb7a012bfc4aca7 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 12:44:42 +0100 Subject: [PATCH 10/17] Run the images one after another and each image's tests concurrently --- .../modules/redis/src/redis-container.test.ts | 20 +++++++++---------- .../valkey/src/valkey-container.test.ts | 16 +++++++-------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/modules/redis/src/redis-container.test.ts b/packages/modules/redis/src/redis-container.test.ts index 83b1b16cf..5bbdd2264 100644 --- a/packages/modules/redis/src/redis-container.test.ts +++ b/packages/modules/redis/src/redis-container.test.ts @@ -7,8 +7,8 @@ import { RedisContainer } from "./redis-container"; const IMAGES = ["redis:6.2.24", "redis:7.4.11", getImage(__dirname)]; const REDISSTACK_IMAGE = getImage(__dirname, 1); -describe.each(IMAGES)("RedisContainer (%s)", { timeout: 240_000 }, (image) => { - it("should connect and execute set-get", async () => { +describe.each(IMAGES)("RedisContainer (%s)", { concurrent: false, timeout: 240_000 }, (image) => { + it.concurrent("should connect and execute set-get", async () => { // redisStartContainer { await using container = await new RedisContainer(image).start(); @@ -22,7 +22,7 @@ describe.each(IMAGES)("RedisContainer (%s)", { timeout: 240_000 }, (image) => { // } }); - it("should connect with password and execute set-get", async () => { + it.concurrent("should connect with password and execute set-get", async () => { await using container = await new RedisContainer(image).withPassword("test").start(); const client = createClient({ url: container.getConnectionUrl() }); @@ -34,7 +34,7 @@ describe.each(IMAGES)("RedisContainer (%s)", { timeout: 240_000 }, (image) => { client.destroy(); }); - it("should reconnect with volume and persistence data", async () => { + it.concurrent("should reconnect with volume and persistence data", async () => { // persistentData { const sourcePath = fs.mkdtempSync("redis-"); @@ -56,7 +56,7 @@ describe.each(IMAGES)("RedisContainer (%s)", { timeout: 240_000 }, (image) => { // } }); - it("should load initial data and can read it", async () => { + it.concurrent("should load initial data and can read it", async () => { // withPredefinedData { await using container = await new RedisContainer(image) .withPassword("test") @@ -77,7 +77,7 @@ describe.each(IMAGES)("RedisContainer (%s)", { timeout: 240_000 }, (image) => { // } }); - it("should load initial data once", async () => { + it.concurrent("should load initial data once", async () => { await using container = await new RedisContainer(image) .withInitialData(path.join(__dirname, "initCount.redis")) .start(); @@ -90,7 +90,7 @@ describe.each(IMAGES)("RedisContainer (%s)", { timeout: 240_000 }, (image) => { client.destroy(); }); - it("should start with credentials and login", async () => { + it.concurrent("should start with credentials and login", async () => { // redisStartWithCredentials { const password = "testPassword"; @@ -108,7 +108,7 @@ describe.each(IMAGES)("RedisContainer (%s)", { timeout: 240_000 }, (image) => { client.destroy(); }); - it("should execute container cmd and return the result", async () => { + it.concurrent("should execute container cmd and return the result", async () => { // executeCommand { await using container = await new RedisContainer(image).start(); @@ -118,7 +118,7 @@ describe.each(IMAGES)("RedisContainer (%s)", { timeout: 240_000 }, (image) => { // } }); - it("should start redis with custom command", async () => { + it.concurrent("should start redis with custom command", async () => { const container = new RedisContainer(image).withCommand(["redis-server", "--loglevel", "verbose"]); await using startedContainer = await container.start(); @@ -134,7 +134,7 @@ describe.each(IMAGES)("RedisContainer (%s)", { timeout: 240_000 }, (image) => { client.destroy(); }); - it("should start redis with custom command and keeping default args", async () => { + it.concurrent("should start redis with custom command and keeping default args", async () => { const sourcePath = fs.mkdtempSync("redis-"); const container = new RedisContainer(image) diff --git a/packages/modules/valkey/src/valkey-container.test.ts b/packages/modules/valkey/src/valkey-container.test.ts index 0ddd88a42..cb303533b 100644 --- a/packages/modules/valkey/src/valkey-container.test.ts +++ b/packages/modules/valkey/src/valkey-container.test.ts @@ -6,8 +6,8 @@ import { ValkeyContainer } from "./valkey-container"; const IMAGES = ["valkey/valkey:7.2.14", "valkey/valkey:8.1.10", getImage(__dirname)]; -describe.each(IMAGES)("ValkeyContainer (%s)", { timeout: 240_000 }, (image) => { - it("should connect and execute set-get", async () => { +describe.each(IMAGES)("ValkeyContainer (%s)", { concurrent: false, timeout: 240_000 }, (image) => { + it.concurrent("should connect and execute set-get", async () => { // valkeyStartContainer { await using container = await new ValkeyContainer(image).start(); @@ -21,7 +21,7 @@ describe.each(IMAGES)("ValkeyContainer (%s)", { timeout: 240_000 }, (image) => { // } }); - it("should connect with password and execute set-get", async () => { + it.concurrent("should connect with password and execute set-get", async () => { await using container = await new ValkeyContainer(image).withPassword("test").start(); const client = createClient({ url: container.getConnectionUrl() }); @@ -33,7 +33,7 @@ describe.each(IMAGES)("ValkeyContainer (%s)", { timeout: 240_000 }, (image) => { client.destroy(); }); - it("should reconnect with volume and persistence data", async () => { + it.concurrent("should reconnect with volume and persistence data", async () => { // valkeyWithPersistentData { const sourcePath = fs.mkdtempSync("valkey-"); @@ -56,7 +56,7 @@ describe.each(IMAGES)("ValkeyContainer (%s)", { timeout: 240_000 }, (image) => { // } }); - it("should load initial data and can read it", async () => { + it.concurrent("should load initial data and can read it", async () => { // valkeyWithPredefinedData { await using container = await new ValkeyContainer(image) .withPassword("test") @@ -77,7 +77,7 @@ describe.each(IMAGES)("ValkeyContainer (%s)", { timeout: 240_000 }, (image) => { // } }); - it("should start with username and password", async () => { + it.concurrent("should start with username and password", async () => { // valkeyWithUsernameAndPassword { const username = "testUser"; const password = "testPassword"; @@ -98,7 +98,7 @@ describe.each(IMAGES)("ValkeyContainer (%s)", { timeout: 240_000 }, (image) => { client.destroy(); }); - it("should start with password only", async () => { + it.concurrent("should start with password only", async () => { // valkeyWithPassword { const password = "testPassword"; @@ -116,7 +116,7 @@ describe.each(IMAGES)("ValkeyContainer (%s)", { timeout: 240_000 }, (image) => { client.destroy(); }); - it("should execute container cmd and return the result", async () => { + it.concurrent("should execute container cmd and return the result", async () => { // valkeyExecuteCommand { await using container = await new ValkeyContainer(image).start(); From 29fcdc098919f8b8dde49aa93346b12af0188d6a Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 13:04:31 +0100 Subject: [PATCH 11/17] Test MongoDB Atlas Local against both supported majors --- .../src/mongodb-atlas-local-container.test.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/modules/mongodb/src/mongodb-atlas-local-container.test.ts b/packages/modules/mongodb/src/mongodb-atlas-local-container.test.ts index b69a73de2..3dd70b52f 100644 --- a/packages/modules/mongodb/src/mongodb-atlas-local-container.test.ts +++ b/packages/modules/mongodb/src/mongodb-atlas-local-container.test.ts @@ -3,7 +3,7 @@ import { IntervalRetry } from "testcontainers"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { MongoDBAtlasLocalContainer } from "./mongodb-atlas-local-container"; -const IMAGE = getImage(__dirname, 1); +const IMAGES = ["mongodb/mongodb-atlas-local:7.0.43", getImage(__dirname, 1)]; const ATLAS_SEARCH_INDEX = { mappings: { dynamic: false, @@ -23,18 +23,18 @@ const ATLAS_SEARCH_INDEX = { }, }; -describe("MongoDBAtlasLocalContainer", { timeout: 240_000 }, () => { - it("should provide a connection string", async () => { +describe.each(IMAGES)("MongoDBAtlasLocalContainer (%s)", { concurrent: false, timeout: 240_000 }, (image) => { + it.concurrent("should provide a connection string", async () => { // connectAtlasLocal { - await using container = await new MongoDBAtlasLocalContainer(IMAGE).start(); + await using container = await new MongoDBAtlasLocalContainer(image).start(); // } expect(container.getConnectionString()).toBe(`mongodb://${container.getHost()}:${container.getMappedPort(27017)}/`); }); - it("should provide a database connection string", async () => { + it.concurrent("should provide a database connection string", async () => { // connectAtlasLocalDatabase { - await using container = await new MongoDBAtlasLocalContainer(IMAGE).start(); + await using container = await new MongoDBAtlasLocalContainer(image).start(); // } expect(container.getDatabaseConnectionString()).toBe( @@ -42,9 +42,9 @@ describe("MongoDBAtlasLocalContainer", { timeout: 240_000 }, () => { ); }); - it("should provide connection strings with credentials", async () => { + it.concurrent("should provide connection strings with credentials", async () => { // connectAtlasLocalWithCredentials { - await using container = await new MongoDBAtlasLocalContainer(IMAGE) + await using container = await new MongoDBAtlasLocalContainer(image) .withUsername("customUsername") .withPassword("customPassword") .start(); @@ -58,8 +58,8 @@ describe("MongoDBAtlasLocalContainer", { timeout: 240_000 }, () => { ); }); - it("should connect to mongodb atlas local", async () => { - await using container = await new MongoDBAtlasLocalContainer(IMAGE).start(); + it.concurrent("should connect to mongodb atlas local", async () => { + await using container = await new MongoDBAtlasLocalContainer(image).start(); const db = await mongoose.createConnection(container.getConnectionString(), { directConnection: true }).asPromise(); @@ -73,8 +73,8 @@ describe("MongoDBAtlasLocalContainer", { timeout: 240_000 }, () => { await db.close(); }); - it("should connect to mongodb atlas local with credentials", async () => { - await using container = await new MongoDBAtlasLocalContainer(IMAGE) + it.concurrent("should connect to mongodb atlas local with credentials", async () => { + await using container = await new MongoDBAtlasLocalContainer(image) .withUsername("customUsername") .withPassword("customPassword") .start(); @@ -93,9 +93,9 @@ describe("MongoDBAtlasLocalContainer", { timeout: 240_000 }, () => { await db.close(); }); - it("should create an atlas search index and search it", async () => { + it.concurrent("should create an atlas search index and search it", async () => { // createAtlasIndexAndSearchIt { - await using atlasLocalContainer = await new MongoDBAtlasLocalContainer(IMAGE).start(); + await using atlasLocalContainer = await new MongoDBAtlasLocalContainer(image).start(); const db = await mongoose .createConnection(atlasLocalContainer.getConnectionString(), { From b5db349a324a2e0ad12fede9afa880fcebd51e1f Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 13:05:16 +0100 Subject: [PATCH 12/17] Remove Redis Stack support --- docs/modules/redis.md | 6 -- packages/modules/redis/Dockerfile | 1 - .../modules/redis/src/redis-container.test.ts | 55 ------------------- packages/modules/redis/src/redis-container.ts | 20 +------ 4 files changed, 3 insertions(+), 79 deletions(-) diff --git a/docs/modules/redis.md b/docs/modules/redis.md index bc3205ad7..96dc9bc78 100644 --- a/docs/modules/redis.md +++ b/docs/modules/redis.md @@ -40,12 +40,6 @@ Choose an image from the [container registry](https://hub.docker.com/_/redis) an [](../../packages/modules/redis/src/redis-container.test.ts) inside_block:withPredefinedData -### Redis stack - - -[](../../packages/modules/redis/src/redis-container.test.ts) inside_block:startWithRedisStack - - ### Execute a command inside the container diff --git a/packages/modules/redis/Dockerfile b/packages/modules/redis/Dockerfile index 32a2e71ba..4214967b7 100644 --- a/packages/modules/redis/Dockerfile +++ b/packages/modules/redis/Dockerfile @@ -1,2 +1 @@ FROM redis:8.10 -FROM redis/redis-stack-server:7.4.0-v4 \ No newline at end of file diff --git a/packages/modules/redis/src/redis-container.test.ts b/packages/modules/redis/src/redis-container.test.ts index 5bbdd2264..41e2ce8af 100644 --- a/packages/modules/redis/src/redis-container.test.ts +++ b/packages/modules/redis/src/redis-container.test.ts @@ -5,7 +5,6 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { RedisContainer } from "./redis-container"; const IMAGES = ["redis:6.2.24", "redis:7.4.11", getImage(__dirname)]; -const REDISSTACK_IMAGE = getImage(__dirname, 1); describe.each(IMAGES)("RedisContainer (%s)", { concurrent: false, timeout: 240_000 }, (image) => { it.concurrent("should connect and execute set-get", async () => { @@ -161,57 +160,3 @@ describe.each(IMAGES)("RedisContainer (%s)", { concurrent: false, timeout: 240_0 fs.rmSync(sourcePath, { force: true, recursive: true }); }); }); - -describe("RedisContainer", { timeout: 240_000 }, () => { - it("should start with redis-stack-server and json module", async () => { - // startWithRedisStack { - await using container = await new RedisContainer(REDISSTACK_IMAGE).withPassword("testPassword").start(); - - const client = createClient({ url: container.getConnectionUrl() }); - await client.connect(); - - await client.json.set("key", "$", { name: "test" }); - const result = await client.json.get("key"); - expect(result).toEqual({ name: "test" }); - - client.destroy(); - // } - }); - - it("should start redis-stack with custom env", async () => { - const container = new RedisContainer(REDISSTACK_IMAGE).withEnvironment({ REDIS_ARGS: "--loglevel verbose" }); - await using startedContainer = await container.start(); - - // @ts-expect-error - accessing private property for testing - expect(container.createOpts.Env).toEqual(["REDIS_ARGS=--loglevel verbose"]); - - const client = createClient({ url: startedContainer.getConnectionUrl() }); - await client.connect(); - - await client.set("key", "val"); - expect(await client.get("key")).toBe("val"); - - client.destroy(); - }); - - it("should start redis-stack with custom env and keeping default args", async () => { - const sourcePath = fs.mkdtempSync("redis-"); - - const container = new RedisContainer(REDISSTACK_IMAGE) - .withEnvironment({ REDIS_ARGS: "--loglevel verbose" }) - .withPersistence(sourcePath); - await using startedContainer = await container.start(); - - // @ts-expect-error - accessing private property for testing - expect(container.createOpts.Env).toEqual(["REDIS_ARGS=--loglevel verbose --save 1 1 --appendonly yes"]); - - const client = createClient({ url: startedContainer.getConnectionUrl() }); - await client.connect(); - - await client.set("key", "val"); - expect(await client.get("key")).toBe("val"); - - client.destroy(); - fs.rmSync(sourcePath, { force: true, recursive: true }); - }); -}); diff --git a/packages/modules/redis/src/redis-container.ts b/packages/modules/redis/src/redis-container.ts index 874cb7ce5..7d2634d1e 100644 --- a/packages/modules/redis/src/redis-container.ts +++ b/packages/modules/redis/src/redis-container.ts @@ -43,23 +43,9 @@ export class RedisContainer extends GenericContainer { ...(this.password ? [`--requirepass ${this.password}`] : []), ...(this.persistenceVolume ? ["--save 1 1 ", "--appendonly yes"] : []), ]; - if (this.imageName.image.includes("redis-stack")) { - const existingRedisArgs = this.environment.REDIS_ARGS ?? ""; - - // merge with filter to remove empty items - const mergedRedisArgs = [existingRedisArgs, ...redisArgs].filter(Boolean).join(" "); - - // remove existing REDIS_ARGS to avoid duplicates - this.createOpts.Env = (this.createOpts.Env || []).filter((e) => !e.startsWith("REDIS_ARGS=")); - - this.withEnvironment({ - REDIS_ARGS: mergedRedisArgs, - }).withEntrypoint(["/entrypoint.sh"]); - } else { - const existingCmd = this.createOpts.Cmd || []; - const baseCmd = existingCmd.length ? existingCmd : ["redis-server"]; - this.withCommand([...baseCmd, ...redisArgs]); - } + const existingCmd = this.createOpts.Cmd || []; + const baseCmd = existingCmd.length ? existingCmd : ["redis-server"]; + this.withCommand([...baseCmd, ...redisArgs]); if (this.persistenceVolume) { this.withBindMounts([{ mode: "rw", source: this.persistenceVolume, target: "/data" }]); } From 58488b18e185142cea93baed38c43ec0a40911f6 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 17:11:33 +0100 Subject: [PATCH 13/17] Test each Elasticsearch major with the client of the same major --- docs/modules/elasticsearch.md | 2 + package-lock.json | 66 ++++++++++++++++++- packages/modules/elasticsearch/package.json | 3 +- .../src/elasticsearch-container.test.ts | 16 +++-- 4 files changed, 80 insertions(+), 7 deletions(-) diff --git a/docs/modules/elasticsearch.md b/docs/modules/elasticsearch.md index 6347bda6c..4740befd4 100644 --- a/docs/modules/elasticsearch.md +++ b/docs/modules/elasticsearch.md @@ -14,6 +14,8 @@ These examples use the following libraries: npm install @elastic/elasticsearch +Use the client major that matches your Elasticsearch major. A 9.x client can't talk to Elasticsearch 8. + Choose an image from the [container registry](https://hub.docker.com/_/elasticsearch) and substitute `IMAGE`. ### Create an index diff --git a/package-lock.json b/package-lock.json index 7c1d8e767..1a3b130a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1738,6 +1738,69 @@ } } }, + "node_modules/@elastic/elasticsearch-8": { + "name": "@elastic/elasticsearch", + "version": "8.19.2", + "resolved": "https://registry.npmjs.org/@elastic/elasticsearch/-/elasticsearch-8.19.2.tgz", + "integrity": "sha512-LMJCju/+AZkDlJArd/MYABWTDrHi4U7j3qGTKi1hYC7+67SaiYmYFItiueACQVrj2j3ECPMcguZ+7WrZeB+Z5g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@elastic/transport": "^8.9.6", + "apache-arrow": "18.x - 21.x", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@elastic/elasticsearch-8/node_modules/@elastic/transport": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@elastic/transport/-/transport-8.11.0.tgz", + "integrity": "sha512-1QD1j7gQF8+tVrf+UOcOrLF1LXnhBYwBnNl4xd+fYxkQONjaOjiTcfqXcALHesKezUv8wRzv3tedEnhtYLBmhw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "1.x", + "@opentelemetry/core": "2.x", + "debug": "^4.4.1", + "hpagent": "^1.2.0", + "ms": "^2.1.3", + "secure-json-parse": "^3.0.1", + "tslib": "^2.8.1", + "undici": "^6.21.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@elastic/elasticsearch-8/node_modules/secure-json-parse": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-3.0.2.tgz", + "integrity": "sha512-H6nS2o8bWfpFEV6U38sOSjS7bTbdgbCGU9wEM6W14P5H0QOsz94KCusifV44GpHDTu2nqZbuDNhTzu+mjDSw1w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/@elastic/elasticsearch-8/node_modules/undici": { + "version": "6.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.0.tgz", + "integrity": "sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/@elastic/transport": { "version": "9.4.0", "resolved": "https://registry.npmjs.org/@elastic/transport/-/transport-9.4.0.tgz", @@ -15319,7 +15382,8 @@ "testcontainers": "^12.2.1" }, "devDependencies": { - "@elastic/elasticsearch": "^9.5.1" + "@elastic/elasticsearch": "^9.5.1", + "@elastic/elasticsearch-8": "npm:@elastic/elasticsearch@^8.19.2" } }, "packages/modules/etcd": { diff --git a/packages/modules/elasticsearch/package.json b/packages/modules/elasticsearch/package.json index 7aff51b33..931673159 100644 --- a/packages/modules/elasticsearch/package.json +++ b/packages/modules/elasticsearch/package.json @@ -29,7 +29,8 @@ "build": "tsc --project tsconfig.build.json" }, "devDependencies": { - "@elastic/elasticsearch": "^9.5.1" + "@elastic/elasticsearch": "^9.5.1", + "@elastic/elasticsearch-8": "npm:@elastic/elasticsearch@^8.19.2" }, "dependencies": { "testcontainers": "^12.2.1" diff --git a/packages/modules/elasticsearch/src/elasticsearch-container.test.ts b/packages/modules/elasticsearch/src/elasticsearch-container.test.ts index b93e916b1..80217559b 100644 --- a/packages/modules/elasticsearch/src/elasticsearch-container.test.ts +++ b/packages/modules/elasticsearch/src/elasticsearch-container.test.ts @@ -1,10 +1,17 @@ -import { Client } from "@elastic/elasticsearch"; +import { Client as Client9 } from "@elastic/elasticsearch"; +import { Client as Client8 } from "@elastic/elasticsearch-8"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { ElasticsearchContainer } from "./elasticsearch-container"; -const IMAGES = ["elasticsearch:8.19.23", getImage(__dirname)]; +// A client can't talk to an older Elasticsearch major, so each image runs with the client of its own major. +const VERSIONS = [ + { image: "elasticsearch:8.19.23", Client: Client8 as unknown as typeof Client9 }, + { image: getImage(__dirname), Client: Client9 }, +]; + +describe.each(VERSIONS)("ElasticsearchContainer ($image)", { concurrent: false, timeout: 180_000 }, (version) => { + const { image, Client } = version; -describe.each(IMAGES)("ElasticsearchContainer (%s)", { concurrent: false, timeout: 180_000 }, (image) => { it.concurrent("should create an index", async () => { // createIndex { await using container = await new ElasticsearchContainer(image).start(); @@ -20,8 +27,7 @@ describe.each(IMAGES)("ElasticsearchContainer (%s)", { concurrent: false, timeou // } }); - // The 9.x client sends a compatible-with=9 media type with request bodies, which Elasticsearch 8 rejects. - it.skipIf(image.startsWith("elasticsearch:8.")).concurrent("should index a document", async () => { + it.concurrent("should index a document", async () => { // indexDocument { await using container = await new ElasticsearchContainer(image).start(); From 0dac9c388d6d915545ac665bc431743503c714d2 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 18:27:04 +0100 Subject: [PATCH 14/17] Name the Redis Stack test block after its image --- packages/modules/redis/src/redis-container.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/redis/src/redis-container.test.ts b/packages/modules/redis/src/redis-container.test.ts index 5bbdd2264..7f4dc5908 100644 --- a/packages/modules/redis/src/redis-container.test.ts +++ b/packages/modules/redis/src/redis-container.test.ts @@ -162,7 +162,7 @@ describe.each(IMAGES)("RedisContainer (%s)", { concurrent: false, timeout: 240_0 }); }); -describe("RedisContainer", { timeout: 240_000 }, () => { +describe(`RedisContainer (${REDISSTACK_IMAGE})`, { timeout: 240_000 }, () => { it("should start with redis-stack-server and json module", async () => { // startWithRedisStack { await using container = await new RedisContainer(REDISSTACK_IMAGE).withPassword("testPassword").start(); From 3831835567bad18c8f6236f905d99c8e6729275e Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 18:51:47 +0100 Subject: [PATCH 15/17] Test Elasticsearch 8 in its own file with the 8.x client --- .../src/elasticsearch-container-v8.test.ts | 67 +++++++++++++++++++ .../src/elasticsearch-container.test.ts | 29 +++----- 2 files changed, 78 insertions(+), 18 deletions(-) create mode 100644 packages/modules/elasticsearch/src/elasticsearch-container-v8.test.ts diff --git a/packages/modules/elasticsearch/src/elasticsearch-container-v8.test.ts b/packages/modules/elasticsearch/src/elasticsearch-container-v8.test.ts new file mode 100644 index 000000000..a0f79dfa3 --- /dev/null +++ b/packages/modules/elasticsearch/src/elasticsearch-container-v8.test.ts @@ -0,0 +1,67 @@ +import { Client } from "@elastic/elasticsearch-8"; +import { ElasticsearchContainer } from "./elasticsearch-container"; + +const IMAGE = "elasticsearch:8.19.23"; + +describe("ElasticsearchContainer 8", { concurrent: false, timeout: 180_000 }, () => { + it("should create an index", async () => { + await using container = await new ElasticsearchContainer(IMAGE).start(); + + const client = new Client({ + node: container.getHttpUrl(), + auth: { username: container.getUsername(), password: container.getPassword() }, + }); + + await client.indices.create({ index: "people" }); + + expect(await client.indices.exists({ index: "people" })).toBe(true); + }); + + it("should index a document", async () => { + await using container = await new ElasticsearchContainer(IMAGE).start(); + + const client = new Client({ + node: container.getHttpUrl(), + auth: { username: container.getUsername(), password: container.getPassword() }, + }); + + const document = { + id: "1", + name: "John Doe", + }; + await client.index({ + index: "people", + id: document.id, + document, + }); + + expect((await client.get({ index: "people", id: document.id }))._source).toStrictEqual(document); + }); + + it("should set custom password", async () => { + await using container = await new ElasticsearchContainer(IMAGE).withPassword("testPassword").start(); + + const client = new Client({ + node: container.getHttpUrl(), + auth: { username: container.getUsername(), password: container.getPassword() }, + }); + + await client.indices.create({ index: "people" }); + + expect(await client.indices.exists({ index: "people" })).toBe(true); + }); + + it("should work with restarted container", async () => { + await using container = await new ElasticsearchContainer(IMAGE).start(); + await container.restart(); + + const client = new Client({ + node: container.getHttpUrl(), + auth: { username: container.getUsername(), password: container.getPassword() }, + }); + + await client.indices.create({ index: "people" }); + + expect(await client.indices.exists({ index: "people" })).toBe(true); + }); +}); diff --git a/packages/modules/elasticsearch/src/elasticsearch-container.test.ts b/packages/modules/elasticsearch/src/elasticsearch-container.test.ts index 80217559b..4690f7cbc 100644 --- a/packages/modules/elasticsearch/src/elasticsearch-container.test.ts +++ b/packages/modules/elasticsearch/src/elasticsearch-container.test.ts @@ -1,20 +1,13 @@ -import { Client as Client9 } from "@elastic/elasticsearch"; -import { Client as Client8 } from "@elastic/elasticsearch-8"; +import { Client } from "@elastic/elasticsearch"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { ElasticsearchContainer } from "./elasticsearch-container"; -// A client can't talk to an older Elasticsearch major, so each image runs with the client of its own major. -const VERSIONS = [ - { image: "elasticsearch:8.19.23", Client: Client8 as unknown as typeof Client9 }, - { image: getImage(__dirname), Client: Client9 }, -]; +const IMAGE = getImage(__dirname); -describe.each(VERSIONS)("ElasticsearchContainer ($image)", { concurrent: false, timeout: 180_000 }, (version) => { - const { image, Client } = version; - - it.concurrent("should create an index", async () => { +describe("ElasticsearchContainer", { timeout: 180_000 }, () => { + it("should create an index", async () => { // createIndex { - await using container = await new ElasticsearchContainer(image).start(); + await using container = await new ElasticsearchContainer(IMAGE).start(); const client = new Client({ node: container.getHttpUrl(), @@ -27,9 +20,9 @@ describe.each(VERSIONS)("ElasticsearchContainer ($image)", { concurrent: false, // } }); - it.concurrent("should index a document", async () => { + it("should index a document", async () => { // indexDocument { - await using container = await new ElasticsearchContainer(image).start(); + await using container = await new ElasticsearchContainer(IMAGE).start(); const client = new Client({ node: container.getHttpUrl(), @@ -50,9 +43,9 @@ describe.each(VERSIONS)("ElasticsearchContainer ($image)", { concurrent: false, // } }); - it.concurrent("should set custom password", async () => { + it("should set custom password", async () => { // withPassword { - await using container = await new ElasticsearchContainer(image).withPassword("testPassword").start(); + await using container = await new ElasticsearchContainer(IMAGE).withPassword("testPassword").start(); const client = new Client({ node: container.getHttpUrl(), @@ -65,8 +58,8 @@ describe.each(VERSIONS)("ElasticsearchContainer ($image)", { concurrent: false, expect(await client.indices.exists({ index: "people" })).toBe(true); }); - it.concurrent("should work with restarted container", async () => { - await using container = await new ElasticsearchContainer(image).start(); + it("should work with restarted container", async () => { + await using container = await new ElasticsearchContainer(IMAGE).start(); await container.restart(); const client = new Client({ From 37e496a5b26a3cc7d2cdb5fb1ebcef47222455a8 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Wed, 14 Oct 2026 13:26:26 +0100 Subject: [PATCH 16/17] List both Elasticsearch client majors in the docs --- docs/modules/elasticsearch.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/modules/elasticsearch.md b/docs/modules/elasticsearch.md index 4740befd4..d9c65bdbd 100644 --- a/docs/modules/elasticsearch.md +++ b/docs/modules/elasticsearch.md @@ -8,13 +8,15 @@ npm install @testcontainers/elasticsearch --save-dev ## Examples -These examples use the following libraries: +These examples use the following libraries. Use the client major that matches your Elasticsearch major: -- [@elastic/elasticsearch](https://www.npmjs.com/package/@elastic/elasticsearch) +- [@elastic/elasticsearch 9](https://www.npmjs.com/package/@elastic/elasticsearch) for Elasticsearch 9 - npm install @elastic/elasticsearch + npm install @elastic/elasticsearch@9 -Use the client major that matches your Elasticsearch major. A 9.x client can't talk to Elasticsearch 8. +- [@elastic/elasticsearch 8](https://www.npmjs.com/package/@elastic/elasticsearch/v/8.19.2) for Elasticsearch 8 + + npm install @elastic/elasticsearch@8 Choose an image from the [container registry](https://hub.docker.com/_/elasticsearch) and substitute `IMAGE`. From 80eb7132cb6dc6ce1aaeea09c0f449be730575f3 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Wed, 14 Oct 2026 13:32:39 +0100 Subject: [PATCH 17/17] Run the main Elasticsearch suite sequentially too --- .../modules/elasticsearch/src/elasticsearch-container.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/elasticsearch/src/elasticsearch-container.test.ts b/packages/modules/elasticsearch/src/elasticsearch-container.test.ts index 4690f7cbc..e63bd10fe 100644 --- a/packages/modules/elasticsearch/src/elasticsearch-container.test.ts +++ b/packages/modules/elasticsearch/src/elasticsearch-container.test.ts @@ -4,7 +4,7 @@ import { ElasticsearchContainer } from "./elasticsearch-container"; const IMAGE = getImage(__dirname); -describe("ElasticsearchContainer", { timeout: 180_000 }, () => { +describe("ElasticsearchContainer", { concurrent: false, timeout: 180_000 }, () => { it("should create an index", async () => { // createIndex { await using container = await new ElasticsearchContainer(IMAGE).start();