1import { beforeEach, describe, expect, it, vi } from "vitest";2import type { StoredVault } from "../shared/types";34type StorageShape = Record<string, unknown>;56describe("vault duress adoption helpers", () => {7 let storage: StorageShape;89 beforeEach(() => {10 vi.resetModules();11 storage = {};1213 const chromeMock = {14 storage: {15 local: {16 get: vi.fn(async (key?: string | string[]) => {17 if (!key) return storage;18 if (typeof key === "string") {19 return { [key]: storage[key] };20 }21 return key.reduce<Record<string, unknown>>((result, entry) => {22 result[entry] = storage[entry];23 return result;24 }, {});25 }),26 set: vi.fn(async (next: Record<string, unknown>) => {27 Object.assign(storage, next);28 }),29 remove: vi.fn(async (keys: string | string[]) => {30 const list = Array.isArray(keys) ? keys : [keys];31 for (const key of list) {32 delete storage[key];33 }34 }),35 },36 },37 alarms: {38 clear: vi.fn(),39 create: vi.fn(),40 onAlarm: { addListener: vi.fn() },41 },42 idle: {43 setDetectionInterval: vi.fn(),44 onStateChanged: { addListener: vi.fn() },45 },46 } as unknown as typeof chrome;4748 (globalThis as { chrome?: typeof chrome }).chrome = chromeMock;4950 vi.stubGlobal("fetch", vi.fn(async () => {51 throw new Error("network disabled in tests");52 }));53 });5455 it("exportDuressSlotForAdoption returns null when duress is not configured", async () => {56 const vault = await import("./vault");57 await vault.createVault("correct horse battery staple");58 expect(await vault.isDuressEnabled()).toBe(false);59 expect(await vault.exportDuressSlotForAdoption()).toBeNull();60 });6162 it("exportDuressSlotForAdoption returns JSON of slot B when duress is configured", async () => {63 const vault = await import("./vault");64 await vault.createVault("correct horse battery staple");65 await vault.setupDuress("decoy-password-67890");66 expect(await vault.isDuressEnabled()).toBe(true);6768 const result = await vault.exportDuressSlotForAdoption();69 expect(result).not.toBeNull();7071 const parsed = JSON.parse(result!) as StoredVault;72 expect(parsed.version).toBe(1);73 expect(typeof parsed.salt).toBe("string");74 expect(typeof parsed.ciphertext).toBe("string");75 expect(typeof parsed.tag).toBe("string");76 });7778 it("installAdoptedDuressSlot writes slot B with matching sealed bytes", async () => {79 const vault = await import("./vault");80 await vault.createVault("correct horse battery staple");81 await vault.setupDuress("decoy-password-67890");8283 const sourceJson = await vault.exportDuressSlotForAdoption();84 expect(sourceJson).not.toBeNull();8586 87 vi.resetModules();88 storage = { ...storage }; 89 const vault2 = await import("./vault");9091 92 await vault2.installAdoptedDuressSlot(sourceJson!);9394 95 const receiverSlotB = storage.peach_vault_b as StoredVault;96 const sourceParsed = JSON.parse(sourceJson!) as StoredVault;97 expect(receiverSlotB.version).toBe(sourceParsed.version);98 expect(receiverSlotB.salt).toBe(sourceParsed.salt);99 expect(receiverSlotB.nonce).toBe(sourceParsed.nonce);100 expect(receiverSlotB.ciphertext).toBe(sourceParsed.ciphertext);101 expect(receiverSlotB.tag).toBe(sourceParsed.tag);102 });103104 it("installAdoptedDuressSlot rejects invalid JSON", async () => {105 const vault = await import("./vault");106 await vault.createVault("correct horse battery staple");107 await expect(108 vault.installAdoptedDuressSlot("not-json"),109 ).rejects.toThrow("invalid_duress_slot_shape");110 });111112 it("installAdoptedDuressSlot rejects a valid JSON object with wrong shape", async () => {113 const vault = await import("./vault");114 await vault.createVault("correct horse battery staple");115 await expect(116 vault.installAdoptedDuressSlot(JSON.stringify({ version: 2, foo: "bar" })),117 ).rejects.toThrow("invalid_duress_slot_shape");118 });119});