1import { describe, expect, it } from "vitest";23import {4 exportEntriesToPeachJson,5 importEntriesFromPeachJson,6 type PeachExportEnvelope,7} from "./backup";89describe("shared backup", () => {10 const sampleEntries = [11 {12 id: "login-1",13 type: "login" as const,14 title: "GitHub",15 username: "june",16 password: "secret123",17 url: "https://github.com",18 notes: "2fa enabled",19 createdAt: 1,20 modifiedAt: 1,21 },22 ];2324 it("importEntriesFromPeachJson decrypts entries with the correct password", async () => {25 const envelope = await exportEntriesToPeachJson(sampleEntries, "export-pass");2627 const result = await importEntriesFromPeachJson(JSON.stringify(envelope), "export-pass");28 expect(result.entries).toMatchObject([29 {30 type: "login",31 title: "GitHub",32 username: "june",33 password: "secret123",34 url: "https://github.com",35 },36 ]);37 });3839 it("importEntriesFromPeachJson rejects the wrong password", async () => {40 const envelope = await exportEntriesToPeachJson(sampleEntries, "export-pass");4142 await expect(43 importEntriesFromPeachJson(JSON.stringify(envelope), "wrong-pass")44 ).rejects.toThrow("INVALID_EXPORT_PASSWORD");45 });4647 it("importEntriesFromPeachJson rejects malformed JSON", async () => {48 await expect(importEntriesFromPeachJson("{not json", "export-pass")).rejects.toThrow(49 "INVALID_PEACH_EXPORT"50 );51 });5253 it("export includes trashed entries; import preserves deletedAt", async () => {54 const trashedAt = 1_700_000_010_000;55 const entries = [56 ...sampleEntries,57 {58 id: "login-2",59 type: "login" as const,60 title: "Old Service",61 username: "june",62 password: "oldpass",63 url: "https://old.example.com",64 createdAt: 1,65 modifiedAt: 1,66 deletedAt: trashedAt,67 },68 ];6970 const envelope = await exportEntriesToPeachJson(entries, "export-pass");71 const { entries: imported } = await importEntriesFromPeachJson(JSON.stringify(envelope), "export-pass");7273 expect(imported).toHaveLength(2);7475 const active = imported.filter((e) => !e.deletedAt);76 const trashed = imported.filter((e) => !!e.deletedAt);7778 expect(active).toHaveLength(1);79 expect(trashed).toHaveLength(1);80 expect(trashed[0].title).toBe("Old Service");81 expect(trashed[0].deletedAt).toBe(trashedAt);82 });8384 it("importEntriesFromPeachJson rejects envelopes with missing required fields", async () => {85 const envelope: Partial<PeachExportEnvelope> = {86 version: 2,87 type: "peach-export",88 kdf: "PBKDF2-SHA256",89 salt: "00",90 nonce: "00",91 ciphertext: "00",92 };9394 await expect(95 importEntriesFromPeachJson(JSON.stringify(envelope), "export-pass")96 ).rejects.toThrow("INVALID_PEACH_EXPORT");97 });9899 it("MV-5: importEntriesFromPeachJson rejects an over-limit raw string with IMPORT_TOO_LARGE", async () => {100 const huge = "x".repeat(11 * 1024 * 1024); 101 await expect(importEntriesFromPeachJson(huge, "export-pass")).rejects.toThrow(102 /IMPORT_TOO_LARGE/,103 );104 });105});