Files
openclaw/src/daemon/service-audit.test.ts
Peter Steinberger 265367d99b fix(gateway): land #28428 from @l0cka
Landed from contributor PR #28428 by @l0cka.

Co-authored-by: Daniel Alkurdi <danielalkurdi@gmail.com>
2026-03-07 22:51:08 +00:00

183 lines
6.2 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
auditGatewayServiceConfig,
checkTokenDrift,
SERVICE_AUDIT_CODES,
} from "./service-audit.js";
import { buildMinimalServicePath } from "./service-env.js";
describe("auditGatewayServiceConfig", () => {
it("flags bun runtime", async () => {
const audit = await auditGatewayServiceConfig({
env: { HOME: "/tmp" },
platform: "darwin",
command: {
programArguments: ["/opt/homebrew/bin/bun", "gateway"],
environment: { PATH: "/usr/bin:/bin" },
},
});
expect(audit.issues.some((issue) => issue.code === SERVICE_AUDIT_CODES.gatewayRuntimeBun)).toBe(
true,
);
});
it("flags version-managed node paths", async () => {
const audit = await auditGatewayServiceConfig({
env: { HOME: "/tmp" },
platform: "darwin",
command: {
programArguments: ["/Users/test/.nvm/versions/node/v22.0.0/bin/node", "gateway"],
environment: {
PATH: "/usr/bin:/bin:/Users/test/.nvm/versions/node/v22.0.0/bin",
},
},
});
expect(
audit.issues.some(
(issue) => issue.code === SERVICE_AUDIT_CODES.gatewayRuntimeNodeVersionManager,
),
).toBe(true);
expect(
audit.issues.some((issue) => issue.code === SERVICE_AUDIT_CODES.gatewayPathNonMinimal),
).toBe(true);
expect(
audit.issues.some((issue) => issue.code === SERVICE_AUDIT_CODES.gatewayPathMissingDirs),
).toBe(true);
});
it("accepts Linux minimal PATH with user directories", async () => {
const env = { HOME: "/home/testuser", PNPM_HOME: "/opt/pnpm" };
const minimalPath = buildMinimalServicePath({ platform: "linux", env });
const audit = await auditGatewayServiceConfig({
env,
platform: "linux",
command: {
programArguments: ["/usr/bin/node", "gateway"],
environment: { PATH: minimalPath },
},
});
expect(
audit.issues.some((issue) => issue.code === SERVICE_AUDIT_CODES.gatewayPathNonMinimal),
).toBe(false);
expect(
audit.issues.some((issue) => issue.code === SERVICE_AUDIT_CODES.gatewayPathMissingDirs),
).toBe(false);
});
it("flags gateway token mismatch when service token is stale", async () => {
const audit = await auditGatewayServiceConfig({
env: { HOME: "/tmp" },
platform: "linux",
expectedGatewayToken: "new-token",
command: {
programArguments: ["/usr/bin/node", "gateway"],
environment: {
PATH: "/usr/local/bin:/usr/bin:/bin",
OPENCLAW_GATEWAY_TOKEN: "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);
});
it("flags embedded service token even when it matches config token", async () => {
const audit = await auditGatewayServiceConfig({
env: { HOME: "/tmp" },
platform: "linux",
expectedGatewayToken: "new-token",
command: {
programArguments: ["/usr/bin/node", "gateway"],
environment: {
PATH: "/usr/local/bin:/usr/bin:/bin",
OPENCLAW_GATEWAY_TOKEN: "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);
});
it("does not flag token issues when service token is not embedded", async () => {
const audit = await auditGatewayServiceConfig({
env: { HOME: "/tmp" },
platform: "linux",
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);
});
});
describe("checkTokenDrift", () => {
it("returns null when both tokens are undefined", () => {
const result = checkTokenDrift({ serviceToken: undefined, configToken: undefined });
expect(result).toBeNull();
});
it("returns null when both tokens are empty strings", () => {
const result = checkTokenDrift({ serviceToken: "", configToken: "" });
expect(result).toBeNull();
});
it("returns null when tokens match", () => {
const result = checkTokenDrift({ serviceToken: "same-token", configToken: "same-token" });
expect(result).toBeNull();
});
it("returns null when tokens match but service token has trailing newline", () => {
const result = checkTokenDrift({ serviceToken: "same-token\n", configToken: "same-token" });
expect(result).toBeNull();
});
it("returns null when tokens match but have surrounding whitespace", () => {
const result = checkTokenDrift({ serviceToken: " same-token ", configToken: "same-token" });
expect(result).toBeNull();
});
it("returns null when both tokens have different whitespace padding", () => {
const result = checkTokenDrift({
serviceToken: "same-token\r\n",
configToken: " same-token ",
});
expect(result).toBeNull();
});
it("detects drift when config has token but service has different token", () => {
const result = checkTokenDrift({ serviceToken: "old-token", configToken: "new-token" });
expect(result).not.toBeNull();
expect(result?.code).toBe(SERVICE_AUDIT_CODES.gatewayTokenDrift);
expect(result?.message).toContain("differs from service token");
});
it("returns null when config has token but service has no token", () => {
const result = checkTokenDrift({ serviceToken: undefined, configToken: "new-token" });
expect(result).toBeNull();
});
it("returns null when service has token but config does not", () => {
// This is not really drift - service will work, just config is incomplete
const result = checkTokenDrift({ serviceToken: "service-token", configToken: undefined });
expect(result).toBeNull();
});
});