device-identity.test.ts

2.7 KB · raw

1import { beforeEach, describe, expect, it, vi } from "vitest";23// PCH-01-004: deviceId must live in chrome.storage.local (device-local), never4// in chrome.storage.sync (which roams across profiles and collides identities).56type StorageShape = Record<string, unknown>;7const DEVICE_ID_KEY = "peach_device_id";89describe("device-identity getOrCreateDeviceId (PCH-01-004)", () => {10  let local: StorageShape;11  let sync: StorageShape;12  let localSet: ReturnType<typeof vi.fn>;13  let syncSet: ReturnType<typeof vi.fn>;14  let syncRemove: ReturnType<typeof vi.fn>;1516  function makeArea(store: StorageShape, set: ReturnType<typeof vi.fn>, remove?: ReturnType<typeof vi.fn>) {17    return {18      get: vi.fn(async (key?: string | string[]) => {19        if (!key) return store;20        if (typeof key === "string") return { [key]: store[key] };21        return key.reduce<Record<string, unknown>>((r, k) => {22          r[k] = store[k];23          return r;24        }, {});25      }),26      set,27      remove: remove ?? vi.fn(async () => undefined),28    };29  }3031  beforeEach(() => {32    vi.resetModules();33    local = {};34    sync = {};35    localSet = vi.fn(async (next: Record<string, unknown>) => {36      Object.assign(local, next);37    });38    syncSet = vi.fn(async (next: Record<string, unknown>) => {39      Object.assign(sync, next);40    });41    syncRemove = vi.fn(async (key: string) => {42      delete sync[key];43    });4445    const chromeMock = {46      storage: {47        local: makeArea(local, localSet),48        sync: makeArea(sync, syncSet, syncRemove),49      },50    } as unknown as typeof chrome;51    (globalThis as { chrome?: typeof chrome }).chrome = chromeMock;52  });5354  it("migrates_legacy_sync_id_to_local_and_removes_from_sync", async () => {55    sync[DEVICE_ID_KEY] = "legacy-roamed-id";56    const { getOrCreateDeviceId } = await import("./device-identity");5758    const id = await getOrCreateDeviceId();5960    expect(id).toBe("legacy-roamed-id");61    expect(local[DEVICE_ID_KEY]).toBe("legacy-roamed-id");62    expect(syncRemove).toHaveBeenCalledWith(DEVICE_ID_KEY);63    expect(sync[DEVICE_ID_KEY]).toBeUndefined();64  });6566  it("fresh_install_writes_id_to_local_only", async () => {67    const { getOrCreateDeviceId } = await import("./device-identity");6869    const id = await getOrCreateDeviceId();7071    expect(typeof id).toBe("string");72    expect(id.length).toBeGreaterThan(0);73    expect(local[DEVICE_ID_KEY]).toBe(id);74    expect(syncSet).not.toHaveBeenCalled();75  });7677  it("returns_a_stable_id_across_calls", async () => {78    const { getOrCreateDeviceId } = await import("./device-identity");7980    const first = await getOrCreateDeviceId();81    const second = await getOrCreateDeviceId();8283    expect(second).toBe(first);84  });85});