From 0a6ca3449cbf59823038b3dd05a071320dab3867 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Mon, 12 Oct 2026 20:23:41 +0100 Subject: [PATCH 1/6] Test Cassandra, ClickHouse, Couchbase, K3s, KurrentDB and Weaviate against their oldest supported versions --- .../cassandra/src/cassandra-container.test.ts | 38 +++++++++++--- .../src/clickhouse-container.test.ts | 34 ++++++++++--- .../couchbase/src/couchbase-container.test.ts | 13 +++-- .../modules/k3s/src/k3s-container.test.ts | 26 ++++++++-- .../kurrentdb/src/kurrentdb-container.test.ts | 49 +++++++++++++++++-- .../weaviate/src/weaviate-container.test.ts | 9 ++-- 6 files changed, 137 insertions(+), 32 deletions(-) diff --git a/packages/modules/cassandra/src/cassandra-container.test.ts b/packages/modules/cassandra/src/cassandra-container.test.ts index 558717610..797796d90 100644 --- a/packages/modules/cassandra/src/cassandra-container.test.ts +++ b/packages/modules/cassandra/src/cassandra-container.test.ts @@ -4,11 +4,12 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { CassandraContainer } from "./cassandra-container"; const IMAGE = getImage(__dirname); +const OLDEST_IMAGE = "cassandra:4.0.21"; -describe("CassandraContainer", { concurrent: false, timeout: 240_000 }, () => { +describe.each([IMAGE, OLDEST_IMAGE])("CassandraContainer (%s)", { concurrent: false, timeout: 240_000 }, (image) => { it("should connect and execute a query with default credentials", async () => { // connectWithDefaultCredentials { - await using container = await new CassandraContainer(IMAGE).start(); + await using container = await new CassandraContainer(image).start(); const client = new Client({ contactPoints: [container.getContactPoint()], @@ -18,7 +19,7 @@ describe("CassandraContainer", { concurrent: false, timeout: 240_000 }, () => { await client.connect(); const result = await client.execute("SELECT release_version FROM system.local"); - expect(result.rows[0].release_version).toBe(ImageName.fromString(IMAGE).tag); + expect(result.rows[0].release_version).toBe(ImageName.fromString(image).tag); await client.shutdown(); // } @@ -29,7 +30,7 @@ describe("CassandraContainer", { concurrent: false, timeout: 240_000 }, () => { const username = "testUser"; const password = "testPassword"; - await using container = await new CassandraContainer(IMAGE).withUsername(username).withPassword(password).start(); + await using container = await new CassandraContainer(image).withUsername(username).withPassword(password).start(); const client = new Client({ contactPoints: [container.getContactPoint()], @@ -52,7 +53,7 @@ describe("CassandraContainer", { concurrent: false, timeout: 240_000 }, () => { const customDataCenter = "customDC"; const customRack = "customRack"; - await using container = await new CassandraContainer(IMAGE) + await using container = await new CassandraContainer(image) .withDatacenter(customDataCenter) .withRack(customRack) .start(); @@ -73,7 +74,7 @@ describe("CassandraContainer", { concurrent: false, timeout: 240_000 }, () => { // createAndFetchData { it("should create keyspace, a table, insert data, and retrieve it", async () => { - await using container = await new CassandraContainer(IMAGE).start(); + await using container = await new CassandraContainer(image).start(); const client = new Client({ contactPoints: [container.getContactPoint()], @@ -112,7 +113,7 @@ describe("CassandraContainer", { concurrent: false, timeout: 240_000 }, () => { // } it("should work with restarted container", async () => { - await using container = await new CassandraContainer(IMAGE).start(); + await using container = await new CassandraContainer(image).start(); await container.restart(); const client = new Client({ @@ -124,8 +125,29 @@ describe("CassandraContainer", { concurrent: false, timeout: 240_000 }, () => { await client.connect(); const result = await client.execute("SELECT release_version FROM system.local"); - expect(result.rows[0].release_version).toBe(ImageName.fromString(IMAGE).tag); + expect(result.rows[0].release_version).toBe(ImageName.fromString(image).tag); await client.shutdown(); }); }); + +describe("CassandraContainer", { concurrent: false, timeout: 240_000 }, () => { + it.each(["cassandra:4.1.12"])( + "should connect and execute a query with default credentials with %s", + async (image) => { + await using container = await new CassandraContainer(image).start(); + + const client = new Client({ + contactPoints: [container.getContactPoint()], + localDataCenter: container.getDatacenter(), + keyspace: "system", + }); + await client.connect(); + + const result = await client.execute("SELECT release_version FROM system.local"); + expect(result.rows[0].release_version).toBe(ImageName.fromString(image).tag); + + await client.shutdown(); + } + ); +}); diff --git a/packages/modules/clickhouse/src/clickhouse-container.test.ts b/packages/modules/clickhouse/src/clickhouse-container.test.ts index 8ee8f2e82..0905b4ab7 100644 --- a/packages/modules/clickhouse/src/clickhouse-container.test.ts +++ b/packages/modules/clickhouse/src/clickhouse-container.test.ts @@ -3,15 +3,16 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { ClickHouseContainer } from "./clickhouse-container"; const IMAGE = getImage(__dirname); +const OLDEST_IMAGE = "clickhouse/clickhouse-server:26.3.49.3-alpine"; interface ClickHouseQueryResponse { data: T[]; } -describe("ClickHouseContainer", { timeout: 180_000 }, () => { +describe.each([IMAGE, OLDEST_IMAGE])("ClickHouseContainer (%s)", { timeout: 180_000 }, (image) => { it("should connect using the client options object", async () => { // connectWithOptions { - await using container = await new ClickHouseContainer(IMAGE).start(); + await using container = await new ClickHouseContainer(image).start(); const client = createClient(container.getClientOptions()); @@ -29,7 +30,7 @@ describe("ClickHouseContainer", { timeout: 180_000 }, () => { it("should connect using the URL", async () => { // connectWithUrl { - await using container = await new ClickHouseContainer(IMAGE).start(); + await using container = await new ClickHouseContainer(image).start(); const client = createClient({ url: container.getConnectionUrl(), @@ -49,7 +50,7 @@ describe("ClickHouseContainer", { timeout: 180_000 }, () => { it("should connect using the username and password", async () => { // connectWithUsernameAndPassword { - await using container = await new ClickHouseContainer(IMAGE) + await using container = await new ClickHouseContainer(image) .withUsername("customUsername") .withPassword("customPassword") .start(); @@ -75,7 +76,7 @@ describe("ClickHouseContainer", { timeout: 180_000 }, () => { it("should set database", async () => { // setDatabase { const db = "customDatabase"; - await using container = await new ClickHouseContainer(IMAGE).withDatabase(db).start(); + await using container = await new ClickHouseContainer(image).withDatabase(db).start(); // } const client = createClient(container.getClientOptions()); @@ -92,7 +93,7 @@ describe("ClickHouseContainer", { timeout: 180_000 }, () => { }); it("should work with restarted container", async () => { - await using container = await new ClickHouseContainer(IMAGE).start(); + await using container = await new ClickHouseContainer(image).start(); await container.restart(); const client = createClient(container.getClientOptions()); @@ -108,3 +109,24 @@ describe("ClickHouseContainer", { timeout: 180_000 }, () => { await client.close(); }); }); + +describe("ClickHouseContainer", { timeout: 180_000 }, () => { + it.each(["clickhouse/clickhouse-server:26.8.25.14-alpine"])( + "should connect using the client options object with %s", + async (image) => { + await using container = await new ClickHouseContainer(image).start(); + + const client = createClient(container.getClientOptions()); + + const result = await client.query({ + query: "SELECT 1 AS value", + format: "JSON", + }); + const data = (await result.json()) as ClickHouseQueryResponse<{ value: number }>; + + expect(data?.data?.[0]?.value).toBe(1); + + await client.close(); + } + ); +}); diff --git a/packages/modules/couchbase/src/couchbase-container.test.ts b/packages/modules/couchbase/src/couchbase-container.test.ts index 213221af6..a6e54ef8f 100644 --- a/packages/modules/couchbase/src/couchbase-container.test.ts +++ b/packages/modules/couchbase/src/couchbase-container.test.ts @@ -8,6 +8,7 @@ import PORTS from "./ports"; const ENTERPRISE_IMAGE = getImage(__dirname, 0); const COMMUNITY_IMAGE = getImage(__dirname, 1); +const COMMUNITY_IMAGES = [COMMUNITY_IMAGE, "couchbase/server:community-7.6.2"]; describe("CouchbaseContainer", { timeout: 180_000 }, () => { const flushBucketAndCheckExists = async ( @@ -21,7 +22,7 @@ describe("CouchbaseContainer", { timeout: 180_000 }, () => { return coll.exists(key); }; - it.each([ENTERPRISE_IMAGE, COMMUNITY_IMAGE])("should connect and query using %s image", async (image) => { + it.each([ENTERPRISE_IMAGE, ...COMMUNITY_IMAGES])("should connect and query using %s image", async (image) => { // connectAndQuery { const bucketDefinition = new BucketDefinition("mybucket"); await using container = await new CouchbaseContainer(image).withBucket(bucketDefinition).start(); @@ -104,9 +105,9 @@ describe("CouchbaseContainer", { timeout: 180_000 }, () => { expect(container.getConnectionString()).toContain("couchbase://"); }); - it("should create and use a bucket with KV service only", async () => { + it.each(COMMUNITY_IMAGES)("should create and use a bucket with KV service only using %s image", async (image) => { const bucketDefinition = new BucketDefinition("kvbucket").withPrimaryIndex(false); - await using container = await new CouchbaseContainer(COMMUNITY_IMAGE) + await using container = await new CouchbaseContainer(image) .withEnabledServices(CouchbaseService.KV) .withBucket(bucketDefinition) .start(); @@ -128,10 +129,8 @@ describe("CouchbaseContainer", { timeout: 180_000 }, () => { await cluster.close(); }); - it("should connect without a bucket", async () => { - await using container = await new CouchbaseContainer(COMMUNITY_IMAGE) - .withEnabledServices(CouchbaseService.KV) - .start(); + it.each(COMMUNITY_IMAGES)("should connect without a bucket using %s image", async (image) => { + await using container = await new CouchbaseContainer(image).withEnabledServices(CouchbaseService.KV).start(); const cluster = await couchbase.Cluster.connect(container.getConnectionString(), { username: container.getUsername(), diff --git a/packages/modules/k3s/src/k3s-container.test.ts b/packages/modules/k3s/src/k3s-container.test.ts index 501644364..e13d12478 100644 --- a/packages/modules/k3s/src/k3s-container.test.ts +++ b/packages/modules/k3s/src/k3s-container.test.ts @@ -4,14 +4,15 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { K3sContainer } from "./k3s-container"; const IMAGE = getImage(__dirname); +const OLDEST_IMAGE = "rancher/k3s:v1.35.9-k3s1"; const KUBECTL_IMAGE = getImage(__dirname, 1); -describe("K3sContainer", { timeout: 120_000 }, () => { +describe.each([IMAGE, OLDEST_IMAGE])("K3sContainer (%s)", { timeout: 120_000 }, (image) => { // K3sContainer runs as a privileged container if (!process.env.CI_ROOTLESS) { it("should start and have listable node", async () => { // k3sListNodes { - await using container = await new K3sContainer(IMAGE).start(); + await using container = await new K3sContainer(image).start(); const kubeConfig = new k8s.KubeConfig(); kubeConfig.loadFromString(container.getKubeConfig()); @@ -25,7 +26,7 @@ describe("K3sContainer", { timeout: 120_000 }, () => { it("should start a pod", async () => { // k3sStartPod { - await using container = await new K3sContainer(IMAGE).start(); + await using container = await new K3sContainer(image).start(); const kubeConfig = new k8s.KubeConfig(); kubeConfig.loadFromString(container.getKubeConfig()); @@ -71,7 +72,7 @@ describe("K3sContainer", { timeout: 120_000 }, () => { it("should expose kubeconfig for a network alias", async () => { // k3sAliasedKubeConfig { await using network = await new Network().start(); - await using container = await new K3sContainer(IMAGE).withNetwork(network).withNetworkAliases("k3s").start(); + await using container = await new K3sContainer(image).withNetwork(network).withNetworkAliases("k3s").start(); const kubeConfig = container.getAliasedKubeConfig("k3s"); @@ -88,3 +89,20 @@ describe("K3sContainer", { timeout: 120_000 }, () => { }); } }); + +describe("K3sContainer", { timeout: 120_000 }, () => { + // K3sContainer runs as a privileged container + if (!process.env.CI_ROOTLESS) { + it.each(["rancher/k3s:v1.36.5-k3s1"])("should start and have listable node with %s", async (image) => { + await using container = await new K3sContainer(image).start(); + + const kubeConfig = new k8s.KubeConfig(); + kubeConfig.loadFromString(container.getKubeConfig()); + + const client = kubeConfig.makeApiClient(k8s.CoreV1Api); + const nodeList = await client.listNode(); + + expect(nodeList.items).toHaveLength(1); + }); + } +}); diff --git a/packages/modules/kurrentdb/src/kurrentdb-container.test.ts b/packages/modules/kurrentdb/src/kurrentdb-container.test.ts index 695d12b7f..94722317a 100644 --- a/packages/modules/kurrentdb/src/kurrentdb-container.test.ts +++ b/packages/modules/kurrentdb/src/kurrentdb-container.test.ts @@ -3,11 +3,12 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { KurrentDbContainer } from "./kurrentdb-container"; const IMAGE = getImage(__dirname); +const OLDEST_IMAGE = "kurrentplatform/kurrentdb:25.0.1"; -describe("KurrentDbContainer", { concurrent: false, timeout: 240_000 }, () => { +describe.each([IMAGE, OLDEST_IMAGE])("KurrentDbContainer (%s)", { concurrent: false, timeout: 240_000 }, (image) => { it("should execute write and read", async () => { // startContainer { - await using container = await new KurrentDbContainer(IMAGE).start(); + await using container = await new KurrentDbContainer(image).start(); const client = KurrentDBClient.connectionString(container.getConnectionString()); @@ -49,7 +50,7 @@ describe("KurrentDbContainer", { concurrent: false, timeout: 240_000 }, () => { it("should use built-in projections", async () => { // usingStandardProjections { - await using container = await new KurrentDbContainer(IMAGE).start(); + await using container = await new KurrentDbContainer(image).start(); const client = KurrentDBClient.connectionString(container.getConnectionString()); await client.appendToStream("Todo-1", [ @@ -97,3 +98,45 @@ describe("KurrentDbContainer", { concurrent: false, timeout: 240_000 }, () => { // } }); }); + +describe("KurrentDbContainer", { concurrent: false, timeout: 240_000 }, () => { + it.each(["kurrentplatform/kurrentdb:26.0.3"])("should execute write and read with %s", async (image) => { + await using container = await new KurrentDbContainer(image).start(); + + const client = KurrentDBClient.connectionString(container.getConnectionString()); + + await client.appendToStream("User-1", [ + { + contentType: "application/json", + data: { email: "john@foo.local" }, + type: "UserCreated", + id: "28ab6bca-d9ae-418b-a1af-eb65dd653c38", + metadata: { someMetadata: "bar" }, + }, + ]); + + expect(await consumeSteamingRead(client.readStream("User-1"))).toEqual([ + expect.objectContaining({ + event: expect.objectContaining({ + data: { email: "john@foo.local" }, + id: "28ab6bca-d9ae-418b-a1af-eb65dd653c38", + isJson: true, + metadata: { someMetadata: "bar" }, + revision: 0n, + streamId: "User-1", + type: "UserCreated", + }), + }), + ]); + + await client.dispose(); + + async function consumeSteamingRead(read: AsyncIterableIterator): Promise { + const events = []; + for await (const event of read) { + events.push(event); + } + return events; + } + }); +}); diff --git a/packages/modules/weaviate/src/weaviate-container.test.ts b/packages/modules/weaviate/src/weaviate-container.test.ts index dee64e9e7..dff547d1c 100644 --- a/packages/modules/weaviate/src/weaviate-container.test.ts +++ b/packages/modules/weaviate/src/weaviate-container.test.ts @@ -3,11 +3,12 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { WeaviateContainer } from "./weaviate-container"; const IMAGE = getImage(__dirname); +const OLDEST_IMAGE = "semitechnologies/weaviate:1.38.21"; -describe("WeaviateContainer", { timeout: 100_000 }, () => { +describe.each([IMAGE, OLDEST_IMAGE])("WeaviateContainer (%s)", { timeout: 100_000 }, (image) => { it("should expose ports", async () => { // connectWeaviate { - await using container = await new WeaviateContainer(IMAGE).start(); + await using container = await new WeaviateContainer(image).start(); expect(container.getHttpHostAddress()).toBeDefined(); expect(container.getGrpcHostAddress()).toBeDefined(); @@ -16,7 +17,7 @@ describe("WeaviateContainer", { timeout: 100_000 }, () => { it("should connect to Weaviate", async () => { // connectWeaviateWithClient { - await using container = await new WeaviateContainer(IMAGE).start(); + await using container = await new WeaviateContainer(image).start(); const client = weaviate.client({ scheme: "http", @@ -42,7 +43,7 @@ describe("WeaviateContainer", { timeout: 100_000 }, () => { BACKUP_FILESYSTEM_PATH: "/tmp/backups", }; - await using container = await new WeaviateContainer(IMAGE).withEnvironment(environment).start(); + await using container = await new WeaviateContainer(image).withEnvironment(environment).start(); const client = weaviate.client({ scheme: "http", From a23bb5237fe24419a6c5b07da18db48cebca4d26 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 10:45:00 +0100 Subject: [PATCH 2/6] Loop the suites over a list of every supported image --- .../cassandra/src/cassandra-container.test.ts | 26 +--------- .../src/clickhouse-container.test.ts | 30 +++--------- .../couchbase/src/couchbase-container.test.ts | 4 +- .../modules/k3s/src/k3s-container.test.ts | 22 +-------- .../kurrentdb/src/kurrentdb-container.test.ts | 47 +------------------ .../weaviate/src/weaviate-container.test.ts | 5 +- 6 files changed, 16 insertions(+), 118 deletions(-) diff --git a/packages/modules/cassandra/src/cassandra-container.test.ts b/packages/modules/cassandra/src/cassandra-container.test.ts index 797796d90..6a5f79e63 100644 --- a/packages/modules/cassandra/src/cassandra-container.test.ts +++ b/packages/modules/cassandra/src/cassandra-container.test.ts @@ -3,10 +3,9 @@ import { ImageName } from "testcontainers"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { CassandraContainer } from "./cassandra-container"; -const IMAGE = getImage(__dirname); -const OLDEST_IMAGE = "cassandra:4.0.21"; +const IMAGES = ["cassandra:4.0.21", "cassandra:4.1.12", getImage(__dirname)]; -describe.each([IMAGE, OLDEST_IMAGE])("CassandraContainer (%s)", { concurrent: false, timeout: 240_000 }, (image) => { +describe.each(IMAGES)("CassandraContainer (%s)", { concurrent: false, timeout: 240_000 }, (image) => { it("should connect and execute a query with default credentials", async () => { // connectWithDefaultCredentials { await using container = await new CassandraContainer(image).start(); @@ -130,24 +129,3 @@ describe.each([IMAGE, OLDEST_IMAGE])("CassandraContainer (%s)", { concurrent: fa await client.shutdown(); }); }); - -describe("CassandraContainer", { concurrent: false, timeout: 240_000 }, () => { - it.each(["cassandra:4.1.12"])( - "should connect and execute a query with default credentials with %s", - async (image) => { - await using container = await new CassandraContainer(image).start(); - - const client = new Client({ - contactPoints: [container.getContactPoint()], - localDataCenter: container.getDatacenter(), - keyspace: "system", - }); - await client.connect(); - - const result = await client.execute("SELECT release_version FROM system.local"); - expect(result.rows[0].release_version).toBe(ImageName.fromString(image).tag); - - await client.shutdown(); - } - ); -}); diff --git a/packages/modules/clickhouse/src/clickhouse-container.test.ts b/packages/modules/clickhouse/src/clickhouse-container.test.ts index 0905b4ab7..5d9b0cc40 100644 --- a/packages/modules/clickhouse/src/clickhouse-container.test.ts +++ b/packages/modules/clickhouse/src/clickhouse-container.test.ts @@ -2,14 +2,17 @@ import { createClient } from "@clickhouse/client"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { ClickHouseContainer } from "./clickhouse-container"; -const IMAGE = getImage(__dirname); -const OLDEST_IMAGE = "clickhouse/clickhouse-server:26.3.49.3-alpine"; +const IMAGES = [ + "clickhouse/clickhouse-server:26.3.49.3-alpine", + "clickhouse/clickhouse-server:26.8.25.14-alpine", + getImage(__dirname), +]; interface ClickHouseQueryResponse { data: T[]; } -describe.each([IMAGE, OLDEST_IMAGE])("ClickHouseContainer (%s)", { timeout: 180_000 }, (image) => { +describe.each(IMAGES)("ClickHouseContainer (%s)", { timeout: 180_000 }, (image) => { it("should connect using the client options object", async () => { // connectWithOptions { await using container = await new ClickHouseContainer(image).start(); @@ -109,24 +112,3 @@ describe.each([IMAGE, OLDEST_IMAGE])("ClickHouseContainer (%s)", { timeout: 180_ await client.close(); }); }); - -describe("ClickHouseContainer", { timeout: 180_000 }, () => { - it.each(["clickhouse/clickhouse-server:26.8.25.14-alpine"])( - "should connect using the client options object with %s", - async (image) => { - await using container = await new ClickHouseContainer(image).start(); - - const client = createClient(container.getClientOptions()); - - const result = await client.query({ - query: "SELECT 1 AS value", - format: "JSON", - }); - const data = (await result.json()) as ClickHouseQueryResponse<{ value: number }>; - - expect(data?.data?.[0]?.value).toBe(1); - - await client.close(); - } - ); -}); diff --git a/packages/modules/couchbase/src/couchbase-container.test.ts b/packages/modules/couchbase/src/couchbase-container.test.ts index a6e54ef8f..2dc2ebdaa 100644 --- a/packages/modules/couchbase/src/couchbase-container.test.ts +++ b/packages/modules/couchbase/src/couchbase-container.test.ts @@ -8,7 +8,7 @@ import PORTS from "./ports"; const ENTERPRISE_IMAGE = getImage(__dirname, 0); const COMMUNITY_IMAGE = getImage(__dirname, 1); -const COMMUNITY_IMAGES = [COMMUNITY_IMAGE, "couchbase/server:community-7.6.2"]; +const COMMUNITY_IMAGES = ["couchbase/server:community-7.6.2", COMMUNITY_IMAGE]; describe("CouchbaseContainer", { timeout: 180_000 }, () => { const flushBucketAndCheckExists = async ( @@ -22,7 +22,7 @@ describe("CouchbaseContainer", { timeout: 180_000 }, () => { return coll.exists(key); }; - it.each([ENTERPRISE_IMAGE, ...COMMUNITY_IMAGES])("should connect and query using %s image", async (image) => { + it.each([...COMMUNITY_IMAGES, ENTERPRISE_IMAGE])("should connect and query using %s image", async (image) => { // connectAndQuery { const bucketDefinition = new BucketDefinition("mybucket"); await using container = await new CouchbaseContainer(image).withBucket(bucketDefinition).start(); diff --git a/packages/modules/k3s/src/k3s-container.test.ts b/packages/modules/k3s/src/k3s-container.test.ts index e13d12478..bf4cd8fe2 100644 --- a/packages/modules/k3s/src/k3s-container.test.ts +++ b/packages/modules/k3s/src/k3s-container.test.ts @@ -3,11 +3,10 @@ import { GenericContainer, Network, Wait } from "testcontainers"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { K3sContainer } from "./k3s-container"; -const IMAGE = getImage(__dirname); -const OLDEST_IMAGE = "rancher/k3s:v1.35.9-k3s1"; +const IMAGES = ["rancher/k3s:v1.35.9-k3s1", "rancher/k3s:v1.36.5-k3s1", getImage(__dirname)]; const KUBECTL_IMAGE = getImage(__dirname, 1); -describe.each([IMAGE, OLDEST_IMAGE])("K3sContainer (%s)", { timeout: 120_000 }, (image) => { +describe.each(IMAGES)("K3sContainer (%s)", { timeout: 120_000 }, (image) => { // K3sContainer runs as a privileged container if (!process.env.CI_ROOTLESS) { it("should start and have listable node", async () => { @@ -89,20 +88,3 @@ describe.each([IMAGE, OLDEST_IMAGE])("K3sContainer (%s)", { timeout: 120_000 }, }); } }); - -describe("K3sContainer", { timeout: 120_000 }, () => { - // K3sContainer runs as a privileged container - if (!process.env.CI_ROOTLESS) { - it.each(["rancher/k3s:v1.36.5-k3s1"])("should start and have listable node with %s", async (image) => { - await using container = await new K3sContainer(image).start(); - - const kubeConfig = new k8s.KubeConfig(); - kubeConfig.loadFromString(container.getKubeConfig()); - - const client = kubeConfig.makeApiClient(k8s.CoreV1Api); - const nodeList = await client.listNode(); - - expect(nodeList.items).toHaveLength(1); - }); - } -}); diff --git a/packages/modules/kurrentdb/src/kurrentdb-container.test.ts b/packages/modules/kurrentdb/src/kurrentdb-container.test.ts index 94722317a..1dd8480ab 100644 --- a/packages/modules/kurrentdb/src/kurrentdb-container.test.ts +++ b/packages/modules/kurrentdb/src/kurrentdb-container.test.ts @@ -2,10 +2,9 @@ import { KurrentDBClient, type StreamSubscription } from "@kurrent/kurrentdb-cli import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { KurrentDbContainer } from "./kurrentdb-container"; -const IMAGE = getImage(__dirname); -const OLDEST_IMAGE = "kurrentplatform/kurrentdb:25.0.1"; +const IMAGES = ["kurrentplatform/kurrentdb:25.0.1", "kurrentplatform/kurrentdb:26.0.3", getImage(__dirname)]; -describe.each([IMAGE, OLDEST_IMAGE])("KurrentDbContainer (%s)", { concurrent: false, timeout: 240_000 }, (image) => { +describe.each(IMAGES)("KurrentDbContainer (%s)", { concurrent: false, timeout: 240_000 }, (image) => { it("should execute write and read", async () => { // startContainer { await using container = await new KurrentDbContainer(image).start(); @@ -98,45 +97,3 @@ describe.each([IMAGE, OLDEST_IMAGE])("KurrentDbContainer (%s)", { concurrent: fa // } }); }); - -describe("KurrentDbContainer", { concurrent: false, timeout: 240_000 }, () => { - it.each(["kurrentplatform/kurrentdb:26.0.3"])("should execute write and read with %s", async (image) => { - await using container = await new KurrentDbContainer(image).start(); - - const client = KurrentDBClient.connectionString(container.getConnectionString()); - - await client.appendToStream("User-1", [ - { - contentType: "application/json", - data: { email: "john@foo.local" }, - type: "UserCreated", - id: "28ab6bca-d9ae-418b-a1af-eb65dd653c38", - metadata: { someMetadata: "bar" }, - }, - ]); - - expect(await consumeSteamingRead(client.readStream("User-1"))).toEqual([ - expect.objectContaining({ - event: expect.objectContaining({ - data: { email: "john@foo.local" }, - id: "28ab6bca-d9ae-418b-a1af-eb65dd653c38", - isJson: true, - metadata: { someMetadata: "bar" }, - revision: 0n, - streamId: "User-1", - type: "UserCreated", - }), - }), - ]); - - await client.dispose(); - - async function consumeSteamingRead(read: AsyncIterableIterator): Promise { - const events = []; - for await (const event of read) { - events.push(event); - } - return events; - } - }); -}); diff --git a/packages/modules/weaviate/src/weaviate-container.test.ts b/packages/modules/weaviate/src/weaviate-container.test.ts index dff547d1c..9b66cc439 100644 --- a/packages/modules/weaviate/src/weaviate-container.test.ts +++ b/packages/modules/weaviate/src/weaviate-container.test.ts @@ -2,10 +2,9 @@ import weaviate from "weaviate-ts-client"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { WeaviateContainer } from "./weaviate-container"; -const IMAGE = getImage(__dirname); -const OLDEST_IMAGE = "semitechnologies/weaviate:1.38.21"; +const IMAGES = ["semitechnologies/weaviate:1.38.21", getImage(__dirname)]; -describe.each([IMAGE, OLDEST_IMAGE])("WeaviateContainer (%s)", { timeout: 100_000 }, (image) => { +describe.each(IMAGES)("WeaviateContainer (%s)", { timeout: 100_000 }, (image) => { it("should expose ports", async () => { // connectWeaviate { await using container = await new WeaviateContainer(image).start(); From 05320b48f79f923fc80e50c82fdc219a6c34e528 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 11:12:19 +0100 Subject: [PATCH 3/6] List one image per supported major version --- .../cassandra/src/cassandra-container.test.ts | 2 +- .../src/clickhouse-container.test.ts | 18 +++++++----------- packages/modules/k3s/src/k3s-container.test.ts | 10 +++++----- .../kurrentdb/src/kurrentdb-container.test.ts | 2 +- .../weaviate/src/weaviate-container.test.ts | 10 +++++----- 5 files changed, 19 insertions(+), 23 deletions(-) diff --git a/packages/modules/cassandra/src/cassandra-container.test.ts b/packages/modules/cassandra/src/cassandra-container.test.ts index 6a5f79e63..1ba6d7c51 100644 --- a/packages/modules/cassandra/src/cassandra-container.test.ts +++ b/packages/modules/cassandra/src/cassandra-container.test.ts @@ -3,7 +3,7 @@ import { ImageName } from "testcontainers"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { CassandraContainer } from "./cassandra-container"; -const IMAGES = ["cassandra:4.0.21", "cassandra:4.1.12", getImage(__dirname)]; +const IMAGES = ["cassandra:4.1.12", getImage(__dirname)]; describe.each(IMAGES)("CassandraContainer (%s)", { concurrent: false, timeout: 240_000 }, (image) => { it("should connect and execute a query with default credentials", async () => { diff --git a/packages/modules/clickhouse/src/clickhouse-container.test.ts b/packages/modules/clickhouse/src/clickhouse-container.test.ts index 5d9b0cc40..8ee8f2e82 100644 --- a/packages/modules/clickhouse/src/clickhouse-container.test.ts +++ b/packages/modules/clickhouse/src/clickhouse-container.test.ts @@ -2,20 +2,16 @@ import { createClient } from "@clickhouse/client"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { ClickHouseContainer } from "./clickhouse-container"; -const IMAGES = [ - "clickhouse/clickhouse-server:26.3.49.3-alpine", - "clickhouse/clickhouse-server:26.8.25.14-alpine", - getImage(__dirname), -]; +const IMAGE = getImage(__dirname); interface ClickHouseQueryResponse { data: T[]; } -describe.each(IMAGES)("ClickHouseContainer (%s)", { timeout: 180_000 }, (image) => { +describe("ClickHouseContainer", { timeout: 180_000 }, () => { it("should connect using the client options object", async () => { // connectWithOptions { - await using container = await new ClickHouseContainer(image).start(); + await using container = await new ClickHouseContainer(IMAGE).start(); const client = createClient(container.getClientOptions()); @@ -33,7 +29,7 @@ describe.each(IMAGES)("ClickHouseContainer (%s)", { timeout: 180_000 }, (image) it("should connect using the URL", async () => { // connectWithUrl { - await using container = await new ClickHouseContainer(image).start(); + await using container = await new ClickHouseContainer(IMAGE).start(); const client = createClient({ url: container.getConnectionUrl(), @@ -53,7 +49,7 @@ describe.each(IMAGES)("ClickHouseContainer (%s)", { timeout: 180_000 }, (image) it("should connect using the username and password", async () => { // connectWithUsernameAndPassword { - await using container = await new ClickHouseContainer(image) + await using container = await new ClickHouseContainer(IMAGE) .withUsername("customUsername") .withPassword("customPassword") .start(); @@ -79,7 +75,7 @@ describe.each(IMAGES)("ClickHouseContainer (%s)", { timeout: 180_000 }, (image) it("should set database", async () => { // setDatabase { const db = "customDatabase"; - await using container = await new ClickHouseContainer(image).withDatabase(db).start(); + await using container = await new ClickHouseContainer(IMAGE).withDatabase(db).start(); // } const client = createClient(container.getClientOptions()); @@ -96,7 +92,7 @@ describe.each(IMAGES)("ClickHouseContainer (%s)", { timeout: 180_000 }, (image) }); it("should work with restarted container", async () => { - await using container = await new ClickHouseContainer(image).start(); + await using container = await new ClickHouseContainer(IMAGE).start(); await container.restart(); const client = createClient(container.getClientOptions()); diff --git a/packages/modules/k3s/src/k3s-container.test.ts b/packages/modules/k3s/src/k3s-container.test.ts index bf4cd8fe2..501644364 100644 --- a/packages/modules/k3s/src/k3s-container.test.ts +++ b/packages/modules/k3s/src/k3s-container.test.ts @@ -3,15 +3,15 @@ import { GenericContainer, Network, Wait } from "testcontainers"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { K3sContainer } from "./k3s-container"; -const IMAGES = ["rancher/k3s:v1.35.9-k3s1", "rancher/k3s:v1.36.5-k3s1", getImage(__dirname)]; +const IMAGE = getImage(__dirname); const KUBECTL_IMAGE = getImage(__dirname, 1); -describe.each(IMAGES)("K3sContainer (%s)", { timeout: 120_000 }, (image) => { +describe("K3sContainer", { timeout: 120_000 }, () => { // K3sContainer runs as a privileged container if (!process.env.CI_ROOTLESS) { it("should start and have listable node", async () => { // k3sListNodes { - await using container = await new K3sContainer(image).start(); + await using container = await new K3sContainer(IMAGE).start(); const kubeConfig = new k8s.KubeConfig(); kubeConfig.loadFromString(container.getKubeConfig()); @@ -25,7 +25,7 @@ describe.each(IMAGES)("K3sContainer (%s)", { timeout: 120_000 }, (image) => { it("should start a pod", async () => { // k3sStartPod { - await using container = await new K3sContainer(image).start(); + await using container = await new K3sContainer(IMAGE).start(); const kubeConfig = new k8s.KubeConfig(); kubeConfig.loadFromString(container.getKubeConfig()); @@ -71,7 +71,7 @@ describe.each(IMAGES)("K3sContainer (%s)", { timeout: 120_000 }, (image) => { it("should expose kubeconfig for a network alias", async () => { // k3sAliasedKubeConfig { await using network = await new Network().start(); - await using container = await new K3sContainer(image).withNetwork(network).withNetworkAliases("k3s").start(); + await using container = await new K3sContainer(IMAGE).withNetwork(network).withNetworkAliases("k3s").start(); const kubeConfig = container.getAliasedKubeConfig("k3s"); diff --git a/packages/modules/kurrentdb/src/kurrentdb-container.test.ts b/packages/modules/kurrentdb/src/kurrentdb-container.test.ts index 1dd8480ab..3c6ca4edb 100644 --- a/packages/modules/kurrentdb/src/kurrentdb-container.test.ts +++ b/packages/modules/kurrentdb/src/kurrentdb-container.test.ts @@ -2,7 +2,7 @@ import { KurrentDBClient, type StreamSubscription } from "@kurrent/kurrentdb-cli import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { KurrentDbContainer } from "./kurrentdb-container"; -const IMAGES = ["kurrentplatform/kurrentdb:25.0.1", "kurrentplatform/kurrentdb:26.0.3", getImage(__dirname)]; +const IMAGES = ["kurrentplatform/kurrentdb:25.0.1", getImage(__dirname)]; describe.each(IMAGES)("KurrentDbContainer (%s)", { concurrent: false, timeout: 240_000 }, (image) => { it("should execute write and read", async () => { diff --git a/packages/modules/weaviate/src/weaviate-container.test.ts b/packages/modules/weaviate/src/weaviate-container.test.ts index 9b66cc439..dee64e9e7 100644 --- a/packages/modules/weaviate/src/weaviate-container.test.ts +++ b/packages/modules/weaviate/src/weaviate-container.test.ts @@ -2,12 +2,12 @@ import weaviate from "weaviate-ts-client"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { WeaviateContainer } from "./weaviate-container"; -const IMAGES = ["semitechnologies/weaviate:1.38.21", getImage(__dirname)]; +const IMAGE = getImage(__dirname); -describe.each(IMAGES)("WeaviateContainer (%s)", { timeout: 100_000 }, (image) => { +describe("WeaviateContainer", { timeout: 100_000 }, () => { it("should expose ports", async () => { // connectWeaviate { - await using container = await new WeaviateContainer(image).start(); + await using container = await new WeaviateContainer(IMAGE).start(); expect(container.getHttpHostAddress()).toBeDefined(); expect(container.getGrpcHostAddress()).toBeDefined(); @@ -16,7 +16,7 @@ describe.each(IMAGES)("WeaviateContainer (%s)", { timeout: 100_000 }, (image) => it("should connect to Weaviate", async () => { // connectWeaviateWithClient { - await using container = await new WeaviateContainer(image).start(); + await using container = await new WeaviateContainer(IMAGE).start(); const client = weaviate.client({ scheme: "http", @@ -42,7 +42,7 @@ describe.each(IMAGES)("WeaviateContainer (%s)", { timeout: 100_000 }, (image) => BACKUP_FILESYSTEM_PATH: "/tmp/backups", }; - await using container = await new WeaviateContainer(image).withEnvironment(environment).start(); + await using container = await new WeaviateContainer(IMAGE).withEnvironment(environment).start(); const client = weaviate.client({ scheme: "http", From 4503a827c18f82a8d3c10e511aecf0a49897f71c Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 13:16:00 +0100 Subject: [PATCH 4/6] Test Couchbase enterprise 7.6 and drop the discontinued seleniarm images --- docs/modules/selenium.md | 6 ++---- packages/modules/couchbase/src/couchbase-container.test.ts | 7 ++++--- packages/modules/selenium/Dockerfile | 2 -- packages/modules/selenium/src/selenium-container.test.ts | 4 ++-- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/modules/selenium.md b/docs/modules/selenium.md index cb04c1ee7..7138ed777 100644 --- a/docs/modules/selenium.md +++ b/docs/modules/selenium.md @@ -17,10 +17,8 @@ These examples use the following libraries: Choose an image from the container registry and substitute `IMAGE`: -- [AMD Standalone Chrome](https://hub.docker.com/r/selenium/standalone-chrome) -- [AMD Standalone Firefox](https://hub.docker.com/r/selenium/standalone-firefox) -- [ARM Standalone Chromium](https://hub.docker.com/r/seleniarm/standalone-chromium) -- [ARM Standalone Firefox](https://hub.docker.com/r/seleniarm/standalone-firefox) +- [Standalone Chrome](https://hub.docker.com/r/selenium/standalone-chrome) +- [Standalone Firefox](https://hub.docker.com/r/selenium/standalone-firefox) ### Navigate to a page diff --git a/packages/modules/couchbase/src/couchbase-container.test.ts b/packages/modules/couchbase/src/couchbase-container.test.ts index 2dc2ebdaa..39d187aac 100644 --- a/packages/modules/couchbase/src/couchbase-container.test.ts +++ b/packages/modules/couchbase/src/couchbase-container.test.ts @@ -8,6 +8,7 @@ import PORTS from "./ports"; const ENTERPRISE_IMAGE = getImage(__dirname, 0); const COMMUNITY_IMAGE = getImage(__dirname, 1); +const ENTERPRISE_IMAGES = ["couchbase/server:enterprise-7.6.12", ENTERPRISE_IMAGE]; const COMMUNITY_IMAGES = ["couchbase/server:community-7.6.2", COMMUNITY_IMAGE]; describe("CouchbaseContainer", { timeout: 180_000 }, () => { @@ -22,7 +23,7 @@ describe("CouchbaseContainer", { timeout: 180_000 }, () => { return coll.exists(key); }; - it.each([...COMMUNITY_IMAGES, ENTERPRISE_IMAGE])("should connect and query using %s image", async (image) => { + it.each([...COMMUNITY_IMAGES, ...ENTERPRISE_IMAGES])("should connect and query using %s image", async (image) => { // connectAndQuery { const bucketDefinition = new BucketDefinition("mybucket"); await using container = await new CouchbaseContainer(image).withBucket(bucketDefinition).start(); @@ -97,8 +98,8 @@ describe("CouchbaseContainer", { timeout: 180_000 }, () => { ); }); - it("should start with analytics service enabled", async () => { - await using container = await new CouchbaseContainer(ENTERPRISE_IMAGE) + it.each(ENTERPRISE_IMAGES)("should start with analytics service enabled using %s image", async (image) => { + await using container = await new CouchbaseContainer(image) .withEnabledServices(CouchbaseService.KV, CouchbaseService.ANALYTICS) .start(); diff --git a/packages/modules/selenium/Dockerfile b/packages/modules/selenium/Dockerfile index 489bd3c74..ba7169415 100644 --- a/packages/modules/selenium/Dockerfile +++ b/packages/modules/selenium/Dockerfile @@ -1,4 +1,2 @@ FROM selenium/standalone-chrome:153.0 -FROM seleniarm/standalone-chromium:124.0 FROM selenium/standalone-firefox:156.0 -FROM seleniarm/standalone-firefox:125.0 \ No newline at end of file diff --git a/packages/modules/selenium/src/selenium-container.test.ts b/packages/modules/selenium/src/selenium-container.test.ts index 10ff0b600..a14c35bbd 100644 --- a/packages/modules/selenium/src/selenium-container.test.ts +++ b/packages/modules/selenium/src/selenium-container.test.ts @@ -6,8 +6,8 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { SELENIUM_VIDEO_IMAGE, SeleniumContainer } from "./selenium-container"; const browsers = [ - ["CHROME", process.arch === "arm64" ? getImage(__dirname, 1) : getImage(__dirname, 0)], - ["FIREFOX", process.arch === "arm64" ? getImage(__dirname, 3) : getImage(__dirname, 2)], + ["CHROME", getImage(__dirname, 0)], + ["FIREFOX", getImage(__dirname, 1)], ] as const; describe.for(browsers)("SeleniumContainer", { timeout: 240_000 }, ([browser, image]) => { From 1907d3ec1b7e6e351b3ccdcb19aa0567742f63a4 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 17:08:04 +0100 Subject: [PATCH 5/6] Loop the Couchbase suite over one list of community and enterprise images --- .../couchbase/src/couchbase-container.test.ts | 115 +++++++++--------- 1 file changed, 60 insertions(+), 55 deletions(-) diff --git a/packages/modules/couchbase/src/couchbase-container.test.ts b/packages/modules/couchbase/src/couchbase-container.test.ts index 39d187aac..344df5e20 100644 --- a/packages/modules/couchbase/src/couchbase-container.test.ts +++ b/packages/modules/couchbase/src/couchbase-container.test.ts @@ -6,12 +6,16 @@ import { CouchbaseContainer } from "./couchbase-container"; import { CouchbaseService } from "./couchbase-service"; import PORTS from "./ports"; -const ENTERPRISE_IMAGE = getImage(__dirname, 0); -const COMMUNITY_IMAGE = getImage(__dirname, 1); -const ENTERPRISE_IMAGES = ["couchbase/server:enterprise-7.6.12", ENTERPRISE_IMAGE]; -const COMMUNITY_IMAGES = ["couchbase/server:community-7.6.2", COMMUNITY_IMAGE]; +const IMAGES = [ + "couchbase/server:community-7.6.2", + "couchbase/server:enterprise-7.6.12", + getImage(__dirname, 1), + getImage(__dirname, 0), +]; + +describe.each(IMAGES)("CouchbaseContainer (%s)", { concurrent: false, timeout: 180_000 }, (image) => { + const enterprise = image.includes(":enterprise-"); -describe("CouchbaseContainer", { timeout: 180_000 }, () => { const flushBucketAndCheckExists = async ( cluster: Cluster, bucket: Bucket, @@ -23,7 +27,7 @@ describe("CouchbaseContainer", { timeout: 180_000 }, () => { return coll.exists(key); }; - it.each([...COMMUNITY_IMAGES, ...ENTERPRISE_IMAGES])("should connect and query using %s image", async (image) => { + it.concurrent("should connect and query", async () => { // connectAndQuery { const bucketDefinition = new BucketDefinition("mybucket"); await using container = await new CouchbaseContainer(image).withBucket(bucketDefinition).start(); @@ -48,57 +52,60 @@ describe("CouchbaseContainer", { timeout: 180_000 }, () => { // } }); - it.each([ENTERPRISE_IMAGE, COMMUNITY_IMAGE])( - "should flush bucket if flushEnabled and check any document exists with %s image", - async (image) => { - const bucketDefinition = new BucketDefinition("mybucket").withFlushEnabled(true); - const container = new CouchbaseContainer(image).withBucket(bucketDefinition); - - await using startedTestContainer = await container.start(); - const cluster = await couchbase.Cluster.connect(startedTestContainer.getConnectionString(), { - username: startedTestContainer.getUsername(), - password: startedTestContainer.getPassword(), - }); - - const bucket = cluster.bucket(bucketDefinition.getName()); - await bucket.waitUntilReady(10_000, { - serviceTypes: [couchbase.ServiceType.KeyValue], - }); + it.concurrent("should flush bucket if flushEnabled and check any document exists", async () => { + const bucketDefinition = new BucketDefinition("mybucket").withFlushEnabled(true); + const container = new CouchbaseContainer(image).withBucket(bucketDefinition); - const coll = bucket.defaultCollection(); + await using startedTestContainer = await container.start(); + const cluster = await couchbase.Cluster.connect(startedTestContainer.getConnectionString(), { + username: startedTestContainer.getUsername(), + password: startedTestContainer.getPassword(), + }); - await coll.upsert("testdoc", { foo: "bar" }); + const bucket = cluster.bucket(bucketDefinition.getName()); + await bucket.waitUntilReady(10_000, { + serviceTypes: [couchbase.ServiceType.KeyValue], + }); - const existResult = await flushBucketAndCheckExists(cluster, bucket, "testdoc"); + const coll = bucket.defaultCollection(); - expect(existResult.exists).toBe(false); - await cluster.close(); - } - ); + await coll.upsert("testdoc", { foo: "bar" }); - it("should throw error if analytics service enabled with community version", async () => { - const container = new CouchbaseContainer(COMMUNITY_IMAGE).withEnabledServices( - CouchbaseService.KV, - CouchbaseService.ANALYTICS - ); + const existResult = await flushBucketAndCheckExists(cluster, bucket, "testdoc"); - await expect(() => container.start()).rejects.toThrowError( - "The Analytics Service is only supported with the Enterprise version" - ); + expect(existResult.exists).toBe(false); + await cluster.close(); }); - it("should throw error if eventing service enabled with community version", async () => { - const container = new CouchbaseContainer(COMMUNITY_IMAGE).withEnabledServices( - CouchbaseService.KV, - CouchbaseService.EVENTING - ); + it.runIf(!enterprise).concurrent( + "should throw error if analytics service enabled with community version", + async () => { + const container = new CouchbaseContainer(image).withEnabledServices( + CouchbaseService.KV, + CouchbaseService.ANALYTICS + ); + + await expect(() => container.start()).rejects.toThrowError( + "The Analytics Service is only supported with the Enterprise version" + ); + } + ); - await expect(() => container.start()).rejects.toThrowError( - "The Eventing Service is only supported with the Enterprise version" - ); - }); + it.runIf(!enterprise).concurrent( + "should throw error if eventing service enabled with community version", + async () => { + const container = new CouchbaseContainer(image).withEnabledServices( + CouchbaseService.KV, + CouchbaseService.EVENTING + ); + + await expect(() => container.start()).rejects.toThrowError( + "The Eventing Service is only supported with the Enterprise version" + ); + } + ); - it.each(ENTERPRISE_IMAGES)("should start with analytics service enabled using %s image", async (image) => { + it.runIf(enterprise).concurrent("should start with analytics service enabled", async () => { await using container = await new CouchbaseContainer(image) .withEnabledServices(CouchbaseService.KV, CouchbaseService.ANALYTICS) .start(); @@ -106,7 +113,7 @@ describe("CouchbaseContainer", { timeout: 180_000 }, () => { expect(container.getConnectionString()).toContain("couchbase://"); }); - it.each(COMMUNITY_IMAGES)("should create and use a bucket with KV service only using %s image", async (image) => { + it.runIf(!enterprise).concurrent("should create and use a bucket with KV service only", async () => { const bucketDefinition = new BucketDefinition("kvbucket").withPrimaryIndex(false); await using container = await new CouchbaseContainer(image) .withEnabledServices(CouchbaseService.KV) @@ -130,7 +137,7 @@ describe("CouchbaseContainer", { timeout: 180_000 }, () => { await cluster.close(); }); - it.each(COMMUNITY_IMAGES)("should connect without a bucket using %s image", async (image) => { + it.runIf(!enterprise).concurrent("should connect without a bucket", async () => { await using container = await new CouchbaseContainer(image).withEnabledServices(CouchbaseService.KV).start(); const cluster = await couchbase.Cluster.connect(container.getConnectionString(), { @@ -145,17 +152,15 @@ describe("CouchbaseContainer", { timeout: 180_000 }, () => { await cluster.close(); }); - it("should fail when setup exceeds the startup timeout", async () => { - const container = new CouchbaseContainer(COMMUNITY_IMAGE) - .withEnabledServices(CouchbaseService.KV) - .withStartupTimeout(2_000); + it.runIf(!enterprise).concurrent("should fail when setup exceeds the startup timeout", async () => { + const container = new CouchbaseContainer(image).withEnabledServices(CouchbaseService.KV).withStartupTimeout(2_000); await expect(() => container.start()).rejects.toThrowError(/URL \/pools not accessible after \d+ms/); }); - it("should preserve fixed host port binding", async () => { + it.runIf(!enterprise).concurrent("should preserve fixed host port binding", async () => { const hostPort = await getPort(); - await using container = await new CouchbaseContainer(COMMUNITY_IMAGE) + await using container = await new CouchbaseContainer(image) .withEnabledServices(CouchbaseService.KV) .withExposedPorts({ container: PORTS.MGMT_PORT, host: hostPort }) .start(); From 5546f10449f83c1ee451d5cdaa3a83288a6fdc06 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 18:55:10 +0100 Subject: [PATCH 6/6] Split the Couchbase tests into a community and an enterprise block --- .../couchbase/src/couchbase-container.test.ts | 201 +++++++++--------- 1 file changed, 99 insertions(+), 102 deletions(-) diff --git a/packages/modules/couchbase/src/couchbase-container.test.ts b/packages/modules/couchbase/src/couchbase-container.test.ts index 344df5e20..9aa4b2a68 100644 --- a/packages/modules/couchbase/src/couchbase-container.test.ts +++ b/packages/modules/couchbase/src/couchbase-container.test.ts @@ -6,114 +6,35 @@ import { CouchbaseContainer } from "./couchbase-container"; import { CouchbaseService } from "./couchbase-service"; import PORTS from "./ports"; -const IMAGES = [ - "couchbase/server:community-7.6.2", - "couchbase/server:enterprise-7.6.12", - getImage(__dirname, 1), - getImage(__dirname, 0), -]; - -describe.each(IMAGES)("CouchbaseContainer (%s)", { concurrent: false, timeout: 180_000 }, (image) => { - const enterprise = image.includes(":enterprise-"); - - const flushBucketAndCheckExists = async ( - cluster: Cluster, - bucket: Bucket, - key: string - ): Promise => { - const coll = bucket.defaultCollection(); - - await cluster.buckets().flushBucket(bucket.name); - return coll.exists(key); - }; - - it.concurrent("should connect and query", async () => { - // connectAndQuery { - const bucketDefinition = new BucketDefinition("mybucket"); - await using container = await new CouchbaseContainer(image).withBucket(bucketDefinition).start(); - - const cluster = await couchbase.Cluster.connect(container.getConnectionString(), { - username: container.getUsername(), - password: container.getPassword(), - }); - - const bucket = cluster.bucket(bucketDefinition.getName()); - await bucket.waitUntilReady(10_000, { - serviceTypes: [couchbase.ServiceType.KeyValue], - }); - - const coll = bucket.defaultCollection(); - await coll.upsert("testdoc", { foo: "bar" }); - const result = await coll.get("testdoc"); +const COMMUNITY_IMAGES = ["couchbase/server:community-7.6.2", getImage(__dirname, 1)]; +const ENTERPRISE_IMAGES = ["couchbase/server:enterprise-7.6.12", getImage(__dirname, 0)]; - expect(result.content).toEqual({ foo: "bar" }); +describe.each(COMMUNITY_IMAGES)("CouchbaseContainer (%s)", { concurrent: false, timeout: 180_000 }, (image) => { + it.concurrent("should connect and query", () => expectStoredDocument(image)); - await cluster.close(); - // } - }); - - it.concurrent("should flush bucket if flushEnabled and check any document exists", async () => { - const bucketDefinition = new BucketDefinition("mybucket").withFlushEnabled(true); - const container = new CouchbaseContainer(image).withBucket(bucketDefinition); - - await using startedTestContainer = await container.start(); - const cluster = await couchbase.Cluster.connect(startedTestContainer.getConnectionString(), { - username: startedTestContainer.getUsername(), - password: startedTestContainer.getPassword(), - }); - - const bucket = cluster.bucket(bucketDefinition.getName()); - await bucket.waitUntilReady(10_000, { - serviceTypes: [couchbase.ServiceType.KeyValue], - }); - - const coll = bucket.defaultCollection(); + it.concurrent("should flush bucket if flushEnabled and check any document exists", () => + expectFlushRemovesDocument(image)); - await coll.upsert("testdoc", { foo: "bar" }); - - const existResult = await flushBucketAndCheckExists(cluster, bucket, "testdoc"); + it.concurrent("should throw error if analytics service enabled with community version", async () => { + const container = new CouchbaseContainer(image).withEnabledServices( + CouchbaseService.KV, + CouchbaseService.ANALYTICS + ); - expect(existResult.exists).toBe(false); - await cluster.close(); + await expect(() => container.start()).rejects.toThrowError( + "The Analytics Service is only supported with the Enterprise version" + ); }); - it.runIf(!enterprise).concurrent( - "should throw error if analytics service enabled with community version", - async () => { - const container = new CouchbaseContainer(image).withEnabledServices( - CouchbaseService.KV, - CouchbaseService.ANALYTICS - ); - - await expect(() => container.start()).rejects.toThrowError( - "The Analytics Service is only supported with the Enterprise version" - ); - } - ); - - it.runIf(!enterprise).concurrent( - "should throw error if eventing service enabled with community version", - async () => { - const container = new CouchbaseContainer(image).withEnabledServices( - CouchbaseService.KV, - CouchbaseService.EVENTING - ); - - await expect(() => container.start()).rejects.toThrowError( - "The Eventing Service is only supported with the Enterprise version" - ); - } - ); - - it.runIf(enterprise).concurrent("should start with analytics service enabled", async () => { - await using container = await new CouchbaseContainer(image) - .withEnabledServices(CouchbaseService.KV, CouchbaseService.ANALYTICS) - .start(); + it.concurrent("should throw error if eventing service enabled with community version", async () => { + const container = new CouchbaseContainer(image).withEnabledServices(CouchbaseService.KV, CouchbaseService.EVENTING); - expect(container.getConnectionString()).toContain("couchbase://"); + await expect(() => container.start()).rejects.toThrowError( + "The Eventing Service is only supported with the Enterprise version" + ); }); - it.runIf(!enterprise).concurrent("should create and use a bucket with KV service only", async () => { + it.concurrent("should create and use a bucket with KV service only", async () => { const bucketDefinition = new BucketDefinition("kvbucket").withPrimaryIndex(false); await using container = await new CouchbaseContainer(image) .withEnabledServices(CouchbaseService.KV) @@ -137,7 +58,7 @@ describe.each(IMAGES)("CouchbaseContainer (%s)", { concurrent: false, timeout: 1 await cluster.close(); }); - it.runIf(!enterprise).concurrent("should connect without a bucket", async () => { + it.concurrent("should connect without a bucket", async () => { await using container = await new CouchbaseContainer(image).withEnabledServices(CouchbaseService.KV).start(); const cluster = await couchbase.Cluster.connect(container.getConnectionString(), { @@ -152,13 +73,13 @@ describe.each(IMAGES)("CouchbaseContainer (%s)", { concurrent: false, timeout: 1 await cluster.close(); }); - it.runIf(!enterprise).concurrent("should fail when setup exceeds the startup timeout", async () => { + it.concurrent("should fail when setup exceeds the startup timeout", async () => { const container = new CouchbaseContainer(image).withEnabledServices(CouchbaseService.KV).withStartupTimeout(2_000); await expect(() => container.start()).rejects.toThrowError(/URL \/pools not accessible after \d+ms/); }); - it.runIf(!enterprise).concurrent("should preserve fixed host port binding", async () => { + it.concurrent("should preserve fixed host port binding", async () => { const hostPort = await getPort(); await using container = await new CouchbaseContainer(image) .withEnabledServices(CouchbaseService.KV) @@ -168,3 +89,79 @@ describe.each(IMAGES)("CouchbaseContainer (%s)", { concurrent: false, timeout: 1 expect(container.getMappedPort(PORTS.MGMT_PORT)).toBe(hostPort); }); }); + +describe.each(ENTERPRISE_IMAGES)("CouchbaseContainer (%s)", { concurrent: false, timeout: 180_000 }, (image) => { + it.concurrent("should connect and query", () => expectStoredDocument(image)); + + it.concurrent("should flush bucket if flushEnabled and check any document exists", () => + expectFlushRemovesDocument(image)); + + it.concurrent("should start with analytics service enabled", async () => { + await using container = await new CouchbaseContainer(image) + .withEnabledServices(CouchbaseService.KV, CouchbaseService.ANALYTICS) + .start(); + + expect(container.getConnectionString()).toContain("couchbase://"); + }); +}); + +async function expectStoredDocument(image: string): Promise { + // connectAndQuery { + const bucketDefinition = new BucketDefinition("mybucket"); + await using container = await new CouchbaseContainer(image).withBucket(bucketDefinition).start(); + + const cluster = await couchbase.Cluster.connect(container.getConnectionString(), { + username: container.getUsername(), + password: container.getPassword(), + }); + + const bucket = cluster.bucket(bucketDefinition.getName()); + await bucket.waitUntilReady(10_000, { + serviceTypes: [couchbase.ServiceType.KeyValue], + }); + + const coll = bucket.defaultCollection(); + await coll.upsert("testdoc", { foo: "bar" }); + const result = await coll.get("testdoc"); + + expect(result.content).toEqual({ foo: "bar" }); + + await cluster.close(); + // } +} + +async function expectFlushRemovesDocument(image: string): Promise { + const bucketDefinition = new BucketDefinition("mybucket").withFlushEnabled(true); + const container = new CouchbaseContainer(image).withBucket(bucketDefinition); + + await using startedTestContainer = await container.start(); + const cluster = await couchbase.Cluster.connect(startedTestContainer.getConnectionString(), { + username: startedTestContainer.getUsername(), + password: startedTestContainer.getPassword(), + }); + + const bucket = cluster.bucket(bucketDefinition.getName()); + await bucket.waitUntilReady(10_000, { + serviceTypes: [couchbase.ServiceType.KeyValue], + }); + + const coll = bucket.defaultCollection(); + + await coll.upsert("testdoc", { foo: "bar" }); + + const existResult = await flushBucketAndCheckExists(cluster, bucket, "testdoc"); + + expect(existResult.exists).toBe(false); + await cluster.close(); +} + +async function flushBucketAndCheckExists( + cluster: Cluster, + bucket: Bucket, + key: string +): Promise { + const coll = bucket.defaultCollection(); + + await cluster.buckets().flushBucket(bucket.name); + return coll.exists(key); +}