| import fs from "node:fs/promises"; |
| import os from "node:os"; |
| import path from "node:path"; |
|
|
| import { describe, expect, it } from "vitest"; |
|
|
| import { parseSchtasksQuery, readScheduledTaskCommand, resolveTaskScriptPath } from "./schtasks.js"; |
|
|
| describe("schtasks runtime parsing", () => { |
| it("parses status and last run info", () => { |
| const output = [ |
| "TaskName: \\OpenClaw Gateway", |
| "Status: Ready", |
| "Last Run Time: 1/8/2026 1:23:45 AM", |
| "Last Run Result: 0x0", |
| ].join("\r\n"); |
| expect(parseSchtasksQuery(output)).toEqual({ |
| status: "Ready", |
| lastRunTime: "1/8/2026 1:23:45 AM", |
| lastRunResult: "0x0", |
| }); |
| }); |
|
|
| it("parses running status", () => { |
| const output = [ |
| "TaskName: \\OpenClaw Gateway", |
| "Status: Running", |
| "Last Run Time: 1/8/2026 1:23:45 AM", |
| "Last Run Result: 0x0", |
| ].join("\r\n"); |
| expect(parseSchtasksQuery(output)).toEqual({ |
| status: "Running", |
| lastRunTime: "1/8/2026 1:23:45 AM", |
| lastRunResult: "0x0", |
| }); |
| }); |
| }); |
|
|
| describe("resolveTaskScriptPath", () => { |
| it("uses default path when OPENCLAW_PROFILE is default", () => { |
| const env = { USERPROFILE: "C:\\Users\\test", OPENCLAW_PROFILE: "default" }; |
| expect(resolveTaskScriptPath(env)).toBe( |
| path.join("C:\\Users\\test", ".openclaw", "gateway.cmd"), |
| ); |
| }); |
|
|
| it("uses default path when OPENCLAW_PROFILE is unset", () => { |
| const env = { USERPROFILE: "C:\\Users\\test" }; |
| expect(resolveTaskScriptPath(env)).toBe( |
| path.join("C:\\Users\\test", ".openclaw", "gateway.cmd"), |
| ); |
| }); |
|
|
| it("uses profile-specific path when OPENCLAW_PROFILE is set to a custom value", () => { |
| const env = { USERPROFILE: "C:\\Users\\test", OPENCLAW_PROFILE: "jbphoenix" }; |
| expect(resolveTaskScriptPath(env)).toBe( |
| path.join("C:\\Users\\test", ".openclaw-jbphoenix", "gateway.cmd"), |
| ); |
| }); |
|
|
| it("prefers OPENCLAW_STATE_DIR over profile-derived defaults", () => { |
| const env = { |
| USERPROFILE: "C:\\Users\\test", |
| OPENCLAW_PROFILE: "rescue", |
| OPENCLAW_STATE_DIR: "C:\\State\\openclaw", |
| }; |
| expect(resolveTaskScriptPath(env)).toBe(path.join("C:\\State\\openclaw", "gateway.cmd")); |
| }); |
|
|
| it("handles case-insensitive 'Default' profile", () => { |
| const env = { USERPROFILE: "C:\\Users\\test", OPENCLAW_PROFILE: "Default" }; |
| expect(resolveTaskScriptPath(env)).toBe( |
| path.join("C:\\Users\\test", ".openclaw", "gateway.cmd"), |
| ); |
| }); |
|
|
| it("handles case-insensitive 'DEFAULT' profile", () => { |
| const env = { USERPROFILE: "C:\\Users\\test", OPENCLAW_PROFILE: "DEFAULT" }; |
| expect(resolveTaskScriptPath(env)).toBe( |
| path.join("C:\\Users\\test", ".openclaw", "gateway.cmd"), |
| ); |
| }); |
|
|
| it("trims whitespace from OPENCLAW_PROFILE", () => { |
| const env = { USERPROFILE: "C:\\Users\\test", OPENCLAW_PROFILE: " myprofile " }; |
| expect(resolveTaskScriptPath(env)).toBe( |
| path.join("C:\\Users\\test", ".openclaw-myprofile", "gateway.cmd"), |
| ); |
| }); |
|
|
| it("falls back to HOME when USERPROFILE is not set", () => { |
| const env = { HOME: "/home/test", OPENCLAW_PROFILE: "default" }; |
| expect(resolveTaskScriptPath(env)).toBe(path.join("/home/test", ".openclaw", "gateway.cmd")); |
| }); |
| }); |
|
|
| describe("readScheduledTaskCommand", () => { |
| it("parses basic command script", async () => { |
| const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-schtasks-test-")); |
| try { |
| const scriptPath = path.join(tmpDir, ".openclaw", "gateway.cmd"); |
| await fs.mkdir(path.dirname(scriptPath), { recursive: true }); |
| await fs.writeFile( |
| scriptPath, |
| ["@echo off", "node gateway.js --port 7860"].join("\r\n"), |
| "utf8", |
| ); |
|
|
| const env = { USERPROFILE: tmpDir, OPENCLAW_PROFILE: "default" }; |
| const result = await readScheduledTaskCommand(env); |
| expect(result).toEqual({ |
| programArguments: ["node", "gateway.js", "--port", "7860"], |
| }); |
| } finally { |
| await fs.rm(tmpDir, { recursive: true, force: true }); |
| } |
| }); |
|
|
| it("parses script with working directory", async () => { |
| const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-schtasks-test-")); |
| try { |
| const scriptPath = path.join(tmpDir, ".openclaw", "gateway.cmd"); |
| await fs.mkdir(path.dirname(scriptPath), { recursive: true }); |
| await fs.writeFile( |
| scriptPath, |
| ["@echo off", "cd /d C:\\Projects\\openclaw", "node gateway.js"].join("\r\n"), |
| "utf8", |
| ); |
|
|
| const env = { USERPROFILE: tmpDir, OPENCLAW_PROFILE: "default" }; |
| const result = await readScheduledTaskCommand(env); |
| expect(result).toEqual({ |
| programArguments: ["node", "gateway.js"], |
| workingDirectory: "C:\\Projects\\openclaw", |
| }); |
| } finally { |
| await fs.rm(tmpDir, { recursive: true, force: true }); |
| } |
| }); |
|
|
| it("parses script with environment variables", async () => { |
| const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-schtasks-test-")); |
| try { |
| const scriptPath = path.join(tmpDir, ".openclaw", "gateway.cmd"); |
| await fs.mkdir(path.dirname(scriptPath), { recursive: true }); |
| await fs.writeFile( |
| scriptPath, |
| ["@echo off", "set NODE_ENV=production", "set PORT=7860", "node gateway.js"].join("\r\n"), |
| "utf8", |
| ); |
|
|
| const env = { USERPROFILE: tmpDir, OPENCLAW_PROFILE: "default" }; |
| const result = await readScheduledTaskCommand(env); |
| expect(result).toEqual({ |
| programArguments: ["node", "gateway.js"], |
| environment: { |
| NODE_ENV: "production", |
| PORT: "7860", |
| }, |
| }); |
| } finally { |
| await fs.rm(tmpDir, { recursive: true, force: true }); |
| } |
| }); |
|
|
| it("parses script with quoted arguments containing spaces", async () => { |
| const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-schtasks-test-")); |
| try { |
| const scriptPath = path.join(tmpDir, ".openclaw", "gateway.cmd"); |
| await fs.mkdir(path.dirname(scriptPath), { recursive: true }); |
| |
| await fs.writeFile( |
| scriptPath, |
| ["@echo off", '"C:/Program Files/Node/node.exe" gateway.js'].join("\r\n"), |
| "utf8", |
| ); |
|
|
| const env = { USERPROFILE: tmpDir, OPENCLAW_PROFILE: "default" }; |
| const result = await readScheduledTaskCommand(env); |
| expect(result).toEqual({ |
| programArguments: ["C:/Program Files/Node/node.exe", "gateway.js"], |
| }); |
| } finally { |
| await fs.rm(tmpDir, { recursive: true, force: true }); |
| } |
| }); |
|
|
| it("returns null when script does not exist", async () => { |
| const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-schtasks-test-")); |
| try { |
| const env = { USERPROFILE: tmpDir, OPENCLAW_PROFILE: "default" }; |
| const result = await readScheduledTaskCommand(env); |
| expect(result).toBeNull(); |
| } finally { |
| await fs.rm(tmpDir, { recursive: true, force: true }); |
| } |
| }); |
|
|
| it("returns null when script has no command", async () => { |
| const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-schtasks-test-")); |
| try { |
| const scriptPath = path.join(tmpDir, ".openclaw", "gateway.cmd"); |
| await fs.mkdir(path.dirname(scriptPath), { recursive: true }); |
| await fs.writeFile( |
| scriptPath, |
| ["@echo off", "rem This is just a comment"].join("\r\n"), |
| "utf8", |
| ); |
|
|
| const env = { USERPROFILE: tmpDir, OPENCLAW_PROFILE: "default" }; |
| const result = await readScheduledTaskCommand(env); |
| expect(result).toBeNull(); |
| } finally { |
| await fs.rm(tmpDir, { recursive: true, force: true }); |
| } |
| }); |
|
|
| it("parses full script with all components", async () => { |
| const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-schtasks-test-")); |
| try { |
| const scriptPath = path.join(tmpDir, ".openclaw", "gateway.cmd"); |
| await fs.mkdir(path.dirname(scriptPath), { recursive: true }); |
| await fs.writeFile( |
| scriptPath, |
| [ |
| "@echo off", |
| "rem OpenClaw Gateway", |
| "cd /d C:\\Projects\\openclaw", |
| "set NODE_ENV=production", |
| "set OPENCLAW_PORT=7860", |
| "node gateway.js --verbose", |
| ].join("\r\n"), |
| "utf8", |
| ); |
|
|
| const env = { USERPROFILE: tmpDir, OPENCLAW_PROFILE: "default" }; |
| const result = await readScheduledTaskCommand(env); |
| expect(result).toEqual({ |
| programArguments: ["node", "gateway.js", "--verbose"], |
| workingDirectory: "C:\\Projects\\openclaw", |
| environment: { |
| NODE_ENV: "production", |
| OPENCLAW_PORT: "7860", |
| }, |
| }); |
| } finally { |
| await fs.rm(tmpDir, { recursive: true, force: true }); |
| } |
| }); |
| }); |
|
|