mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-29 17:25:14 +00:00
fix(cli): route node status hints to stdout (#85780)
This commit is contained in:
116
src/cli/node-cli/daemon.test.ts
Normal file
116
src/cli/node-cli/daemon.test.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { GatewayServiceRuntime } from "../../daemon/service-runtime.js";
|
||||
import { runNodeDaemonStatus } from "./daemon.js";
|
||||
|
||||
const mocks = vi.hoisted(() => {
|
||||
const service = {
|
||||
label: "Node service",
|
||||
loadedText: "loaded",
|
||||
notLoadedText: "not loaded",
|
||||
stage: vi.fn(),
|
||||
install: vi.fn(),
|
||||
uninstall: vi.fn(),
|
||||
stop: vi.fn(),
|
||||
restart: vi.fn(),
|
||||
isLoaded: vi.fn(async () => true),
|
||||
readCommand: vi.fn(async () => null),
|
||||
readRuntime: vi.fn<() => Promise<GatewayServiceRuntime>>(async () => ({ status: "running" })),
|
||||
};
|
||||
return {
|
||||
runtime: {
|
||||
log: vi.fn<(line: string) => void>(),
|
||||
error: vi.fn<(line: string) => void>(),
|
||||
writeJson: vi.fn(),
|
||||
exit: vi.fn(),
|
||||
},
|
||||
service,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../../runtime.js", () => ({
|
||||
defaultRuntime: mocks.runtime,
|
||||
}));
|
||||
|
||||
vi.mock("../../daemon/node-service.js", () => ({
|
||||
resolveNodeService: () => mocks.service,
|
||||
}));
|
||||
|
||||
vi.mock("../../daemon/runtime-hints.js", () => ({
|
||||
buildPlatformRuntimeLogHints: () => [
|
||||
"Logs: node service log",
|
||||
"Restart attempts: node restart log",
|
||||
],
|
||||
buildPlatformServiceStartHints: () => ["openclaw node install", "openclaw node start"],
|
||||
}));
|
||||
|
||||
vi.mock("../../terminal/theme.js", async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import("../../terminal/theme.js")>("../../terminal/theme.js");
|
||||
return {
|
||||
...actual,
|
||||
colorize: (_rich: boolean, _theme: unknown, text: string) => text,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../daemon-cli/shared.js", async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import("../daemon-cli/shared.js")>("../daemon-cli/shared.js");
|
||||
return {
|
||||
...actual,
|
||||
createCliStatusTextStyles: () => ({
|
||||
rich: false,
|
||||
label: (text: string) => text,
|
||||
accent: (text: string) => text,
|
||||
infoText: (text: string) => text,
|
||||
okText: (text: string) => text,
|
||||
warnText: (text: string) => text,
|
||||
errorText: (text: string) => text,
|
||||
}),
|
||||
formatRuntimeStatus: (runtime: GatewayServiceRuntime | undefined) => runtime?.status ?? "",
|
||||
resolveRuntimeStatusColor: () => "",
|
||||
};
|
||||
});
|
||||
|
||||
describe("runNodeDaemonStatus", () => {
|
||||
function stdout(): string {
|
||||
return mocks.runtime.log.mock.calls.map(([line]) => line).join("\n");
|
||||
}
|
||||
|
||||
function stderr(): string {
|
||||
return mocks.runtime.error.mock.calls.map(([line]) => line).join("\n");
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mocks.runtime.log.mockClear();
|
||||
mocks.runtime.error.mockClear();
|
||||
mocks.runtime.writeJson.mockClear();
|
||||
mocks.runtime.exit.mockClear();
|
||||
mocks.service.isLoaded.mockReset().mockResolvedValue(true);
|
||||
mocks.service.readCommand.mockReset().mockResolvedValue(null);
|
||||
mocks.service.readRuntime.mockReset().mockResolvedValue({ status: "running" });
|
||||
});
|
||||
|
||||
it("keeps missing service-unit status on stderr and prints recovery hints on stdout", async () => {
|
||||
mocks.service.readRuntime.mockResolvedValue({ status: "stopped", missingUnit: true });
|
||||
|
||||
await runNodeDaemonStatus();
|
||||
|
||||
expect(stderr()).toContain("Service unit not found.");
|
||||
expect(stdout()).toContain("Logs: node service log");
|
||||
expect(stdout()).toContain("Restart attempts: node restart log");
|
||||
expect(stderr()).not.toContain("Logs: node service log");
|
||||
expect(stderr()).not.toContain("Restart attempts: node restart log");
|
||||
});
|
||||
|
||||
it("keeps stopped status on stderr and prints recovery hints on stdout", async () => {
|
||||
mocks.service.readRuntime.mockResolvedValue({ status: "stopped" });
|
||||
|
||||
await runNodeDaemonStatus();
|
||||
|
||||
expect(stderr()).toContain("Service is loaded but not running.");
|
||||
expect(stdout()).toContain("Logs: node service log");
|
||||
expect(stdout()).toContain("Restart attempts: node restart log");
|
||||
expect(stderr()).not.toContain("Logs: node service log");
|
||||
expect(stderr()).not.toContain("Restart attempts: node restart log");
|
||||
});
|
||||
});
|
||||
@@ -277,7 +277,7 @@ export async function runNodeDaemonStatus(opts: NodeDaemonStatusOptions = {}) {
|
||||
if (runtime?.missingUnit) {
|
||||
defaultRuntime.error(errorText("Service unit not found."));
|
||||
for (const hint of buildNodeRuntimeHints(hintEnv)) {
|
||||
defaultRuntime.error(errorText(hint));
|
||||
defaultRuntime.log(errorText(hint));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -285,7 +285,7 @@ export async function runNodeDaemonStatus(opts: NodeDaemonStatusOptions = {}) {
|
||||
if (runtime?.status === "stopped") {
|
||||
defaultRuntime.error(errorText("Service is loaded but not running."));
|
||||
for (const hint of buildNodeRuntimeHints(hintEnv)) {
|
||||
defaultRuntime.error(errorText(hint));
|
||||
defaultRuntime.log(errorText(hint));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user