import { describe, expect, it, vi } from "vitest"; import { clearDeviceAuthTokenFromStore, loadDeviceAuthTokenFromStore, storeDeviceAuthTokenInStore, type DeviceAuthStoreAdapter, } from "./device-auth-store.js"; function createAdapter(initialStore: ReturnType = null) { let store = initialStore; const writes: unknown[] = []; const adapter: DeviceAuthStoreAdapter = { readStore: () => store, writeStore: (next) => { store = next; writes.push(next); }, }; return { adapter, writes, readStore: () => store }; } describe("device-auth-store", () => { it("loads only matching device ids and normalized roles", () => { const { adapter } = createAdapter({ version: 1, deviceId: "device-1", tokens: { operator: { token: "secret", role: "operator", scopes: ["operator.read"], updatedAtMs: 1, }, }, }); expect( loadDeviceAuthTokenFromStore({ adapter, deviceId: "device-1", role: " operator ", }), ).toMatchObject({ token: "secret" }); expect( loadDeviceAuthTokenFromStore({ adapter, deviceId: "device-2", role: "operator", }), ).toBeNull(); }); it("returns null for missing stores and malformed token entries", () => { expect( loadDeviceAuthTokenFromStore({ adapter: createAdapter().adapter, deviceId: "device-1", role: "operator", }), ).toBeNull(); const { adapter } = createAdapter({ version: 1, deviceId: "device-1", tokens: { operator: { token: 123 as unknown as string, role: "operator", scopes: [], updatedAtMs: 1, }, }, }); expect( loadDeviceAuthTokenFromStore({ adapter, deviceId: "device-1", role: "operator", }), ).toBeNull(); }); it("stores normalized roles and deduped sorted scopes while preserving same-device tokens", () => { vi.spyOn(Date, "now").mockReturnValue(1234); const { adapter, writes, readStore } = createAdapter({ version: 1, deviceId: "device-1", tokens: { node: { token: "node-token", role: "node", scopes: ["node.invoke"], updatedAtMs: 10, }, }, }); const entry = storeDeviceAuthTokenInStore({ adapter, deviceId: "device-1", role: " operator ", token: "operator-token", scopes: [" operator.write ", "operator.read", "operator.read", ""], }); expect(entry).toEqual({ token: "operator-token", role: "operator", scopes: ["operator.read", "operator.write"], updatedAtMs: 1234, }); expect(writes).toHaveLength(1); expect(readStore()).toEqual({ version: 1, deviceId: "device-1", tokens: { node: { token: "node-token", role: "node", scopes: ["node.invoke"], updatedAtMs: 10, }, operator: entry, }, }); }); it("replaces stale stores from other devices instead of merging them", () => { const { adapter, readStore } = createAdapter({ version: 1, deviceId: "device-2", tokens: { operator: { token: "old-token", role: "operator", scopes: [], updatedAtMs: 1, }, }, }); storeDeviceAuthTokenInStore({ adapter, deviceId: "device-1", role: "node", token: "node-token", }); expect(readStore()).toEqual({ version: 1, deviceId: "device-1", tokens: { node: { token: "node-token", role: "node", scopes: [], updatedAtMs: expect.any(Number), }, }, }); }); it("overwrites existing entries for the same normalized role", () => { vi.spyOn(Date, "now").mockReturnValue(2222); const { adapter, readStore } = createAdapter({ version: 1, deviceId: "device-1", tokens: { operator: { token: "old-token", role: "operator", scopes: ["operator.read"], updatedAtMs: 10, }, }, }); const entry = storeDeviceAuthTokenInStore({ adapter, deviceId: "device-1", role: " operator ", token: "new-token", scopes: ["operator.write"], }); expect(entry).toEqual({ token: "new-token", role: "operator", scopes: ["operator.write"], updatedAtMs: 2222, }); expect(readStore()).toEqual({ version: 1, deviceId: "device-1", tokens: { operator: entry, }, }); }); it("avoids writes when clearing missing roles or mismatched devices", () => { const missingRole = createAdapter({ version: 1, deviceId: "device-1", tokens: {}, }); clearDeviceAuthTokenFromStore({ adapter: missingRole.adapter, deviceId: "device-1", role: "operator", }); expect(missingRole.writes).toHaveLength(0); const otherDevice = createAdapter({ version: 1, deviceId: "device-2", tokens: { operator: { token: "secret", role: "operator", scopes: [], updatedAtMs: 1, }, }, }); clearDeviceAuthTokenFromStore({ adapter: otherDevice.adapter, deviceId: "device-1", role: "operator", }); expect(otherDevice.writes).toHaveLength(0); }); it("removes normalized roles when clearing stored tokens", () => { const { adapter, writes, readStore } = createAdapter({ version: 1, deviceId: "device-1", tokens: { operator: { token: "secret", role: "operator", scopes: ["operator.read"], updatedAtMs: 1, }, node: { token: "node-token", role: "node", scopes: [], updatedAtMs: 2, }, }, }); clearDeviceAuthTokenFromStore({ adapter, deviceId: "device-1", role: " operator ", }); expect(writes).toHaveLength(1); expect(readStore()).toEqual({ version: 1, deviceId: "device-1", tokens: { node: { token: "node-token", role: "node", scopes: [], updatedAtMs: 2, }, }, }); }); });