diff --git a/src/cli/daemon-cli/lifecycle.test.ts b/src/cli/daemon-cli/lifecycle.test.ts index 71d51080322..cae9ca8e0a8 100644 --- a/src/cli/daemon-cli/lifecycle.test.ts +++ b/src/cli/daemon-cli/lifecycle.test.ts @@ -55,6 +55,29 @@ const loadConfig = vi.hoisted(() => vi.fn(() => ({}))); const recoverInstalledLaunchAgent = vi.hoisted(() => vi.fn()); const repairLoadedGatewayServiceForStart = vi.hoisted(() => vi.fn()); +function requireMockCallArg( + mockFn: { mock: { calls: unknown[][] } }, + label: string, + index = 0, +): Record { + const arg = mockFn.mock.calls[index]?.[0] as Record | undefined; + if (!arg) { + throw new Error(`expected ${label} call #${index + 1}`); + } + return arg; +} + +async function expectRestartError( + promise: Promise, +): Promise { + try { + await promise; + } catch (error) { + return error as Error & { hints?: string[] }; + } + throw new Error("expected restart to fail"); +} + vi.mock("../../config/config.js", () => ({ getRuntimeConfig: () => loadConfig(), loadConfig: () => loadConfig(), @@ -329,18 +352,22 @@ describe("runDaemonRestart health checks", () => { await runDaemonStart({ json: true }); - expect(repairLoadedGatewayServiceForStart).toHaveBeenCalledWith( - expect.objectContaining({ - service, - json: true, - state: expect.objectContaining({ - command: expect.objectContaining({ - environment: { OPENCLAW_SERVICE_VERSION: "2026.4.24" }, - }), - }), - issues: [expect.objectContaining({ code: "version-mismatch" })], - }), - ); + const repairParams = requireMockCallArg( + repairLoadedGatewayServiceForStart, + "repairLoadedGatewayServiceForStart", + ) as { + service?: unknown; + json?: unknown; + state?: { command?: { environment?: unknown } }; + issues?: Array<{ code?: unknown }>; + }; + expect(repairParams.service).toBe(service); + expect(repairParams.json).toBe(true); + expect(repairParams.state?.command?.environment).toEqual({ + OPENCLAW_SERVICE_VERSION: "2026.4.24", + }); + expect(repairParams.issues).toHaveLength(1); + expect(repairParams.issues?.[0]?.code).toBe("version-mismatch"); }); it("kills stale gateway pids and retries restart", async () => { @@ -398,13 +425,12 @@ describe("runDaemonRestart health checks", () => { }; waitForGatewayHealthyRestart.mockResolvedValue(unhealthy); - await expect(runDaemonRestart({ json: true })).rejects.toMatchObject({ - message: "Gateway restart timed out after 60s waiting for health checks.", - hints: [ - formatCliCommand("openclaw gateway status --deep"), - formatCliCommand("openclaw doctor"), - ], - }); + const error = await expectRestartError(runDaemonRestart({ json: true })); + expect(error.message).toBe("Gateway restart timed out after 60s waiting for health checks."); + expect(error.hints).toEqual([ + formatCliCommand("openclaw gateway status --deep"), + formatCliCommand("openclaw doctor"), + ]); expect(terminateStaleGatewayPids).not.toHaveBeenCalled(); expect(renderRestartDiagnostics).toHaveBeenCalledTimes(1); }); @@ -420,14 +446,19 @@ describe("runDaemonRestart health checks", () => { await runDaemonRestart({ json: true }); - expect(waitForGatewayHealthyRestart).toHaveBeenCalledWith( - expect.objectContaining({ - attempts: 360, - delayMs: 500, - includeUnknownListenersAsStale: true, - port: 18789, - }), - ); + const waitParams = requireMockCallArg( + waitForGatewayHealthyRestart, + "waitForGatewayHealthyRestart", + ) as { + attempts?: unknown; + delayMs?: unknown; + includeUnknownListenersAsStale?: unknown; + port?: unknown; + }; + expect(waitParams.attempts).toBe(360); + expect(waitParams.delayMs).toBe(500); + expect(waitParams.includeUnknownListenersAsStale).toBe(true); + expect(waitParams.port).toBe(18789); }); it("fails restart with a stopped-free message when the waiter exits early", async () => { @@ -442,14 +473,14 @@ describe("runDaemonRestart health checks", () => { }; waitForGatewayHealthyRestart.mockResolvedValue(unhealthy); - await expect(runDaemonRestart({ json: true })).rejects.toMatchObject({ - message: - "Gateway restart failed after 13s: service stayed stopped and health checks never came up.", - hints: [ - formatCliCommand("openclaw gateway status --deep"), - formatCliCommand("openclaw doctor"), - ], - }); + const error = await expectRestartError(runDaemonRestart({ json: true })); + expect(error.message).toBe( + "Gateway restart failed after 13s: service stayed stopped and health checks never came up.", + ); + expect(error.hints).toEqual([ + formatCliCommand("openclaw gateway status --deep"), + formatCliCommand("openclaw doctor"), + ]); expect(terminateStaleGatewayPids).not.toHaveBeenCalled(); expect(renderRestartDiagnostics).toHaveBeenCalledTimes(1); }); @@ -472,12 +503,12 @@ describe("runDaemonRestart health checks", () => { await runDaemonStop({ json: true, disable: true }); - expect(runServiceStop).toHaveBeenCalledWith( - expect.objectContaining({ - opts: { json: true, disable: true }, - stopWhenNotLoaded: true, - }), - ); + const stopParams = requireMockCallArg(runServiceStop, "runServiceStop") as { + opts?: unknown; + stopWhenNotLoaded?: unknown; + }; + expect(stopParams.opts).toEqual({ json: true, disable: true }); + expect(stopParams.stopWhenNotLoaded).toBe(true); }); it("skips gateway port resolution on stop when the service manager handles the stop", async () => {