import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { PassThrough } from "node:stream"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { installScheduledTask, readScheduledTaskCommand } from "./schtasks.js"; const schtasksCalls: string[][] = []; vi.mock("./schtasks-exec.js", () => ({ execSchtasks: async (argv: string[]) => { schtasksCalls.push(argv); return { code: 0, stdout: "", stderr: "" }; }, })); beforeEach(() => { schtasksCalls.length = 0; }); describe("installScheduledTask", () => { async function withUserProfileDir( run: (tmpDir: string, env: Record) => Promise, ) { const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-schtasks-install-")); const env = { USERPROFILE: tmpDir, OPENCLAW_PROFILE: "default", }; try { await run(tmpDir, env); } finally { await fs.rm(tmpDir, { recursive: true, force: true }); } } it("writes quoted set assignments and escapes metacharacters", async () => { await withUserProfileDir(async (_tmpDir, env) => { const { scriptPath } = await installScheduledTask({ env, stdout: new PassThrough(), programArguments: [ "node", "gateway.js", "--display-name", "safe&whoami", "--percent", "%TEMP%", "--bang", "!token!", ], workingDirectory: "C:\\temp\\poc&calc", environment: { OC_INJECT: "safe & whoami | calc", OC_CARET: "a^b", OC_PERCENT: "%TEMP%", OC_BANG: "!token!", OC_QUOTE: 'he said "hi"', OC_EMPTY: "", }, }); const script = await fs.readFile(scriptPath, "utf8"); expect(script).toContain('cd /d "C:\\temp\\poc&calc"'); expect(script).toContain( 'node gateway.js --display-name "safe&whoami" --percent "%%TEMP%%" --bang "^!token^!"', ); expect(script).toContain('set "OC_INJECT=safe & whoami | calc"'); expect(script).toContain('set "OC_CARET=a^^b"'); expect(script).toContain('set "OC_PERCENT=%%TEMP%%"'); expect(script).toContain('set "OC_BANG=^!token^!"'); expect(script).toContain('set "OC_QUOTE=he said ^"hi^""'); expect(script).not.toContain('set "OC_EMPTY='); expect(script).not.toContain("set OC_INJECT="); const parsed = await readScheduledTaskCommand(env); expect(parsed).toMatchObject({ programArguments: [ "node", "gateway.js", "--display-name", "safe&whoami", "--percent", "%TEMP%", "--bang", "!token!", ], workingDirectory: "C:\\temp\\poc&calc", }); expect(parsed?.environment).toMatchObject({ OC_INJECT: "safe & whoami | calc", OC_CARET: "a^b", OC_PERCENT: "%TEMP%", OC_BANG: "!token!", OC_QUOTE: 'he said "hi"', }); expect(parsed?.environment).not.toHaveProperty("OC_EMPTY"); expect(schtasksCalls[0]).toEqual(["/Query"]); expect(schtasksCalls[1]?.[0]).toBe("/Create"); expect(schtasksCalls[2]).toEqual(["/Run", "/TN", "OpenClaw Gateway"]); }); }); it("rejects line breaks in command arguments, env vars, and descriptions", async () => { await withUserProfileDir(async (_tmpDir, env) => { await expect( installScheduledTask({ env, stdout: new PassThrough(), programArguments: ["node", "gateway.js", "bad\narg"], environment: {}, }), ).rejects.toThrow(/Command argument cannot contain CR or LF/); await expect( installScheduledTask({ env, stdout: new PassThrough(), programArguments: ["node", "gateway.js"], environment: { BAD: "line1\r\nline2" }, }), ).rejects.toThrow(/Environment variable value cannot contain CR or LF/); await expect( installScheduledTask({ env, stdout: new PassThrough(), description: "bad\ndescription", programArguments: ["node", "gateway.js"], environment: {}, }), ).rejects.toThrow(/Task description cannot contain CR or LF/); }); }); it("does not persist a frozen PATH snapshot into the generated task script", async () => { await withUserProfileDir(async (_tmpDir, env) => { const { scriptPath } = await installScheduledTask({ env, stdout: new PassThrough(), programArguments: ["node", "gateway.js"], environment: { PATH: "C:\\Windows\\System32;C:\\Program Files\\Docker\\Docker\\resources\\bin", OPENCLAW_GATEWAY_PORT: "18789", }, }); const script = await fs.readFile(scriptPath, "utf8"); expect(script).not.toContain('set "PATH='); expect(script).toContain('set "OPENCLAW_GATEWAY_PORT=18789"'); }); }); });