From 3381e4c375bddcb62e59d4b6204647bb4a19adec Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 20 Apr 2026 16:08:15 +0100 Subject: [PATCH] test: share devices cli fixtures --- src/cli/devices-cli.test.ts | 142 +++++++++++++++--------------------- 1 file changed, 60 insertions(+), 82 deletions(-) diff --git a/src/cli/devices-cli.test.ts b/src/cli/devices-cli.test.ts index 8acd1336a99..46aba5f8618 100644 --- a/src/cli/devices-cli.test.ts +++ b/src/cli/devices-cli.test.ts @@ -69,6 +69,55 @@ function readRuntimeCallText(call: unknown[] | undefined): string { return typeof value === "string" ? value : ""; } +function readRuntimeOutput(): string { + return runtime.log.mock.calls.map((entry) => readRuntimeCallText(entry)).join("\n"); +} + +function pendingDevice(overrides: Record = {}) { + return { + requestId: "req-1", + deviceId: "device-1", + displayName: "Device One", + role: "operator", + scopes: ["operator.admin"], + ts: 1, + ...overrides, + }; +} + +function pairedDevice(overrides: Record = {}) { + return { + deviceId: "device-1", + displayName: "Device One", + roles: ["operator"], + scopes: ["operator.read"], + ...overrides, + }; +} + +function mockGatewayPairingList( + pendingOverrides: Record = {}, + pairedOverrides: Record = {}, +) { + callGateway.mockResolvedValueOnce({ + pending: [pendingDevice(pendingOverrides)], + paired: [pairedDevice(pairedOverrides)], + }); +} + +function rejectGatewayForLocalFallback(message = "gateway closed (1008): pairing required") { + callGateway.mockRejectedValueOnce(new Error(message)); +} + +function mockLocalPairingFallback(message?: string) { + rejectGatewayForLocalFallback(message); + listDevicePairing.mockResolvedValueOnce({ + pending: [{ requestId: "req-1", deviceId: "device-1", publicKey: "pk", ts: 1 }], + paired: [], + }); + summarizeDeviceTokens.mockReturnValue(undefined); +} + describe("devices cli approve", () => { it("approves an explicit request id without listing", async () => { callGateway.mockResolvedValueOnce({ device: { deviceId: "device-1" } }); @@ -401,12 +450,7 @@ describe("devices cli local fallback", () => { const fallbackNotice = "Direct scope access failed; using local fallback."; it("falls back to local pairing list when gateway returns pairing required on loopback", async () => { - callGateway.mockRejectedValueOnce(new Error("gateway closed (1008): pairing required")); - listDevicePairing.mockResolvedValueOnce({ - pending: [{ requestId: "req-1", deviceId: "device-1", publicKey: "pk", ts: 1 }], - paired: [], - }); - summarizeDeviceTokens.mockReturnValue(undefined); + mockLocalPairingFallback(); await runDevicesCommand(["list"]); @@ -418,7 +462,7 @@ describe("devices cli local fallback", () => { }); it("falls back to local approve when gateway returns pairing required on loopback", async () => { - callGateway.mockRejectedValueOnce(new Error("gateway closed (1008): pairing required")); + rejectGatewayForLocalFallback(); approveDevicePairing.mockResolvedValueOnce({ requestId: "req-latest", device: { @@ -440,14 +484,7 @@ describe("devices cli local fallback", () => { }); it("falls back to local pairing list when gateway returns a scope upgrade message on loopback", async () => { - callGateway.mockRejectedValueOnce( - new Error("scope upgrade pending approval (requestId: req-123)"), - ); - listDevicePairing.mockResolvedValueOnce({ - pending: [{ requestId: "req-1", deviceId: "device-1", publicKey: "pk", ts: 1 }], - paired: [], - }); - summarizeDeviceTokens.mockReturnValue(undefined); + mockLocalPairingFallback("scope upgrade pending approval (requestId: req-123)"); await runDevicesCommand(["list"]); @@ -456,7 +493,7 @@ describe("devices cli local fallback", () => { }); it("does not use local fallback when an explicit --url is provided", async () => { - callGateway.mockRejectedValueOnce(new Error("gateway closed (1008): pairing required")); + rejectGatewayForLocalFallback(); await expect( runDevicesCommand(["list", "--json", "--url", "ws://127.0.0.1:18789"]), @@ -467,30 +504,11 @@ describe("devices cli local fallback", () => { describe("devices cli list", () => { it("renders requested versus approved access for pending upgrades", async () => { - callGateway.mockResolvedValueOnce({ - pending: [ - { - requestId: "req-1", - deviceId: "device-1", - displayName: "Device One", - role: "operator", - scopes: ["operator.admin", "operator.read"], - ts: 1, - }, - ], - paired: [ - { - deviceId: "device-1", - displayName: "Device One", - roles: ["operator"], - scopes: ["operator.read"], - }, - ], - }); + mockGatewayPairingList({ scopes: ["operator.admin", "operator.read"] }); await runDevicesCommand(["list"]); - const output = runtime.log.mock.calls.map((entry) => readRuntimeCallText(entry)).join("\n"); + const output = readRuntimeOutput(); expect(output).toContain("Requested"); expect(output).toContain("Approved"); expect(output).toContain("operator.write"); @@ -499,61 +517,21 @@ describe("devices cli list", () => { }); it("normalizes pending device ids before matching paired approvals", async () => { - callGateway.mockResolvedValueOnce({ - pending: [ - { - requestId: "req-1", - deviceId: " device-1 ", - displayName: "Device One", - role: "operator", - scopes: ["operator.admin"], - ts: 1, - }, - ], - paired: [ - { - deviceId: "device-1", - displayName: "Device One", - roles: ["operator"], - scopes: ["operator.read"], - }, - ], - }); + mockGatewayPairingList({ deviceId: " device-1 " }); await runDevicesCommand(["list"]); - const output = runtime.log.mock.calls.map((entry) => readRuntimeCallText(entry)).join("\n"); + const output = readRuntimeOutput(); expect(output).toContain("scope upgrade"); expect(output).toContain("operator.read"); }); it("does not show upgrade context for key-mismatched pending requests", async () => { - callGateway.mockResolvedValueOnce({ - pending: [ - { - requestId: "req-1", - deviceId: "device-1", - publicKey: "new-key", - displayName: "Device One", - role: "operator", - scopes: ["operator.admin"], - ts: 1, - }, - ], - paired: [ - { - deviceId: "device-1", - publicKey: "old-key", - displayName: "Device One", - roles: ["operator"], - scopes: ["operator.read"], - }, - ], - }); + mockGatewayPairingList({ publicKey: "new-key" }, { publicKey: "old-key" }); await runDevicesCommand(["list"]); - const output = runtime.log.mock.calls.map((entry) => readRuntimeCallText(entry)).join("\n"); + const output = readRuntimeOutput(); expect(output).toContain("new pairing"); expect(output).not.toContain("scope upgrade"); expect(output).not.toContain("roles: operator; scopes: operator.read"); @@ -585,7 +563,7 @@ describe("devices cli list", () => { await runDevicesCommand(["list"]); - const output = runtime.log.mock.calls.map((entry) => readRuntimeCallText(entry)).join("\n"); + const output = readRuntimeOutput(); expect(output).not.toContain("\u001b"); expect(output).not.toContain("\r"); expect(output).toContain("BadName");