1import { describe, expect, it, beforeEach } from "vitest";2import type { PairedDevice } from "../shared/sync-types";3import {4 clearLastKnownIp,5 clearPairedDevices,6 findPairedDeviceById,7 findPairedDeviceByPubkey,8 getLastKnownIp,9 getLastSyncAt,10 listPairedDevices,11 markLastSyncedAt,12 removePairedDeviceById,13 setLastKnownIp,14 setLastSyncAt,15 upsertPairedDevice,16} from "./paired-devices";1718function makeStorageStub() {19 const sync = new Map<string, unknown>();20 const local = new Map<string, unknown>();21 function area(store: Map<string, unknown>) {22 return {23 async get(key?: string | string[] | null) {24 if (key === null || key === undefined) {25 const all: Record<string, unknown> = {};26 for (const [k, v] of store) all[k] = v;27 return all;28 }29 const keys = Array.isArray(key) ? key : [key];30 const out: Record<string, unknown> = {};31 for (const k of keys) if (store.has(k)) out[k] = store.get(k);32 return out;33 },34 async set(values: Record<string, unknown>) {35 for (const [k, v] of Object.entries(values)) store.set(k, v);36 },37 async remove(key: string | string[]) {38 const keys = Array.isArray(key) ? key : [key];39 for (const k of keys) store.delete(k);40 },41 };42 }43 (globalThis as unknown as { chrome: unknown }).chrome = {44 storage: { sync: area(sync), local: area(local) },45 };46 return { sync, local };47}4849const SAMPLE: PairedDevice = {50 deviceId: "AAA-111",51 deviceName: "June's Pixel 9",52 deviceType: "android",53 longTermPublicKeyHex: "0".repeat(64),54 pairedAt: 1700000000000,55};5657describe("paired-devices registry", () => {58 beforeEach(() => makeStorageStub());5960 it("upsert + list", async () => {61 await upsertPairedDevice(SAMPLE);62 expect(await listPairedDevices()).toEqual([SAMPLE]);63 });6465 it("upsert is idempotent on the same deviceId — updates in place, no duplicate", async () => {66 await upsertPairedDevice(SAMPLE);67 await upsertPairedDevice({ ...SAMPLE, deviceName: "Renamed Pixel" });68 const all = await listPairedDevices();69 expect(all).toHaveLength(1);70 expect(all[0].deviceName).toBe("Renamed Pixel");71 });7273 it("upsert with a new longTermPublicKeyHex on re-pair replaces the old key", async () => {74 await upsertPairedDevice(SAMPLE);75 const newPubkey = "f".repeat(64);76 await upsertPairedDevice({ ...SAMPLE, longTermPublicKeyHex: newPubkey });77 const found = await findPairedDeviceById(SAMPLE.deviceId);78 expect(found?.longTermPublicKeyHex).toBe(newPubkey);79 });8081 it("rejects upsert with missing fields", async () => {82 await expect(upsertPairedDevice({ ...SAMPLE, deviceId: "" })).rejects.toThrow(/deviceId/);83 await expect(upsertPairedDevice({ ...SAMPLE, longTermPublicKeyHex: undefined })).rejects.toThrow(/longTermPublicKeyHex/);84 await expect(upsertPairedDevice({ ...SAMPLE, deviceName: undefined })).rejects.toThrow(/deviceName/);85 await expect(upsertPairedDevice({ ...SAMPLE, deviceType: undefined })).rejects.toThrow(/deviceType/);86 });8788 it("findPairedDeviceByPubkey returns the correct device", async () => {89 const other: PairedDevice = { ...SAMPLE, deviceId: "BBB-222", longTermPublicKeyHex: "1".repeat(64) };90 await upsertPairedDevice(SAMPLE);91 await upsertPairedDevice(other);92 expect(await findPairedDeviceByPubkey("1".repeat(64))).toEqual(other);93 expect(await findPairedDeviceByPubkey("9".repeat(64))).toBeNull();94 });9596 it("removePairedDeviceById drops the device and its IP cache", async () => {97 await upsertPairedDevice(SAMPLE);98 await setLastKnownIp(SAMPLE.longTermPublicKeyHex!, "192.168.1.42");99 await removePairedDeviceById(SAMPLE.deviceId);100 expect(await listPairedDevices()).toEqual([]);101 expect(await getLastKnownIp(SAMPLE.longTermPublicKeyHex!)).toBeNull();102 });103104 it("clearPairedDevices wipes the whole registry", async () => {105 await upsertPairedDevice(SAMPLE);106 await clearPairedDevices();107 expect(await listPairedDevices()).toEqual([]);108 });109110 it("markLastSyncedAt mutates only the target device", async () => {111 const other: PairedDevice = { ...SAMPLE, deviceId: "BBB-222", longTermPublicKeyHex: "1".repeat(64) };112 await upsertPairedDevice(SAMPLE);113 await upsertPairedDevice(other);114 await markLastSyncedAt(SAMPLE.deviceId, 1700001000000);115 const found = await findPairedDeviceById(SAMPLE.deviceId);116 expect(found?.lastSyncedAt).toBe(1700001000000);117 const untouched = await findPairedDeviceById(other.deviceId);118 expect(untouched?.lastSyncedAt).toBeUndefined();119 });120121 it("getLastKnownIp / setLastKnownIp round-trip", async () => {122 await setLastKnownIp("ab".repeat(32), "10.0.0.5");123 expect(await getLastKnownIp("ab".repeat(32))).toBe("10.0.0.5");124 await clearLastKnownIp("ab".repeat(32));125 expect(await getLastKnownIp("ab".repeat(32))).toBeNull();126 });127128 it("last sync timestamp accepts a legacy string value", async () => {129 (globalThis as unknown as { chrome: { storage: { local: { set: (v: Record<string, unknown>) => Promise<void> } } } }).chrome.storage.local.set({130 peach_last_sync_at: "1700002000000",131 });132 expect(await getLastSyncAt()).toBe(1700002000000);133 });134135 it("setLastSyncAt persists a numeric value", async () => {136 await setLastSyncAt(1700003000000);137 expect(await getLastSyncAt()).toBe(1700003000000);138 });139140 it("listPairedDevices ignores garbage entries silently", async () => {141 (globalThis as unknown as { chrome: { storage: { local: { set: (v: Record<string, unknown>) => Promise<void> } } } }).chrome.storage.local.set({142 peach_paired_devices_v3: [143 { deviceId: "real", pairedAt: 1, deviceName: "x", deviceType: "android", longTermPublicKeyHex: "0".repeat(64) },144 { not: "a device" },145 null,146 42,147 ],148 });149 const devices = await listPairedDevices();150 expect(devices).toHaveLength(1);151 expect(devices[0].deviceId).toBe("real");152 });153});