diff --git a/src/gateway/device-auth.test.ts b/src/gateway/device-auth.test.ts index 9d7ac3fb7b5..8db88428ce9 100644 --- a/src/gateway/device-auth.test.ts +++ b/src/gateway/device-auth.test.ts @@ -1,29 +1,69 @@ import { describe, expect, it } from "vitest"; -import { buildDeviceAuthPayloadV3, normalizeDeviceMetadataForAuth } from "./device-auth.js"; +import { + buildDeviceAuthPayload, + buildDeviceAuthPayloadV3, + normalizeDeviceMetadataForAuth, +} from "./device-auth.js"; describe("device-auth payload vectors", () => { - it("builds canonical v3 payload", () => { - const payload = buildDeviceAuthPayloadV3({ - deviceId: "dev-1", - clientId: "openclaw-macos", - clientMode: "ui", - role: "operator", - scopes: ["operator.admin", "operator.read"], - signedAtMs: 1_700_000_000_000, - token: "tok-123", - nonce: "nonce-abc", - platform: " IOS ", - deviceFamily: " iPhone ", - }); - - expect(payload).toBe( - "v3|dev-1|openclaw-macos|ui|operator|operator.admin,operator.read|1700000000000|tok-123|nonce-abc|ios|iphone", - ); + it.each([ + { + name: "builds canonical v2 payloads", + build: () => + buildDeviceAuthPayload({ + deviceId: "dev-1", + clientId: "openclaw-macos", + clientMode: "ui", + role: "operator", + scopes: ["operator.admin", "operator.read"], + signedAtMs: 1_700_000_000_000, + token: null, + nonce: "nonce-abc", + }), + expected: + "v2|dev-1|openclaw-macos|ui|operator|operator.admin,operator.read|1700000000000||nonce-abc", + }, + { + name: "builds canonical v3 payloads", + build: () => + buildDeviceAuthPayloadV3({ + deviceId: "dev-1", + clientId: "openclaw-macos", + clientMode: "ui", + role: "operator", + scopes: ["operator.admin", "operator.read"], + signedAtMs: 1_700_000_000_000, + token: "tok-123", + nonce: "nonce-abc", + platform: " IOS ", + deviceFamily: " iPhone ", + }), + expected: + "v3|dev-1|openclaw-macos|ui|operator|operator.admin,operator.read|1700000000000|tok-123|nonce-abc|ios|iphone", + }, + { + name: "keeps empty metadata slots in v3 payloads", + build: () => + buildDeviceAuthPayloadV3({ + deviceId: "dev-2", + clientId: "openclaw-ios", + clientMode: "ui", + role: "operator", + scopes: ["operator.read"], + signedAtMs: 1_700_000_000_001, + nonce: "nonce-def", + }), + expected: "v3|dev-2|openclaw-ios|ui|operator|operator.read|1700000000001||nonce-def||", + }, + ])("$name", ({ build, expected }) => { + expect(build()).toBe(expected); }); - it("normalizes metadata with ASCII-only lowercase", () => { - expect(normalizeDeviceMetadataForAuth(" İOS ")).toBe("İos"); - expect(normalizeDeviceMetadataForAuth(" MAC ")).toBe("mac"); - expect(normalizeDeviceMetadataForAuth(undefined)).toBe(""); + it.each([ + { input: " İOS ", expected: "İos" }, + { input: " MAC ", expected: "mac" }, + { input: undefined, expected: "" }, + ])("normalizes metadata %j", ({ input, expected }) => { + expect(normalizeDeviceMetadataForAuth(input)).toBe(expected); }); }); diff --git a/src/gateway/probe-auth.test.ts b/src/gateway/probe-auth.test.ts index 7a6d639e10a..314702c33db 100644 --- a/src/gateway/probe-auth.test.ts +++ b/src/gateway/probe-auth.test.ts @@ -6,8 +6,9 @@ import { } from "./probe-auth.js"; describe("resolveGatewayProbeAuthSafe", () => { - it("returns probe auth credentials when available", () => { - const result = resolveGatewayProbeAuthSafe({ + it.each([ + { + name: "returns probe auth credentials when available", cfg: { gateway: { auth: { @@ -15,20 +16,17 @@ describe("resolveGatewayProbeAuthSafe", () => { }, }, } as OpenClawConfig, - mode: "local", + mode: "local" as const, env: {} as NodeJS.ProcessEnv, - }); - - expect(result).toEqual({ - auth: { - token: "token-value", - password: undefined, + expected: { + auth: { + token: "token-value", + password: undefined, + }, }, - }); - }); - - it("returns warning and empty auth when token SecretRef is unresolved", () => { - const result = resolveGatewayProbeAuthSafe({ + }, + { + name: "returns warning and empty auth when a local token SecretRef is unresolved", cfg: { gateway: { auth: { @@ -42,17 +40,15 @@ describe("resolveGatewayProbeAuthSafe", () => { }, }, } as OpenClawConfig, - mode: "local", + mode: "local" as const, env: {} as NodeJS.ProcessEnv, - }); - - expect(result.auth).toEqual({}); - expect(result.warning).toContain("gateway.auth.token"); - expect(result.warning).toContain("unresolved"); - }); - - it("does not fall through to remote token when local token SecretRef is unresolved", () => { - const result = resolveGatewayProbeAuthSafe({ + expected: { + auth: {}, + warningIncludes: ["gateway.auth.token", "unresolved"], + }, + }, + { + name: "does not fall through to remote token when the local SecretRef is unresolved", cfg: { gateway: { mode: "local", @@ -70,17 +66,15 @@ describe("resolveGatewayProbeAuthSafe", () => { }, }, } as OpenClawConfig, - mode: "local", + mode: "local" as const, env: {} as NodeJS.ProcessEnv, - }); - - expect(result.auth).toEqual({}); - expect(result.warning).toContain("gateway.auth.token"); - expect(result.warning).toContain("unresolved"); - }); - - it("ignores unresolved local token SecretRef in remote mode when remote-only auth is requested", () => { - const result = resolveGatewayProbeAuthSafe({ + expected: { + auth: {}, + warningIncludes: ["gateway.auth.token", "unresolved"], + }, + }, + { + name: "ignores unresolved local token SecretRefs in remote mode", cfg: { gateway: { mode: "remote", @@ -98,16 +92,22 @@ describe("resolveGatewayProbeAuthSafe", () => { }, }, } as OpenClawConfig, - mode: "remote", + mode: "remote" as const, env: {} as NodeJS.ProcessEnv, - }); - - expect(result).toEqual({ - auth: { - token: undefined, - password: undefined, + expected: { + auth: { + token: undefined, + password: undefined, + }, }, - }); + }, + ])("$name", ({ cfg, mode, env, expected }) => { + const result = resolveGatewayProbeAuthSafe({ cfg, mode, env }); + + expect(result.auth).toEqual(expected.auth); + for (const fragment of expected.warningIncludes ?? []) { + expect(result.warning).toContain(fragment); + } }); });