Files
Moltbot/src/daemon/schtasks.test.ts
Artale ab0d8ef8c1 fix(daemon): preserve backslashes in parseCommandLine on Windows (#15642)
* fix(daemon): preserve backslashes in parseCommandLine on Windows

Only treat backslash as escape when followed by a quote or another
backslash. Bare backslashes are kept as-is so Windows paths survive.

Fixes #15587

* fix(daemon): preserve UNC backslashes in schtasks parsing (#15642) (thanks @arosstale)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-02-13 19:27:06 +01:00

308 lines
11 KiB
TypeScript

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 18789"].join("\r\n"),
"utf8",
);
const env = { USERPROFILE: tmpDir, OPENCLAW_PROFILE: "default" };
const result = await readScheduledTaskCommand(env);
expect(result).toEqual({
programArguments: ["node", "gateway.js", "--port", "18789"],
});
} 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=18789", "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: "18789",
},
});
} 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 });
// Use forward slashes which work in Windows cmd and avoid escape parsing issues
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=18789",
"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: "18789",
},
});
} finally {
await fs.rm(tmpDir, { recursive: true, force: true });
}
});
it("parses command with Windows backslash paths", 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\\nodejs\\node.exe" C:\\Users\\test\\AppData\\Roaming\\npm\\node_modules\\openclaw\\dist\\index.js gateway --port 18789',
].join("\r\n"),
"utf8",
);
const env = { USERPROFILE: tmpDir, OPENCLAW_PROFILE: "default" };
const result = await readScheduledTaskCommand(env);
expect(result).toEqual({
programArguments: [
"C:\\Program Files\\nodejs\\node.exe",
"C:\\Users\\test\\AppData\\Roaming\\npm\\node_modules\\openclaw\\dist\\index.js",
"gateway",
"--port",
"18789",
],
});
} finally {
await fs.rm(tmpDir, { recursive: true, force: true });
}
});
it("preserves UNC paths in command arguments", 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",
'"\\\\fileserver\\OpenClaw Share\\node.exe" "\\\\fileserver\\OpenClaw Share\\dist\\index.js" gateway --port 18789',
].join("\r\n"),
"utf8",
);
const env = { USERPROFILE: tmpDir, OPENCLAW_PROFILE: "default" };
const result = await readScheduledTaskCommand(env);
expect(result).toEqual({
programArguments: [
"\\\\fileserver\\OpenClaw Share\\node.exe",
"\\\\fileserver\\OpenClaw Share\\dist\\index.js",
"gateway",
"--port",
"18789",
],
});
} finally {
await fs.rm(tmpDir, { recursive: true, force: true });
}
});
});