openclaw / src /cli /daemon-cli /response.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
f778c12 verified
Raw
History Blame Contribute Delete
5.84 kB
// Daemon response tests cover normalized daemon command response shapes.
import { afterEach, describe, expect, it, vi } from "vitest";
import type { GatewayService } from "../../daemon/service.js";
import { defaultRuntime } from "../../runtime.js";
import { createDaemonActionContext, installDaemonServiceAndEmit } from "./response.js";
describe("daemon action JSON hints", () => {
afterEach(() => {
vi.restoreAllMocks();
});
it("classifies common daemon hint kinds", () => {
const hints = [
"openclaw gateway install",
"Restart the container or the service that manages it for openclaw-demo-container.",
"systemd user services are unavailable; install/enable systemd or run the gateway under your supervisor.",
"On a headless server (SSH/no desktop session): run `sudo loginctl enable-linger $(whoami)` to persist your systemd user session across logins.",
"If you're in a container, run the gateway in the foreground instead of `openclaw gateway`.",
"WSL2 needs systemd enabled: edit /etc/wsl.conf with [boot]\\nsystemd=true",
];
const writeJson = vi.spyOn(defaultRuntime, "writeJson").mockImplementation(() => {});
createDaemonActionContext({ action: "install", json: true }).emit({ ok: false, hints });
expect(writeJson).toHaveBeenCalledWith(
expect.objectContaining({
action: "install",
hints,
hintItems: [
{ kind: "install", text: "openclaw gateway install" },
{
kind: "container-restart",
text: "Restart the container or the service that manages it for openclaw-demo-container.",
},
{
kind: "systemd-unavailable",
text: "systemd user services are unavailable; install/enable systemd or run the gateway under your supervisor.",
},
{
kind: "systemd-headless",
text: "On a headless server (SSH/no desktop session): run `sudo loginctl enable-linger $(whoami)` to persist your systemd user session across logins.",
},
{
kind: "container-foreground",
text: "If you're in a container, run the gateway in the foreground instead of `openclaw gateway`.",
},
{
kind: "wsl-systemd",
text: "WSL2 needs systemd enabled: edit /etc/wsl.conf with [boot]\\nsystemd=true",
},
],
}),
);
});
it.each([
"openclaw --profile work gateway install",
"openclaw --container demo gateway install",
"openclaw node install",
"openclaw --profile work node install",
"openclaw --container demo node install",
])("classifies scoped Gateway and node service install hints: %s", (hint) => {
const writeJson = vi.spyOn(defaultRuntime, "writeJson").mockImplementation(() => {});
createDaemonActionContext({ action: "start", json: true }).emit({ ok: false, hints: [hint] });
expect(writeJson).toHaveBeenCalledWith(
expect.objectContaining({ hintItems: [{ kind: "install", text: hint }] }),
);
});
});
describe("daemon install verification", () => {
function createInstallParams(
isLoaded: GatewayService["isLoaded"],
onVerified?: () => Promise<void>,
) {
const service = {
label: "systemd user",
loadedText: "enabled",
notLoadedText: "disabled",
isLoaded,
} as GatewayService;
return {
serviceNoun: "Gateway",
service,
warnings: [],
emit: vi.fn(),
fail: vi.fn(),
install: vi.fn(async () => {}),
onVerified,
};
}
it("fails install when service-manager verification throws", async () => {
const params = createInstallParams(
vi.fn(async () => {
throw new Error("manager access denied");
}),
);
await installDaemonServiceAndEmit(params);
expect(params.fail).toHaveBeenCalledWith(
"Gateway install verification failed: Error: manager access denied",
undefined,
);
expect(params.emit).not.toHaveBeenCalled();
});
it("fails install when the service is not loaded after installation", async () => {
const params = createInstallParams(vi.fn(async () => false));
await installDaemonServiceAndEmit(params);
expect(params.fail).toHaveBeenCalledWith(
"Gateway install verification failed: service is not enabled.",
);
expect(params.emit).not.toHaveBeenCalled();
});
it("emits success only after the service-manager verification succeeds", async () => {
const params = createInstallParams(vi.fn(async () => true));
await installDaemonServiceAndEmit(params);
expect(params.fail).not.toHaveBeenCalled();
expect(params.emit).toHaveBeenCalledWith(
expect.objectContaining({
ok: true,
result: "installed",
service: expect.objectContaining({ loaded: true }),
}),
);
});
it("runs onVerified after verification succeeds and before the success emit", async () => {
const onVerified = vi.fn(async () => {});
const params = createInstallParams(
vi.fn(async () => true),
onVerified,
);
await installDaemonServiceAndEmit(params);
expect(onVerified).toHaveBeenCalledTimes(1);
expect(params.fail).not.toHaveBeenCalled();
expect(params.emit).toHaveBeenCalledWith(
expect.objectContaining({ ok: true, result: "installed" }),
);
});
it("fails with no success emit when onVerified throws", async () => {
const params = createInstallParams(
vi.fn(async () => true),
async () => {
throw new Error("post-check boom");
},
);
await installDaemonServiceAndEmit(params);
expect(params.fail).toHaveBeenCalledWith(
"Gateway post-install check failed: Error: post-check boom",
);
expect(params.emit).not.toHaveBeenCalled();
});
});