Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1038321
Test Valkey, Redis, NATS, etcd and CouchDB against their oldest suppo…
cristianrgreco Oct 12, 2026
34bf681
Test Elasticsearch, OpenSearch and MongoDB against their supported ve…
cristianrgreco Oct 12, 2026
9e2c74d
Drop support for MongoDB below 5.0 and Kafka KRaft below Confluent Pl…
cristianrgreco Oct 13, 2026
44a737e
Remove the Kafka version guards and the Neo4j 4 plugin settings
cristianrgreco Oct 13, 2026
4416c14
Loop the suites over a list of every supported image
cristianrgreco Oct 13, 2026
2677362
Loop the suites over a list of every supported image
cristianrgreco Oct 13, 2026
ac6f9e5
Merge branch 'claude/supported-versions-search-document' into claude/…
cristianrgreco Oct 13, 2026
1dd99f3
Merge remote-tracking branch 'origin/main' into claude/drop-eol-kafka…
cristianrgreco Oct 13, 2026
1b3f8c0
List one image per supported major version
cristianrgreco Oct 13, 2026
cf32376
List one image per supported major version
cristianrgreco Oct 13, 2026
235ee09
Merge branch 'claude/supported-versions-search-document' into claude/…
cristianrgreco Oct 13, 2026
a48b73f
Run the images one after another and each image's tests concurrently
cristianrgreco Oct 13, 2026
03d42c5
Run the images one after another and each image's tests concurrently
cristianrgreco Oct 13, 2026
26c3bec
Merge branch 'claude/supported-versions-search-document' into claude/…
cristianrgreco Oct 13, 2026
29fcdc0
Test MongoDB Atlas Local against both supported majors
cristianrgreco Oct 13, 2026
b0d9ffb
Merge branch 'claude/supported-versions-search-document' into claude/…
cristianrgreco Oct 13, 2026
ad7822f
Merge branch 'claude/supported-versions-key-value' into claude/drop-e…
cristianrgreco Oct 13, 2026
b5db349
Remove Redis Stack support
cristianrgreco Oct 13, 2026
b82a0a0
Merge remote-tracking branch 'origin/main' into claude/drop-eol-kafka…
cristianrgreco Oct 13, 2026
c856c89
Merge remote-tracking branch 'origin/main' into claude/supported-vers…
cristianrgreco Oct 13, 2026
58488b1
Test each Elasticsearch major with the client of the same major
cristianrgreco Oct 13, 2026
a42d17e
Merge branch 'claude/supported-versions-search-document' into claude/…
cristianrgreco Oct 13, 2026
0dac9c3
Name the Redis Stack test block after its image
cristianrgreco Oct 13, 2026
fdedfae
Merge branch 'claude/supported-versions-key-value' into claude/drop-e…
cristianrgreco Oct 13, 2026
9709ab4
Merge remote-tracking branch 'origin/main' into claude/drop-eol-kafka…
cristianrgreco Oct 13, 2026
3831835
Test Elasticsearch 8 in its own file with the 8.x client
cristianrgreco Oct 13, 2026
7656356
Merge branch 'claude/supported-versions-search-document' into claude/…
cristianrgreco Oct 13, 2026
37e496a
List both Elasticsearch client majors in the docs
cristianrgreco Oct 14, 2026
80d18c3
Merge branch 'claude/supported-versions-search-document' into claude/…
cristianrgreco Oct 14, 2026
80eb713
Run the main Elasticsearch suite sequentially too
cristianrgreco Oct 14, 2026
dc81c16
Merge branch 'claude/supported-versions-search-document' into claude/…
cristianrgreco Oct 14, 2026
1d71e81
Merge remote-tracking branch 'origin/main' into claude/drop-eol-kafka…
cristianrgreco Oct 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions docs/modules/redis.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,6 @@ Choose an image from the [container registry](https://hub.docker.com/_/redis) an
[](../../packages/modules/redis/src/redis-container.test.ts) inside_block:withPredefinedData
<!--/codeinclude-->

### Redis stack

<!--codeinclude-->
[](../../packages/modules/redis/src/redis-container.test.ts) inside_block:startWithRedisStack
<!--/codeinclude-->

### Execute a command inside the container

<!--codeinclude-->
Expand Down
1 change: 0 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 0 additions & 30 deletions packages/modules/kafka/src/kafka-container-7.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,40 +222,10 @@ describe("KafkaContainer", { timeout: 240_000 }, () => {
await assertMessageProducedAndConsumed(container);
});

it("should throw an error when using kraft and and confluence platfom below 7.0.0", async () => {
expect(() => new KafkaContainer("confluentinc/cp-kafka:6.2.14").withKraft()).toThrow(
"Provided Confluent Platform's version 6.2.14 is not supported in Kraft mode (must be 7.0.0 or above)"
);
});

it("should connect using kraft and custom network", async () => {
await using network = await new Network().start();
await using container = await new KafkaContainer(IMAGE).withKraft().withNetwork(network).start();

await assertMessageProducedAndConsumed(container);
});

it("should throw an error when using kraft wit sasl and confluence platfom below 7.5.0", async () => {
const kafkaContainer = new KafkaContainer("confluentinc/cp-kafka:7.4.0").withKraft().withSaslSslListener({
port: 9094,
sasl: {
mechanism: "SCRAM-SHA-512",
user: {
name: "app-user",
password: "userPassword",
},
},
keystore: {
content: "fake",
passphrase: "serverKeystorePassword",
},
truststore: {
content: "fake",
passphrase: "serverTruststorePassword",
},
});
await expect(() => kafkaContainer.start()).rejects.toThrow(
"Provided Confluent Platform's version 7.4.0 is not supported in Kraft mode with sasl (must be 7.5.0 or above)"
);
});
});
36 changes: 1 addition & 35 deletions packages/modules/kafka/src/kafka-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ const DEFAULT_CLUSTER_ID = "4L6g3nShT-eMCtK--X86sw";
const STARTER_SCRIPT = "/tmp/testcontainers_start.sh";
const WAIT_FOR_SCRIPT_MESSAGE = "Waiting for script...";

// https://docs.confluent.io/platform/7.0.0/release-notes/index.html#ak-raft-kraft
const MIN_KRAFT_VERSION = "7.0.0";
const MIN_KRAFT_SASL_VERSION = "7.5.0";

export interface SaslSslListenerOptions {
sasl: SaslOptions;
port: number;
Expand Down Expand Up @@ -93,7 +89,6 @@ export class KafkaContainer extends GenericContainer {
}

public withKraft(): this {
this.verifyMinKraftVersion();
this.mode = KafkaMode.KRAFT;
this.zooKeeperHost = undefined;
this.zooKeeperPort = undefined;
Expand Down Expand Up @@ -147,15 +142,6 @@ export class KafkaContainer extends GenericContainer {
}

public override async start(): Promise<StartedKafkaContainer> {
if (
this.mode === KafkaMode.KRAFT &&
this.saslSslConfig &&
satisfies(this.imageName.tag, `<${MIN_KRAFT_SASL_VERSION}`)
) {
throw new Error(
`Provided Confluent Platform's version ${this.imageName.tag} is not supported in Kraft mode with sasl (must be ${MIN_KRAFT_SASL_VERSION} or above)`
);
}
return new StartedKafkaContainer(await super.start());
}

Expand All @@ -168,17 +154,14 @@ export class KafkaContainer extends GenericContainer {
// exporting KAFKA_ADVERTISED_LISTENERS with the container hostname
command += `export KAFKA_ADVERTISED_LISTENERS=${advertisedListeners}\n`;

if (this.mode !== KafkaMode.KRAFT || satisfies(this.imageName.tag, "<7.4.0")) {
if (this.mode !== KafkaMode.KRAFT) {
// Optimization: skip the checks
command += "echo '' > /etc/confluent/docker/ensure \n";
}
if (this.mode === KafkaMode.KRAFT) {
if (this.saslSslConfig) {
command += this.commandKraftCreateUser(this.saslSslConfig);
}
if (satisfies(this.imageName.tag, "<7.4.0")) {
command += this.commandKraft();
}
} else if (this.mode === KafkaMode.EMBEDDED_ZOOKEEPER) {
command += this.commandZookeeper();
}
Expand Down Expand Up @@ -275,14 +258,6 @@ export class KafkaContainer extends GenericContainer {
}
}

private verifyMinKraftVersion() {
if (satisfies(this.imageName.tag, `<${MIN_KRAFT_VERSION}`)) {
throw new Error(
`Provided Confluent Platform's version ${this.imageName.tag} is not supported in Kraft mode (must be ${MIN_KRAFT_VERSION} or above)`
);
}
}

private commandKraftCreateUser(saslOptions: SaslSslListenerOptions): string {
return (
"echo 'kafka-storage format --ignore-formatted " +
Expand All @@ -292,15 +267,6 @@ export class KafkaContainer extends GenericContainer {
);
}

private commandKraft(): string {
let command = "sed -i '/KAFKA_ZOOKEEPER_CONNECT/d' /etc/confluent/docker/configure\n";
command +=
"echo 'kafka-storage format --ignore-formatted " +
`-t "${this.environment.CLUSTER_ID}" ` +
"-c /etc/kafka/kafka.properties' >> /etc/confluent/docker/configure\n";
return command;
}

private commandZookeeper(): string {
let command = `echo 'clientPort=${DEFAULT_ZOOKEEPER_PORT}' > zookeeper.properties\n`;
command += "echo 'dataDir=/var/lib/zookeeper/data' >> zookeeper.properties\n";
Expand Down
1 change: 0 additions & 1 deletion packages/modules/mongodb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"mongoose": "^9.10.4"
},
"dependencies": {
"compare-versions": "^6.1.1",
"testcontainers": "^12.2.1"
}
}
21 changes: 2 additions & 19 deletions packages/modules/mongodb/src/mongodb-container.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { satisfies } from "compare-versions";
import { AbstractStartedContainer, GenericContainer, type StartedTestContainer, Wait } from "testcontainers";

const MONGODB_PORT = 27017;
Expand Down Expand Up @@ -63,12 +62,7 @@ export class MongoDBContainer extends GenericContainer {
}

private buildMongoEvalCommand(command: string) {
const args = [];
if (this.isV5OrLater()) {
args.push("mongosh");
} else {
args.push("mongo", "admin");
}
const args = ["mongosh"];
if (this.username && this.password) {
args.push("-u", this.username, "-p", this.password);
}
Expand All @@ -77,18 +71,7 @@ export class MongoDBContainer extends GenericContainer {
}

private getRsInitCmd() {
if (this.isV5OrLater()) {
return `'try { rs.status(); } catch (e) { rs.initiate(); } while (db.runCommand({isMaster: 1}).ismaster==false) { sleep(100); }'`;
}
return `'try { rs.initiate(); } catch (e) {} while (db.runCommand({isMaster: 1}).ismaster==false) { sleep(100); }'`;
}

private isV5OrLater() {
try {
return satisfies(this.imageName.tag, ">=5.0.0");
} catch {
return false;
}
return `'try { rs.status(); } catch (e) { rs.initiate(); } while (db.runCommand({isMaster: 1}).ismaster==false) { sleep(100); }'`;
}
}

Expand Down
3 changes: 1 addition & 2 deletions packages/modules/neo4j/src/neo4j-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,9 @@ export class Neo4jContainer extends GenericContainer {
.join(",");

this.withEnvironment({
NEO4JLABS_PLUGINS: JSON.stringify(this.plugins), // Older variant for older images.
NEO4J_PLUGINS: JSON.stringify(this.plugins),
NEO4J_dbms_security_procedures_unrestricted: whitelists,
NEO4J_dbms_security_procedures_whitelist: whitelists,
NEO4J_dbms_security_procedures_allowlist: whitelists,
});
}
return new StartedNeo4jContainer(await super.start(), this.password);
Expand Down
1 change: 0 additions & 1 deletion packages/modules/redis/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
FROM redis:8.10
FROM redis/redis-stack-server:7.4.0-v4
55 changes: 0 additions & 55 deletions packages/modules/redis/src/redis-container.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { getImage } from "../../../testcontainers/src/utils/test-helper";
import { RedisContainer } from "./redis-container";

const IMAGES = ["redis:6.2.24", "redis:7.4.11", getImage(__dirname)];
const REDISSTACK_IMAGE = getImage(__dirname, 1);

describe.each(IMAGES)("RedisContainer (%s)", { concurrent: false, timeout: 240_000 }, (image) => {
it.concurrent("should connect and execute set-get", async () => {
Expand Down Expand Up @@ -161,57 +160,3 @@ describe.each(IMAGES)("RedisContainer (%s)", { concurrent: false, timeout: 240_0
fs.rmSync(sourcePath, { force: true, recursive: true });
});
});

describe(`RedisContainer (${REDISSTACK_IMAGE})`, { timeout: 240_000 }, () => {
it("should start with redis-stack-server and json module", async () => {
// startWithRedisStack {
await using container = await new RedisContainer(REDISSTACK_IMAGE).withPassword("testPassword").start();

const client = createClient({ url: container.getConnectionUrl() });
await client.connect();

await client.json.set("key", "$", { name: "test" });
const result = await client.json.get("key");
expect(result).toEqual({ name: "test" });

client.destroy();
// }
});

it("should start redis-stack with custom env", async () => {
const container = new RedisContainer(REDISSTACK_IMAGE).withEnvironment({ REDIS_ARGS: "--loglevel verbose" });
await using startedContainer = await container.start();

// @ts-expect-error - accessing private property for testing
expect(container.createOpts.Env).toEqual(["REDIS_ARGS=--loglevel verbose"]);

const client = createClient({ url: startedContainer.getConnectionUrl() });
await client.connect();

await client.set("key", "val");
expect(await client.get("key")).toBe("val");

client.destroy();
});

it("should start redis-stack with custom env and keeping default args", async () => {
const sourcePath = fs.mkdtempSync("redis-");

const container = new RedisContainer(REDISSTACK_IMAGE)
.withEnvironment({ REDIS_ARGS: "--loglevel verbose" })
.withPersistence(sourcePath);
await using startedContainer = await container.start();

// @ts-expect-error - accessing private property for testing
expect(container.createOpts.Env).toEqual(["REDIS_ARGS=--loglevel verbose --save 1 1 --appendonly yes"]);

const client = createClient({ url: startedContainer.getConnectionUrl() });
await client.connect();

await client.set("key", "val");
expect(await client.get("key")).toBe("val");

client.destroy();
fs.rmSync(sourcePath, { force: true, recursive: true });
});
});
20 changes: 3 additions & 17 deletions packages/modules/redis/src/redis-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,23 +43,9 @@ export class RedisContainer extends GenericContainer {
...(this.password ? [`--requirepass ${this.password}`] : []),
...(this.persistenceVolume ? ["--save 1 1 ", "--appendonly yes"] : []),
];
if (this.imageName.image.includes("redis-stack")) {
const existingRedisArgs = this.environment.REDIS_ARGS ?? "";

// merge with filter to remove empty items
const mergedRedisArgs = [existingRedisArgs, ...redisArgs].filter(Boolean).join(" ");

// remove existing REDIS_ARGS to avoid duplicates
this.createOpts.Env = (this.createOpts.Env || []).filter((e) => !e.startsWith("REDIS_ARGS="));

this.withEnvironment({
REDIS_ARGS: mergedRedisArgs,
}).withEntrypoint(["/entrypoint.sh"]);
} else {
const existingCmd = this.createOpts.Cmd || [];
const baseCmd = existingCmd.length ? existingCmd : ["redis-server"];
this.withCommand([...baseCmd, ...redisArgs]);
}
const existingCmd = this.createOpts.Cmd || [];
const baseCmd = existingCmd.length ? existingCmd : ["redis-server"];
this.withCommand([...baseCmd, ...redisArgs]);
if (this.persistenceVolume) {
this.withBindMounts([{ mode: "rw", source: this.persistenceVolume, target: "/data" }]);
}
Expand Down
Loading