From c5a6e703a11f2c56e6fc312c40a5f5b51199eac0 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Mon, 12 Oct 2026 19:58:13 +0100 Subject: [PATCH 1/4] Test Kafka and Redpanda against their supported versions --- .../modules/kafka/src/kafka-container-7.test.ts | 8 ++++---- .../kafka/src/kafka-container-latest.test.ts | 6 ++++++ .../modules/redpanda/src/redpanda-container.test.ts | 13 +++++++------ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/modules/kafka/src/kafka-container-7.test.ts b/packages/modules/kafka/src/kafka-container-7.test.ts index cedffc668..aa74fa152 100644 --- a/packages/modules/kafka/src/kafka-container-7.test.ts +++ b/packages/modules/kafka/src/kafka-container-7.test.ts @@ -4,7 +4,7 @@ import { GenericContainer, Network } from "testcontainers"; import { KafkaContainer } from "./kafka-container"; import { assertMessageProducedAndConsumed } from "./test-helper"; -const IMAGE = "confluentinc/cp-kafka:7.9.1"; +const IMAGE = "confluentinc/cp-kafka:7.9.10"; describe("KafkaContainer", { timeout: 240_000 }, () => { it("should connect using in-built zoo-keeper", async () => { @@ -43,7 +43,7 @@ describe("KafkaContainer", { timeout: 240_000 }, () => { const zooKeeperHost = "zookeeper"; const zooKeeperPort = 2181; - await using _ = await new GenericContainer("confluentinc/cp-zookeeper:7.5.0") + await using _ = await new GenericContainer("confluentinc/cp-zookeeper:7.9.10") .withNetwork(network) .withNetworkAliases(zooKeeperHost) .withEnvironment({ ZOOKEEPER_CLIENT_PORT: zooKeeperPort.toString() }) @@ -71,7 +71,7 @@ describe("KafkaContainer", { timeout: 240_000 }, () => { it(`should connect locally with ZK`, async () => { // kafkaSsl { - await using container = await new KafkaContainer("confluentinc/cp-kafka:7.5.0") + await using container = await new KafkaContainer(IMAGE) .withSaslSslListener({ port: 9096, sasl: { @@ -111,7 +111,7 @@ describe("KafkaContainer", { timeout: 240_000 }, () => { }); it(`should connect locally with Kraft`, async () => { - await using container = await new KafkaContainer("confluentinc/cp-kafka:7.5.0") + await using container = await new KafkaContainer(IMAGE) .withKraft() .withSaslSslListener({ port: 9096, diff --git a/packages/modules/kafka/src/kafka-container-latest.test.ts b/packages/modules/kafka/src/kafka-container-latest.test.ts index 0207bb695..f767e4ba6 100644 --- a/packages/modules/kafka/src/kafka-container-latest.test.ts +++ b/packages/modules/kafka/src/kafka-container-latest.test.ts @@ -18,6 +18,12 @@ describe("KafkaContainer", { timeout: 240_000 }, () => { // } }); + it.each(["confluentinc/cp-kafka:8.2.4"])("should connect with %s", async (image) => { + await using container = await new KafkaContainer(image).start(); + + await assertMessageProducedAndConsumed(container); + }); + it("should connect with custom network", async () => { await using network = await new Network().start(); await using container = await new KafkaContainer(IMAGE).withNetwork(network).start(); diff --git a/packages/modules/redpanda/src/redpanda-container.test.ts b/packages/modules/redpanda/src/redpanda-container.test.ts index 59f0111ca..1a4aa46b6 100644 --- a/packages/modules/redpanda/src/redpanda-container.test.ts +++ b/packages/modules/redpanda/src/redpanda-container.test.ts @@ -5,6 +5,7 @@ import { RedpandaContainer } from "./redpanda-container"; import { assertMessageProducedAndConsumed } from "./test-helper"; const IMAGE = getImage(__dirname); +const OLDEST_IMAGE = "docker.io/redpandadata/redpanda:v26.1.18"; // Writes each copied file in two parts with a pause between them, as a busy Docker host can. class SlowCopyRedpandaContainer extends RedpandaContainer { @@ -25,24 +26,24 @@ class SlowCopyRedpandaContainer extends RedpandaContainer { } } -describe("RedpandaContainer", { timeout: 240_000 }, () => { +describe.each([IMAGE, OLDEST_IMAGE])("RedpandaContainer (%s)", { timeout: 240_000 }, (image) => { it("should connect", async () => { // connectToKafka { - await using container = await new RedpandaContainer(IMAGE).start(); + await using container = await new RedpandaContainer(image).start(); await assertMessageProducedAndConsumed(container); // } }); it("should connect when the config reaches the container late and in parts", async () => { - await using container = await new SlowCopyRedpandaContainer(IMAGE).start(); + await using container = await new SlowCopyRedpandaContainer(image).start(); await assertMessageProducedAndConsumed(container); }); it("should connect to schema registry", async () => { // connectToSchemaRegistry { - await using container = await new RedpandaContainer(IMAGE).start(); + await using container = await new RedpandaContainer(image).start(); const schemaRegistryUrl = container.getSchemaRegistryAddress(); const response = await fetch(`${schemaRegistryUrl}/subjects`, { @@ -58,7 +59,7 @@ describe("RedpandaContainer", { timeout: 240_000 }, () => { it("should connect to admin", async () => { // connectToAdmin { - await using container = await new RedpandaContainer(IMAGE).start(); + await using container = await new RedpandaContainer(image).start(); const adminUrl = `${container.getAdminAddress()}/v1`; const response = await fetch(adminUrl); @@ -69,7 +70,7 @@ describe("RedpandaContainer", { timeout: 240_000 }, () => { it("should connect to rest proxy", async () => { // connectToRestProxy { - await using container = await new RedpandaContainer(IMAGE).start(); + await using container = await new RedpandaContainer(image).start(); const restProxyUrl = `${container.getRestProxyAddress()}/topics`; const response = await fetch(restProxyUrl); From 242d847a9fa9bf3af39cd9675e2569010408af15 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 10:40:08 +0100 Subject: [PATCH 2/4] Loop the suites over a list of every supported image --- .../kafka/src/kafka-container-latest.test.ts | 24 +++++++------------ .../redpanda/src/redpanda-container.test.ts | 5 ++-- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/packages/modules/kafka/src/kafka-container-latest.test.ts b/packages/modules/kafka/src/kafka-container-latest.test.ts index f767e4ba6..927f710b4 100644 --- a/packages/modules/kafka/src/kafka-container-latest.test.ts +++ b/packages/modules/kafka/src/kafka-container-latest.test.ts @@ -5,35 +5,29 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { KafkaContainer, type SaslSslListenerOptions } from "./kafka-container"; import { assertMessageProducedAndConsumed } from "./test-helper"; -const IMAGE = getImage(__dirname); +const IMAGES = ["confluentinc/cp-kafka:8.2.4", getImage(__dirname)]; -describe("KafkaContainer", { timeout: 240_000 }, () => { +describe.each(IMAGES)("KafkaContainer (%s)", { timeout: 240_000 }, (image) => { const certificatesDir = path.resolve(__dirname, "..", "test-certs"); it("should connect", async () => { // kafkaLatestConnect { - await using container = await new KafkaContainer(IMAGE).start(); - - await assertMessageProducedAndConsumed(container); - // } - }); - - it.each(["confluentinc/cp-kafka:8.2.4"])("should connect with %s", async (image) => { await using container = await new KafkaContainer(image).start(); await assertMessageProducedAndConsumed(container); + // } }); it("should connect with custom network", async () => { await using network = await new Network().start(); - await using container = await new KafkaContainer(IMAGE).withNetwork(network).start(); + await using container = await new KafkaContainer(image).withNetwork(network).start(); await assertMessageProducedAndConsumed(container); }); it("should be reusable", async () => { - await using container1 = await new KafkaContainer(IMAGE).withReuse().start(); - const container2 = await new KafkaContainer(IMAGE).withReuse().start(); + await using container1 = await new KafkaContainer(image).withReuse().start(); + const container2 = await new KafkaContainer(image).withReuse().start(); expect(container2.getId()).toBe(container1.getId()); }); @@ -59,7 +53,7 @@ describe("KafkaContainer", { timeout: 240_000 }, () => { }, }; - await using container = await new KafkaContainer(IMAGE).withSaslSslListener(saslConfig).start(); + await using container = await new KafkaContainer(image).withSaslSslListener(saslConfig).start(); await assertMessageProducedAndConsumed( container, @@ -101,13 +95,13 @@ describe("KafkaContainer", { timeout: 240_000 }, () => { }, }; - await using _ = await new KafkaContainer(IMAGE) + await using _ = await new KafkaContainer(image) .withNetwork(network) .withNetworkAliases("kafka") .withSaslSslListener(saslConfig) .start(); - await using kafkaCliContainer = await new GenericContainer(IMAGE) + await using kafkaCliContainer = await new GenericContainer(image) .withNetwork(network) .withCommand(["bash", "-c", "sleep infinity"]) .withCopyFilesToContainer([ diff --git a/packages/modules/redpanda/src/redpanda-container.test.ts b/packages/modules/redpanda/src/redpanda-container.test.ts index 1a4aa46b6..61b3b84a4 100644 --- a/packages/modules/redpanda/src/redpanda-container.test.ts +++ b/packages/modules/redpanda/src/redpanda-container.test.ts @@ -4,8 +4,7 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { RedpandaContainer } from "./redpanda-container"; import { assertMessageProducedAndConsumed } from "./test-helper"; -const IMAGE = getImage(__dirname); -const OLDEST_IMAGE = "docker.io/redpandadata/redpanda:v26.1.18"; +const IMAGES = ["docker.io/redpandadata/redpanda:v26.1.18", getImage(__dirname)]; // Writes each copied file in two parts with a pause between them, as a busy Docker host can. class SlowCopyRedpandaContainer extends RedpandaContainer { @@ -26,7 +25,7 @@ class SlowCopyRedpandaContainer extends RedpandaContainer { } } -describe.each([IMAGE, OLDEST_IMAGE])("RedpandaContainer (%s)", { timeout: 240_000 }, (image) => { +describe.each(IMAGES)("RedpandaContainer (%s)", { timeout: 240_000 }, (image) => { it("should connect", async () => { // connectToKafka { await using container = await new RedpandaContainer(image).start(); From 4facbc2877cb1934706619ecdd01d296f7d94db4 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 10:55:32 +0100 Subject: [PATCH 3/4] Run the Kafka 8 suite sequentially --- packages/modules/kafka/src/kafka-container-latest.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/kafka/src/kafka-container-latest.test.ts b/packages/modules/kafka/src/kafka-container-latest.test.ts index 927f710b4..f94215c1a 100644 --- a/packages/modules/kafka/src/kafka-container-latest.test.ts +++ b/packages/modules/kafka/src/kafka-container-latest.test.ts @@ -7,7 +7,7 @@ import { assertMessageProducedAndConsumed } from "./test-helper"; const IMAGES = ["confluentinc/cp-kafka:8.2.4", getImage(__dirname)]; -describe.each(IMAGES)("KafkaContainer (%s)", { timeout: 240_000 }, (image) => { +describe.each(IMAGES)("KafkaContainer (%s)", { concurrent: false, timeout: 240_000 }, (image) => { const certificatesDir = path.resolve(__dirname, "..", "test-certs"); it("should connect", async () => { From bb743d09ca97fa02285c3faf4817bc58190799c9 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Tue, 13 Oct 2026 11:12:11 +0100 Subject: [PATCH 4/4] Keep Kafka 8 and Redpanda on the Dockerfile image only --- .../kafka/src/kafka-container-latest.test.ts | 18 +++++++++--------- .../redpanda/src/redpanda-container.test.ts | 14 +++++++------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/modules/kafka/src/kafka-container-latest.test.ts b/packages/modules/kafka/src/kafka-container-latest.test.ts index f94215c1a..0207bb695 100644 --- a/packages/modules/kafka/src/kafka-container-latest.test.ts +++ b/packages/modules/kafka/src/kafka-container-latest.test.ts @@ -5,14 +5,14 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { KafkaContainer, type SaslSslListenerOptions } from "./kafka-container"; import { assertMessageProducedAndConsumed } from "./test-helper"; -const IMAGES = ["confluentinc/cp-kafka:8.2.4", getImage(__dirname)]; +const IMAGE = getImage(__dirname); -describe.each(IMAGES)("KafkaContainer (%s)", { concurrent: false, timeout: 240_000 }, (image) => { +describe("KafkaContainer", { timeout: 240_000 }, () => { const certificatesDir = path.resolve(__dirname, "..", "test-certs"); it("should connect", async () => { // kafkaLatestConnect { - await using container = await new KafkaContainer(image).start(); + await using container = await new KafkaContainer(IMAGE).start(); await assertMessageProducedAndConsumed(container); // } @@ -20,14 +20,14 @@ describe.each(IMAGES)("KafkaContainer (%s)", { concurrent: false, timeout: 240_0 it("should connect with custom network", async () => { await using network = await new Network().start(); - await using container = await new KafkaContainer(image).withNetwork(network).start(); + await using container = await new KafkaContainer(IMAGE).withNetwork(network).start(); await assertMessageProducedAndConsumed(container); }); it("should be reusable", async () => { - await using container1 = await new KafkaContainer(image).withReuse().start(); - const container2 = await new KafkaContainer(image).withReuse().start(); + await using container1 = await new KafkaContainer(IMAGE).withReuse().start(); + const container2 = await new KafkaContainer(IMAGE).withReuse().start(); expect(container2.getId()).toBe(container1.getId()); }); @@ -53,7 +53,7 @@ describe.each(IMAGES)("KafkaContainer (%s)", { concurrent: false, timeout: 240_0 }, }; - await using container = await new KafkaContainer(image).withSaslSslListener(saslConfig).start(); + await using container = await new KafkaContainer(IMAGE).withSaslSslListener(saslConfig).start(); await assertMessageProducedAndConsumed( container, @@ -95,13 +95,13 @@ describe.each(IMAGES)("KafkaContainer (%s)", { concurrent: false, timeout: 240_0 }, }; - await using _ = await new KafkaContainer(image) + await using _ = await new KafkaContainer(IMAGE) .withNetwork(network) .withNetworkAliases("kafka") .withSaslSslListener(saslConfig) .start(); - await using kafkaCliContainer = await new GenericContainer(image) + await using kafkaCliContainer = await new GenericContainer(IMAGE) .withNetwork(network) .withCommand(["bash", "-c", "sleep infinity"]) .withCopyFilesToContainer([ diff --git a/packages/modules/redpanda/src/redpanda-container.test.ts b/packages/modules/redpanda/src/redpanda-container.test.ts index 61b3b84a4..59f0111ca 100644 --- a/packages/modules/redpanda/src/redpanda-container.test.ts +++ b/packages/modules/redpanda/src/redpanda-container.test.ts @@ -4,7 +4,7 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper"; import { RedpandaContainer } from "./redpanda-container"; import { assertMessageProducedAndConsumed } from "./test-helper"; -const IMAGES = ["docker.io/redpandadata/redpanda:v26.1.18", getImage(__dirname)]; +const IMAGE = getImage(__dirname); // Writes each copied file in two parts with a pause between them, as a busy Docker host can. class SlowCopyRedpandaContainer extends RedpandaContainer { @@ -25,24 +25,24 @@ class SlowCopyRedpandaContainer extends RedpandaContainer { } } -describe.each(IMAGES)("RedpandaContainer (%s)", { timeout: 240_000 }, (image) => { +describe("RedpandaContainer", { timeout: 240_000 }, () => { it("should connect", async () => { // connectToKafka { - await using container = await new RedpandaContainer(image).start(); + await using container = await new RedpandaContainer(IMAGE).start(); await assertMessageProducedAndConsumed(container); // } }); it("should connect when the config reaches the container late and in parts", async () => { - await using container = await new SlowCopyRedpandaContainer(image).start(); + await using container = await new SlowCopyRedpandaContainer(IMAGE).start(); await assertMessageProducedAndConsumed(container); }); it("should connect to schema registry", async () => { // connectToSchemaRegistry { - await using container = await new RedpandaContainer(image).start(); + await using container = await new RedpandaContainer(IMAGE).start(); const schemaRegistryUrl = container.getSchemaRegistryAddress(); const response = await fetch(`${schemaRegistryUrl}/subjects`, { @@ -58,7 +58,7 @@ describe.each(IMAGES)("RedpandaContainer (%s)", { timeout: 240_000 }, (image) => it("should connect to admin", async () => { // connectToAdmin { - await using container = await new RedpandaContainer(image).start(); + await using container = await new RedpandaContainer(IMAGE).start(); const adminUrl = `${container.getAdminAddress()}/v1`; const response = await fetch(adminUrl); @@ -69,7 +69,7 @@ describe.each(IMAGES)("RedpandaContainer (%s)", { timeout: 240_000 }, (image) => it("should connect to rest proxy", async () => { // connectToRestProxy { - await using container = await new RedpandaContainer(image).start(); + await using container = await new RedpandaContainer(IMAGE).start(); const restProxyUrl = `${container.getRestProxyAddress()}/topics`; const response = await fetch(restProxyUrl);