From c90b10b02fa418556bed3396a10d187980793cec Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 14 Mar 2026 00:10:44 +0000 Subject: [PATCH] test: share daemon cli service helpers --- src/cli/daemon-cli/lifecycle-core.test.ts | 52 +++++---- src/daemon/service-audit.test.ts | 125 +++++++++++----------- 2 files changed, 84 insertions(+), 93 deletions(-) diff --git a/src/cli/daemon-cli/lifecycle-core.test.ts b/src/cli/daemon-cli/lifecycle-core.test.ts index 6e86ad0d23a..2f17269eb6c 100644 --- a/src/cli/daemon-cli/lifecycle-core.test.ts +++ b/src/cli/daemon-cli/lifecycle-core.test.ts @@ -29,6 +29,21 @@ let runServiceRestart: typeof import("./lifecycle-core.js").runServiceRestart; let runServiceStart: typeof import("./lifecycle-core.js").runServiceStart; let runServiceStop: typeof import("./lifecycle-core.js").runServiceStop; +function readJsonLog() { + const jsonLine = runtimeLogs.find((line) => line.trim().startsWith("{")); + return JSON.parse(jsonLine ?? "{}") as T; +} + +function createServiceRunArgs(checkTokenDrift?: boolean) { + return { + serviceNoun: "Gateway", + service, + renderStartHints: () => [], + opts: { json: true as const }, + ...(checkTokenDrift ? { checkTokenDrift } : {}), + }; +} + describe("runServiceRestart token drift", () => { beforeAll(async () => { ({ runServiceRestart, runServiceStart, runServiceStop } = await import("./lifecycle-core.js")); @@ -53,17 +68,10 @@ describe("runServiceRestart token drift", () => { }); it("emits drift warning when enabled", async () => { - await runServiceRestart({ - serviceNoun: "Gateway", - service, - renderStartHints: () => [], - opts: { json: true }, - checkTokenDrift: true, - }); + await runServiceRestart(createServiceRunArgs(true)); expect(loadConfig).toHaveBeenCalledTimes(1); - const jsonLine = runtimeLogs.find((line) => line.trim().startsWith("{")); - const payload = JSON.parse(jsonLine ?? "{}") as { warnings?: string[] }; + const payload = readJsonLog<{ warnings?: string[] }>(); expect(payload.warnings).toEqual( expect.arrayContaining([expect.stringContaining("gateway install --force")]), ); @@ -83,16 +91,9 @@ describe("runServiceRestart token drift", () => { }); vi.stubEnv("OPENCLAW_GATEWAY_TOKEN", "env-token"); - await runServiceRestart({ - serviceNoun: "Gateway", - service, - renderStartHints: () => [], - opts: { json: true }, - checkTokenDrift: true, - }); + await runServiceRestart(createServiceRunArgs(true)); - const jsonLine = runtimeLogs.find((line) => line.trim().startsWith("{")); - const payload = JSON.parse(jsonLine ?? "{}") as { warnings?: string[] }; + const payload = readJsonLog<{ warnings?: string[] }>(); expect(payload.warnings).toEqual( expect.arrayContaining([expect.stringContaining("gateway install --force")]), ); @@ -108,8 +109,7 @@ describe("runServiceRestart token drift", () => { expect(loadConfig).not.toHaveBeenCalled(); expect(service.readCommand).not.toHaveBeenCalled(); - const jsonLine = runtimeLogs.find((line) => line.trim().startsWith("{")); - const payload = JSON.parse(jsonLine ?? "{}") as { warnings?: string[] }; + const payload = readJsonLog<{ warnings?: string[] }>(); expect(payload.warnings).toBeUndefined(); }); @@ -126,8 +126,7 @@ describe("runServiceRestart token drift", () => { }), }); - const jsonLine = runtimeLogs.find((line) => line.trim().startsWith("{")); - const payload = JSON.parse(jsonLine ?? "{}") as { result?: string; message?: string }; + const payload = readJsonLog<{ result?: string; message?: string }>(); expect(payload.result).toBe("stopped"); expect(payload.message).toContain("unmanaged process"); expect(service.stop).not.toHaveBeenCalled(); @@ -152,8 +151,7 @@ describe("runServiceRestart token drift", () => { expect(postRestartCheck).toHaveBeenCalledTimes(1); expect(service.restart).not.toHaveBeenCalled(); expect(service.readCommand).not.toHaveBeenCalled(); - const jsonLine = runtimeLogs.find((line) => line.trim().startsWith("{")); - const payload = JSON.parse(jsonLine ?? "{}") as { result?: string; message?: string }; + const payload = readJsonLog<{ result?: string; message?: string }>(); expect(payload.result).toBe("restarted"); expect(payload.message).toContain("unmanaged process"); }); @@ -172,8 +170,7 @@ describe("runServiceRestart token drift", () => { expect(result).toBe(true); expect(postRestartCheck).not.toHaveBeenCalled(); - const jsonLine = runtimeLogs.find((line) => line.trim().startsWith("{")); - const payload = JSON.parse(jsonLine ?? "{}") as { result?: string; message?: string }; + const payload = readJsonLog<{ result?: string; message?: string }>(); expect(payload.result).toBe("scheduled"); expect(payload.message).toBe("restart scheduled, gateway will restart momentarily"); }); @@ -189,8 +186,7 @@ describe("runServiceRestart token drift", () => { }); expect(service.isLoaded).toHaveBeenCalledTimes(1); - const jsonLine = runtimeLogs.find((line) => line.trim().startsWith("{")); - const payload = JSON.parse(jsonLine ?? "{}") as { result?: string; message?: string }; + const payload = readJsonLog<{ result?: string; message?: string }>(); expect(payload.result).toBe("scheduled"); expect(payload.message).toBe("restart scheduled, gateway will restart momentarily"); }); diff --git a/src/daemon/service-audit.test.ts b/src/daemon/service-audit.test.ts index ffdd0fa526d..505f8f97d7d 100644 --- a/src/daemon/service-audit.test.ts +++ b/src/daemon/service-audit.test.ts @@ -6,6 +6,53 @@ import { } from "./service-audit.js"; import { buildMinimalServicePath } from "./service-env.js"; +function hasIssue( + audit: Awaited>, + code: (typeof SERVICE_AUDIT_CODES)[keyof typeof SERVICE_AUDIT_CODES], +) { + return audit.issues.some((issue) => issue.code === code); +} + +function createGatewayAudit({ + expectedGatewayToken, + path = "/usr/local/bin:/usr/bin:/bin", + serviceToken, + environmentValueSources, +}: { + expectedGatewayToken?: string; + path?: string; + serviceToken?: string; + environmentValueSources?: Record; +} = {}) { + return auditGatewayServiceConfig({ + env: { HOME: "/tmp" }, + platform: "linux", + expectedGatewayToken, + command: { + programArguments: ["/usr/bin/node", "gateway"], + environment: { + PATH: path, + ...(serviceToken ? { OPENCLAW_GATEWAY_TOKEN: serviceToken } : {}), + }, + ...(environmentValueSources ? { environmentValueSources } : {}), + }, + }); +} + +function expectTokenAudit( + audit: Awaited>, + { + embedded, + mismatch, + }: { + embedded: boolean; + mismatch: boolean; + }, +) { + expect(hasIssue(audit, SERVICE_AUDIT_CODES.gatewayTokenEmbedded)).toBe(embedded); + expect(hasIssue(audit, SERVICE_AUDIT_CODES.gatewayTokenMismatch)).toBe(mismatch); +} + describe("auditGatewayServiceConfig", () => { it("flags bun runtime", async () => { const audit = await auditGatewayServiceConfig({ @@ -66,89 +113,37 @@ describe("auditGatewayServiceConfig", () => { }); it("flags gateway token mismatch when service token is stale", async () => { - const audit = await auditGatewayServiceConfig({ - env: { HOME: "/tmp" }, - platform: "linux", + const audit = await createGatewayAudit({ expectedGatewayToken: "new-token", - command: { - programArguments: ["/usr/bin/node", "gateway"], - environment: { - PATH: "/usr/local/bin:/usr/bin:/bin", - OPENCLAW_GATEWAY_TOKEN: "old-token", - }, - }, + serviceToken: "old-token", }); - expect( - audit.issues.some((issue) => issue.code === SERVICE_AUDIT_CODES.gatewayTokenEmbedded), - ).toBe(true); - expect( - audit.issues.some((issue) => issue.code === SERVICE_AUDIT_CODES.gatewayTokenMismatch), - ).toBe(true); + expectTokenAudit(audit, { embedded: true, mismatch: true }); }); it("flags embedded service token even when it matches config token", async () => { - const audit = await auditGatewayServiceConfig({ - env: { HOME: "/tmp" }, - platform: "linux", + const audit = await createGatewayAudit({ expectedGatewayToken: "new-token", - command: { - programArguments: ["/usr/bin/node", "gateway"], - environment: { - PATH: "/usr/local/bin:/usr/bin:/bin", - OPENCLAW_GATEWAY_TOKEN: "new-token", - }, - }, + serviceToken: "new-token", }); - expect( - audit.issues.some((issue) => issue.code === SERVICE_AUDIT_CODES.gatewayTokenEmbedded), - ).toBe(true); - expect( - audit.issues.some((issue) => issue.code === SERVICE_AUDIT_CODES.gatewayTokenMismatch), - ).toBe(false); + expectTokenAudit(audit, { embedded: true, mismatch: false }); }); it("does not flag token issues when service token is not embedded", async () => { - const audit = await auditGatewayServiceConfig({ - env: { HOME: "/tmp" }, - platform: "linux", + const audit = await createGatewayAudit({ expectedGatewayToken: "new-token", - command: { - programArguments: ["/usr/bin/node", "gateway"], - environment: { - PATH: "/usr/local/bin:/usr/bin:/bin", - }, - }, }); - expect( - audit.issues.some((issue) => issue.code === SERVICE_AUDIT_CODES.gatewayTokenEmbedded), - ).toBe(false); - expect( - audit.issues.some((issue) => issue.code === SERVICE_AUDIT_CODES.gatewayTokenMismatch), - ).toBe(false); + expectTokenAudit(audit, { embedded: false, mismatch: false }); }); it("does not treat EnvironmentFile-backed tokens as embedded", async () => { - const audit = await auditGatewayServiceConfig({ - env: { HOME: "/tmp" }, - platform: "linux", + const audit = await createGatewayAudit({ expectedGatewayToken: "new-token", - command: { - programArguments: ["/usr/bin/node", "gateway"], - environment: { - PATH: "/usr/local/bin:/usr/bin:/bin", - OPENCLAW_GATEWAY_TOKEN: "old-token", - }, - environmentValueSources: { - OPENCLAW_GATEWAY_TOKEN: "file", - }, + serviceToken: "old-token", + environmentValueSources: { + OPENCLAW_GATEWAY_TOKEN: "file", }, }); - expect( - audit.issues.some((issue) => issue.code === SERVICE_AUDIT_CODES.gatewayTokenEmbedded), - ).toBe(false); - expect( - audit.issues.some((issue) => issue.code === SERVICE_AUDIT_CODES.gatewayTokenMismatch), - ).toBe(false); + expectTokenAudit(audit, { embedded: false, mismatch: false }); }); });