feat: complete release preparation, 10+ ad mediation, CI/CD, and docker deployment
Some checks failed
CI Pipeline / Code Quality & Typecheck (push) Waiting to run
CI Pipeline / Test Suite (macos-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (ubuntu-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (windows-latest) (push) Blocked by required conditions
CI Pipeline / Build Validation (admin) (push) Blocked by required conditions
CI Pipeline / Build Validation (desktop) (push) Blocked by required conditions
Deploy Landing Page / deploy (push) Blocked by required conditions
Deploy Landing Page / build (push) Waiting to run
Release & Packaging Pipeline / Build & Publish Admin Docker Image (push) Failing after 8s
Release & Code Signing CA Pipeline / build-and-sign-windows (push) Failing after 1m51s
Build macOS / Build & Package (macOS) (push) Failing after 4s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
Release & Code Signing CA Pipeline / build-and-sign-macos (push) Failing after 3s
Release & Packaging Pipeline / Package macOS Desktop App (push) Failing after 4s
Release & Packaging Pipeline / Package Windows Desktop App (push) Failing after 2m28s
Release & Packaging Pipeline / Publish Official GitHub Release (push) Has been skipped

This commit is contained in:
Yun Chan 2026-08-20 11:12:05 +09:00
parent 5cd1de6859
commit 708e20f747
406 changed files with 42464 additions and 6199 deletions

View file

@ -0,0 +1,180 @@
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')
})
})