// 받아쓰기 템플릿 필드 id 회귀 테스트 (redteam r3-7) // 버그: 편집기가 필드 개수로 id를 만들고(중간 삭제 후 재사용) 이름을 그대로 id로 써서 // 빈 id·중복 id 템플릿이 저장됐다. 세션에서 같은 id 필드의 받아쓴 값이 앞 값을 덮어써 // 출력({{id}})에서 앞 값이 조용히 사라졌다. 이제 서비스가 저장 전에 TemplateInvalidFormat 으로 거부한다. import { describe, expect, it } from 'vitest' import { D3ROError, ErrorCode } from '@d3ro/core/errors' import type { TemplateField } from '@d3ro/core/types' import { getDictationTemplateService } from '../../src/main/services/DictationTemplateService' import { useRedHarness } from './harness' useRedHarness() function field(id: string): TemplateField { return { id, name: id, label: id, promptText: '', required: true, maxDurationSec: 30 } } function expectInvalidFormat(run: () => unknown): void { let caught: unknown try { run() } catch (err) { caught = err } expect(caught).toBeInstanceOf(D3ROError) expect((caught as D3ROError).code).toBe(ErrorCode.TemplateInvalidFormat) } describe('받아쓰기 템플릿 필드 id 검증', () => { it('중복 필드 id 템플릿 생성을 거부하고 저장하지 않는다', () => { const svc = getDictationTemplateService() const before = svc.getAll().length // 편집기 시나리오: [field1, field2, field3] → field1 삭제 → 추가 → [field2, field3, field3] expectInvalidFormat(() => svc.create({ name: 'dup', description: '', fields: [field('field2'), field('field3'), field('field3')], outputFormat: '{{field2}} {{field3}}', }), ) expect(svc.getAll()).toHaveLength(before) }) it('빈/공백 필드 id 템플릿 생성을 거부한다', () => { const svc = getDictationTemplateService() expectInvalidFormat(() => svc.create({ name: 'empty', description: '', fields: [field('a'), field('')], outputFormat: '{{a}}' }), ) expectInvalidFormat(() => svc.create({ name: 'blank', description: '', fields: [field(' ')], outputFormat: '' }), ) }) it('수정으로 중복 필드 id를 넣으면 거부하고 기존 필드를 유지한다', () => { const svc = getDictationTemplateService() const created = svc.create({ name: 'ok', description: '', fields: [field('a'), field('b')], outputFormat: '{{a}} {{b}}', }) expectInvalidFormat(() => svc.update({ id: created.id, fields: [field('a'), field('a')] })) expect(svc.getById(created.id)?.fields.map((f) => f.id)).toEqual(['a', 'b']) }) it('필드를 바꾸지 않는 수정과 유효한 필드 수정은 그대로 된다', () => { const svc = getDictationTemplateService() const created = svc.create({ name: 'ok', description: '', fields: [field('a')], outputFormat: '{{a}}' }) expect(svc.update({ id: created.id, name: 'renamed' }).name).toBe('renamed') expect(svc.update({ id: created.id, fields: [field('a'), field('b')] }).fields).toHaveLength(2) }) it('유효한 템플릿은 필드마다 받아쓴 값이 모두 출력에 남는다', () => { const svc = getDictationTemplateService() const created = svc.create({ name: 'flow', description: '', fields: [field('field2'), field('field3'), field('field4')], outputFormat: '{{field2}}|{{field3}}|{{field4}}', }) const outputs: string[] = [] svc.on('session-completed', (e: { outputText: string }) => outputs.push(e.outputText)) svc.startSession(created.id) svc.consumeDictatedText('one') svc.consumeDictatedText('two') svc.consumeDictatedText('three') expect(outputs).toEqual(['one|two|three']) }) })