From 7e87f99ac4270845591e1916f275fe2d064d23f2 Mon Sep 17 00:00:00 2001 From: ziggornif Date: Fri, 3 Oct 2025 13:37:42 +0200 Subject: [PATCH 1/3] Fix Redis container to accept input command See issue #1150 --- .../modules/redis/src/redis-container.test.ts | 43 +++++++++++++++++++ packages/modules/redis/src/redis-container.ts | 4 +- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/packages/modules/redis/src/redis-container.test.ts b/packages/modules/redis/src/redis-container.test.ts index a622c24ef..32a5470dc 100644 --- a/packages/modules/redis/src/redis-container.test.ts +++ b/packages/modules/redis/src/redis-container.test.ts @@ -119,4 +119,47 @@ describe("RedisContainer", { timeout: 240_000 }, () => { client.destroy(); // } }); + + it("should start redis with custom command", async () => { + const container = new RedisContainer(IMAGE).withCommand(["redis-server", "--loglevel", "verbose"]); + const startedContainer = await container.start(); + + // @ts-expect-error - accessing private property for testing + expect(container.createOpts.Cmd).toEqual(["redis-server", "--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 with custom command and keeping default args", async () => { + const sourcePath = fs.mkdtempSync("redis-"); + + const container = new RedisContainer(IMAGE) + .withCommand(["redis-server", "--loglevel", "verbose"]) + .withPersistence(sourcePath); + const startedContainer = await container.start(); + + // @ts-expect-error - accessing private property for testing + expect(container.createOpts.Cmd).toEqual([ + "redis-server", + "--loglevel", + "verbose", + "--save 1 1 ", + "--appendonly yes", + ]); + + const client = createClient({ url: startedContainer.getConnectionUrl() }); + await client.connect(); + + await client.set("key", "val"); + expect(await client.get("key")).toBe("val"); + + client.destroy(); + fs.rmSync(sourcePath, { force: true, recursive: true }); + }); }); diff --git a/packages/modules/redis/src/redis-container.ts b/packages/modules/redis/src/redis-container.ts index 2b5819843..0e1bb9786 100644 --- a/packages/modules/redis/src/redis-container.ts +++ b/packages/modules/redis/src/redis-container.ts @@ -48,7 +48,9 @@ export class RedisContainer extends GenericContainer { REDIS_ARGS: redisArgs.join(" "), }).withEntrypoint(["/entrypoint.sh"]); } else { - this.withCommand(["redis-server", ...redisArgs]); + const existingCmd = this.createOpts.Cmd || []; + const baseCmd = existingCmd.length ? existingCmd : ["redis-server"]; + this.withCommand([...baseCmd, ...redisArgs]); } if (this.persistenceVolume) { this.withBindMounts([{ mode: "rw", source: this.persistenceVolume, target: "/data" }]); From 3e6fa916da825c2d33966a354c3e5ff1d7d5a93e Mon Sep 17 00:00:00 2001 From: ziggornif Date: Mon, 6 Oct 2025 12:52:57 +0200 Subject: [PATCH 2/3] Fix Redis stack container to accept input env vars Same as redis image with input command See issue #1150 --- .../modules/redis/src/redis-container.test.ts | 37 +++++++++++++++++++ packages/modules/redis/src/redis-container.ts | 11 +++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/packages/modules/redis/src/redis-container.test.ts b/packages/modules/redis/src/redis-container.test.ts index 32a5470dc..cf8e51f0d 100644 --- a/packages/modules/redis/src/redis-container.test.ts +++ b/packages/modules/redis/src/redis-container.test.ts @@ -136,6 +136,22 @@ describe("RedisContainer", { timeout: 240_000 }, () => { client.destroy(); }); + it("should start redis-stack with custom env", async () => { + const container = new RedisContainer(REDISSTACK_IMAGE).withEnvironment({ REDIS_ARGS: "--loglevel verbose" }); + const 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 with custom command and keeping default args", async () => { const sourcePath = fs.mkdtempSync("redis-"); @@ -162,4 +178,25 @@ describe("RedisContainer", { timeout: 240_000 }, () => { client.destroy(); fs.rmSync(sourcePath, { force: true, recursive: true }); }); + + 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); + const startedContainer = await container.start(); + + // @ts-expect-error - accessing private property for testing + expect(container.createOpts.Env).toEqual(["REDIS_ARGS=--loglevel verbose --save 1 1 --appendonly yes"]); + + const client = createClient({ url: startedContainer.getConnectionUrl() }); + await client.connect(); + + await client.set("key", "val"); + expect(await client.get("key")).toBe("val"); + + client.destroy(); + fs.rmSync(sourcePath, { force: true, recursive: true }); + }); }); diff --git a/packages/modules/redis/src/redis-container.ts b/packages/modules/redis/src/redis-container.ts index 0e1bb9786..1df1a0d12 100644 --- a/packages/modules/redis/src/redis-container.ts +++ b/packages/modules/redis/src/redis-container.ts @@ -44,8 +44,17 @@ export class RedisContainer extends GenericContainer { ...(this.persistenceVolume ? ["--save 1 1 ", "--appendonly yes"] : []), ]; if (this.imageName.image.includes("redis-stack")) { + const existingRedisArgs = + (this.createOpts.Env || []).find((e) => e.startsWith("REDIS_ARGS="))?.split("=", 2)[1] || ""; + + // 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: redisArgs.join(" "), + REDIS_ARGS: mergedRedisArgs, }).withEntrypoint(["/entrypoint.sh"]); } else { const existingCmd = this.createOpts.Cmd || []; From fd64d0d26adf4845087c8279716df8863f85db6e Mon Sep 17 00:00:00 2001 From: ziggornif Date: Mon, 10 Nov 2025 19:08:11 +0100 Subject: [PATCH 3/3] Apply suggestions --- packages/modules/redis/src/redis-container.test.ts | 8 ++++---- packages/modules/redis/src/redis-container.ts | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/modules/redis/src/redis-container.test.ts b/packages/modules/redis/src/redis-container.test.ts index cf8e51f0d..f8f4fba82 100644 --- a/packages/modules/redis/src/redis-container.test.ts +++ b/packages/modules/redis/src/redis-container.test.ts @@ -122,7 +122,7 @@ describe("RedisContainer", { timeout: 240_000 }, () => { it("should start redis with custom command", async () => { const container = new RedisContainer(IMAGE).withCommand(["redis-server", "--loglevel", "verbose"]); - const startedContainer = await container.start(); + await using startedContainer = await container.start(); // @ts-expect-error - accessing private property for testing expect(container.createOpts.Cmd).toEqual(["redis-server", "--loglevel", "verbose"]); @@ -138,7 +138,7 @@ describe("RedisContainer", { timeout: 240_000 }, () => { it("should start redis-stack with custom env", async () => { const container = new RedisContainer(REDISSTACK_IMAGE).withEnvironment({ REDIS_ARGS: "--loglevel verbose" }); - const startedContainer = await container.start(); + await using startedContainer = await container.start(); // @ts-expect-error - accessing private property for testing expect(container.createOpts.Env).toEqual(["REDIS_ARGS=--loglevel verbose"]); @@ -158,7 +158,7 @@ describe("RedisContainer", { timeout: 240_000 }, () => { const container = new RedisContainer(IMAGE) .withCommand(["redis-server", "--loglevel", "verbose"]) .withPersistence(sourcePath); - const startedContainer = await container.start(); + await using startedContainer = await container.start(); // @ts-expect-error - accessing private property for testing expect(container.createOpts.Cmd).toEqual([ @@ -185,7 +185,7 @@ describe("RedisContainer", { timeout: 240_000 }, () => { const container = new RedisContainer(REDISSTACK_IMAGE) .withEnvironment({ REDIS_ARGS: "--loglevel verbose" }) .withPersistence(sourcePath); - const startedContainer = await container.start(); + 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"]); diff --git a/packages/modules/redis/src/redis-container.ts b/packages/modules/redis/src/redis-container.ts index 1df1a0d12..94540dfb6 100644 --- a/packages/modules/redis/src/redis-container.ts +++ b/packages/modules/redis/src/redis-container.ts @@ -44,8 +44,7 @@ export class RedisContainer extends GenericContainer { ...(this.persistenceVolume ? ["--save 1 1 ", "--appendonly yes"] : []), ]; if (this.imageName.image.includes("redis-stack")) { - const existingRedisArgs = - (this.createOpts.Env || []).find((e) => e.startsWith("REDIS_ARGS="))?.split("=", 2)[1] || ""; + const existingRedisArgs = this.environment["REDIS_ARGS"] ?? ""; // merge with filter to remove empty items const mergedRedisArgs = [existingRedisArgs, ...redisArgs].filter(Boolean).join(" ");