File size: 5,836 Bytes
f778c12 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | // 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();
});
});
|