diff --git a/src/agents/bash-tools.build-docker-exec-args.test.ts b/src/agents/bash-tools.build-docker-exec-args.test.ts new file mode 100644 index 00000000000..b759a51b58f --- /dev/null +++ b/src/agents/bash-tools.build-docker-exec-args.test.ts @@ -0,0 +1,93 @@ +import { describe, expect, it } from "vitest"; +import { buildDockerExecArgs } from "./bash-tools.shared.js"; + +describe("buildDockerExecArgs", () => { + it("prepends custom PATH after login shell sourcing to preserve both custom and system tools", () => { + const args = buildDockerExecArgs({ + containerName: "test-container", + command: "echo hello", + env: { + PATH: "/custom/bin:/usr/local/bin:/usr/bin", + HOME: "/home/user", + }, + tty: false, + }); + + const commandArg = args[args.length - 1]; + expect(args).toContain("OPENCLAW_PREPEND_PATH=/custom/bin:/usr/local/bin:/usr/bin"); + expect(commandArg).toContain('export PATH="${OPENCLAW_PREPEND_PATH}:$PATH"'); + expect(commandArg).toContain("echo hello"); + expect(commandArg).toBe( + 'export PATH="${OPENCLAW_PREPEND_PATH}:$PATH"; unset OPENCLAW_PREPEND_PATH; echo hello', + ); + }); + + it("does not interpolate PATH into the shell command", () => { + const injectedPath = "$(touch /tmp/openclaw-path-injection)"; + const args = buildDockerExecArgs({ + containerName: "test-container", + command: "echo hello", + env: { + PATH: injectedPath, + HOME: "/home/user", + }, + tty: false, + }); + + const commandArg = args[args.length - 1]; + expect(args).toContain(`OPENCLAW_PREPEND_PATH=${injectedPath}`); + expect(commandArg).not.toContain(injectedPath); + expect(commandArg).toContain("OPENCLAW_PREPEND_PATH"); + }); + + it("does not add PATH export when PATH is not in env", () => { + const args = buildDockerExecArgs({ + containerName: "test-container", + command: "echo hello", + env: { + HOME: "/home/user", + }, + tty: false, + }); + + const commandArg = args[args.length - 1]; + expect(commandArg).toBe("echo hello"); + expect(commandArg).not.toContain("export PATH"); + }); + + it("includes workdir flag when specified", () => { + const args = buildDockerExecArgs({ + containerName: "test-container", + command: "pwd", + workdir: "/workspace", + env: { HOME: "/home/user" }, + tty: false, + }); + + expect(args).toContain("-w"); + expect(args).toContain("/workspace"); + }); + + it("uses login shell for consistent environment", () => { + const args = buildDockerExecArgs({ + containerName: "test-container", + command: "echo test", + env: { HOME: "/home/user" }, + tty: false, + }); + + expect(args).toContain("sh"); + expect(args).toContain("-lc"); + }); + + it("includes tty flag when requested", () => { + const args = buildDockerExecArgs({ + containerName: "test-container", + command: "bash", + env: { HOME: "/home/user" }, + tty: true, + }); + + expect(args).toContain("-t"); + }); +}); diff --git a/src/agents/bash-tools.e2e.test.ts b/src/agents/bash-tools.e2e.test.ts index f4c716f5af9..547ab31b358 100644 --- a/src/agents/bash-tools.e2e.test.ts +++ b/src/agents/bash-tools.e2e.test.ts @@ -4,7 +4,6 @@ import { peekSystemEvents, resetSystemEventsForTest } from "../infra/system-even import { captureEnv } from "../test-utils/env.js"; import { getFinishedSession, resetProcessRegistryForTests } from "./bash-process-registry.js"; import { createExecTool, createProcessTool } from "./bash-tools.js"; -import { buildDockerExecArgs } from "./bash-tools.shared.js"; import { resolveShellFromPath, sanitizeBinaryOutput } from "./shell-utils.js"; const isWin = process.platform === "win32"; @@ -451,94 +450,3 @@ describe("exec PATH handling", () => { expect(entries).toContain(basePath); }); }); - -describe("buildDockerExecArgs", () => { - it("prepends custom PATH after login shell sourcing to preserve both custom and system tools", () => { - const args = buildDockerExecArgs({ - containerName: "test-container", - command: "echo hello", - env: { - PATH: "/custom/bin:/usr/local/bin:/usr/bin", - HOME: "/home/user", - }, - tty: false, - }); - - const commandArg = args[args.length - 1]; - expect(args).toContain("OPENCLAW_PREPEND_PATH=/custom/bin:/usr/local/bin:/usr/bin"); - expect(commandArg).toContain('export PATH="${OPENCLAW_PREPEND_PATH}:$PATH"'); - expect(commandArg).toContain("echo hello"); - expect(commandArg).toBe( - 'export PATH="${OPENCLAW_PREPEND_PATH}:$PATH"; unset OPENCLAW_PREPEND_PATH; echo hello', - ); - }); - - it("does not interpolate PATH into the shell command", () => { - const injectedPath = "$(touch /tmp/openclaw-path-injection)"; - const args = buildDockerExecArgs({ - containerName: "test-container", - command: "echo hello", - env: { - PATH: injectedPath, - HOME: "/home/user", - }, - tty: false, - }); - - const commandArg = args[args.length - 1]; - expect(args).toContain(`OPENCLAW_PREPEND_PATH=${injectedPath}`); - expect(commandArg).not.toContain(injectedPath); - expect(commandArg).toContain("OPENCLAW_PREPEND_PATH"); - }); - - it("does not add PATH export when PATH is not in env", () => { - const args = buildDockerExecArgs({ - containerName: "test-container", - command: "echo hello", - env: { - HOME: "/home/user", - }, - tty: false, - }); - - const commandArg = args[args.length - 1]; - expect(commandArg).toBe("echo hello"); - expect(commandArg).not.toContain("export PATH"); - }); - - it("includes workdir flag when specified", () => { - const args = buildDockerExecArgs({ - containerName: "test-container", - command: "pwd", - workdir: "/workspace", - env: { HOME: "/home/user" }, - tty: false, - }); - - expect(args).toContain("-w"); - expect(args).toContain("/workspace"); - }); - - it("uses login shell for consistent environment", () => { - const args = buildDockerExecArgs({ - containerName: "test-container", - command: "echo test", - env: { HOME: "/home/user" }, - tty: false, - }); - - expect(args).toContain("sh"); - expect(args).toContain("-lc"); - }); - - it("includes tty flag when requested", () => { - const args = buildDockerExecArgs({ - containerName: "test-container", - command: "bash", - env: { HOME: "/home/user" }, - tty: true, - }); - - expect(args).toContain("-t"); - }); -});