import { describe, expect, it } from "vitest"; import { withEnvAsync } from "../test-utils/env.js"; import { runCommandWithTimeout, shouldSpawnWithShell } from "./exec.js"; describe("runCommandWithTimeout", () => { it("never enables shell execution (Windows cmd.exe injection hardening)", () => { expect( shouldSpawnWithShell({ resolvedCommand: "npm.cmd", platform: "win32", }), ).toBe(false); }); it("merges custom env with process.env", async () => { await withEnvAsync({ OPENCLAW_BASE_ENV: "base" }, async () => { const result = await runCommandWithTimeout( [ process.execPath, "-e", 'process.stdout.write((process.env.OPENCLAW_BASE_ENV ?? "") + "|" + (process.env.OPENCLAW_TEST_ENV ?? ""))', ], { timeoutMs: 5_000, env: { OPENCLAW_TEST_ENV: "ok" }, }, ); expect(result.code).toBe(0); expect(result.stdout).toBe("base|ok"); expect(result.termination).toBe("exit"); }); }); it("kills command when no output timeout elapses", async () => { const result = await runCommandWithTimeout( [process.execPath, "-e", "setTimeout(() => {}, 40)"], { timeoutMs: 500, noOutputTimeoutMs: 20, }, ); expect(result.termination).toBe("no-output-timeout"); expect(result.noOutputTimedOut).toBe(true); expect(result.code).not.toBe(0); }); it("resets no output timer when command keeps emitting output", async () => { const result = await runCommandWithTimeout( [ process.execPath, "-e", [ 'process.stdout.write(".");', "let count = 0;", 'const ticker = setInterval(() => { process.stdout.write(".");', "count += 1;", "if (count === 4) {", "clearInterval(ticker);", "process.exit(0);", "}", "}, 60);", ].join(" "), ], { timeoutMs: 5_000, noOutputTimeoutMs: 250, }, ); expect(result.signal).toBeNull(); expect(result.code ?? 0).toBe(0); expect(result.termination).toBe("exit"); expect(result.noOutputTimedOut).toBe(false); expect(result.stdout.length).toBeGreaterThanOrEqual(5); }); it("reports global timeout termination when overall timeout elapses", async () => { const result = await runCommandWithTimeout( [process.execPath, "-e", "setTimeout(() => {}, 40)"], { timeoutMs: 15, }, ); expect(result.termination).toBe("timeout"); expect(result.noOutputTimedOut).toBe(false); expect(result.code).not.toBe(0); }); });