import { beforeEach, describe, expect, it, vi } from "vitest"; import type { StoredVault } from "../shared/types"; type StorageShape = Record; function makeChromeStorageMock(storage: StorageShape) { return { storage, chrome: { storage: { local: { get: vi.fn(async (key?: string | string[]) => { if (!key) return { ...storage }; if (typeof key === "string") return { [key]: storage[key] }; return key.reduce((acc, k) => { acc[k] = storage[k]; return acc; }, {}); }), set: vi.fn(async (next: StorageShape) => { Object.assign(storage, next); }), remove: vi.fn(async (keys: string | string[]) => { for (const key of Array.isArray(keys) ? keys : [keys]) delete storage[key]; }), }, }, alarms: { clear: vi.fn(), create: vi.fn(), onAlarm: { addListener: vi.fn() } }, idle: { setDetectionInterval: vi.fn(), onStateChanged: { addListener: vi.fn() } }, } as unknown as typeof globalThis.chrome, }; } const UUID_SHAPE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; /** * A Peach entry id is a UUID, but an imported or restored file carries whatever * id its author used and no reader bounds it. One oversized id would ride into * every future sync frame and be rejected there — a permanent, opaque failure * on both platforms, caused by data that was already local. * * The seam is restoreEntries, and only restoreEntries. Sync adopts a peer's id * verbatim, which is the only thing keeping two devices agreed on which entry * they are discussing, so reminting there would fork identity on every device. */ describe("vault entry-id namespace", () => { let storage: StorageShape; let vault: typeof import("./vault"); const password = "entry-id-namespace-pass"; beforeEach(async () => { vi.resetModules(); storage = {}; (globalThis as Record).chrome = makeChromeStorageMock(storage).chrome; vi.stubGlobal("fetch", vi.fn(async () => { throw new Error("network disabled in tests"); })); vault = await import("./vault"); await vault.createVault(password); }); function imported(id: string, title: string) { return { id, type: "login" as const, title, password: "pw", createdAt: 1, modifiedAt: Date.now(), }; } it("keeps a well-formed id the file already carried", async () => { const fromAnotherPeach = "b1f0c8e2-4a6d-4f1a-9c3e-7d5b2a8e1f40"; const fromAVendor = "sj4kd9f2q7m1x8zb3nv6ct0h"; const summary = await vault.restoreEntries( [imported(fromAnotherPeach, "Bitwarden"), imported(fromAVendor, "OnePassword")], "replace", ); expect(summary.added).toBe(2); expect(vault.getEntries().map((e) => e.id).sort()).toEqual( [fromAVendor, fromAnotherPeach].sort(), ); }); it("mints a fresh id for one that is not a Peach id, keeping the entry", async () => { const summary = await vault.restoreEntries( [ imported("x".repeat(200), "Too long"), imported('a b"c\n{"injected":true}', "Not an id"), ], "replace", ); expect(summary.added).toBe(2); const minted = vault.getEntries(); expect(minted.map((e) => e.title).sort()).toEqual(["Not an id", "Too long"]); expect(new Set(minted.map((e) => e.id)).size).toBe(2); for (const entry of minted) expect(entry.id).toMatch(UUID_SHAPE); }); it("adopts a sync peer's id verbatim", async () => { const peerId = `peer-${"x".repeat(120)}`; const result = await vault.applyMergedRemoteEntries( [{ ...imported(peerId, "From peer") }], {}, ); expect(result.applied).toBe(1); expect(vault.getEntries().map((e) => e.id)).toEqual([peerId]); }); it("persists the naturalised ids across a lock and unlock", async () => { await vault.restoreEntries([imported("x".repeat(200), "Too long")], "replace"); const minted = vault.getEntries()[0].id; vault.lock(); await vault.unlock(password); expect(vault.getEntries().map((e) => e.id)).toEqual([minted]); expect(storage.peach_vault_a as StoredVault).toBeDefined(); }); });