feat(release): prepare 1.1.0 candidate
This commit is contained in:
parent
5a34f66981
commit
5205dcdfa9
736 changed files with 115667 additions and 12203 deletions
125
apps/mobile-rn/__tests__/data-portability-documents.test.ts
Normal file
125
apps/mobile-rn/__tests__/data-portability-documents.test.ts
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
import {
|
||||
buildDictionaryCsv,
|
||||
buildDocx,
|
||||
buildMeetingExport,
|
||||
buildMeetingMarkdown,
|
||||
buildPdf,
|
||||
type MeetingExportInput,
|
||||
type PortableDictionaryRow,
|
||||
} from '../src/features/data-portability';
|
||||
|
||||
const OWNER = '4f642f87-2d12-4dcf-9aa1-f4427d4078a8';
|
||||
|
||||
function input(): MeetingExportInput {
|
||||
return {
|
||||
meeting: {
|
||||
id: '292a4f9d-288e-4277-aab6-b07132a80c69',
|
||||
user_id: OWNER,
|
||||
team_id: null,
|
||||
title: '모바일 출시 점검',
|
||||
status: 'completed',
|
||||
started_at: '2026-08-21T00:00:00.000Z',
|
||||
ended_at: '2026-08-21T01:00:00.000Z',
|
||||
duration_ms: 3_600_000,
|
||||
raw_transcript: null,
|
||||
edited_transcript: null,
|
||||
minutes_markdown: '## 요약\n\n원자적 복원을 검증했다.',
|
||||
minutes_json: null,
|
||||
stt_model: 'whisper-large-v3',
|
||||
llm_model: 'gpt-5',
|
||||
stt_latency_ms: 100,
|
||||
llm_latency_ms: 200,
|
||||
error_message: null,
|
||||
audio_storage_key: null,
|
||||
created_at: '2026-08-21T00:00:00.000Z',
|
||||
updated_at: '2026-08-21T01:00:00.000Z',
|
||||
},
|
||||
transcripts: [
|
||||
{
|
||||
id: '2d47b3b6-a0dc-4ce4-8cd4-37b32ef3a29c',
|
||||
meeting_id: '292a4f9d-288e-4277-aab6-b07132a80c69',
|
||||
segment_index: 0,
|
||||
timestamp_ms: 5_000,
|
||||
duration_ms: 1_000,
|
||||
text: '전체 실패는 롤백합니다.',
|
||||
speaker: '화자 1',
|
||||
edited: false,
|
||||
created_at: '2026-08-21T00:00:05.000Z',
|
||||
updated_at: '2026-08-21T00:00:05.000Z',
|
||||
},
|
||||
],
|
||||
memos: [
|
||||
{
|
||||
id: '8ebac53d-6bb6-4da6-973a-9d4e12e93d3c',
|
||||
meeting_id: '292a4f9d-288e-4277-aab6-b07132a80c69',
|
||||
user_id: OWNER,
|
||||
content: '체크섬 | 재검증',
|
||||
timestamp_ms: 3_000,
|
||||
created_at: '2026-08-21T00:00:03.000Z',
|
||||
},
|
||||
],
|
||||
documents: [],
|
||||
};
|
||||
}
|
||||
|
||||
function ascii(bytes: Uint8Array): string {
|
||||
return String.fromCharCode(...bytes.slice(0, 32));
|
||||
}
|
||||
|
||||
describe('meeting and tabular exports', () => {
|
||||
it('builds Markdown with transcript, memo escaping, and metadata', () => {
|
||||
const markdown = buildMeetingMarkdown(input());
|
||||
expect(markdown).toContain('# Meeting Minutes — 모바일 출시 점검');
|
||||
expect(markdown).toContain('체크섬 \\| 재검증');
|
||||
expect(markdown).toContain('[00:05] 화자 1: 전체 실패는 롤백합니다.');
|
||||
});
|
||||
|
||||
it('creates a structurally valid PDF container with Korean CID text', () => {
|
||||
const pdf = buildPdf(buildMeetingMarkdown(input()));
|
||||
const complete = String.fromCharCode(...pdf);
|
||||
expect(ascii(pdf)).toMatch(/^%PDF-1\.4/);
|
||||
expect(complete).toContain('/UniKS-UCS2-H');
|
||||
expect(complete).toContain('%%EOF');
|
||||
});
|
||||
|
||||
it('creates an uncompressed OOXML DOCX ZIP containing Unicode document XML', () => {
|
||||
const docx = buildDocx(
|
||||
buildMeetingMarkdown(input()),
|
||||
'모바일 출시 점검',
|
||||
new Date('2026-08-21T00:00:00.000Z'),
|
||||
);
|
||||
const complete = String.fromCharCode(...docx);
|
||||
expect(ascii(docx)).toMatch(/^PK\x03\x04/);
|
||||
expect(complete).toContain('word/document.xml');
|
||||
expect(complete).toContain('[Content_Types].xml');
|
||||
expect(complete).toContain('PK\x05\x06');
|
||||
});
|
||||
|
||||
it('returns all four real meeting output formats', () => {
|
||||
expect(buildMeetingExport(input(), 'md').mimeType).toBe('text/markdown');
|
||||
expect(buildMeetingExport(input(), 'txt').mimeType).toBe('text/plain');
|
||||
expect(buildMeetingExport(input(), 'pdf').bytes?.slice(0, 5)).toEqual(
|
||||
Uint8Array.from([37, 80, 68, 70, 45]),
|
||||
);
|
||||
expect(buildMeetingExport(input(), 'docx').bytes?.slice(0, 2)).toEqual(
|
||||
Uint8Array.from([80, 75]),
|
||||
);
|
||||
});
|
||||
|
||||
it('neutralizes spreadsheet formulas while preserving RFC4180 fields', () => {
|
||||
const row: PortableDictionaryRow = {
|
||||
id: '7e050e11-c90c-4e95-8581-d37ad687d836',
|
||||
user_id: OWNER,
|
||||
word: '=HYPERLINK("https://attacker.invalid")',
|
||||
pronunciation: '쉼표, 따옴표 "확인"',
|
||||
category: 'user',
|
||||
usage_count: 0,
|
||||
last_used_at: null,
|
||||
created_at: '2026-08-21T00:00:00.000Z',
|
||||
updated_at: '2026-08-21T00:00:00.000Z',
|
||||
};
|
||||
const csv = buildDictionaryCsv([row]);
|
||||
expect(csv).toContain("'=HYPERLINK");
|
||||
expect(csv).toContain('"쉼표, 따옴표 ""확인"""');
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue