From 88d117f8ec3b13bfd844b1e895553c3a6ff7fed6 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Mon, 12 Oct 2026 19:55:42 +0100 Subject: [PATCH 1/6] Test MariaDB, MySQL, PostgreSQL and CockroachDB against their oldest supported versions --- .../src/cockroachdb-container.test.ts | 39 +++++++++++++---- .../mariadb/src/mariadb-container.test.ts | 37 ++++++++++++---- .../modules/mysql/src/mysql-container.test.ts | 38 +++++++++++++---- .../src/postgresql-container-snapshot.test.ts | 11 ++--- .../src/postgresql-container.test.ts | 42 +++++++++++++++---- 5 files changed, 130 insertions(+), 37 deletions(-) diff --git a/packages/modules/cockroachdb/src/cockroachdb-container.test.ts b/packages/modules/cockroachdb/src/cockroachdb-container.test.ts index 9c338e159..45f42fe0e 100644 --- a/packages/modules/cockroachdb/src/cockroachdb-container.test.ts +++ b/packages/modules/cockroachdb/src/cockroachdb-container.test.ts @@ -3,11 +3,12 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { CockroachDbContainer } from "./cockroachdb-container"; const IMAGE = getImage(__dirname); +const OLDEST_IMAGE = "cockroachdb/cockroach:v24.3.36"; -describe("CockroachDbContainer", { timeout: 180_000 }, () => { +describe.each([IMAGE, OLDEST_IMAGE])("CockroachDbContainer (%s)", { timeout: 180_000 }, (image) => { it("should connect and return a query result", async () => { // cockroachConnect { - await using container = await new CockroachDbContainer(IMAGE).start(); + await using container = await new CockroachDbContainer(image).start(); const client = new Client({ host: container.getHost(), @@ -28,7 +29,7 @@ describe("CockroachDbContainer", { timeout: 180_000 }, () => { it("should work with database URI", async () => { // uriConnect { - await using container = await new CockroachDbContainer(IMAGE).start(); + await using container = await new CockroachDbContainer(image).start(); const client = new Client({ connectionString: container.getConnectionUri(), @@ -45,7 +46,7 @@ describe("CockroachDbContainer", { timeout: 180_000 }, () => { it("should set database", async () => { // setDatabase { - await using container = await new CockroachDbContainer(IMAGE).withDatabase("custom_database").start(); + await using container = await new CockroachDbContainer(image).withDatabase("custom_database").start(); // } const client = new Client({ @@ -65,7 +66,7 @@ describe("CockroachDbContainer", { timeout: 180_000 }, () => { it("should set username", async () => { // setUsername { - await using container = await new CockroachDbContainer(IMAGE).withUsername("custom_username").start(); + await using container = await new CockroachDbContainer(image).withUsername("custom_username").start(); // } const client = new Client({ @@ -84,7 +85,7 @@ describe("CockroachDbContainer", { timeout: 180_000 }, () => { }); it("should work with restarted container", async () => { - await using container = await new CockroachDbContainer(IMAGE).start(); + await using container = await new CockroachDbContainer(image).start(); await container.restart(); const client = new Client({ @@ -102,7 +103,7 @@ describe("CockroachDbContainer", { timeout: 180_000 }, () => { }); it("should allow custom healthcheck", async () => { - const container = new CockroachDbContainer(IMAGE).withHealthCheck({ + const container = new CockroachDbContainer(image).withHealthCheck({ test: ["CMD-SHELL", "exit 1"], interval: 100, retries: 0, @@ -112,3 +113,27 @@ describe("CockroachDbContainer", { timeout: 180_000 }, () => { await expect(() => container.start()).rejects.toThrow(); }); }); + +describe("CockroachDbContainer", { timeout: 180_000 }, () => { + it.each(["cockroachdb/cockroach:v25.2.24", "cockroachdb/cockroach:v25.4.17", "cockroachdb/cockroach:v26.2.7"])( + "should connect and return a query result with %s", + async (image) => { + await using container = await new CockroachDbContainer(image).start(); + + const client = new Client({ + host: container.getHost(), + port: container.getPort(), + database: container.getDatabase(), + user: container.getUsername(), + ssl: false, + }); + + await client.connect(); + + const result = await client.query("SELECT 1"); + expect(result.rows[0]).toEqual({ "?column?": "1" }); + + await client.end(); + } + ); +}); diff --git a/packages/modules/mariadb/src/mariadb-container.test.ts b/packages/modules/mariadb/src/mariadb-container.test.ts index a82390b06..e9506e6c2 100644 --- a/packages/modules/mariadb/src/mariadb-container.test.ts +++ b/packages/modules/mariadb/src/mariadb-container.test.ts @@ -3,11 +3,12 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { MariaDbContainer } from "./mariadb-container"; const IMAGE = getImage(__dirname); +const OLDEST_IMAGE = "mariadb:10.11.19"; -describe("MariaDbContainer", { timeout: 240_000 }, () => { +describe.each([IMAGE, OLDEST_IMAGE])("MariaDbContainer (%s)", { timeout: 240_000 }, (image) => { it("should connect and execute query", async () => { // mariaDbConnect { - await using container = await new MariaDbContainer(IMAGE).start(); + await using container = await new MariaDbContainer(image).start(); const client = await createConnection({ host: container.getHost(), @@ -31,7 +32,7 @@ describe("MariaDbContainer", { timeout: 240_000 }, () => { const database = "testDB"; // Test non-root user - await using container = await new MariaDbContainer(IMAGE) + await using container = await new MariaDbContainer(image) .withUsername(username) .withUserPassword(password) .withDatabase(database) @@ -42,7 +43,7 @@ describe("MariaDbContainer", { timeout: 240_000 }, () => { ); // Test root user - await using rootContainer = await new MariaDbContainer(IMAGE) + await using rootContainer = await new MariaDbContainer(image) .withRootPassword(password) .withDatabase(database) .start(); @@ -55,7 +56,7 @@ describe("MariaDbContainer", { timeout: 240_000 }, () => { it("should set database", async () => { // mariaDbSetDatabase { - await using container = await new MariaDbContainer(IMAGE).withDatabase("customDatabase").start(); + await using container = await new MariaDbContainer(image).withDatabase("customDatabase").start(); const client = await createConnection({ host: container.getHost(), @@ -74,7 +75,7 @@ describe("MariaDbContainer", { timeout: 240_000 }, () => { it("should set username", async () => { // mariaDbSetUsername { - await using container = await new MariaDbContainer(IMAGE).withUsername("customUsername").start(); + await using container = await new MariaDbContainer(image).withUsername("customUsername").start(); const client = await createConnection({ host: container.getHost(), @@ -92,7 +93,7 @@ describe("MariaDbContainer", { timeout: 240_000 }, () => { }); it("should work with restarted container", async () => { - await using container = await new MariaDbContainer(IMAGE).start(); + await using container = await new MariaDbContainer(image).start(); await container.restart(); const client = await createConnection({ @@ -109,3 +110,25 @@ describe("MariaDbContainer", { timeout: 240_000 }, () => { await client.end(); }); }); + +describe("MariaDbContainer", { timeout: 240_000 }, () => { + it.each(["mariadb:11.4.13", "mariadb:11.8.9", "mariadb:12.3.3"])( + "should connect and execute query with %s", + async (image) => { + await using container = await new MariaDbContainer(image).start(); + + const client = await createConnection({ + host: container.getHost(), + port: container.getPort(), + database: container.getDatabase(), + user: container.getUsername(), + password: container.getUserPassword(), + }); + + const rows = await client.query("SELECT 1 as res"); + expect(rows).toEqual([{ res: 1 }]); + + await client.end(); + } + ); +}); diff --git a/packages/modules/mysql/src/mysql-container.test.ts b/packages/modules/mysql/src/mysql-container.test.ts index 3b0a4ebea..c4feffac3 100644 --- a/packages/modules/mysql/src/mysql-container.test.ts +++ b/packages/modules/mysql/src/mysql-container.test.ts @@ -3,11 +3,12 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { MySqlContainer } from "./mysql-container"; const IMAGE = getImage(__dirname); +const OLDEST_IMAGE = "mysql:8.4.11"; -describe("MySqlContainer", { timeout: 240_000 }, () => { +describe.each([IMAGE, OLDEST_IMAGE])("MySqlContainer (%s)", { timeout: 240_000 }, (image) => { it("should connect and execute query", async () => { // mysqlConnect { - await using container = await new MySqlContainer(IMAGE).start(); + await using container = await new MySqlContainer(image).start(); const client = await createConnection({ host: container.getHost(), @@ -30,7 +31,7 @@ describe("MySqlContainer", { timeout: 240_000 }, () => { const password = "testPassword"; const database = "testDB"; - await using container = await new MySqlContainer(IMAGE) + await using container = await new MySqlContainer(image) .withUsername(username) .withUserPassword(password) .withDatabase(database) @@ -40,7 +41,7 @@ describe("MySqlContainer", { timeout: 240_000 }, () => { `mysql://${username}:${password}@${container.getHost()}:${container.getPort()}/${database}` ); - await using rootContainer = await new MySqlContainer(IMAGE) + await using rootContainer = await new MySqlContainer(image) .withRootPassword(password) .withDatabase(database) .start(); @@ -53,7 +54,7 @@ describe("MySqlContainer", { timeout: 240_000 }, () => { it("should set database", async () => { // mysqlSetDatabase { - await using container = await new MySqlContainer(IMAGE).withDatabase("customDatabase").start(); + await using container = await new MySqlContainer(image).withDatabase("customDatabase").start(); const client = await createConnection({ host: container.getHost(), @@ -72,7 +73,7 @@ describe("MySqlContainer", { timeout: 240_000 }, () => { // mysqlSetUsername { it("should set username", async () => { - await using container = await new MySqlContainer(IMAGE).withUsername("customUsername").start(); + await using container = await new MySqlContainer(image).withUsername("customUsername").start(); const client = await createConnection({ host: container.getHost(), @@ -91,7 +92,7 @@ describe("MySqlContainer", { timeout: 240_000 }, () => { it("should execute a query and return the result", async () => { // mysqlExecuteQuery { - await using container = await new MySqlContainer(IMAGE).start(); + await using container = await new MySqlContainer(image).start(); const result = await container.executeQuery("SELECT 1 as res"); expect(result).toEqual(expect.stringContaining("res\n1\n")); @@ -99,7 +100,7 @@ describe("MySqlContainer", { timeout: 240_000 }, () => { }); it("should execute a query as root user", async () => { - await using container = await new MySqlContainer(IMAGE).withUsername("customUsername").start(); + await using container = await new MySqlContainer(image).withUsername("customUsername").start(); // Test non-root user const queryResult = await container.executeQuery("SELECT CURRENT_USER() as user"); @@ -111,7 +112,7 @@ describe("MySqlContainer", { timeout: 240_000 }, () => { }); it("should work with restarted container", async () => { - await using container = await new MySqlContainer(IMAGE).start(); + await using container = await new MySqlContainer(image).start(); await container.restart(); const client = await createConnection({ @@ -128,3 +129,22 @@ describe("MySqlContainer", { timeout: 240_000 }, () => { await client.end(); }); }); + +describe("MySqlContainer", { timeout: 240_000 }, () => { + it.each(["mysql:9.7.2"])("should connect and execute query with %s", async (image) => { + await using container = await new MySqlContainer(image).start(); + + const client = await createConnection({ + host: container.getHost(), + port: container.getPort(), + database: container.getDatabase(), + user: container.getUsername(), + password: container.getUserPassword(), + }); + + const [rows] = await client.execute("SELECT 1 as res"); + expect(rows).toEqual([{ res: 1 }]); + + await client.end(); + }); +}); diff --git a/packages/modules/postgresql/src/postgresql-container-snapshot.test.ts b/packages/modules/postgresql/src/postgresql-container-snapshot.test.ts index e85838374..03ab9fa08 100644 --- a/packages/modules/postgresql/src/postgresql-container-snapshot.test.ts +++ b/packages/modules/postgresql/src/postgresql-container-snapshot.test.ts @@ -3,11 +3,12 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { PostgreSqlContainer } from "./postgresql-container"; const IMAGE = getImage(__dirname); +const OLDEST_IMAGE = "postgres:15.19-alpine"; -describe("PostgreSqlContainer snapshot and restore", { timeout: 180_000 }, () => { +describe.each([IMAGE, OLDEST_IMAGE])("PostgreSqlContainer snapshot and restore (%s)", { timeout: 180_000 }, (image) => { it("should create and restore from snapshot", async () => { // createAndRestoreFromSnapshot { - await using container = await new PostgreSqlContainer(IMAGE).start(); + await using container = await new PostgreSqlContainer(image).start(); // Connect to the database let client = new Client({ @@ -62,7 +63,7 @@ describe("PostgreSqlContainer snapshot and restore", { timeout: 180_000 }, () => }); it("should use custom snapshot name", async () => { - await using container = await new PostgreSqlContainer(IMAGE).start(); + await using container = await new PostgreSqlContainer(image).start(); const customSnapshotName = "my_custom_snapshot"; // Connect to the database @@ -111,7 +112,7 @@ describe("PostgreSqlContainer snapshot and restore", { timeout: 180_000 }, () => }); it("should handle multiple snapshots", async () => { - await using container = await new PostgreSqlContainer(IMAGE).start(); + await using container = await new PostgreSqlContainer(image).start(); // Connect to the database let client = new Client({ @@ -193,7 +194,7 @@ describe("PostgreSqlContainer snapshot and restore", { timeout: 180_000 }, () => }); it("should throw an error when trying to snapshot postgres system database", async () => { - await using container = await new PostgreSqlContainer(IMAGE).withDatabase("postgres").start(); + await using container = await new PostgreSqlContainer(image).withDatabase("postgres").start(); await expect(container.snapshot()).rejects.toThrow( "Snapshot feature is not supported when using the postgres system database" diff --git a/packages/modules/postgresql/src/postgresql-container.test.ts b/packages/modules/postgresql/src/postgresql-container.test.ts index ee1338a16..a38ec820e 100644 --- a/packages/modules/postgresql/src/postgresql-container.test.ts +++ b/packages/modules/postgresql/src/postgresql-container.test.ts @@ -4,14 +4,15 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { PostgreSqlContainer } from "./postgresql-container"; const IMAGE = getImage(__dirname); +const OLDEST_IMAGE = "postgres:15.19-alpine"; const SSL_CA_CERT = path.resolve(__dirname, "test-certs/ca.crt"); const SSL_SERVER_CERT = path.resolve(__dirname, "test-certs/server.crt"); const SSL_SERVER_KEY = path.resolve(__dirname, "test-certs/server.key"); -describe("PostgreSqlContainer", { timeout: 180_000 }, () => { +describe.each([IMAGE, OLDEST_IMAGE])("PostgreSqlContainer (%s)", { timeout: 180_000 }, (image) => { it("should connect and return a query result", async () => { // pgConnect { - await using container = await new PostgreSqlContainer(IMAGE).start(); + await using container = await new PostgreSqlContainer(image).start(); const client = new Client({ host: container.getHost(), @@ -31,7 +32,7 @@ describe("PostgreSqlContainer", { timeout: 180_000 }, () => { it("should work with database URI", async () => { // pgUriConnect { - await using container = await new PostgreSqlContainer(IMAGE).start(); + await using container = await new PostgreSqlContainer(image).start(); const client = new Client({ connectionString: container.getConnectionUri(), @@ -47,7 +48,7 @@ describe("PostgreSqlContainer", { timeout: 180_000 }, () => { it("should set database", async () => { // pgSetDatabase { - await using container = await new PostgreSqlContainer(IMAGE).withDatabase("customDatabase").start(); + await using container = await new PostgreSqlContainer(image).withDatabase("customDatabase").start(); // } const client = new Client({ @@ -67,7 +68,7 @@ describe("PostgreSqlContainer", { timeout: 180_000 }, () => { it("should set username", async () => { // pgSetUsername { - await using container = await new PostgreSqlContainer(IMAGE).withUsername("customUsername").start(); + await using container = await new PostgreSqlContainer(image).withUsername("customUsername").start(); // } const client = new Client({ @@ -86,7 +87,7 @@ describe("PostgreSqlContainer", { timeout: 180_000 }, () => { }); it("should work with restarted container", async () => { - await using container = await new PostgreSqlContainer(IMAGE).start(); + await using container = await new PostgreSqlContainer(image).start(); await container.restart(); const client = new Client({ @@ -105,7 +106,7 @@ describe("PostgreSqlContainer", { timeout: 180_000 }, () => { }); it("should allow custom healthcheck", async () => { - const container = new PostgreSqlContainer(IMAGE).withHealthCheck({ + const container = new PostgreSqlContainer(image).withHealthCheck({ test: ["CMD-SHELL", "exit 1"], interval: 100, retries: 0, @@ -117,7 +118,7 @@ describe("PostgreSqlContainer", { timeout: 180_000 }, () => { it("should connect with SSL", async () => { // pgSslConnect { - await using container = await new PostgreSqlContainer(IMAGE) + await using container = await new PostgreSqlContainer(image) .withSSLCert(SSL_CA_CERT, SSL_SERVER_CERT, SSL_SERVER_KEY) .start(); @@ -141,7 +142,7 @@ describe("PostgreSqlContainer", { timeout: 180_000 }, () => { }); it("should validate SSL certificate paths", () => { - const container = new PostgreSqlContainer(IMAGE); + const container = new PostgreSqlContainer(image); expect(() => container.withSSL("", "server.key")).toThrow("SSL certificate file should not be empty."); expect(() => container.withSSL("server.crt", "")).toThrow("SSL key file should not be empty."); @@ -150,3 +151,26 @@ describe("PostgreSqlContainer", { timeout: 180_000 }, () => { ); }); }); + +describe("PostgreSqlContainer", { timeout: 180_000 }, () => { + it.each(["postgres:16.15-alpine", "postgres:17.11-alpine"])( + "should connect and return a query result with %s", + async (image) => { + await using container = await new PostgreSqlContainer(image).start(); + + const client = new Client({ + host: container.getHost(), + port: container.getPort(), + database: container.getDatabase(), + user: container.getUsername(), + password: container.getPassword(), + }); + await client.connect(); + + const result = await client.query("SELECT 1"); + expect(result.rows[0]).toEqual({ "?column?": 1 }); + + await client.end(); + } + ); +}); From 92f790970b8bb1b111454dbb8df99c0c19fccbab Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 10:39:04 +0100 Subject: [PATCH 2/6] Loop the suites over a list of every supported image --- .../src/cockroachdb-container.test.ts | 37 +++++-------------- .../mariadb/src/mariadb-container.test.ts | 27 +------------- .../modules/mysql/src/mysql-container.test.ts | 24 +----------- .../src/postgresql-container-snapshot.test.ts | 5 +-- .../src/postgresql-container.test.ts | 28 +------------- 5 files changed, 17 insertions(+), 104 deletions(-) diff --git a/packages/modules/cockroachdb/src/cockroachdb-container.test.ts b/packages/modules/cockroachdb/src/cockroachdb-container.test.ts index 45f42fe0e..d8dcb0b33 100644 --- a/packages/modules/cockroachdb/src/cockroachdb-container.test.ts +++ b/packages/modules/cockroachdb/src/cockroachdb-container.test.ts @@ -2,10 +2,15 @@ import { Client } from "pg"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { CockroachDbContainer } from "./cockroachdb-container"; -const IMAGE = getImage(__dirname); -const OLDEST_IMAGE = "cockroachdb/cockroach:v24.3.36"; - -describe.each([IMAGE, OLDEST_IMAGE])("CockroachDbContainer (%s)", { timeout: 180_000 }, (image) => { +const IMAGES = [ + "cockroachdb/cockroach:v24.3.36", + "cockroachdb/cockroach:v25.2.24", + "cockroachdb/cockroach:v25.4.17", + "cockroachdb/cockroach:v26.2.7", + getImage(__dirname), +]; + +describe.each(IMAGES)("CockroachDbContainer (%s)", { timeout: 180_000 }, (image) => { it("should connect and return a query result", async () => { // cockroachConnect { await using container = await new CockroachDbContainer(image).start(); @@ -113,27 +118,3 @@ describe.each([IMAGE, OLDEST_IMAGE])("CockroachDbContainer (%s)", { timeout: 180 await expect(() => container.start()).rejects.toThrow(); }); }); - -describe("CockroachDbContainer", { timeout: 180_000 }, () => { - it.each(["cockroachdb/cockroach:v25.2.24", "cockroachdb/cockroach:v25.4.17", "cockroachdb/cockroach:v26.2.7"])( - "should connect and return a query result with %s", - async (image) => { - await using container = await new CockroachDbContainer(image).start(); - - const client = new Client({ - host: container.getHost(), - port: container.getPort(), - database: container.getDatabase(), - user: container.getUsername(), - ssl: false, - }); - - await client.connect(); - - const result = await client.query("SELECT 1"); - expect(result.rows[0]).toEqual({ "?column?": "1" }); - - await client.end(); - } - ); -}); diff --git a/packages/modules/mariadb/src/mariadb-container.test.ts b/packages/modules/mariadb/src/mariadb-container.test.ts index e9506e6c2..b04bb29c5 100644 --- a/packages/modules/mariadb/src/mariadb-container.test.ts +++ b/packages/modules/mariadb/src/mariadb-container.test.ts @@ -2,10 +2,9 @@ import { createConnection } from "mariadb"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { MariaDbContainer } from "./mariadb-container"; -const IMAGE = getImage(__dirname); -const OLDEST_IMAGE = "mariadb:10.11.19"; +const IMAGES = ["mariadb:10.11.19", "mariadb:11.4.13", "mariadb:11.8.9", "mariadb:12.3.3", getImage(__dirname)]; -describe.each([IMAGE, OLDEST_IMAGE])("MariaDbContainer (%s)", { timeout: 240_000 }, (image) => { +describe.each(IMAGES)("MariaDbContainer (%s)", { timeout: 240_000 }, (image) => { it("should connect and execute query", async () => { // mariaDbConnect { await using container = await new MariaDbContainer(image).start(); @@ -110,25 +109,3 @@ describe.each([IMAGE, OLDEST_IMAGE])("MariaDbContainer (%s)", { timeout: 240_000 await client.end(); }); }); - -describe("MariaDbContainer", { timeout: 240_000 }, () => { - it.each(["mariadb:11.4.13", "mariadb:11.8.9", "mariadb:12.3.3"])( - "should connect and execute query with %s", - async (image) => { - await using container = await new MariaDbContainer(image).start(); - - const client = await createConnection({ - host: container.getHost(), - port: container.getPort(), - database: container.getDatabase(), - user: container.getUsername(), - password: container.getUserPassword(), - }); - - const rows = await client.query("SELECT 1 as res"); - expect(rows).toEqual([{ res: 1 }]); - - await client.end(); - } - ); -}); diff --git a/packages/modules/mysql/src/mysql-container.test.ts b/packages/modules/mysql/src/mysql-container.test.ts index c4feffac3..3d7f6a58a 100644 --- a/packages/modules/mysql/src/mysql-container.test.ts +++ b/packages/modules/mysql/src/mysql-container.test.ts @@ -2,10 +2,9 @@ import { createConnection } from "mysql2/promise"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { MySqlContainer } from "./mysql-container"; -const IMAGE = getImage(__dirname); -const OLDEST_IMAGE = "mysql:8.4.11"; +const IMAGES = ["mysql:8.4.11", "mysql:9.7.2", getImage(__dirname)]; -describe.each([IMAGE, OLDEST_IMAGE])("MySqlContainer (%s)", { timeout: 240_000 }, (image) => { +describe.each(IMAGES)("MySqlContainer (%s)", { timeout: 240_000 }, (image) => { it("should connect and execute query", async () => { // mysqlConnect { await using container = await new MySqlContainer(image).start(); @@ -129,22 +128,3 @@ describe.each([IMAGE, OLDEST_IMAGE])("MySqlContainer (%s)", { timeout: 240_000 } await client.end(); }); }); - -describe("MySqlContainer", { timeout: 240_000 }, () => { - it.each(["mysql:9.7.2"])("should connect and execute query with %s", async (image) => { - await using container = await new MySqlContainer(image).start(); - - const client = await createConnection({ - host: container.getHost(), - port: container.getPort(), - database: container.getDatabase(), - user: container.getUsername(), - password: container.getUserPassword(), - }); - - const [rows] = await client.execute("SELECT 1 as res"); - expect(rows).toEqual([{ res: 1 }]); - - await client.end(); - }); -}); diff --git a/packages/modules/postgresql/src/postgresql-container-snapshot.test.ts b/packages/modules/postgresql/src/postgresql-container-snapshot.test.ts index 03ab9fa08..df2932b7a 100644 --- a/packages/modules/postgresql/src/postgresql-container-snapshot.test.ts +++ b/packages/modules/postgresql/src/postgresql-container-snapshot.test.ts @@ -2,10 +2,9 @@ import { Client } from "pg"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { PostgreSqlContainer } from "./postgresql-container"; -const IMAGE = getImage(__dirname); -const OLDEST_IMAGE = "postgres:15.19-alpine"; +const IMAGES = ["postgres:15.19-alpine", "postgres:16.15-alpine", "postgres:17.11-alpine", getImage(__dirname)]; -describe.each([IMAGE, OLDEST_IMAGE])("PostgreSqlContainer snapshot and restore (%s)", { timeout: 180_000 }, (image) => { +describe.each(IMAGES)("PostgreSqlContainer snapshot and restore (%s)", { timeout: 180_000 }, (image) => { it("should create and restore from snapshot", async () => { // createAndRestoreFromSnapshot { await using container = await new PostgreSqlContainer(image).start(); diff --git a/packages/modules/postgresql/src/postgresql-container.test.ts b/packages/modules/postgresql/src/postgresql-container.test.ts index a38ec820e..3cc3a70b1 100644 --- a/packages/modules/postgresql/src/postgresql-container.test.ts +++ b/packages/modules/postgresql/src/postgresql-container.test.ts @@ -3,13 +3,12 @@ import { Client } from "pg"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { PostgreSqlContainer } from "./postgresql-container"; -const IMAGE = getImage(__dirname); -const OLDEST_IMAGE = "postgres:15.19-alpine"; +const IMAGES = ["postgres:15.19-alpine", "postgres:16.15-alpine", "postgres:17.11-alpine", getImage(__dirname)]; const SSL_CA_CERT = path.resolve(__dirname, "test-certs/ca.crt"); const SSL_SERVER_CERT = path.resolve(__dirname, "test-certs/server.crt"); const SSL_SERVER_KEY = path.resolve(__dirname, "test-certs/server.key"); -describe.each([IMAGE, OLDEST_IMAGE])("PostgreSqlContainer (%s)", { timeout: 180_000 }, (image) => { +describe.each(IMAGES)("PostgreSqlContainer (%s)", { timeout: 180_000 }, (image) => { it("should connect and return a query result", async () => { // pgConnect { await using container = await new PostgreSqlContainer(image).start(); @@ -151,26 +150,3 @@ describe.each([IMAGE, OLDEST_IMAGE])("PostgreSqlContainer (%s)", { timeout: 180_ ); }); }); - -describe("PostgreSqlContainer", { timeout: 180_000 }, () => { - it.each(["postgres:16.15-alpine", "postgres:17.11-alpine"])( - "should connect and return a query result with %s", - async (image) => { - await using container = await new PostgreSqlContainer(image).start(); - - const client = new Client({ - host: container.getHost(), - port: container.getPort(), - database: container.getDatabase(), - user: container.getUsername(), - password: container.getPassword(), - }); - await client.connect(); - - const result = await client.query("SELECT 1"); - expect(result.rows[0]).toEqual({ "?column?": 1 }); - - await client.end(); - } - ); -}); From 49f3afeac8f42ef19076f9934691354feec54e43 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 10:46:14 +0100 Subject: [PATCH 3/6] Share the PostgreSQL image list between the looped test files --- .../postgresql/src/postgresql-container-snapshot.test.ts | 4 +--- packages/modules/postgresql/src/postgresql-container.test.ts | 3 +-- packages/modules/postgresql/src/test-helper.ts | 3 +++ packages/modules/postgresql/tsconfig.build.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 packages/modules/postgresql/src/test-helper.ts diff --git a/packages/modules/postgresql/src/postgresql-container-snapshot.test.ts b/packages/modules/postgresql/src/postgresql-container-snapshot.test.ts index df2932b7a..2c2873956 100644 --- a/packages/modules/postgresql/src/postgresql-container-snapshot.test.ts +++ b/packages/modules/postgresql/src/postgresql-container-snapshot.test.ts @@ -1,8 +1,6 @@ import { Client } from "pg"; -import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { PostgreSqlContainer } from "./postgresql-container"; - -const IMAGES = ["postgres:15.19-alpine", "postgres:16.15-alpine", "postgres:17.11-alpine", getImage(__dirname)]; +import { IMAGES } from "./test-helper"; describe.each(IMAGES)("PostgreSqlContainer snapshot and restore (%s)", { timeout: 180_000 }, (image) => { it("should create and restore from snapshot", async () => { diff --git a/packages/modules/postgresql/src/postgresql-container.test.ts b/packages/modules/postgresql/src/postgresql-container.test.ts index 3cc3a70b1..34061e2c5 100644 --- a/packages/modules/postgresql/src/postgresql-container.test.ts +++ b/packages/modules/postgresql/src/postgresql-container.test.ts @@ -1,9 +1,8 @@ import path from "node:path"; import { Client } from "pg"; -import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { PostgreSqlContainer } from "./postgresql-container"; +import { IMAGES } from "./test-helper"; -const IMAGES = ["postgres:15.19-alpine", "postgres:16.15-alpine", "postgres:17.11-alpine", getImage(__dirname)]; const SSL_CA_CERT = path.resolve(__dirname, "test-certs/ca.crt"); const SSL_SERVER_CERT = path.resolve(__dirname, "test-certs/server.crt"); const SSL_SERVER_KEY = path.resolve(__dirname, "test-certs/server.key"); diff --git a/packages/modules/postgresql/src/test-helper.ts b/packages/modules/postgresql/src/test-helper.ts new file mode 100644 index 000000000..a6c67a5f3 --- /dev/null +++ b/packages/modules/postgresql/src/test-helper.ts @@ -0,0 +1,3 @@ +import { getImage } from "../../../testcontainers/src/utils/test-helper"; + +export const IMAGES = ["postgres:15.19-alpine", "postgres:16.15-alpine", "postgres:17.11-alpine", getImage(__dirname)]; diff --git a/packages/modules/postgresql/tsconfig.build.json b/packages/modules/postgresql/tsconfig.build.json index 91dc68846..aba4d4661 100644 --- a/packages/modules/postgresql/tsconfig.build.json +++ b/packages/modules/postgresql/tsconfig.build.json @@ -1,6 +1,6 @@ { "extends": "./tsconfig.json", - "exclude": ["build", "src/**/*.test.ts"], + "exclude": ["build", "src/**/*.test.ts", "src/test-helper.ts"], "references": [ { "path": "../../testcontainers" From 3fed1128c6e34a5bd2b5721b590d90a80ccd70c7 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 11:12:08 +0100 Subject: [PATCH 4/6] List one image per supported major version --- .../modules/cockroachdb/src/cockroachdb-container.test.ts | 8 +------- packages/modules/mariadb/src/mariadb-container.test.ts | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/modules/cockroachdb/src/cockroachdb-container.test.ts b/packages/modules/cockroachdb/src/cockroachdb-container.test.ts index d8dcb0b33..c2e239941 100644 --- a/packages/modules/cockroachdb/src/cockroachdb-container.test.ts +++ b/packages/modules/cockroachdb/src/cockroachdb-container.test.ts @@ -2,13 +2,7 @@ import { Client } from "pg"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { CockroachDbContainer } from "./cockroachdb-container"; -const IMAGES = [ - "cockroachdb/cockroach:v24.3.36", - "cockroachdb/cockroach:v25.2.24", - "cockroachdb/cockroach:v25.4.17", - "cockroachdb/cockroach:v26.2.7", - getImage(__dirname), -]; +const IMAGES = ["cockroachdb/cockroach:v24.3.36", "cockroachdb/cockroach:v25.4.17", getImage(__dirname)]; describe.each(IMAGES)("CockroachDbContainer (%s)", { timeout: 180_000 }, (image) => { it("should connect and return a query result", async () => { diff --git a/packages/modules/mariadb/src/mariadb-container.test.ts b/packages/modules/mariadb/src/mariadb-container.test.ts index b04bb29c5..d8b875b7f 100644 --- a/packages/modules/mariadb/src/mariadb-container.test.ts +++ b/packages/modules/mariadb/src/mariadb-container.test.ts @@ -2,7 +2,7 @@ import { createConnection } from "mariadb"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { MariaDbContainer } from "./mariadb-container"; -const IMAGES = ["mariadb:10.11.19", "mariadb:11.4.13", "mariadb:11.8.9", "mariadb:12.3.3", getImage(__dirname)]; +const IMAGES = ["mariadb:10.11.19", "mariadb:11.8.9", "mariadb:12.3.3", getImage(__dirname)]; describe.each(IMAGES)("MariaDbContainer (%s)", { timeout: 240_000 }, (image) => { it("should connect and execute query", async () => { From cb1f937d8dd61038c9a354b96bd9cb7dc658b3ac Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 12:44:46 +0100 Subject: [PATCH 5/6] Run the images one after another and each image's tests concurrently --- .../src/cockroachdb-container.test.ts | 14 +- .../mariadb/src/mariadb-container.test.ts | 12 +- .../modules/mysql/src/mysql-container.test.ts | 16 +- .../src/postgresql-container-snapshot.test.ts | 324 +++++++++--------- .../src/postgresql-container.test.ts | 18 +- 5 files changed, 194 insertions(+), 190 deletions(-) diff --git a/packages/modules/cockroachdb/src/cockroachdb-container.test.ts b/packages/modules/cockroachdb/src/cockroachdb-container.test.ts index c2e239941..19655ab59 100644 --- a/packages/modules/cockroachdb/src/cockroachdb-container.test.ts +++ b/packages/modules/cockroachdb/src/cockroachdb-container.test.ts @@ -4,8 +4,8 @@ import { CockroachDbContainer } from "./cockroachdb-container"; const IMAGES = ["cockroachdb/cockroach:v24.3.36", "cockroachdb/cockroach:v25.4.17", getImage(__dirname)]; -describe.each(IMAGES)("CockroachDbContainer (%s)", { timeout: 180_000 }, (image) => { - it("should connect and return a query result", async () => { +describe.each(IMAGES)("CockroachDbContainer (%s)", { concurrent: false, timeout: 180_000 }, (image) => { + it.concurrent("should connect and return a query result", async () => { // cockroachConnect { await using container = await new CockroachDbContainer(image).start(); @@ -26,7 +26,7 @@ describe.each(IMAGES)("CockroachDbContainer (%s)", { timeout: 180_000 }, (image) // } }); - it("should work with database URI", async () => { + it.concurrent("should work with database URI", async () => { // uriConnect { await using container = await new CockroachDbContainer(image).start(); @@ -43,7 +43,7 @@ describe.each(IMAGES)("CockroachDbContainer (%s)", { timeout: 180_000 }, (image) await client.end(); }); - it("should set database", async () => { + it.concurrent("should set database", async () => { // setDatabase { await using container = await new CockroachDbContainer(image).withDatabase("custom_database").start(); // } @@ -63,7 +63,7 @@ describe.each(IMAGES)("CockroachDbContainer (%s)", { timeout: 180_000 }, (image) await client.end(); }); - it("should set username", async () => { + it.concurrent("should set username", async () => { // setUsername { await using container = await new CockroachDbContainer(image).withUsername("custom_username").start(); // } @@ -83,7 +83,7 @@ describe.each(IMAGES)("CockroachDbContainer (%s)", { timeout: 180_000 }, (image) await client.end(); }); - it("should work with restarted container", async () => { + it.concurrent("should work with restarted container", async () => { await using container = await new CockroachDbContainer(image).start(); await container.restart(); @@ -101,7 +101,7 @@ describe.each(IMAGES)("CockroachDbContainer (%s)", { timeout: 180_000 }, (image) await client.end(); }); - it("should allow custom healthcheck", async () => { + it.concurrent("should allow custom healthcheck", async () => { const container = new CockroachDbContainer(image).withHealthCheck({ test: ["CMD-SHELL", "exit 1"], interval: 100, diff --git a/packages/modules/mariadb/src/mariadb-container.test.ts b/packages/modules/mariadb/src/mariadb-container.test.ts index d8b875b7f..4666b6f56 100644 --- a/packages/modules/mariadb/src/mariadb-container.test.ts +++ b/packages/modules/mariadb/src/mariadb-container.test.ts @@ -4,8 +4,8 @@ import { MariaDbContainer } from "./mariadb-container"; const IMAGES = ["mariadb:10.11.19", "mariadb:11.8.9", "mariadb:12.3.3", getImage(__dirname)]; -describe.each(IMAGES)("MariaDbContainer (%s)", { timeout: 240_000 }, (image) => { - it("should connect and execute query", async () => { +describe.each(IMAGES)("MariaDbContainer (%s)", { concurrent: false, timeout: 240_000 }, (image) => { + it.concurrent("should connect and execute query", async () => { // mariaDbConnect { await using container = await new MariaDbContainer(image).start(); @@ -24,7 +24,7 @@ describe.each(IMAGES)("MariaDbContainer (%s)", { timeout: 240_000 }, (image) => // } }); - it("should work with database URI", async () => { + it.concurrent("should work with database URI", async () => { // mariaDbUriConnect { const username = "testUser"; const password = "testPassword"; @@ -53,7 +53,7 @@ describe.each(IMAGES)("MariaDbContainer (%s)", { timeout: 240_000 }, (image) => // } }); - it("should set database", async () => { + it.concurrent("should set database", async () => { // mariaDbSetDatabase { await using container = await new MariaDbContainer(image).withDatabase("customDatabase").start(); @@ -72,7 +72,7 @@ describe.each(IMAGES)("MariaDbContainer (%s)", { timeout: 240_000 }, (image) => // } }); - it("should set username", async () => { + it.concurrent("should set username", async () => { // mariaDbSetUsername { await using container = await new MariaDbContainer(image).withUsername("customUsername").start(); @@ -91,7 +91,7 @@ describe.each(IMAGES)("MariaDbContainer (%s)", { timeout: 240_000 }, (image) => // } }); - it("should work with restarted container", async () => { + it.concurrent("should work with restarted container", async () => { await using container = await new MariaDbContainer(image).start(); await container.restart(); diff --git a/packages/modules/mysql/src/mysql-container.test.ts b/packages/modules/mysql/src/mysql-container.test.ts index 3d7f6a58a..f873610e6 100644 --- a/packages/modules/mysql/src/mysql-container.test.ts +++ b/packages/modules/mysql/src/mysql-container.test.ts @@ -4,8 +4,8 @@ import { MySqlContainer } from "./mysql-container"; const IMAGES = ["mysql:8.4.11", "mysql:9.7.2", getImage(__dirname)]; -describe.each(IMAGES)("MySqlContainer (%s)", { timeout: 240_000 }, (image) => { - it("should connect and execute query", async () => { +describe.each(IMAGES)("MySqlContainer (%s)", { concurrent: false, timeout: 240_000 }, (image) => { + it.concurrent("should connect and execute query", async () => { // mysqlConnect { await using container = await new MySqlContainer(image).start(); @@ -24,7 +24,7 @@ describe.each(IMAGES)("MySqlContainer (%s)", { timeout: 240_000 }, (image) => { // } }); - it("should work with database URI", async () => { + it.concurrent("should work with database URI", async () => { // mysqlUriConnect { const username = "testUser"; const password = "testPassword"; @@ -51,7 +51,7 @@ describe.each(IMAGES)("MySqlContainer (%s)", { timeout: 240_000 }, (image) => { // } }); - it("should set database", async () => { + it.concurrent("should set database", async () => { // mysqlSetDatabase { await using container = await new MySqlContainer(image).withDatabase("customDatabase").start(); @@ -71,7 +71,7 @@ describe.each(IMAGES)("MySqlContainer (%s)", { timeout: 240_000 }, (image) => { }); // mysqlSetUsername { - it("should set username", async () => { + it.concurrent("should set username", async () => { await using container = await new MySqlContainer(image).withUsername("customUsername").start(); const client = await createConnection({ @@ -89,7 +89,7 @@ describe.each(IMAGES)("MySqlContainer (%s)", { timeout: 240_000 }, (image) => { }); // } - it("should execute a query and return the result", async () => { + it.concurrent("should execute a query and return the result", async () => { // mysqlExecuteQuery { await using container = await new MySqlContainer(image).start(); @@ -98,7 +98,7 @@ describe.each(IMAGES)("MySqlContainer (%s)", { timeout: 240_000 }, (image) => { // } }); - it("should execute a query as root user", async () => { + it.concurrent("should execute a query as root user", async () => { await using container = await new MySqlContainer(image).withUsername("customUsername").start(); // Test non-root user @@ -110,7 +110,7 @@ describe.each(IMAGES)("MySqlContainer (%s)", { timeout: 240_000 }, (image) => { expect(rootQueryResult).toEqual(expect.stringContaining("user\nroot")); }); - it("should work with restarted container", async () => { + it.concurrent("should work with restarted container", async () => { await using container = await new MySqlContainer(image).start(); await container.restart(); diff --git a/packages/modules/postgresql/src/postgresql-container-snapshot.test.ts b/packages/modules/postgresql/src/postgresql-container-snapshot.test.ts index 2c2873956..ab3c2f5ef 100644 --- a/packages/modules/postgresql/src/postgresql-container-snapshot.test.ts +++ b/packages/modules/postgresql/src/postgresql-container-snapshot.test.ts @@ -2,203 +2,207 @@ import { Client } from "pg"; import { PostgreSqlContainer } from "./postgresql-container"; import { IMAGES } from "./test-helper"; -describe.each(IMAGES)("PostgreSqlContainer snapshot and restore (%s)", { timeout: 180_000 }, (image) => { - it("should create and restore from snapshot", async () => { - // createAndRestoreFromSnapshot { - await using container = await new PostgreSqlContainer(image).start(); - - // Connect to the database - let client = new Client({ - connectionString: container.getConnectionUri(), +describe.each(IMAGES)( + "PostgreSqlContainer snapshot and restore (%s)", + { concurrent: false, timeout: 180_000 }, + (image) => { + it.concurrent("should create and restore from snapshot", async () => { + // createAndRestoreFromSnapshot { + await using container = await new PostgreSqlContainer(image).start(); + + // Connect to the database + let client = new Client({ + connectionString: container.getConnectionUri(), + }); + await client.connect(); + + // Create some test data + await client.query("CREATE TABLE test_table (id SERIAL PRIMARY KEY, name TEXT)"); + await client.query("INSERT INTO test_table (name) VALUES ('initial data')"); + + // Close connection before snapshot (otherwise we'll get an error because user is already connected) + await client.end(); + + // Take a snapshot + await container.snapshot(); + + // Reconnect to database + client = new Client({ + connectionString: container.getConnectionUri(), + }); + await client.connect(); + + // Modify the database + await client.query("INSERT INTO test_table (name) VALUES ('data after snapshot')"); + + // Verify both records exist + let result = await client.query("SELECT * FROM test_table ORDER BY id"); + expect(result.rows).toHaveLength(2); + expect(result.rows[0].name).toEqual("initial data"); + expect(result.rows[1].name).toEqual("data after snapshot"); + + // Close connection before restore (same reason as above) + await client.end(); + + // Restore to the snapshot + await container.restoreSnapshot(); + + // Reconnect to database + client = new Client({ + connectionString: container.getConnectionUri(), + }); + await client.connect(); + + // Verify only the initial data exists after restore + result = await client.query("SELECT * FROM test_table ORDER BY id"); + expect(result.rows).toHaveLength(1); + expect(result.rows[0].name).toEqual("initial data"); + + await client.end(); + // } }); - await client.connect(); - // Create some test data - await client.query("CREATE TABLE test_table (id SERIAL PRIMARY KEY, name TEXT)"); - await client.query("INSERT INTO test_table (name) VALUES ('initial data')"); + it.concurrent("should use custom snapshot name", async () => { + await using container = await new PostgreSqlContainer(image).start(); + const customSnapshotName = "my_custom_snapshot"; - // Close connection before snapshot (otherwise we'll get an error because user is already connected) - await client.end(); + // Connect to the database + let client = new Client({ + connectionString: container.getConnectionUri(), + }); + await client.connect(); - // Take a snapshot - await container.snapshot(); + // Create a test table and insert data + await client.query("CREATE TABLE test_table (id SERIAL PRIMARY KEY, name TEXT)"); + await client.query("INSERT INTO test_table (name) VALUES ('initial data')"); - // Reconnect to database - client = new Client({ - connectionString: container.getConnectionUri(), - }); - await client.connect(); - - // Modify the database - await client.query("INSERT INTO test_table (name) VALUES ('data after snapshot')"); + // Close connection before snapshot + await client.end(); - // Verify both records exist - let result = await client.query("SELECT * FROM test_table ORDER BY id"); - expect(result.rows).toHaveLength(2); - expect(result.rows[0].name).toEqual("initial data"); - expect(result.rows[1].name).toEqual("data after snapshot"); + // Take a snapshot with custom name + await container.snapshot(customSnapshotName); - // Close connection before restore (same reason as above) - await client.end(); + // Reconnect to database + client = new Client({ + connectionString: container.getConnectionUri(), + }); + await client.connect(); - // Restore to the snapshot - await container.restoreSnapshot(); + // Modify the database + await client.query("INSERT INTO test_table (name) VALUES ('data after snapshot')"); - // Reconnect to database - client = new Client({ - connectionString: container.getConnectionUri(), - }); - await client.connect(); + // Close connection before restore + await client.end(); - // Verify only the initial data exists after restore - result = await client.query("SELECT * FROM test_table ORDER BY id"); - expect(result.rows).toHaveLength(1); - expect(result.rows[0].name).toEqual("initial data"); + // Restore using the custom snapshot name + await container.restoreSnapshot(customSnapshotName); - await client.end(); - // } - }); + // Reconnect to database + client = new Client({ + connectionString: container.getConnectionUri(), + }); + await client.connect(); - it("should use custom snapshot name", async () => { - await using container = await new PostgreSqlContainer(image).start(); - const customSnapshotName = "my_custom_snapshot"; + // Verify only the initial data exists after restore + const result = await client.query("SELECT * FROM test_table ORDER BY id"); + expect(result.rows).toHaveLength(1); + expect(result.rows[0].name).toEqual("initial data"); - // Connect to the database - let client = new Client({ - connectionString: container.getConnectionUri(), + await client.end(); }); - await client.connect(); - // Create a test table and insert data - await client.query("CREATE TABLE test_table (id SERIAL PRIMARY KEY, name TEXT)"); - await client.query("INSERT INTO test_table (name) VALUES ('initial data')"); + it.concurrent("should handle multiple snapshots", async () => { + await using container = await new PostgreSqlContainer(image).start(); - // Close connection before snapshot - await client.end(); + // Connect to the database + let client = new Client({ + connectionString: container.getConnectionUri(), + }); + await client.connect(); - // Take a snapshot with custom name - await container.snapshot(customSnapshotName); - - // Reconnect to database - client = new Client({ - connectionString: container.getConnectionUri(), - }); - await client.connect(); + // Create a test table + await client.query("CREATE TABLE test_table (id SERIAL PRIMARY KEY, name TEXT)"); - // Modify the database - await client.query("INSERT INTO test_table (name) VALUES ('data after snapshot')"); - - // Close connection before restore - await client.end(); - - // Restore using the custom snapshot name - await container.restoreSnapshot(customSnapshotName); - - // Reconnect to database - client = new Client({ - connectionString: container.getConnectionUri(), - }); - await client.connect(); + // Close connection before snapshot + await client.end(); - // Verify only the initial data exists after restore - const result = await client.query("SELECT * FROM test_table ORDER BY id"); - expect(result.rows).toHaveLength(1); - expect(result.rows[0].name).toEqual("initial data"); + // Take first snapshot with empty table + await container.snapshot("snapshot1"); - await client.end(); - }); + // Reconnect to database + client = new Client({ + connectionString: container.getConnectionUri(), + }); + await client.connect(); - it("should handle multiple snapshots", async () => { - await using container = await new PostgreSqlContainer(image).start(); + // Add first record + await client.query("INSERT INTO test_table (name) VALUES ('data for snapshot 2')"); - // Connect to the database - let client = new Client({ - connectionString: container.getConnectionUri(), - }); - await client.connect(); + // Close connection before snapshot + await client.end(); - // Create a test table - await client.query("CREATE TABLE test_table (id SERIAL PRIMARY KEY, name TEXT)"); + // Take second snapshot with one record + await container.snapshot("snapshot2"); - // Close connection before snapshot - await client.end(); + // Reconnect to database + client = new Client({ + connectionString: container.getConnectionUri(), + }); + await client.connect(); - // Take first snapshot with empty table - await container.snapshot("snapshot1"); + // Add second record + await client.query("INSERT INTO test_table (name) VALUES ('data after snapshots')"); - // Reconnect to database - client = new Client({ - connectionString: container.getConnectionUri(), - }); - await client.connect(); + // Verify we have two records + let result = await client.query("SELECT COUNT(*) as count FROM test_table"); + expect(result.rows[0].count).toEqual("2"); - // Add first record - await client.query("INSERT INTO test_table (name) VALUES ('data for snapshot 2')"); + // Close connection before restore + await client.end(); - // Close connection before snapshot - await client.end(); + // Restore to first snapshot (empty table) + await container.restoreSnapshot("snapshot1"); - // Take second snapshot with one record - await container.snapshot("snapshot2"); + // Reconnect to database + client = new Client({ + connectionString: container.getConnectionUri(), + }); + await client.connect(); - // Reconnect to database - client = new Client({ - connectionString: container.getConnectionUri(), - }); - await client.connect(); + // Verify table is empty + result = await client.query("SELECT COUNT(*) as count FROM test_table"); + expect(result.rows[0].count).toEqual("0"); - // Add second record - await client.query("INSERT INTO test_table (name) VALUES ('data after snapshots')"); + // Close connection before restore + await client.end(); - // Verify we have two records - let result = await client.query("SELECT COUNT(*) as count FROM test_table"); - expect(result.rows[0].count).toEqual("2"); + // Restore to second snapshot (one record) + await container.restoreSnapshot("snapshot2"); - // Close connection before restore - await client.end(); + // Reconnect to database + client = new Client({ + connectionString: container.getConnectionUri(), + }); + await client.connect(); - // Restore to first snapshot (empty table) - await container.restoreSnapshot("snapshot1"); + // Verify we have one record + result = await client.query("SELECT * FROM test_table"); + expect(result.rows).toHaveLength(1); + expect(result.rows[0].name).toEqual("data for snapshot 2"); - // Reconnect to database - client = new Client({ - connectionString: container.getConnectionUri(), + await client.end(); }); - await client.connect(); - // Verify table is empty - result = await client.query("SELECT COUNT(*) as count FROM test_table"); - expect(result.rows[0].count).toEqual("0"); + it.concurrent("should throw an error when trying to snapshot postgres system database", async () => { + await using container = await new PostgreSqlContainer(image).withDatabase("postgres").start(); - // Close connection before restore - await client.end(); + await expect(container.snapshot()).rejects.toThrow( + "Snapshot feature is not supported when using the postgres system database" + ); - // Restore to second snapshot (one record) - await container.restoreSnapshot("snapshot2"); - - // Reconnect to database - client = new Client({ - connectionString: container.getConnectionUri(), + await expect(container.restoreSnapshot()).rejects.toThrow( + "Snapshot feature is not supported when using the postgres system database" + ); }); - await client.connect(); - - // Verify we have one record - result = await client.query("SELECT * FROM test_table"); - expect(result.rows).toHaveLength(1); - expect(result.rows[0].name).toEqual("data for snapshot 2"); - - await client.end(); - }); - - it("should throw an error when trying to snapshot postgres system database", async () => { - await using container = await new PostgreSqlContainer(image).withDatabase("postgres").start(); - - await expect(container.snapshot()).rejects.toThrow( - "Snapshot feature is not supported when using the postgres system database" - ); - - await expect(container.restoreSnapshot()).rejects.toThrow( - "Snapshot feature is not supported when using the postgres system database" - ); - }); -}); + } +); diff --git a/packages/modules/postgresql/src/postgresql-container.test.ts b/packages/modules/postgresql/src/postgresql-container.test.ts index 34061e2c5..d44b27523 100644 --- a/packages/modules/postgresql/src/postgresql-container.test.ts +++ b/packages/modules/postgresql/src/postgresql-container.test.ts @@ -7,8 +7,8 @@ const SSL_CA_CERT = path.resolve(__dirname, "test-certs/ca.crt"); const SSL_SERVER_CERT = path.resolve(__dirname, "test-certs/server.crt"); const SSL_SERVER_KEY = path.resolve(__dirname, "test-certs/server.key"); -describe.each(IMAGES)("PostgreSqlContainer (%s)", { timeout: 180_000 }, (image) => { - it("should connect and return a query result", async () => { +describe.each(IMAGES)("PostgreSqlContainer (%s)", { concurrent: false, timeout: 180_000 }, (image) => { + it.concurrent("should connect and return a query result", async () => { // pgConnect { await using container = await new PostgreSqlContainer(image).start(); @@ -28,7 +28,7 @@ describe.each(IMAGES)("PostgreSqlContainer (%s)", { timeout: 180_000 }, (image) // } }); - it("should work with database URI", async () => { + it.concurrent("should work with database URI", async () => { // pgUriConnect { await using container = await new PostgreSqlContainer(image).start(); @@ -44,7 +44,7 @@ describe.each(IMAGES)("PostgreSqlContainer (%s)", { timeout: 180_000 }, (image) await client.end(); }); - it("should set database", async () => { + it.concurrent("should set database", async () => { // pgSetDatabase { await using container = await new PostgreSqlContainer(image).withDatabase("customDatabase").start(); // } @@ -64,7 +64,7 @@ describe.each(IMAGES)("PostgreSqlContainer (%s)", { timeout: 180_000 }, (image) await client.end(); }); - it("should set username", async () => { + it.concurrent("should set username", async () => { // pgSetUsername { await using container = await new PostgreSqlContainer(image).withUsername("customUsername").start(); // } @@ -84,7 +84,7 @@ describe.each(IMAGES)("PostgreSqlContainer (%s)", { timeout: 180_000 }, (image) await client.end(); }); - it("should work with restarted container", async () => { + it.concurrent("should work with restarted container", async () => { await using container = await new PostgreSqlContainer(image).start(); await container.restart(); @@ -103,7 +103,7 @@ describe.each(IMAGES)("PostgreSqlContainer (%s)", { timeout: 180_000 }, (image) await client.end(); }); - it("should allow custom healthcheck", async () => { + it.concurrent("should allow custom healthcheck", async () => { const container = new PostgreSqlContainer(image).withHealthCheck({ test: ["CMD-SHELL", "exit 1"], interval: 100, @@ -114,7 +114,7 @@ describe.each(IMAGES)("PostgreSqlContainer (%s)", { timeout: 180_000 }, (image) await expect(() => container.start()).rejects.toThrow(); }); - it("should connect with SSL", async () => { + it.concurrent("should connect with SSL", async () => { // pgSslConnect { await using container = await new PostgreSqlContainer(image) .withSSLCert(SSL_CA_CERT, SSL_SERVER_CERT, SSL_SERVER_KEY) @@ -139,7 +139,7 @@ describe.each(IMAGES)("PostgreSqlContainer (%s)", { timeout: 180_000 }, (image) // } }); - it("should validate SSL certificate paths", () => { + it.concurrent("should validate SSL certificate paths", () => { const container = new PostgreSqlContainer(image); expect(() => container.withSSL("", "server.key")).toThrow("SSL certificate file should not be empty."); From 96e0fc43eee8f70fb0b04f7f87a83a1331e67c3f Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 13:18:05 +0100 Subject: [PATCH 6/6] Run the pgvector, PostGIS and Timescale tests on their own images --- packages/modules/postgresql/Dockerfile | 3 +++ .../postgresql/src/pgvector-container.test.ts | 2 +- .../postgresql/src/postgis-container.test.ts | 13 +++++++++---- .../postgresql/src/timescale-container.test.ts | 2 +- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/modules/postgresql/Dockerfile b/packages/modules/postgresql/Dockerfile index 38f750c05..3a45116dd 100644 --- a/packages/modules/postgresql/Dockerfile +++ b/packages/modules/postgresql/Dockerfile @@ -1 +1,4 @@ FROM postgres:18.6-alpine +FROM pgvector/pgvector:0.8.7-pg18 +FROM postgis/postgis:18-3.6 +FROM timescale/timescaledb:2.30.2-pg18 diff --git a/packages/modules/postgresql/src/pgvector-container.test.ts b/packages/modules/postgresql/src/pgvector-container.test.ts index 2b48b58ab..8f10a46bd 100644 --- a/packages/modules/postgresql/src/pgvector-container.test.ts +++ b/packages/modules/postgresql/src/pgvector-container.test.ts @@ -3,7 +3,7 @@ import { Client } from "pg"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { PostgreSqlContainer } from "./postgresql-container"; -const IMAGE = getImage(__dirname); +const IMAGE = getImage(__dirname, 1); const SSL_SERVER_CERT = path.resolve(__dirname, "test-certs/server.crt"); const SSL_SERVER_KEY = path.resolve(__dirname, "test-certs/server.key"); diff --git a/packages/modules/postgresql/src/postgis-container.test.ts b/packages/modules/postgresql/src/postgis-container.test.ts index e5e3ebcd4..362104f90 100644 --- a/packages/modules/postgresql/src/postgis-container.test.ts +++ b/packages/modules/postgresql/src/postgis-container.test.ts @@ -3,13 +3,15 @@ import { Client } from "pg"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { PostgreSqlContainer } from "./postgresql-container"; -const IMAGE = getImage(__dirname); +const IMAGE = getImage(__dirname, 2); +// postgis/postgis publishes no arm64 image. +const PLATFORM = "linux/amd64"; const SSL_SERVER_CERT = path.resolve(__dirname, "test-certs/server.crt"); const SSL_SERVER_KEY = path.resolve(__dirname, "test-certs/server.key"); describe("PostgisContainer", { timeout: 180_000 }, () => { it("should work", async () => { - await using container = await new PostgreSqlContainer(IMAGE).start(); + await using container = await new PostgreSqlContainer(IMAGE).withPlatform(PLATFORM).start(); const client = new Client({ host: container.getHost(), @@ -27,7 +29,7 @@ describe("PostgisContainer", { timeout: 180_000 }, () => { }); it("should restart", async () => { - await using container = await new PostgreSqlContainer(IMAGE).start(); + await using container = await new PostgreSqlContainer(IMAGE).withPlatform(PLATFORM).start(); await container.restart(); const client = new Client({ @@ -46,7 +48,10 @@ describe("PostgisContainer", { timeout: 180_000 }, () => { }); it("should connect with SSL", async () => { - await using container = await new PostgreSqlContainer(IMAGE).withSSL(SSL_SERVER_CERT, SSL_SERVER_KEY).start(); + await using container = await new PostgreSqlContainer(IMAGE) + .withPlatform(PLATFORM) + .withSSL(SSL_SERVER_CERT, SSL_SERVER_KEY) + .start(); const client = new Client({ host: container.getHost(), diff --git a/packages/modules/postgresql/src/timescale-container.test.ts b/packages/modules/postgresql/src/timescale-container.test.ts index 00db75557..2d1126bae 100644 --- a/packages/modules/postgresql/src/timescale-container.test.ts +++ b/packages/modules/postgresql/src/timescale-container.test.ts @@ -3,7 +3,7 @@ import { Client } from "pg"; import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { PostgreSqlContainer } from "./postgresql-container"; -const IMAGE = getImage(__dirname); +const IMAGE = getImage(__dirname, 3); const SSL_SERVER_CERT = path.resolve(__dirname, "test-certs/server.crt"); const SSL_SERVER_KEY = path.resolve(__dirname, "test-certs/server.key");