d3ro-voice/apps/mobile-rn/__tests__/data-portability-legacy.test.ts
2026-08-29 18:33:45 +09:00

109 lines
3.2 KiB
TypeScript

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');
});
});