12345import { describe, expect, it } from "vitest";67import { deserialiseVault, serialiseVault } from "./cjkSerialise";8import type { VaultEntry } from "../shared/types";9import type { VaultSettings } from "../background/vault";1011function makeLogin(overrides: Partial<VaultEntry> = {}): VaultEntry {12 return {13 id: "id-login",14 type: "login",15 title: "Untitled",16 username: "",17 password: "",18 url: "",19 createdAt: 1_700_000_000_000,20 modifiedAt: 1_700_000_000_000,21 ...overrides,22 };23}2425describe("serialiseVault / deserialiseVault (v3 JSON payload)", () => {26 it("round-trips logins field-by-field", () => {27 const entries: VaultEntry[] = [28 makeLogin({29 id: "one",30 title: "GitHub",31 url: "https://github.com",32 username: "[email protected]",33 password: "hunter2",34 totp: "JBSWY3DPEHPK3PXP",35 notes: "personal",36 tags: ["work", "critical"],37 }),38 ];39 const settings: VaultSettings = { theme: "dark", autoLockMinutes: 15 };4041 const bytes = serialiseVault(entries, settings);42 const payload = deserialiseVault(bytes);4344 expect(payload.v).toBe(3);45 expect(payload.entries).toHaveLength(1);46 const [a] = payload.entries;47 expect(a.id).toBe("one");48 expect(a.title).toBe("GitHub");49 expect(a.url).toBe("https://github.com");50 expect(a.username).toBe("[email protected]");51 expect(a.password).toBe("hunter2");52 expect(a.totp).toBe("JBSWY3DPEHPK3PXP");53 expect(a.notes).toBe("personal");54 expect(a.tags).toEqual(["work", "critical"]);55 expect(payload.settings.theme).toBe("dark");56 expect(payload.settings.autoLockMinutes).toBe(15);57 });5859 it("preserves notes and passkey entries", () => {60 const entries: VaultEntry[] = [61 {62 id: "note-1",63 type: "note",64 title: "Safe combo",65 notes: "left-30-right-12-left-4",66 createdAt: 1,67 modifiedAt: 2,68 },69 {70 id: "passkey-1",71 type: "passkey",72 title: "example.com",73 rpId: "example.com",74 rpName: "Example",75 credentialId: "cred-abc",76 publicKeyCose: "cose-bytes-b64",77 privateKeyJwk: { kty: "EC", crv: "P-256", x: "x", y: "y", d: "d" },78 userHandle: "user-handle",79 userName: "june",80 userDisplayName: "June",81 algorithm: -7,82 signCount: 0,83 aaguid: "AAAAAAAAAAAAAAAAAAAAAA",84 backupEligible: true,85 backupState: false,86 createdAt: 3,87 modifiedAt: 4,88 },89 ];9091 const bytes = serialiseVault(entries, {});92 const payload = deserialiseVault(bytes);9394 expect(payload.entries).toHaveLength(2);95 const note = payload.entries.find((e) => e.type === "note")!;96 expect(note.notes).toBe("left-30-right-12-left-4");9798 const passkey = payload.entries.find((e) => e.type === "passkey")!;99 expect(passkey.rpId).toBe("example.com");100 expect(passkey.credentialId).toBe("cred-abc");101 expect(passkey.privateKeyJwk?.kty).toBe("EC");102 expect(passkey.algorithm).toBe(-7);103 });104105 it("preserves canary and breach data", () => {106 const entries: VaultEntry[] = [107 makeLogin({108 id: "c1",109 title: "honey",110 password: "trap",111 url: "honey.example",112 canary: { webhookUrl: "https://hook.example/x", label: "AWS decoy", pingCount: 3 },113 breach: 42,114 }),115 ];116 const bytes = serialiseVault(entries, {});117 const payload = deserialiseVault(bytes);118 expect(payload.entries[0].canary?.webhookUrl).toBe("https://hook.example/x");119 expect(payload.entries[0].canary?.label).toBe("AWS decoy");120 expect(payload.entries[0].canary?.pingCount).toBe(3);121 expect(payload.entries[0].breach).toBe(42);122 });123124 it("throws DESERIALISE_MALFORMED on garbage bytes", () => {125 const bytes = new TextEncoder().encode("not-json");126 expect(() => deserialiseVault(bytes)).toThrow(/DESERIALISE_MALFORMED/);127 });128129 it("throws DESERIALISE_MALFORMED on wrong version", () => {130 const bytes = new TextEncoder().encode(131 JSON.stringify({ v: 2, entries: [], settings: {} })132 );133 expect(() => deserialiseVault(bytes)).toThrow(/DESERIALISE_MALFORMED/);134 });135});