diff --git a/src/infra/exec-approval-command-display.test.ts b/src/infra/exec-approval-command-display.test.ts new file mode 100644 index 00000000000..9fefeec1aed --- /dev/null +++ b/src/infra/exec-approval-command-display.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, it } from "vitest"; +import { + resolveExecApprovalCommandDisplay, + sanitizeExecApprovalDisplayText, +} from "./exec-approval-command-display.js"; + +describe("sanitizeExecApprovalDisplayText", () => { + it("escapes unicode format characters but leaves other text intact", () => { + expect(sanitizeExecApprovalDisplayText("echo hi\u200Bthere")).toBe("echo hi\\u{200B}there"); + }); +}); + +describe("resolveExecApprovalCommandDisplay", () => { + it("prefers explicit command fields and drops identical previews after trimming", () => { + expect( + resolveExecApprovalCommandDisplay({ + command: "echo hi", + commandPreview: " echo hi ", + host: "gateway", + }), + ).toEqual({ + commandText: "echo hi", + commandPreview: null, + }); + }); + + it("falls back to node systemRunPlan values and sanitizes preview text", () => { + expect( + resolveExecApprovalCommandDisplay({ + command: "", + host: "node", + systemRunPlan: { + argv: ["python3", "-c", "print(1)"], + cwd: null, + commandText: 'python3 -c "print(1)"', + commandPreview: "print\u200B(1)", + agentId: null, + sessionKey: null, + }, + }), + ).toEqual({ + commandText: 'python3 -c "print(1)"', + commandPreview: "print\\u{200B}(1)", + }); + }); + + it("ignores systemRunPlan fallback for non-node hosts", () => { + expect( + resolveExecApprovalCommandDisplay({ + command: "", + host: "sandbox", + systemRunPlan: { + argv: ["echo", "hi"], + cwd: null, + commandText: "echo hi", + commandPreview: "echo hi", + agentId: null, + sessionKey: null, + }, + }), + ).toEqual({ + commandText: "", + commandPreview: null, + }); + }); +}); diff --git a/src/infra/openclaw-exec-env.test.ts b/src/infra/openclaw-exec-env.test.ts new file mode 100644 index 00000000000..488fa1dd5ef --- /dev/null +++ b/src/infra/openclaw-exec-env.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vitest"; +import { + ensureOpenClawExecMarkerOnProcess, + markOpenClawExecEnv, + OPENCLAW_CLI_ENV_VALUE, + OPENCLAW_CLI_ENV_VAR, +} from "./openclaw-exec-env.js"; + +describe("markOpenClawExecEnv", () => { + it("returns a cloned env object with the exec marker set", () => { + const env = { PATH: "/usr/bin", OPENCLAW_CLI: "0" }; + const marked = markOpenClawExecEnv(env); + + expect(marked).toEqual({ + PATH: "/usr/bin", + OPENCLAW_CLI: OPENCLAW_CLI_ENV_VALUE, + }); + expect(marked).not.toBe(env); + expect(env.OPENCLAW_CLI).toBe("0"); + }); +}); + +describe("ensureOpenClawExecMarkerOnProcess", () => { + it("mutates and returns the provided process env", () => { + const env: NodeJS.ProcessEnv = { PATH: "/usr/bin" }; + + expect(ensureOpenClawExecMarkerOnProcess(env)).toBe(env); + expect(env[OPENCLAW_CLI_ENV_VAR]).toBe(OPENCLAW_CLI_ENV_VALUE); + }); +}); diff --git a/src/infra/shell-inline-command.test.ts b/src/infra/shell-inline-command.test.ts new file mode 100644 index 00000000000..8ac552f9fee --- /dev/null +++ b/src/infra/shell-inline-command.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it } from "vitest"; +import { + POSIX_INLINE_COMMAND_FLAGS, + POWERSHELL_INLINE_COMMAND_FLAGS, + resolveInlineCommandMatch, +} from "./shell-inline-command.js"; + +describe("resolveInlineCommandMatch", () => { + it("extracts the next token for exact inline-command flags", () => { + expect( + resolveInlineCommandMatch(["bash", "-lc", "echo hi"], POSIX_INLINE_COMMAND_FLAGS), + ).toEqual({ + command: "echo hi", + valueTokenIndex: 2, + }); + expect( + resolveInlineCommandMatch( + ["pwsh", "-Command", "Get-ChildItem"], + POWERSHELL_INLINE_COMMAND_FLAGS, + ), + ).toEqual({ + command: "Get-ChildItem", + valueTokenIndex: 2, + }); + }); + + it("supports combined -c forms only when enabled", () => { + expect( + resolveInlineCommandMatch(["sh", "-cecho hi"], POSIX_INLINE_COMMAND_FLAGS, { + allowCombinedC: true, + }), + ).toEqual({ + command: "echo hi", + valueTokenIndex: 1, + }); + expect( + resolveInlineCommandMatch(["sh", "-cecho hi"], POSIX_INLINE_COMMAND_FLAGS, { + allowCombinedC: false, + }), + ).toEqual({ + command: null, + valueTokenIndex: null, + }); + }); + + it("returns a value index even when the flag is present without a usable command", () => { + expect(resolveInlineCommandMatch(["bash", "-lc", " "], POSIX_INLINE_COMMAND_FLAGS)).toEqual({ + command: null, + valueTokenIndex: 2, + }); + expect(resolveInlineCommandMatch(["bash", "-lc"], POSIX_INLINE_COMMAND_FLAGS)).toEqual({ + command: null, + valueTokenIndex: null, + }); + }); + + it("stops parsing after --", () => { + expect( + resolveInlineCommandMatch(["bash", "--", "-lc", "echo hi"], POSIX_INLINE_COMMAND_FLAGS), + ).toEqual({ + command: null, + valueTokenIndex: null, + }); + }); +}); diff --git a/src/infra/supervisor-markers.test.ts b/src/infra/supervisor-markers.test.ts new file mode 100644 index 00000000000..fb49ec6eaf5 --- /dev/null +++ b/src/infra/supervisor-markers.test.ts @@ -0,0 +1,67 @@ +import { describe, expect, it } from "vitest"; +import { detectRespawnSupervisor, SUPERVISOR_HINT_ENV_VARS } from "./supervisor-markers.js"; + +describe("SUPERVISOR_HINT_ENV_VARS", () => { + it("includes the cross-platform supervisor hint env vars", () => { + expect(SUPERVISOR_HINT_ENV_VARS).toEqual( + expect.arrayContaining([ + "LAUNCH_JOB_LABEL", + "INVOCATION_ID", + "OPENCLAW_WINDOWS_TASK_NAME", + "OPENCLAW_SERVICE_MARKER", + "OPENCLAW_SERVICE_KIND", + ]), + ); + }); +}); + +describe("detectRespawnSupervisor", () => { + it("detects launchd and systemd only from non-blank platform-specific hints", () => { + expect(detectRespawnSupervisor({ LAUNCH_JOB_LABEL: " ai.openclaw.gateway " }, "darwin")).toBe( + "launchd", + ); + expect(detectRespawnSupervisor({ LAUNCH_JOB_LABEL: " " }, "darwin")).toBeNull(); + + expect(detectRespawnSupervisor({ INVOCATION_ID: "abc123" }, "linux")).toBe("systemd"); + expect(detectRespawnSupervisor({ JOURNAL_STREAM: "" }, "linux")).toBeNull(); + }); + + it("detects scheduled-task supervision on Windows from either hint family", () => { + expect( + detectRespawnSupervisor({ OPENCLAW_WINDOWS_TASK_NAME: "OpenClaw Gateway" }, "win32"), + ).toBe("schtasks"); + expect( + detectRespawnSupervisor( + { + OPENCLAW_SERVICE_MARKER: "openclaw", + OPENCLAW_SERVICE_KIND: "gateway", + }, + "win32", + ), + ).toBe("schtasks"); + expect( + detectRespawnSupervisor( + { + OPENCLAW_SERVICE_MARKER: "openclaw", + OPENCLAW_SERVICE_KIND: "worker", + }, + "win32", + ), + ).toBeNull(); + }); + + it("ignores service markers on non-Windows platforms and unknown platforms", () => { + expect( + detectRespawnSupervisor( + { + OPENCLAW_SERVICE_MARKER: "openclaw", + OPENCLAW_SERVICE_KIND: "gateway", + }, + "linux", + ), + ).toBeNull(); + expect( + detectRespawnSupervisor({ LAUNCH_JOB_LABEL: "ai.openclaw.gateway" }, "freebsd"), + ).toBeNull(); + }); +});