// DRAFT DESTINATION: src/background/vault-createVaultFromRootKey.test.ts // T-A01..T-A05: createVaultFromRootKey adoption invariants. import { beforeEach, describe, expect, it, vi } from "vitest"; import { computeVaultId } from "../shared/sync-identity"; type StorageShape = Record; function makeChromeStorageMock(): { storage: StorageShape; chrome: typeof chrome } { const storage: StorageShape = {}; const 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 k of Array.isArray(keys) ? keys : [keys]) delete storage[k]; }), }, session: { get: vi.fn(async () => ({})), set: vi.fn(), remove: vi.fn() }, }, alarms: { clear: vi.fn(), create: vi.fn(), onAlarm: { addListener: vi.fn() } }, idle: { setDetectionInterval: vi.fn(), onStateChanged: { addListener: vi.fn() } }, } as unknown as typeof chrome; return { storage, chrome }; } describe("vault adoption: createVaultFromRootKey", () => { const SOURCE_PASSWORD = "source-adoption-passphrase-123"; const RECEIVER_PASSWORD = "receiver-local-passphrase-456"; beforeEach(() => { vi.resetModules(); const mock = makeChromeStorageMock(); vi.stubGlobal("chrome", mock.chrome); vi.stubGlobal("fetch", vi.fn(async () => { throw new Error("no network"); })); }); it("T-A01: round-trips an existing root key under a different local password", async () => { const sourceVault = await import("./vault"); await sourceVault.createVault(SOURCE_PASSWORD); const rootKeyHex = sourceVault.getRootKeyHex(); expect(rootKeyHex).toMatch(/^[0-9a-f]{64}$/); const sourceVaultId = await computeVaultId(new Uint8Array(Buffer.from(rootKeyHex!, "hex"))); vi.resetModules(); const mock = makeChromeStorageMock(); vi.stubGlobal("chrome", mock.chrome); const receiverVault = await import("./vault"); const result = await receiverVault.createVaultFromRootKey(RECEIVER_PASSWORD, rootKeyHex!); expect(result.vaultId).toBe(sourceVaultId); receiverVault.lock(); expect(await receiverVault.unlock(RECEIVER_PASSWORD)).toBe(true); expect(receiverVault.getRootKeyHex()).toBe(rootKeyHex); }); it("T-A02: recovery words match the source vault because they derive from the same root key prefix", async () => { const sourceVault = await import("./vault"); await sourceVault.createVault(SOURCE_PASSWORD); const rootKeyHex = sourceVault.getRootKeyHex()!; const sourceWords = await sourceVault.getRecoveryWords(); vi.resetModules(); const mock = makeChromeStorageMock(); vi.stubGlobal("chrome", mock.chrome); const receiverVault = await import("./vault"); await receiverVault.createVaultFromRootKey(RECEIVER_PASSWORD, rootKeyHex); expect(await receiverVault.getRecoveryWords()).toEqual(sourceWords); }); it.each([ ["63 chars", "a".repeat(63)], ["65 chars", "a".repeat(65)], ["uppercase", "A".repeat(64)], ["non-hex", "g".repeat(64)], ["empty", ""], ])("T-A03: rejects invalid root key input: %s", async (_label, rootKeyHex) => { const vault = await import("./vault"); await expect(vault.createVaultFromRootKey(RECEIVER_PASSWORD, rootKeyHex)) .rejects.toThrow(/ADOPT_INVALID_ROOT_KEY/); }); it("T-A04: zeroes the decoded root key buffer on success and failure", async () => { const fillSpy = vi.spyOn(Uint8Array.prototype, "fill"); const vault = await import("./vault"); await vault.createVaultFromRootKey(RECEIVER_PASSWORD, "11".repeat(32)); expect(fillSpy).toHaveBeenCalledWith(0); fillSpy.mockClear(); await expect(vault.createVaultFromRootKey("", "22".repeat(32))).rejects.toThrow(); expect(fillSpy).toHaveBeenCalledWith(0); }); it("T-A05: adopted vault supports normal entry writes and keeps the original vault id", async () => { const vault = await import("./vault"); const rootKeyHex = "33".repeat(32); const { vaultId } = await vault.createVaultFromRootKey(RECEIVER_PASSWORD, rootKeyHex); const entry = await vault.addEntry({ type: "login", title: "Example", url: "https://example.com", username: "june", password: "secret", }); expect(vault.getEntries()).toContainEqual(expect.objectContaining({ id: entry.id, title: "Example" })); expect(vaultId).toBe(await computeVaultId(new Uint8Array(Buffer.from(rootKeyHex, "hex")))); }); });