




















@@ -0,0 +1,116 @@
1+import { beforeEach, describe, expect, it, vi } from "vitest";
2+import type { GatewayServiceRuntime } from "../../daemon/service-runtime.js";
3+import { runNodeDaemonStatus } from "./daemon.js";
4+5+const mocks = vi.hoisted(() => {
6+const service = {
7+label: "Node service",
8+loadedText: "loaded",
9+notLoadedText: "not loaded",
10+stage: vi.fn(),
11+install: vi.fn(),
12+uninstall: vi.fn(),
13+stop: vi.fn(),
14+restart: vi.fn(),
15+isLoaded: vi.fn(async () => true),
16+readCommand: vi.fn(async () => null),
17+readRuntime: vi.fn<() => Promise<GatewayServiceRuntime>>(async () => ({ status: "running" })),
18+};
19+return {
20+runtime: {
21+log: vi.fn<(line: string) => void>(),
22+error: vi.fn<(line: string) => void>(),
23+writeJson: vi.fn(),
24+exit: vi.fn(),
25+},
26+ service,
27+};
28+});
29+30+vi.mock("../../runtime.js", () => ({
31+defaultRuntime: mocks.runtime,
32+}));
33+34+vi.mock("../../daemon/node-service.js", () => ({
35+resolveNodeService: () => mocks.service,
36+}));
37+38+vi.mock("../../daemon/runtime-hints.js", () => ({
39+buildPlatformRuntimeLogHints: () => [
40+"Logs: node service log",
41+"Restart attempts: node restart log",
42+],
43+buildPlatformServiceStartHints: () => ["openclaw node install", "openclaw node start"],
44+}));
45+46+vi.mock("../../terminal/theme.js", async () => {
47+const actual =
48+await vi.importActual<typeof import("../../terminal/theme.js")>("../../terminal/theme.js");
49+return {
50+ ...actual,
51+colorize: (_rich: boolean, _theme: unknown, text: string) => text,
52+};
53+});
54+55+vi.mock("../daemon-cli/shared.js", async () => {
56+const actual =
57+await vi.importActual<typeof import("../daemon-cli/shared.js")>("../daemon-cli/shared.js");
58+return {
59+ ...actual,
60+createCliStatusTextStyles: () => ({
61+rich: false,
62+label: (text: string) => text,
63+accent: (text: string) => text,
64+infoText: (text: string) => text,
65+okText: (text: string) => text,
66+warnText: (text: string) => text,
67+errorText: (text: string) => text,
68+}),
69+formatRuntimeStatus: (runtime: GatewayServiceRuntime | undefined) => runtime?.status ?? "",
70+resolveRuntimeStatusColor: () => "",
71+};
72+});
73+74+describe("runNodeDaemonStatus", () => {
75+function stdout(): string {
76+return mocks.runtime.log.mock.calls.map(([line]) => line).join("\n");
77+}
78+79+function stderr(): string {
80+return mocks.runtime.error.mock.calls.map(([line]) => line).join("\n");
81+}
82+83+beforeEach(() => {
84+mocks.runtime.log.mockClear();
85+mocks.runtime.error.mockClear();
86+mocks.runtime.writeJson.mockClear();
87+mocks.runtime.exit.mockClear();
88+mocks.service.isLoaded.mockReset().mockResolvedValue(true);
89+mocks.service.readCommand.mockReset().mockResolvedValue(null);
90+mocks.service.readRuntime.mockReset().mockResolvedValue({ status: "running" });
91+});
92+93+it("keeps missing service-unit status on stderr and prints recovery hints on stdout", async () => {
94+mocks.service.readRuntime.mockResolvedValue({ status: "stopped", missingUnit: true });
95+96+await runNodeDaemonStatus();
97+98+expect(stderr()).toContain("Service unit not found.");
99+expect(stdout()).toContain("Logs: node service log");
100+expect(stdout()).toContain("Restart attempts: node restart log");
101+expect(stderr()).not.toContain("Logs: node service log");
102+expect(stderr()).not.toContain("Restart attempts: node restart log");
103+});
104+105+it("keeps stopped status on stderr and prints recovery hints on stdout", async () => {
106+mocks.service.readRuntime.mockResolvedValue({ status: "stopped" });
107+108+await runNodeDaemonStatus();
109+110+expect(stderr()).toContain("Service is loaded but not running.");
111+expect(stdout()).toContain("Logs: node service log");
112+expect(stdout()).toContain("Restart attempts: node restart log");
113+expect(stderr()).not.toContain("Logs: node service log");
114+expect(stderr()).not.toContain("Restart attempts: node restart log");
115+});
116+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。