import { describe, it, expect } from 'vitest' import { D3ROError, ErrorCode } from '@d3ro/core/errors' import { getDictationTemplateService } from '../../src/main/services/DictationTemplateService' import { getMeetingDocTemplateService } from '../../src/main/services/MeetingDocTemplateService' import { useRedHarness } from './harness' import { USER_TEXT } from './fixtures' useRedHarness() describe('유스케이스: 딕테이션 템플릿 CRUD / 필드 세션', () => { it('빌트인 템플릿이 있다', () => { const all = getDictationTemplateService().getAll() expect(all.length).toBeGreaterThanOrEqual(3) expect(all.some((t) => t.id === 'builtin-email')).toBe(true) }) it('사용자 템플릿을 만든다', () => { const created = getDictationTemplateService().create({ name: '내 템플릿', description: 'd', fields: [{ id: 'f1', name: 'f1', label: 'F', promptText: 'say', required: true, maxDurationSec: 10 }], outputFormat: '{{f1}}', }) expect(created.isBuiltin).toBe(false) expect(getDictationTemplateService().getById(created.id)?.name).toBe('내 템플릿') }) it('없는 템플릿 수정은 TemplateNotFound 다', () => { expect(() => getDictationTemplateService().update({ id: 'missing', name: 'x' })).toThrow( D3ROError, ) try { getDictationTemplateService().update({ id: 'missing', name: 'x' }) } catch (err) { expect((err as D3ROError).code).toBe(ErrorCode.TemplateNotFound) } }) it('빌트인 삭제는 거부된다', () => { expect(() => getDictationTemplateService().delete('builtin-email')).toThrow(D3ROError) expect(getDictationTemplateService().getById('builtin-email')).not.toBeNull() }) it('사용자 템플릿은 삭제된다', () => { const created = getDictationTemplateService().create({ name: 'del', description: '', fields: [{ id: 'f1', name: 'f1', label: 'F', promptText: 'p', required: false, maxDurationSec: 5 }], outputFormat: '{{f1}}', }) getDictationTemplateService().delete(created.id) expect(getDictationTemplateService().getById(created.id)).toBeNull() }) it('없는 템플릿 세션 시작은 TemplateNotFound 다', () => { expect(() => getDictationTemplateService().startSession('missing')).toThrow(D3ROError) }) it('이메일 템플릿 세션을 시작하고 필드를 채운다', () => { const svc = getDictationTemplateService() svc.startSession('builtin-email') const state = svc.getSessionState() expect(state?.templateId).toBe('builtin-email') expect(state?.currentField?.id).toBe('recipient') svc.setFieldValue('recipient', 'a@b.c') expect(svc.getSessionState()?.currentField?.id).toBe('subject') }) it('이미 세션이 있으면 재시작은 TemplateSessionAlreadyActive 다', () => { const svc = getDictationTemplateService() svc.startSession('builtin-email') expect(() => svc.startSession('builtin-report')).toThrow(D3ROError) try { svc.startSession('builtin-report') } catch (err) { expect((err as D3ROError).code).toBe(ErrorCode.TemplateSessionAlreadyActive) } }) it('세션 없이 필드 입력은 TemplateSessionNotActive 다', () => { expect(() => getDictationTemplateService().setFieldValue('x', 'y')).toThrow(D3ROError) }) it('세션을 취소하면 상태가 null 이다', () => { const svc = getDictationTemplateService() svc.startSession('builtin-email') svc.cancelSession() expect(svc.getSessionState()).toBeNull() }) it('모든 필드를 채우면 출력이 치환되고 세션이 끝난다', () => { const svc = getDictationTemplateService() let output: string | null = null svc.on('session-completed', (e: { outputText: string }) => { output = e.outputText }) svc.startSession('builtin-email') svc.setFieldValue('recipient', 'to@x.test') svc.setFieldValue('subject', 'hello') svc.setFieldValue('body', USER_TEXT.KO) expect(svc.getSessionState()).toBeNull() expect(output).toContain('to@x.test') expect(output).toContain('hello') expect(output).toContain(USER_TEXT.KO) }) it('빈 필드 템플릿은 시작할 수 없다', () => { const created = getDictationTemplateService().create({ name: 'nofield', description: '', fields: [], outputFormat: 'x', }) expect(() => getDictationTemplateService().startSession(created.id)).toThrow(D3ROError) }) }) describe('유스케이스: 회의 문서 템플릿 CRUD', () => { it('빌트인 회의록/보고서/아이디어/마인드맵이 있다', () => { const all = getMeetingDocTemplateService().getAll() expect(all.some((t) => t.id === 'builtin-minutes')).toBe(true) expect(all.some((t) => t.id === 'builtin-report')).toBe(true) expect(all.some((t) => t.id === 'builtin-idea-note')).toBe(true) expect(all.some((t) => t.id === 'builtin-mindmap')).toBe(true) }) it('커스텀 문서 템플릿을 만든다', () => { const created = getMeetingDocTemplateService().create({ name: '내 양식', description: 'd', systemPrompt: '요약만', }) expect(created.isBuiltin).toBe(false) expect(getMeetingDocTemplateService().getById(created.id)?.systemPrompt).toBe('요약만') }) it('없는 템플릿 수정은 MeetingDocTemplateNotFound 다', () => { expect(() => getMeetingDocTemplateService().update({ id: 'missing', name: 'x' })).toThrow( D3ROError, ) try { getMeetingDocTemplateService().update({ id: 'missing', name: 'x' }) } catch (err) { expect((err as D3ROError).code).toBe(ErrorCode.MeetingDocTemplateNotFound) } }) it('빌트인 삭제는 MeetingDocTemplateBuiltinDelete 다', () => { expect(() => getMeetingDocTemplateService().delete('builtin-minutes')).toThrow(D3ROError) try { getMeetingDocTemplateService().delete('builtin-minutes') } catch (err) { expect((err as D3ROError).code).toBe(ErrorCode.MeetingDocTemplateBuiltinDelete) } }) it('커스텀 템플릿은 삭제된다', () => { const created = getMeetingDocTemplateService().create({ name: 'del', description: '', systemPrompt: 'p', }) getMeetingDocTemplateService().delete(created.id) expect(getMeetingDocTemplateService().getById(created.id)).toBeNull() }) it('없는 템플릿 삭제는 MeetingDocTemplateNotFound 다', () => { expect(() => getMeetingDocTemplateService().delete('missing')).toThrow(D3ROError) }) it('템플릿 이름 수정이 반영된다', () => { const created = getMeetingDocTemplateService().create({ name: 'old', description: '', systemPrompt: 'p', }) const updated = getMeetingDocTemplateService().update({ id: created.id, name: 'new' }) expect(updated.name).toBe('new') }) })