vault.duress-adoption.test.ts

4.3 KB · raw

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    // Reset to simulate a fresh receiver vault (no duress configured yet)87    vi.resetModules();88    storage = { ...storage }; // keep existing storage89    const vault2 = await import("./vault");9091    // Install the source's duress slot on the receiver92    await vault2.installAdoptedDuressSlot(sourceJson!);9394    // Verify the stored slot B matches the source exactly (passthrough)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});