131 lines
5.3 KiB
TypeScript
131 lines
5.3 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest'
|
|
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')
|
|
})
|
|
})
|