/** * Vault serialisation round-trip for the full-fidelity v3 payload. */ import { describe, expect, it } from "vitest"; import { deserialiseVault, serialiseVault } from "./cjkSerialise"; import type { VaultEntry } from "../shared/types"; import type { VaultSettings } from "../background/vault"; function makeLogin(overrides: Partial = {}): VaultEntry { return { id: "id-login", type: "login", title: "Untitled", username: "", password: "", url: "", createdAt: 1_700_000_000_000, modifiedAt: 1_700_000_000_000, ...overrides, }; } describe("serialiseVault / deserialiseVault (v3 JSON payload)", () => { it("round-trips logins field-by-field", () => { const entries: VaultEntry[] = [ makeLogin({ id: "one", title: "GitHub", url: "https://github.com", username: "june@example.com", password: "hunter2", totp: "JBSWY3DPEHPK3PXP", notes: "personal", tags: ["work", "critical"], }), ]; const settings: VaultSettings = { theme: "dark", autoLockMinutes: 15 }; const bytes = serialiseVault(entries, settings); const payload = deserialiseVault(bytes); expect(payload.v).toBe(3); expect(payload.entries).toHaveLength(1); const [a] = payload.entries; expect(a.id).toBe("one"); expect(a.title).toBe("GitHub"); expect(a.url).toBe("https://github.com"); expect(a.username).toBe("june@example.com"); expect(a.password).toBe("hunter2"); expect(a.totp).toBe("JBSWY3DPEHPK3PXP"); expect(a.notes).toBe("personal"); expect(a.tags).toEqual(["work", "critical"]); expect(payload.settings.theme).toBe("dark"); expect(payload.settings.autoLockMinutes).toBe(15); }); it("preserves notes and passkey entries", () => { const entries: VaultEntry[] = [ { id: "note-1", type: "note", title: "Safe combo", notes: "left-30-right-12-left-4", createdAt: 1, modifiedAt: 2, }, { id: "passkey-1", type: "passkey", title: "example.com", rpId: "example.com", rpName: "Example", credentialId: "cred-abc", publicKeyCose: "cose-bytes-b64", privateKeyJwk: { kty: "EC", crv: "P-256", x: "x", y: "y", d: "d" }, userHandle: "user-handle", userName: "june", userDisplayName: "June", algorithm: -7, signCount: 0, aaguid: "AAAAAAAAAAAAAAAAAAAAAA", backupEligible: true, backupState: false, createdAt: 3, modifiedAt: 4, }, ]; const bytes = serialiseVault(entries, {}); const payload = deserialiseVault(bytes); expect(payload.entries).toHaveLength(2); const note = payload.entries.find((e) => e.type === "note")!; expect(note.notes).toBe("left-30-right-12-left-4"); const passkey = payload.entries.find((e) => e.type === "passkey")!; expect(passkey.rpId).toBe("example.com"); expect(passkey.credentialId).toBe("cred-abc"); expect(passkey.privateKeyJwk?.kty).toBe("EC"); expect(passkey.algorithm).toBe(-7); }); it("preserves canary and breach data", () => { const entries: VaultEntry[] = [ makeLogin({ id: "c1", title: "honey", password: "trap", url: "honey.example", canary: { webhookUrl: "https://hook.example/x", label: "AWS decoy", pingCount: 3 }, breach: 42, }), ]; const bytes = serialiseVault(entries, {}); const payload = deserialiseVault(bytes); expect(payload.entries[0].canary?.webhookUrl).toBe("https://hook.example/x"); expect(payload.entries[0].canary?.label).toBe("AWS decoy"); expect(payload.entries[0].canary?.pingCount).toBe(3); expect(payload.entries[0].breach).toBe(42); }); it("throws DESERIALISE_MALFORMED on garbage bytes", () => { const bytes = new TextEncoder().encode("not-json"); expect(() => deserialiseVault(bytes)).toThrow(/DESERIALISE_MALFORMED/); }); it("throws DESERIALISE_MALFORMED on wrong version", () => { const bytes = new TextEncoder().encode( JSON.stringify({ v: 2, entries: [], settings: {} }) ); expect(() => deserialiseVault(bytes)).toThrow(/DESERIALISE_MALFORMED/); }); });