1import { beforeEach, describe, expect, it, vi } from "vitest";23describe("recovery confirmation state", () => {4 let local: { get: ReturnType<typeof vi.fn>; set: ReturnType<typeof vi.fn>; remove: ReturnType<typeof vi.fn> };5 let session: { get: ReturnType<typeof vi.fn>; set: ReturnType<typeof vi.fn>; remove: ReturnType<typeof vi.fn> };6 let sessionValues: Record<string, unknown>;78 beforeEach(() => {9 vi.resetModules();10 sessionValues = {};11 local = {12 get: vi.fn(async () => ({})),13 set: vi.fn(async () => undefined),14 remove: vi.fn(async () => undefined),15 };16 session = {17 get: vi.fn(async (key: string) => ({ [key]: sessionValues[key] })),18 set: vi.fn(async (values: Record<string, unknown>) => Object.assign(sessionValues, values)),19 remove: vi.fn(async (key: string) => { delete sessionValues[key]; }),20 };21 vi.stubGlobal("chrome", { storage: { local, session } });22 });2324 it("stores the pending exemption in session storage, never local storage", async () => {25 const pending = await import("./recovery-pending");2627 await pending.setRecoveryConfirmPending();2829 expect(session.set).toHaveBeenCalledWith({30 peach_recovery_pending: expect.any(Number),31 });32 expect(local.set).not.toHaveBeenCalled();33 await expect(pending.isRecoveryConfirmPending()).resolves.toBe(true);34 });3536 it("clears the session-owned exemption", async () => {37 const pending = await import("./recovery-pending");38 await pending.setRecoveryConfirmPending();3940 await pending.clearRecoveryConfirmPending();4142 expect(session.remove).toHaveBeenCalledWith("peach_recovery_pending");43 await expect(pending.isRecoveryConfirmPending()).resolves.toBe(false);44 expect(local.remove).not.toHaveBeenCalled();45 });4647 it("uses background memory when session storage is unavailable", async () => {48 vi.stubGlobal("chrome", { storage: { local } });49 const pending = await import("./recovery-pending");5051 await pending.setRecoveryConfirmPending();5253 await expect(pending.isRecoveryConfirmPending()).resolves.toBe(true);54 await pending.clearRecoveryConfirmPending();55 await expect(pending.isRecoveryConfirmPending()).resolves.toBe(false);56 expect(local.set).not.toHaveBeenCalled();57 });5859 it("expires the recovery exemption after its hard deadline", async () => {60 vi.useFakeTimers();61 try {62 const pending = await import("./recovery-pending");63 await pending.setRecoveryConfirmPending();6465 await vi.advanceTimersByTimeAsync(10 * 60 * 1000 + 1);6667 await expect(pending.isRecoveryConfirmPending()).resolves.toBe(false);68 expect(session.remove).toHaveBeenCalledWith("peach_recovery_pending");69 } finally {70 vi.useRealTimers();71 }72 });7374 it("preserves the recovery obligation when the vault locks", async () => {75 const pending = await import("./recovery-pending");76 const vault = await import("./vault");77 await pending.setRecoveryConfirmPending();7879 await vault.lock();8081 expect(await pending.isRecoveryConfirmPending()).toBe(true);82 expect(sessionValues.peach_recovery_pending).toBeDefined();83 });84});