// tests/red/dictionary-redteam-r1-12.test.ts // 회귀: 사전 편집에서 발음을 지울 수 없던 문제, 같은 단어로 이름을 바꿀 때 // DictionaryDuplicate 대신 일반 DB 오류로 조용히 실패하던 문제. import { describe, it, expect } from 'vitest' import { D3ROError, ErrorCode } from '@d3ro/core/errors' import { IPC_CHANNELS } from '@d3ro/core/ipc-channels' import { toDictionaryUpdateParams } from '@d3ro/core/dictionary-policy' import { getDictionaryService } from '../../src/main/services/DictionaryService' import { registerDictionaryHandlers } from '../../src/main/ipc/dictionary-handlers' import { invokeIpc, useRedHarness } from './harness' useRedHarness() describe('사전 편집 회귀 (redteam r1 #12)', () => { it('편집에서 발음 입력을 비우면 저장된 발음이 지워진다', () => { const svc = getDictionaryService() const entry = svc.add({ word: 'GPT', pronunciation: 'gee-pee-tee' }) const updated = svc.update(toDictionaryUpdateParams(entry.id, { word: 'GPT', pronunciation: '' })) expect(updated?.pronunciation).toBeNull() const reloaded = svc.list({ page: 0, pageSize: 10 }).entries.find((e) => e.id === entry.id) expect(reloaded?.pronunciation).toBeNull() }) it('pronunciation: null 은 발음을 지우고, 생략하면 유지한다', () => { const svc = getDictionaryService() const entry = svc.add({ word: 'Whisper', pronunciation: '위스퍼' }) expect(svc.update({ id: entry.id, word: 'Whisper2' })?.pronunciation).toBe('위스퍼') expect(svc.update({ id: entry.id, pronunciation: null })?.pronunciation).toBeNull() }) it('공백뿐인 발음은 빈 문자열로 저장하지 않고 지운다', () => { const svc = getDictionaryService() const entry = svc.add({ word: 'Blank', pronunciation: '블랭크' }) expect(svc.update({ id: entry.id, pronunciation: ' ' })?.pronunciation).toBeNull() }) it('같은 카테고리의 기존 단어로 이름을 바꾸면 DictionaryDuplicate 이다', () => { const svc = getDictionaryService() svc.add({ word: 'Ollama' }) const other = svc.add({ word: 'Llama' }) try { svc.update({ id: other.id, word: 'Ollama' }) expect.unreachable() } catch (err) { expect(err).toBeInstanceOf(D3ROError) expect((err as D3ROError).code).toBe(ErrorCode.DictionaryDuplicate) } }) it('IPC update 도 이름 중복을 DictionaryDuplicate 로 돌려준다', async () => { registerDictionaryHandlers() const svc = getDictionaryService() svc.add({ word: 'dup-target' }) const other = svc.add({ word: 'dup-source' }) const res = await invokeIpc(IPC_CHANNELS.DICTIONARY.UPDATE, { id: other.id, word: 'dup-target' }) expect(res.success).toBe(false) if (!res.success) expect(res.error.code).toBe(ErrorCode.DictionaryDuplicate) }) it('편집에서 단어를 비우면 거부하고, 단어는 trim 해 저장한다', () => { const svc = getDictionaryService() const entry = svc.add({ word: 'Trim' }) expect(() => svc.update({ id: entry.id, word: ' ' })).toThrow(D3ROError) expect(svc.update({ id: entry.id, word: ' Trimmed ' })?.word).toBe('Trimmed') }) })