import { createPortableArchive, parsePortableArchive, parsePortablePayload, serializePortableArchive, stableJsonStringify, type PortablePayload, } from '../src/features/data-portability'; const OWNER = '4f642f87-2d12-4dcf-9aa1-f4427d4078a8'; function payload(): PortablePayload { return { format: 'd3ro-account-portability', schema_version: 1, exported_at: '2026-08-21T00:00:00.000Z', owner_id: OWNER, source: 'mobile', account: { profile: null, settings: null, subscription: null }, datasets: { dictionary: [], history: [], meetings: [], transcripts: [], meeting_memos: [], meeting_documents: [], custom_instructions: [], }, exclusions: [ 'raw_audio', 'storage_objects', 'payment_credentials', 'push_tokens', ], }; } describe('data portability archive schema', () => { it('round-trips the strict versioned archive and checksum', () => { const canonical = stableJsonStringify(payload()); const archive = createPortableArchive(canonical); const parsed = parsePortableArchive( serializePortableArchive(archive), OWNER, ); expect(parsed.archive.checksum).toMatch(/^[0-9a-f]{64}$/); expect(parsed.payload).toEqual(payload()); }); it('rejects a checksum-tampered payload', () => { const archive = createPortableArchive(stableJsonStringify(payload())); const tampered = JSON.stringify({ ...archive, canonical_payload: archive.canonical_payload.replace( '"source":"mobile"', '"source":"cloud"', ), }); expect(() => parsePortableArchive(tampered, OWNER)).toThrow('checksum'); }); it('rejects unsupported fields instead of ignoring them', () => { const invalid = { ...payload(), storage_objects: [{ path: 'private/audio.wav' }], }; expect(() => parsePortablePayload(stableJsonStringify(invalid))).toThrow( 'unsupported fields', ); }); it('rejects a foreign owner before an RPC can run', () => { const foreign = { ...payload(), owner_id: '1d30574e-5da7-4b22-bf4a-028fd0084962', }; const archive = createPortableArchive(stableJsonStringify(foreign)); expect(() => parsePortableArchive(serializePortableArchive(archive), OWNER), ).toThrow('different account'); }); it('rejects orphan meeting children', () => { const invalid = payload(); invalid.datasets.transcripts.push({ id: '2d47b3b6-a0dc-4ce4-8cd4-37b32ef3a29c', meeting_id: '292a4f9d-288e-4277-aab6-b07132a80c69', segment_index: 0, timestamp_ms: 0, duration_ms: null, text: 'orphan', speaker: null, edited: false, created_at: '2026-08-21T00:00:00.000Z', updated_at: '2026-08-21T00:00:00.000Z', }); expect(() => parsePortablePayload(stableJsonStringify(invalid))).toThrow( 'without its parent', ); }); it('rejects the same row id twice', () => { const dictionary = { id: '7e050e11-c90c-4e95-8581-d37ad687d836', user_id: OWNER, word: 'D3RO', pronunciation: null, category: 'technical' as const, usage_count: 0, last_used_at: null, created_at: '2026-08-21T00:00:00.000Z', updated_at: '2026-08-21T00:00:00.000Z', }; const invalid = payload(); invalid.datasets.dictionary = [dictionary, dictionary]; expect(() => parsePortablePayload(stableJsonStringify(invalid))).toThrow( 'repeats a row id', ); }); });