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
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:
parent
5cd1de6859
commit
708e20f747
406 changed files with 42464 additions and 6199 deletions
194
apps/desktop/tests/red/conversation.usecase.test.ts
Normal file
194
apps/desktop/tests/red/conversation.usecase.test.ts
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { EventEmitter } from 'events'
|
||||
import { ErrorCode } from '@d3ro/core/errors'
|
||||
import { getVoiceConversationService } from '../../src/main/services/VoiceConversationService'
|
||||
import { getVoiceActionService } from '../../src/main/services/VoiceActionService'
|
||||
import { useRedHarness } from './harness'
|
||||
import { FX, USER_TEXT } from './fixtures'
|
||||
|
||||
const audioBus = new EventEmitter()
|
||||
|
||||
vi.mock('../../src/main/services/AudioCaptureService', () => ({
|
||||
getAudioCaptureService: () => ({
|
||||
start: vi.fn(async () => undefined),
|
||||
stop: vi.fn(async () => undefined),
|
||||
on: (ev: string, fn: (...args: unknown[]) => void) => audioBus.on(ev, fn),
|
||||
off: (ev: string, fn: (...args: unknown[]) => void) => audioBus.off(ev, fn),
|
||||
}),
|
||||
calculateRMS: () => 0,
|
||||
}))
|
||||
|
||||
vi.mock('../../src/main/services/LocalSTTService', () => ({
|
||||
getLocalSTTService: () => ({
|
||||
initialize: vi.fn(async () => undefined),
|
||||
transcribe: vi.fn(async () => ({
|
||||
text: FX.STT_OK,
|
||||
segments: [],
|
||||
language: 'ko',
|
||||
duration: 1,
|
||||
processingTime: 1,
|
||||
})),
|
||||
getModels: () => [],
|
||||
}),
|
||||
resetLocalSTTServiceForTests: () => undefined,
|
||||
}))
|
||||
|
||||
vi.mock('../../src/main/services/PremiumLLMService', () => ({
|
||||
getPremiumLLMService: () => ({
|
||||
processText: vi.fn(async () => FX.LLM_OK),
|
||||
generate: vi.fn(async () => ({ text: FX.LLM_OK })),
|
||||
chatStream: async function* () {
|
||||
yield FX.LLM_OK
|
||||
return FX.LLM_OK
|
||||
},
|
||||
cancelGeneration: vi.fn(),
|
||||
isAvailable: () => false,
|
||||
}),
|
||||
resetPremiumLLMServiceForTests: () => undefined,
|
||||
}))
|
||||
|
||||
vi.mock('../../src/main/services/LocalLLMService', () => ({
|
||||
getLocalLLMService: () => ({
|
||||
isAvailable: () => true,
|
||||
processText: vi.fn(async () => FX.LLM_OK),
|
||||
chatStream: async function* () {
|
||||
yield FX.LLM_OK
|
||||
return FX.LLM_OK
|
||||
},
|
||||
cancelGeneration: vi.fn(),
|
||||
getStatus: () => ({
|
||||
connectionState: 'connected',
|
||||
serverUrl: 'http://localhost:11434',
|
||||
activeModel: 'x',
|
||||
serverVersion: null,
|
||||
}),
|
||||
}),
|
||||
resetLocalLLMServiceForTests: () => undefined,
|
||||
}))
|
||||
|
||||
vi.mock('../../src/main/services/TTSPlaybackService', () => ({
|
||||
getTTSPlaybackService: () => ({
|
||||
speak: vi.fn(async (text: string) => {
|
||||
if (!text.trim()) {
|
||||
const { D3ROError, ErrorCode } = await import('@d3ro/core/errors')
|
||||
throw new D3ROError(ErrorCode.TTSTextEmpty, 'Empty TTS text')
|
||||
}
|
||||
}),
|
||||
speakSentences: vi.fn(async () => undefined),
|
||||
stop: vi.fn(),
|
||||
isSpeaking: false,
|
||||
on: vi.fn(),
|
||||
off: vi.fn(),
|
||||
removeAllListeners: vi.fn(),
|
||||
}),
|
||||
resetTTSPlaybackServiceForTests: () => undefined,
|
||||
}))
|
||||
|
||||
vi.mock('../../src/main/services/SoundEffectService', () => ({
|
||||
getSoundEffectService: () => ({ play: vi.fn() }),
|
||||
}))
|
||||
|
||||
vi.mock('../../src/main/services/LicenseService', () => ({
|
||||
getLicenseService: () => ({
|
||||
canUse: () => ({ allowed: true, reason: 'ok' }),
|
||||
promptUpgrade: vi.fn(),
|
||||
}),
|
||||
resetLicenseServiceForTests: () => undefined,
|
||||
}))
|
||||
|
||||
useRedHarness()
|
||||
|
||||
describe('유스케이스: 음성 대화 세션 / 텍스트 전송 / 히스토리', () => {
|
||||
it('초기 대화 상태는 idle 이다', () => {
|
||||
const svc = getVoiceConversationService()
|
||||
expect(svc.state).toBe('idle')
|
||||
expect(svc.isActive).toBe(false)
|
||||
expect(svc.getHistory()).toEqual([])
|
||||
})
|
||||
|
||||
it('세션을 시작하면 listening 이다', async () => {
|
||||
const svc = getVoiceConversationService()
|
||||
await svc.startSession()
|
||||
expect(svc.isActive).toBe(true)
|
||||
expect(svc.state).toBe('listening')
|
||||
svc.stopSession()
|
||||
})
|
||||
|
||||
it('중복 시작은 ConversationSessionAlreadyActive 다', async () => {
|
||||
const svc = getVoiceConversationService()
|
||||
await svc.startSession()
|
||||
await expect(svc.startSession()).rejects.toMatchObject({
|
||||
code: ErrorCode.ConversationSessionAlreadyActive,
|
||||
})
|
||||
svc.stopSession()
|
||||
})
|
||||
|
||||
it('세션 없이 텍스트 전송은 ConversationNoActiveSession 다', async () => {
|
||||
await expect(getVoiceConversationService().sendTextMessage('hi')).rejects.toMatchObject({
|
||||
code: ErrorCode.ConversationNoActiveSession,
|
||||
})
|
||||
})
|
||||
|
||||
it('텍스트 메시지를 보내면 히스토리에 user 가 남는다', async () => {
|
||||
const svc = getVoiceConversationService()
|
||||
await svc.startSession()
|
||||
await svc.sendTextMessage(USER_TEXT.KO)
|
||||
const hist = svc.getHistory()
|
||||
expect(hist.some((m) => m.role === 'user' && m.content === USER_TEXT.KO)).toBe(true)
|
||||
svc.stopSession()
|
||||
})
|
||||
|
||||
it('히스토리를 비운다', async () => {
|
||||
const svc = getVoiceConversationService()
|
||||
await svc.startSession()
|
||||
await svc.sendTextMessage(USER_TEXT.EN)
|
||||
svc.clearHistory()
|
||||
expect(svc.getHistory()).toEqual([])
|
||||
svc.stopSession()
|
||||
})
|
||||
|
||||
it('세션을 중지하면 idle 이다', async () => {
|
||||
const svc = getVoiceConversationService()
|
||||
await svc.startSession()
|
||||
svc.stopSession()
|
||||
expect(svc.isActive).toBe(false)
|
||||
expect(svc.state).toBe('idle')
|
||||
})
|
||||
|
||||
it('비활성 중지/취소는 던지지 않는다', () => {
|
||||
expect(() => getVoiceConversationService().stopSession()).not.toThrow()
|
||||
expect(() => getVoiceConversationService().cancelResponse()).not.toThrow()
|
||||
})
|
||||
|
||||
it('프리미엄 불가 시 로컬 LLM 경로로 텍스트가 히스토리에 남는다', async () => {
|
||||
const svc = getVoiceConversationService()
|
||||
await svc.startSession()
|
||||
await svc.sendTextMessage(USER_TEXT.KO)
|
||||
const hist = svc.getHistory()
|
||||
expect(hist.some((m) => m.role === 'user' && m.content === USER_TEXT.KO)).toBe(true)
|
||||
expect(hist.some((m) => m.role === 'assistant' && m.content === FX.LLM_OK)).toBe(true)
|
||||
svc.stopSession()
|
||||
})
|
||||
})
|
||||
|
||||
describe('유스케이스: 보이스 액션', () => {
|
||||
it('초기 액션은 비활성이다', () => {
|
||||
expect(getVoiceActionService().isEnabled).toBe(false)
|
||||
})
|
||||
|
||||
it('활성 토글이 동작한다', () => {
|
||||
getVoiceActionService().setEnabled(true)
|
||||
expect(getVoiceActionService().isEnabled).toBe(true)
|
||||
getVoiceActionService().setEnabled(false)
|
||||
expect(getVoiceActionService().isEnabled).toBe(false)
|
||||
})
|
||||
|
||||
it('프리셋 목록이 비어 있지 않다', () => {
|
||||
expect(getVoiceActionService().getPresets().length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('히스토리를 비운다', () => {
|
||||
getVoiceActionService().clearHistory()
|
||||
expect(getVoiceActionService().getHistory()).toEqual([])
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue