import { describe, expect, it, beforeEach } from "vitest"; import type { PairedDevice } from "../shared/sync-types"; import { clearLastKnownIp, clearPairedDevices, findPairedDeviceById, findPairedDeviceByPubkey, getLastKnownIp, getLastSyncAt, listPairedDevices, markLastSyncedAt, removePairedDeviceById, setLastKnownIp, setLastSyncAt, upsertPairedDevice, } from "./paired-devices"; function makeStorageStub() { const sync = new Map(); const local = new Map(); function area(store: Map) { return { async get(key?: string | string[] | null) { if (key === null || key === undefined) { const all: Record = {}; for (const [k, v] of store) all[k] = v; return all; } const keys = Array.isArray(key) ? key : [key]; const out: Record = {}; for (const k of keys) if (store.has(k)) out[k] = store.get(k); return out; }, async set(values: Record) { for (const [k, v] of Object.entries(values)) store.set(k, v); }, async remove(key: string | string[]) { const keys = Array.isArray(key) ? key : [key]; for (const k of keys) store.delete(k); }, }; } (globalThis as unknown as { chrome: unknown }).chrome = { storage: { sync: area(sync), local: area(local) }, }; return { sync, local }; } const SAMPLE: PairedDevice = { deviceId: "AAA-111", deviceName: "June's Pixel 9", deviceType: "android", longTermPublicKeyHex: "0".repeat(64), pairedAt: 1700000000000, }; describe("paired-devices registry", () => { beforeEach(() => makeStorageStub()); it("upsert + list", async () => { await upsertPairedDevice(SAMPLE); expect(await listPairedDevices()).toEqual([SAMPLE]); }); it("upsert is idempotent on the same deviceId — updates in place, no duplicate", async () => { await upsertPairedDevice(SAMPLE); await upsertPairedDevice({ ...SAMPLE, deviceName: "Renamed Pixel" }); const all = await listPairedDevices(); expect(all).toHaveLength(1); expect(all[0].deviceName).toBe("Renamed Pixel"); }); it("upsert with a new longTermPublicKeyHex on re-pair replaces the old key", async () => { await upsertPairedDevice(SAMPLE); const newPubkey = "f".repeat(64); await upsertPairedDevice({ ...SAMPLE, longTermPublicKeyHex: newPubkey }); const found = await findPairedDeviceById(SAMPLE.deviceId); expect(found?.longTermPublicKeyHex).toBe(newPubkey); }); it("rejects upsert with missing fields", async () => { await expect(upsertPairedDevice({ ...SAMPLE, deviceId: "" })).rejects.toThrow(/deviceId/); await expect(upsertPairedDevice({ ...SAMPLE, longTermPublicKeyHex: undefined })).rejects.toThrow(/longTermPublicKeyHex/); await expect(upsertPairedDevice({ ...SAMPLE, deviceName: undefined })).rejects.toThrow(/deviceName/); await expect(upsertPairedDevice({ ...SAMPLE, deviceType: undefined })).rejects.toThrow(/deviceType/); }); it("findPairedDeviceByPubkey returns the correct device", async () => { const other: PairedDevice = { ...SAMPLE, deviceId: "BBB-222", longTermPublicKeyHex: "1".repeat(64) }; await upsertPairedDevice(SAMPLE); await upsertPairedDevice(other); expect(await findPairedDeviceByPubkey("1".repeat(64))).toEqual(other); expect(await findPairedDeviceByPubkey("9".repeat(64))).toBeNull(); }); it("removePairedDeviceById drops the device and its IP cache", async () => { await upsertPairedDevice(SAMPLE); await setLastKnownIp(SAMPLE.longTermPublicKeyHex!, "192.168.1.42"); await removePairedDeviceById(SAMPLE.deviceId); expect(await listPairedDevices()).toEqual([]); expect(await getLastKnownIp(SAMPLE.longTermPublicKeyHex!)).toBeNull(); }); it("clearPairedDevices wipes the whole registry", async () => { await upsertPairedDevice(SAMPLE); await clearPairedDevices(); expect(await listPairedDevices()).toEqual([]); }); it("markLastSyncedAt mutates only the target device", async () => { const other: PairedDevice = { ...SAMPLE, deviceId: "BBB-222", longTermPublicKeyHex: "1".repeat(64) }; await upsertPairedDevice(SAMPLE); await upsertPairedDevice(other); await markLastSyncedAt(SAMPLE.deviceId, 1700001000000); const found = await findPairedDeviceById(SAMPLE.deviceId); expect(found?.lastSyncedAt).toBe(1700001000000); const untouched = await findPairedDeviceById(other.deviceId); expect(untouched?.lastSyncedAt).toBeUndefined(); }); it("getLastKnownIp / setLastKnownIp round-trip", async () => { await setLastKnownIp("ab".repeat(32), "10.0.0.5"); expect(await getLastKnownIp("ab".repeat(32))).toBe("10.0.0.5"); await clearLastKnownIp("ab".repeat(32)); expect(await getLastKnownIp("ab".repeat(32))).toBeNull(); }); it("last sync timestamp accepts a legacy string value", async () => { (globalThis as unknown as { chrome: { storage: { local: { set: (v: Record) => Promise } } } }).chrome.storage.local.set({ peach_last_sync_at: "1700002000000", }); expect(await getLastSyncAt()).toBe(1700002000000); }); it("setLastSyncAt persists a numeric value", async () => { await setLastSyncAt(1700003000000); expect(await getLastSyncAt()).toBe(1700003000000); }); it("listPairedDevices ignores garbage entries silently", async () => { (globalThis as unknown as { chrome: { storage: { sync: { set: (v: Record) => Promise } } } }).chrome.storage.sync.set({ peach_paired_devices_v3: [ { deviceId: "real", pairedAt: 1, deviceName: "x", deviceType: "android", longTermPublicKeyHex: "0".repeat(64) }, { not: "a device" }, null, 42, ], }); const devices = await listPairedDevices(); expect(devices).toHaveLength(1); expect(devices[0].deviceId).toBe("real"); }); });