/** * 한·영 깨진 토큰 감지·수리 — 순수 로직 테스트. */ import { findBrokenHangulTokens, repairBrokenHangul } from '../src/agent/hangulHygiene'; describe('findBrokenHangulTokens', () => { test('깨진 토큰 감지: 한글+영문소문자 연속', () => { const t = '문서를 단순히 텍스트 덩ey로 두지 말고, 결ently 헤더를 강제해야 합니다.'; const broken = findBrokenHangulTokens(t); expect(broken.some(b => b.includes('덩ey'))).toBe(true); expect(broken.some(b => b.includes('결ently'))).toBe(true); }); test.each([ 'API를 호출하고 Code의 구조를 본다', // 영문 단어 + 한글 조사 = 정상 'VS Code랑 LM Studio에서 모델을 로드한다', '플랜B로 진행하고 옵션A를 검토한다', // 한글+대문자 1글자 = 정상 'RAG 파이프라인과 recall@1 지표', ])('정상 표기는 미감지: %s', (t) => { expect(findBrokenHangulTokens(t)).toHaveLength(0); }); test('코드 블록 안은 제외', () => { const t = '설명\n```js\nconst 덩ey = 1;\n```\n그리고 `값ab` 도 변수다'; expect(findBrokenHangulTokens(t)).toHaveLength(0); }); }); describe('repairBrokenHangul — 검증 게이트', () => { const broken = ['덩ey로']; const text = '텍스트 덩ey로 두지 말 것. '.repeat(3); test('수리 성공 (깨진 토큰 감소 + 길이 유지) → 채택', async () => { const fixed = text.replace(/덩ey로/g, '덩어리로'); const r = await repairBrokenHangul(text, broken, async () => fixed); expect(r).toBe(fixed.trim()); }); test('수리 결과가 너무 짧으면 기각 (내용 유실 방지)', async () => { const r = await repairBrokenHangul(text, broken, async () => '덩어리.'); expect(r).toBeNull(); }); test('깨진 토큰이 줄지 않으면 기각', async () => { const r = await repairBrokenHangul(text, broken, async () => text); expect(r).toBeNull(); }); test('LLM 실패 시 null (원문 유지)', async () => { const r = await repairBrokenHangul(text, broken, async () => { throw new Error('down'); }); expect(r).toBeNull(); }); test('깨진 토큰 없으면 호출 자체를 안 함', async () => { let called = false; const r = await repairBrokenHangul(text, [], async () => { called = true; return text; }); expect(r).toBeNull(); expect(called).toBe(false); }); });