import { beforeEach, describe, expect, it, vi } from "vitest"; import { deriveKey } from "../crypto/argon2"; // PCH-01-008: a password change must refresh the MV3 session-key escrow so the // next service-worker restore decrypts the new-key Slot A. The backing stores // are closed over by the chrome mock on globalThis, so they survive the // vi.resetModules() we use to simulate a service-worker restart mid-test. type StorageShape = Record; describe("vault session escrow refresh (PCH-01-008)", () => { let local: StorageShape; let session: StorageShape; beforeEach(() => { vi.resetModules(); local = {}; session = {}; const area = (store: StorageShape) => ({ get: vi.fn(async (key?: string | string[]) => { if (!key) return store; if (typeof key === "string") return { [key]: store[key] }; return key.reduce>((r, k) => { r[k] = store[k]; return r; }, {}); }), set: vi.fn(async (next: Record) => { Object.assign(store, next); }), remove: vi.fn(async (keys: string | string[]) => { for (const k of Array.isArray(keys) ? keys : [keys]) delete store[k]; }), }); (globalThis as { chrome?: typeof chrome }).chrome = { storage: { local: area(local), session: area(session) }, alarms: { clear: vi.fn(), create: vi.fn(), onAlarm: { addListener: vi.fn() } }, idle: { setDetectionInterval: vi.fn(), onStateChanged: { addListener: vi.fn() } }, } as unknown as typeof chrome; vi.stubGlobal("fetch", vi.fn(async () => { throw new Error("network disabled"); })); }); it("change_password_updates_session_escrow", async () => { const v1 = await import("./vault"); await v1.createVault("old-pass-abc123"); await v1.addEntry({ type: "login", title: "Acct", password: "pw" }); expect(await v1.changePassword("old-pass-abc123", "new-pass-xyz789")).toBe(true); // Flush the fire-and-forget saveSessionKey microtask, then simulate a SW // restart: a fresh module has derivedKey=null but session/local persist. await Promise.resolve(); vi.resetModules(); const v2 = await import("./vault"); expect(await v2.restoreFromSession()).toBe(true); expect(v2.getEntries().some((e) => e.title === "Acct")).toBe(true); }); it("change_password_with_new_key_updates_session_escrow", async () => { const v1 = await import("./vault"); await v1.createVault("orig-pass-abc123"); await v1.addEntry({ type: "login", title: "Recovered", password: "pw" }); const newSalt = crypto.getRandomValues(new Uint8Array(16)); const newKey = await deriveKey("reset-pass-xyz789", newSalt); await v1.changePasswordWithNewKey(newSalt, newKey); await Promise.resolve(); vi.resetModules(); const v2 = await import("./vault"); expect(await v2.restoreFromSession()).toBe(true); expect(v2.getEntries().some((e) => e.title === "Recovered")).toBe(true); }); });