import fs from 'node:fs'; import path from 'node:path'; import { parseImportedPortableFile, PortabilityError, } from '../src/features/data-portability'; const OWNER = '4f642f87-2d12-4dcf-9aa1-f4427d4078a8'; const FIXTURE_ROOT = path.join(__dirname, 'fixtures', 'data-portability'); function fixture(name: string): string { return fs.readFileSync(path.join(FIXTURE_ROOT, name), 'utf8'); } describe('desktop and web legacy portability', () => { it('imports the declared desktop dictionary JSON shape deterministically', () => { const raw = fixture('desktop-dictionary.json'); const first = parseImportedPortableFile( raw, 'dictionary.json', 'application/json', OWNER, '2026-08-21T00:00:00.000Z', ); const second = parseImportedPortableFile( raw, 'dictionary.json', 'application/json', OWNER, '2026-08-21T00:00:00.000Z', ); expect(first.detectedFormat).toBe('legacy-json'); expect(first.payload.datasets.dictionary).toHaveLength(2); expect(first.payload.datasets.dictionary[0].usage_count).toBe(7); expect(first.archive.checksum).toBe(second.archive.checksum); expect(first.payload.datasets.dictionary[1].id).toBe( second.payload.datasets.dictionary[1].id, ); }); it('parses RFC4180 commas and escaped quotes in dictionary CSV', () => { const result = parseImportedPortableFile( fixture('desktop-dictionary.csv'), 'dictionary.csv', 'text/csv', OWNER, '2026-08-21T00:00:00.000Z', ); expect(result.detectedFormat).toBe('dictionary-csv'); expect(result.payload.datasets.dictionary.map(row => row.word)).toEqual([ '쉼표, 포함', '따옴표 "포함"', ]); }); it('converts the real desktop meeting Markdown markers', () => { const result = parseImportedPortableFile( fixture('desktop-meeting.md'), 'meeting.md', 'text/markdown', OWNER, '2026-08-21T00:00:00.000Z', ); const meeting = result.payload.datasets.meetings[0]; expect(result.detectedFormat).toBe('desktop-meeting-markdown'); expect(meeting.title).toBe('모바일 출시 점검'); expect(meeting.minutes_markdown).toContain('다른 사용자 데이터는 거부한다'); expect(meeting.raw_transcript).toContain('전체를 롤백합니다'); }); it('rejects a legacy file that declares a different owner', () => { expect(() => parseImportedPortableFile( fixture('foreign-owner.json'), 'foreign.json', 'application/json', OWNER, ), ).toThrow(PortabilityError); try { parseImportedPortableFile( fixture('foreign-owner.json'), 'foreign.json', 'application/json', OWNER, ); } catch (error) { expect((error as PortabilityError).code).toBe('owner'); } }); it('fails closed when one row in a multi-row file is invalid', () => { const malformed = JSON.stringify({ entries: [ { word: 'valid', category: 'user' }, { word: '', category: 'user' }, ], }); expect(() => parseImportedPortableFile( malformed, 'dictionary.json', 'application/json', OWNER, ), ).toThrow('invalid length'); }); });