// tests/main/services/dictionary-file-codec.test.ts // 사전 파일 형식(CSV·JSON) 순수 코덱 — DictionaryService 에서 분리한 로직의 단위 테스트 import { describe, it, expect } from 'vitest' import { DICTIONARY_CSV_HEADER, parseDictionaryFile, serializeDictionary, toDictionaryImportRow } from '../../../src/main/services/dictionary/dictionary-file-codec' import type { DictionaryEntry } from '@d3ro/core/types' const entry: DictionaryEntry = { id: 'e1', word: '=SUM, "quoted"', pronunciation: null, category: 'technical', usageCount: 3, lastUsedAt: null, createdAt: 100, updatedAt: 200 } describe('serializeDictionary', () => { it('CSV 는 BOM·헤더·CRLF 를 쓰고 수식·따옴표를 이스케이프한다', () => { const csv = serializeDictionary([entry], 'csv') expect(csv.startsWith(`\uFEFF${DICTIONARY_CSV_HEADER.join(',')}\r\n`)).toBe(true) expect(csv).toContain('"=SUM, ""quoted""",,technical,3,100,200\r\n') }) it('JSON 은 entries 객체로 쓴다', () => { expect(JSON.parse(serializeDictionary([entry], 'json'))).toEqual({ entries: [entry] }) }) it('CSV 로 쓴 내용을 다시 읽으면 같은 값이 나온다', () => { const [record] = parseDictionaryFile(serializeDictionary([entry], 'csv'), 'csv') expect(record).toMatchObject({ word: entry.word, category: 'technical', usageCount: '3' }) }) }) describe('parseDictionaryFile', () => { it('JSON 배열과 entries 객체를 모두 받는다', () => { expect(parseDictionaryFile('[{"word":"a"}, 1, null]', 'json')).toEqual([{ word: 'a' }]) expect(parseDictionaryFile('{"entries":[{"word":"b"}]}', 'json')).toEqual([{ word: 'b' }]) }) it('형식이 맞지 않으면 Error 를 던진다', () => { expect(() => parseDictionaryFile('{"x":1}', 'json')).toThrow(/entries/) expect(() => parseDictionaryFile('', 'csv')).toThrow(/empty CSV/) expect(() => parseDictionaryFile('term\r\nx\r\n', 'csv')).toThrow(/word/) }) }) describe('toDictionaryImportRow', () => { it('필드를 정규화하고 기본값을 채운다', () => { expect( toDictionaryImportRow( { word: ' AI ', category: 'bogus', usage_count: '2.7', created_at: 50 }, 999 ) ).toEqual({ ok: true, row: { word: 'AI', pronunciation: null, category: 'user', usageCount: 2, lastUsedAt: null, createdAt: 50, updatedAt: 50 } }) }) it('빈 단어·너무 긴 값을 문제로 돌려준다', () => { expect(toDictionaryImportRow({ pronunciation: 'x' }, 1)).toMatchObject({ ok: false, problem: { reason: 'empty-word' } }) expect(toDictionaryImportRow({ word: 'w'.repeat(121) }, 1)).toMatchObject({ ok: false, problem: { reason: 'too-long', field: 'word', max: 120 } }) expect(toDictionaryImportRow({ word: 'ok', pronunciation: 'p'.repeat(201) }, 1)).toMatchObject({ ok: false, problem: { reason: 'too-long', field: 'pronunciation', max: 200 } }) }) })