cjkSerialise.test.ts

4.2 KB · raw

1/**2 * Vault serialisation round-trip for the full-fidelity v3 payload.3 */45import { 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});