

























@@ -1,14 +1,26 @@
11import fs from "node:fs/promises";
22import os from "node:os";
33import path from "node:path";
4-import { describe, expect, it } from "vitest";
4+import { beforeEach, describe, expect, it, vi } from "vitest";
55import {
6-deriveScheduledTaskRuntimeStatus,
76parseSchtasksQuery,
87readScheduledTaskCommand,
8+readScheduledTaskRuntime,
99resolveTaskScriptPath,
1010} from "./schtasks.js";
111112+const schtasksResponses = vi.hoisted(
13+(): Array<{ code: number; stdout: string; stderr: string }> => [],
14+);
15+16+vi.mock("./schtasks-exec.js", () => ({
17+execSchtasks: async () => schtasksResponses.shift() ?? { code: 0, stdout: "", stderr: "" },
18+}));
19+20+beforeEach(() => {
21+schtasksResponses.length = 0;
22+});
23+1224describe("schtasks runtime parsing", () => {
1325it.each(["Ready", "Running"])("parses %s status", (status) => {
1426const output = [
@@ -40,83 +52,85 @@ describe("schtasks runtime parsing", () => {
4052});
41534254describe("scheduled task runtime derivation", () => {
43-it("treats Running + 0x41301 as running", () => {
44-expect(
45-deriveScheduledTaskRuntimeStatus({
46-status: "Running",
47-lastRunResult: "0x41301",
48-}),
49-).toEqual({ status: "running" });
55+async function readRuntimeFromQueryOutput(output: string) {
56+schtasksResponses.push(
57+{ code: 0, stdout: "", stderr: "" },
58+{ code: 0, stdout: output, stderr: "" },
59+);
60+return await readScheduledTaskRuntime({
61+USERPROFILE: "C:\\Users\\test",
62+OPENCLAW_PROFILE: "default",
63+});
64+}
65+66+function taskQueryOutput(lines: string[]): string {
67+return [
68+"TaskName: \\OpenClaw Gateway",
69+"Last Run Time: 1/8/2026 1:23:45 AM",
70+ ...lines,
71+"",
72+].join("\r\n");
73+}
74+75+it("treats Running + 0x41301 as running", async () => {
76+await expect(
77+readRuntimeFromQueryOutput(taskQueryOutput(["Status: Running", "Last Run Result: 0x41301"])),
78+).resolves.toMatchObject({ status: "running" });
5079});
518052-it("treats Running + decimal 267009 as running", () => {
53-expect(
54-deriveScheduledTaskRuntimeStatus({
55-status: "Running",
56-lastRunResult: "267009",
57-}),
58-).toEqual({ status: "running" });
81+it("treats Running + decimal 267009 as running", async () => {
82+await expect(
83+readRuntimeFromQueryOutput(taskQueryOutput(["Status: Running", "Last Run Result: 267009"])),
84+).resolves.toMatchObject({ status: "running" });
5985});
608661-it("treats Running without numeric result as unknown", () => {
62-expect(
63-deriveScheduledTaskRuntimeStatus({
64-status: "Running",
65-}),
66-).toEqual({
87+it("treats Running without numeric result as unknown", async () => {
88+await expect(
89+readRuntimeFromQueryOutput(taskQueryOutput(["Status: Running"])),
90+).resolves.toMatchObject({
6791status: "unknown",
6892detail: "Task status is locale-dependent and no numeric Last Run Result was available.",
6993});
7094});
719572-it("treats non-running result codes as stopped", () => {
73-expect(
74-deriveScheduledTaskRuntimeStatus({
75-status: "Running",
76-lastRunResult: "0x0",
77-}),
78-).toEqual({
96+it("treats non-running result codes as stopped", async () => {
97+await expect(
98+readRuntimeFromQueryOutput(taskQueryOutput(["Status: Running", "Last Run Result: 0x0"])),
99+).resolves.toMatchObject({
79100status: "stopped",
80101detail: "Task Last Run Result=0x0; treating as not running.",
81102});
82103});
8310484-it("detects running via result code when status is localized (German)", () => {
85-expect(
86-deriveScheduledTaskRuntimeStatus({
87-status: "Wird ausgeführt",
88-lastRunResult: "0x41301",
89-}),
90-).toEqual({ status: "running" });
105+it("detects running via result code when status is localized (German)", async () => {
106+await expect(
107+readRuntimeFromQueryOutput(
108+taskQueryOutput(["Status: Wird ausgeführt", "Last Run Result: 0x41301"]),
109+),
110+).resolves.toMatchObject({ status: "running" });
91111});
9211293-it("detects running via result code when status is localized (French)", () => {
94-expect(
95-deriveScheduledTaskRuntimeStatus({
96-status: "En cours",
97-lastRunResult: "267009",
98-}),
99-).toEqual({ status: "running" });
113+it("detects running via result code when status is localized (French)", async () => {
114+await expect(
115+readRuntimeFromQueryOutput(taskQueryOutput(["Status: En cours", "Last Run Result: 267009"])),
116+).resolves.toMatchObject({ status: "running" });
100117});
101118102-it("treats localized status as stopped when result code is not a running code", () => {
103-expect(
104-deriveScheduledTaskRuntimeStatus({
105-status: "Wird ausgeführt",
106-lastRunResult: "0x0",
107-}),
108-).toEqual({
119+it("treats localized status as stopped when result code is not a running code", async () => {
120+await expect(
121+readRuntimeFromQueryOutput(
122+taskQueryOutput(["Status: Wird ausgeführt", "Last Run Result: 0x0"]),
123+),
124+).resolves.toMatchObject({
109125status: "stopped",
110126detail: "Task Last Run Result=0x0; treating as not running.",
111127});
112128});
113129114-it("treats localized status without result code as unknown", () => {
115-expect(
116-deriveScheduledTaskRuntimeStatus({
117-status: "Wird ausgeführt",
118-}),
119-).toEqual({
130+it("treats localized status without result code as unknown", async () => {
131+await expect(
132+readRuntimeFromQueryOutput(taskQueryOutput(["Status: Wird ausgeführt"])),
133+).resolves.toMatchObject({
120134status: "unknown",
121135detail: "Task status is locale-dependent and no numeric Last Run Result was available.",
122136});
@@ -149,9 +163,32 @@ describe("resolveTaskScriptPath", () => {
149163env: { HOME: "/home/test", OPENCLAW_PROFILE: "default" },
150164expected: path.join("/home/test", ".openclaw", "gateway.cmd"),
151165},
166+{
167+name: "uses a custom task script file name inside the state directory",
168+env: {
169+USERPROFILE: "C:\\Users\\test",
170+OPENCLAW_TASK_SCRIPT_NAME: "gateway-node.cmd",
171+},
172+expected: path.join("C:\\Users\\test", ".openclaw", "gateway-node.cmd"),
173+},
152174])("$name", ({ env, expected }) => {
153175expect(resolveTaskScriptPath(env)).toBe(expected);
154176});
177+178+it.each([
179+"../gateway.cmd",
180+"..\\gateway.cmd",
181+"nested/gateway.cmd",
182+"nested\\gateway.cmd",
183+"gateway..cmd",
184+])("rejects non-file task script name %s", (scriptName) => {
185+expect(() =>
186+resolveTaskScriptPath({
187+USERPROFILE: "C:\\Users\\test",
188+OPENCLAW_TASK_SCRIPT_NAME: scriptName,
189+}),
190+).toThrow("OPENCLAW_TASK_SCRIPT_NAME must be a file name only");
191+});
155192});
156193157194describe("readScheduledTaskCommand", () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。