12345678910111213import { describe, expect, it } from "vitest";14import {15 exportEntriesToPeachJson,16 importEntriesFromPeachJson,17 derivePeachExportKey,18} from "../../shared/backup";19import { decrypt, encrypt } from "../../background/aes";20import type { VaultEntry } from "../../shared/types";21222324const NFD_PASSWORD = "café"; 25const NFC_PASSWORD = "café"; 262728if (NFD_PASSWORD === NFC_PASSWORD) {29 throw new Error("Test assumption broken: NFD and NFC passwords are identical on this platform");30}31if (NFD_PASSWORD.normalize("NFC") !== NFC_PASSWORD) {32 throw new Error("Test assumption broken: NFD normalize(NFC) !== NFC");33}3435function sampleEntries(): VaultEntry[] {36 return [37 {38 id: "test-entry-1",39 type: "login",40 title: "Test Site",41 username: "[email protected]",42 password: "correct-horse-battery",43 url: "https://example.com",44 createdAt: 1700000000000,45 modifiedAt: 1700000000000,46 },47 ];48}4950describe("T-006: backup/import NFC raw-fallback decrypt", () => {51 52 53 it("T-006a: export sealed with NFC password → import with NFC password succeeds", async () => {54 const envelope = await exportEntriesToPeachJson(sampleEntries(), NFC_PASSWORD);55 const result = await importEntriesFromPeachJson(JSON.stringify(envelope), NFC_PASSWORD);56 expect(result.entries).toHaveLength(1);57 expect(result.entries[0].title).toBe("Test Site");58 });5960 61 62 63 64 65 66 67 it("T-006b: export sealed with NFD password (raw) → import with NFC password succeeds via fallback", async () => {68 69 const salt = crypto.getRandomValues(new Uint8Array(16));70 const keyBytes = await derivePeachExportKey(NFD_PASSWORD, salt, { raw: true });71 const inner = { entries: sampleEntries() };72 const { nonce, ciphertext, tag } = await encrypt(keyBytes, JSON.stringify(inner));7374 function toHex(buf: Uint8Array): string {75 return Array.from(buf).map(b => b.toString(16).padStart(2, "0")).join("");76 }7778 const envelope = JSON.stringify({79 version: 2,80 type: "peach-export",81 kdf: "PBKDF2-SHA256",82 salt: toHex(salt),83 nonce,84 ciphertext,85 tag,86 });8788 89 const result = await importEntriesFromPeachJson(envelope, NFC_PASSWORD);90 expect(result.entries).toHaveLength(1);91 expect(result.entries[0].title).toBe("Test Site");92 });9394 95 96 97 it("T-006c: export sealed with NFC password → import with NFD password succeeds", async () => {98 const envelope = await exportEntriesToPeachJson(sampleEntries(), NFC_PASSWORD);99 100 101 const result = await importEntriesFromPeachJson(JSON.stringify(envelope), NFD_PASSWORD);102 expect(result.entries).toHaveLength(1);103 expect(result.entries[0].title).toBe("Test Site");104 });105106 107 108 it("T-006d: completely wrong password → fails (neither NFC nor raw match)", async () => {109 const envelope = await exportEntriesToPeachJson(sampleEntries(), NFC_PASSWORD);110 await expect(111 importEntriesFromPeachJson(JSON.stringify(envelope), "wrong-password-entirely"),112 ).rejects.toThrow(/INVALID_EXPORT_PASSWORD/);113 });114115 116 117 118 it("T-006e: NFD 'café' (e + combining acute) and NFC 'café' (single é) open the same vault", async () => {119 const envelope = await exportEntriesToPeachJson(sampleEntries(), NFC_PASSWORD);120 const envelopeStr = JSON.stringify(envelope);121122 123 const resultNfc = await importEntriesFromPeachJson(envelopeStr, NFC_PASSWORD);124 const resultNfd = await importEntriesFromPeachJson(envelopeStr, NFD_PASSWORD);125126 expect(resultNfc.entries[0].title).toBe("Test Site");127 expect(resultNfd.entries[0].title).toBe("Test Site");128 expect(resultNfc.entries[0].password).toBe(resultNfd.entries[0].password);129 });130});