d3ro-voice/apps/desktop/tests/red/silent-errors.usecase.test.ts
Yun Chan 708e20f747
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
feat: complete release preparation, 10+ ad mediation, CI/CD, and docker deployment
2026-08-20 11:12:05 +09:00

132 lines
5.3 KiB
TypeScript

import { describe, it, expect, vi } from 'vitest'
import { ErrorCode } from '@d3ro/core/errors'
import { Feature } from '@d3ro/core/types'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { getHistoryService } from '../../src/main/services/HistoryService'
import { getLicenseService } from '../../src/main/services/LicenseService'
import { getAutoLaunchService } from '../../src/main/services/AutoLaunchService'
import { getCaptionService } from '../../src/main/services/CaptionService'
import { configGet, configSet } from '../../src/main/services/ConfigService'
import { registerLLMHandlers } from '../../src/main/ipc/llm-handlers'
import { registerHistoryHandlers } from '../../src/main/ipc/history-handlers'
import { registerLicenseHandlers } from '../../src/main/ipc/license-handlers'
import { historyInput, invokeIpc, useRedHarness } from './harness'
import { FX, USER_TEXT } from './fixtures'
useRedHarness()
describe('유스케이스: 사일런트 에러 / 폴백 성공 위장 탐지', () => {
it('자동 제목 LLM 실패 후 히스토리는 completed 로 남되 title 공백을 성공 제목으로 쓰지 않는다', async () => {
const entry = getHistoryService().create(historyInput({ originalText: USER_TEXT.KO }))
const title = await getHistoryService().generateTitle(entry.id)
expect(title === null || title.length > 0).toBe(true)
if (title === null) {
expect(getHistoryService().getById(entry.id)?.title ?? null).toBeNull()
}
})
it('라이선스 사용량 DB 가 열려 있을 때 getUsage 가 실패를 0으로 숨기지 않는다', () => {
const svc = getLicenseService()
svc.initialize()
const usage = svc.getUsage(Feature.DICTATION)
expect(usage.used).toBe(0)
expect(usage.limit).toBe(-1)
})
it('자동 시작 OS API 실패 시 config 와 OS 가 어긋난 채 성공으로 끝나지 않는다', () => {
const errors: string[] = []
const orig = console.error
try {
getAutoLaunchService().setEnabled(true)
expect(configGet('autoLaunch')).toBe(true)
} catch (err) {
errors.push(String(err))
expect(configGet('autoLaunch')).not.toBe(true)
}
void orig
})
it('캡션 초기 상태는 inactive 이며 start 실패가 active+빈세그먼트로 위장되지 않는다', async () => {
expect(getCaptionService().getState()).toBe('inactive')
try {
await getCaptionService().start()
expect(['starting', 'active', 'inactive']).toContain(getCaptionService().getState())
} catch (err) {
expect(getCaptionService().getState()).toBe('inactive')
expect(err).toBeTruthy()
}
})
it('IPC llm:process 가 원문을 processedText 로 그대로 돌려주며 success 하지 않는다 (미인증)', async () => {
configSet('llmBackend', 'online')
configSet('authToken', null)
registerLLMHandlers()
const res = await invokeIpc(IPC_CHANNELS.LLM.PROCESS, {
text: USER_TEXT.KO,
action: 'refine',
})
expect(res.success).toBe(false)
if (res.success) {
expect(res.data.processedText).not.toBe(USER_TEXT.KO)
}
})
it('IPC 히스토리 삭제가 없는 id 를 success 로 위장하지 않는다', async () => {
registerHistoryHandlers()
const res = await invokeIpc(IPC_CHANNELS.HISTORY.DELETE, { id: 'ghost' })
expect(res.success).toBe(false)
})
it('프리미엄 상태 IPC 가 로그인 없이 isAvailable:true 로 위장하지 않는다', async () => {
registerLLMHandlers()
const res = await invokeIpc(IPC_CHANNELS.LLM.PREMIUM_GET_STATUS)
expect(res.success).toBe(true)
if (res.success) expect(res.data.isAvailable).toBe(false)
})
it('라이선스 빈 키 IPC 활성화가 tier=pro 로 위장하지 않는다', async () => {
getLicenseService().initialize()
registerLicenseHandlers()
const res = await invokeIpc(IPC_CHANNELS.LICENSE.ACTIVATE, { licenseKey: '' })
expect(res.success).toBe(true)
if (res.success) {
expect(res.data.success).toBe(false)
expect(res.data.tier).toBe('free')
}
expect(getLicenseService().tier).toBe('free')
})
it('잘못된 키 활성화 메시지가 비어 있지 않다', async () => {
const result = await getLicenseService().activate(FX.LICENSE_BAD)
expect(result.success).toBe(false)
expect(result.message.length).toBeGreaterThan(0)
})
it('configSet 후 같은 키 get 이 이전 기본값을 반환하지 않는다', () => {
configSet('soundEnabled', false)
expect(configGet('soundEnabled')).toBe(false)
})
it('authToken 만료 클리어 후 Online 경로가 캐시된 성공을 내지 않는다', async () => {
configSet('authToken', FX.AUTH_TOKEN)
configSet('llmBackend', 'online')
registerLLMHandlers()
vi.stubGlobal(
'fetch',
vi.fn(async () => ({ ok: false, status: 401, json: async () => ({}) })),
)
const res = await invokeIpc(IPC_CHANNELS.LLM.PROCESS, { text: 'x', action: 'refine' })
expect(res.success).toBe(false)
expect(configGet('authToken')).toBeNull()
})
it('캡션 setConfig 후 getConfig 가 반영된다', () => {
getCaptionService().setConfig({ fontSize: 22 })
expect(getCaptionService().getConfig().fontSize).toBe(22)
})
it('캡션 stop 은 inactive 에서 던져도 상태가 깨지지 않는다', async () => {
await getCaptionService().stop()
expect(getCaptionService().getState()).toBe('inactive')
})
})