d3ro-voice/apps/desktop/tests/red/harness.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

181 lines
7 KiB
TypeScript

// RED 유스케이스 공통 하네스: 실제 서비스 + in-memory DB + IPC 캡처
// 네트워크/모델 I/O 만 더블로 교체한다.
import { afterEach, beforeEach, vi } from 'vitest'
import { ipcMain } from 'electron'
import { createTestDb } from '../helpers/createTestDb'
import { bindTestDatabase, unbindTestDatabase } from '../../src/main/db'
import {
initInMemoryConfig,
resetInMemoryConfig,
} from '../../src/main/services/ConfigService'
import { resetHistoryServiceForTests } from '../../src/main/services/HistoryService'
import { resetDictionaryServiceForTests } from '../../src/main/services/DictionaryService'
import { resetCustomInstructionServiceForTests } from '../../src/main/services/CustomInstructionService'
import { resetChainServiceForTests } from '../../src/main/services/ChainService'
import { resetVoiceCommandServiceForTests } from '../../src/main/services/VoiceCommandService'
import { resetLicenseServiceForTests } from '../../src/main/services/LicenseService'
import { resetVoiceModeServiceForTests } from '../../src/main/services/VoiceModeService'
import { resetMemoServiceForTests } from '../../src/main/services/MemoService'
import { resetMeetingModeServiceForTests } from '../../src/main/services/MeetingModeService'
import { resetMeetingDocTemplateServiceForTests } from '../../src/main/services/MeetingDocTemplateService'
import { resetDictationTemplateServiceForTests } from '../../src/main/services/DictationTemplateService'
import { resetVoiceConversationServiceForTests } from '../../src/main/services/VoiceConversationService'
import { resetRAGServiceForTests } from '../../src/main/services/RAGService'
import { resetVoiceActionServiceForTests } from '../../src/main/services/VoiceActionService'
import { resetCaptionServiceForTests } from '../../src/main/services/CaptionService'
import { resetFileTranscriptionServiceForTests } from '../../src/main/services/FileTranscriptionService'
import { resetCloudSTTServiceForTests } from '../../src/main/services/CloudSTTService'
import { resetPremiumLLMServiceForTests } from '../../src/main/services/PremiumLLMService'
import { resetOnlineLLMServiceForTests } from '../../src/main/services/OnlineLLMService'
import { resetCloudSyncServiceForTests } from '../../src/main/services/CloudSyncService'
import { resetTTSPlaybackServiceForTests } from '../../src/main/services/TTSPlaybackService'
import { resetMeetingSummaryServiceForTests } from '../../src/main/services/MeetingSummaryService'
import { resetLocalLLMServiceForTests } from '../../src/main/services/LocalLLMService'
import { resetLocalSTTServiceForTests } from '../../src/main/services/LocalSTTService'
import type { IPCResult } from '@d3ro/core/errors'
import type { NewHistory } from '../../src/main/db/schema'
const ipcHandlers = new Map<string, (...args: unknown[]) => unknown>()
export function resetIpcHandlers(): void {
ipcHandlers.clear()
vi.mocked(ipcMain.handle).mockImplementation((channel: string, handler: (...args: unknown[]) => unknown) => {
ipcHandlers.set(channel, handler)
})
}
export async function invokeIpc<T = unknown>(
channel: string,
...args: unknown[]
): Promise<IPCResult<T>> {
const handler = ipcHandlers.get(channel)
if (!handler) {
throw new Error(`IPC handler not registered: ${channel}`)
}
return (await handler({}, ...args)) as IPCResult<T>
}
export function hasIpc(channel: string): boolean {
return ipcHandlers.has(channel)
}
export function historyInput(
overrides: Partial<Omit<NewHistory, 'id' | 'createdAt' | 'updatedAt'>> = {},
): Omit<NewHistory, 'id' | 'createdAt' | 'updatedAt'> {
return {
originalText: '오늘 회의는 오후 세 시에 시작합니다',
duration: 3.5,
wordCount: 8,
mode: 'dictation',
status: 'completed',
appVersion: '1.0.0',
...overrides,
}
}
export function resetAllSingletons(): void {
resetHistoryServiceForTests()
resetDictionaryServiceForTests()
resetCustomInstructionServiceForTests()
resetChainServiceForTests()
resetVoiceCommandServiceForTests()
resetLicenseServiceForTests()
resetVoiceModeServiceForTests()
resetMemoServiceForTests()
resetMeetingModeServiceForTests()
resetMeetingDocTemplateServiceForTests()
resetDictationTemplateServiceForTests()
resetVoiceConversationServiceForTests()
resetRAGServiceForTests()
resetVoiceActionServiceForTests()
resetCaptionServiceForTests()
resetFileTranscriptionServiceForTests()
resetCloudSTTServiceForTests()
resetPremiumLLMServiceForTests()
resetOnlineLLMServiceForTests()
resetCloudSyncServiceForTests()
resetTTSPlaybackServiceForTests()
resetMeetingSummaryServiceForTests()
resetLocalLLMServiceForTests()
resetLocalSTTServiceForTests()
}
export function useRedHarness(): void {
let testdb: ReturnType<typeof createTestDb> | null = null
beforeEach(() => {
testdb = createTestDb()
bindTestDatabase(testdb.db)
initInMemoryConfig()
resetAllSingletons()
resetIpcHandlers()
})
afterEach(() => {
resetAllSingletons()
unbindTestDatabase()
testdb?.close()
testdb = null
resetInMemoryConfig()
vi.unstubAllGlobals()
})
}
export type InvokeFnResult = { data: unknown; error: { message: string } | null }
export function makeCloudSyncFake(opts?: {
authenticated?: boolean
invoke?: (name: string, body: Record<string, unknown>) => Promise<InvokeFnResult>
}): {
isEnabled: () => boolean
isAuthenticated: () => boolean
getState: () => {
authenticated: boolean
userEmail: string | null
lastSyncAt: number | null
syncing: boolean
}
getUser: () => { id: string; email: string } | null
invokeFunction: (name: string, body: Record<string, unknown>) => Promise<InvokeFnResult>
pushOne: () => Promise<void>
pushAll: () => Promise<{ pushed: number; errors: string[] }>
pullAll: () => Promise<{ pushed: number; errors: string[] }>
startSignIn: (provider: string) => Promise<void>
handleAuthCallback: (code: string) => Promise<void>
signOut: () => Promise<void>
signInAnonymously: () => Promise<void>
on: () => void
off: () => void
} {
const authenticated = opts?.authenticated ?? false
return {
isEnabled: () => true,
isAuthenticated: () => authenticated,
getState: () => ({
authenticated,
userEmail: authenticated ? 'fx.user@example.test' : null,
lastSyncAt: null,
syncing: false,
}),
getUser: () =>
authenticated ? { id: 'fx-user-id', email: 'fx.user@example.test' } : null,
invokeFunction: opts?.invoke
?? (async () => ({ data: null, error: { message: 'fx.cloud.unconfigured' } })),
pushOne: async () => undefined,
pushAll: async () => ({ pushed: 0, errors: [] }),
pullAll: async () => ({ pushed: 0, errors: [] }),
startSignIn: async () => {
throw new Error('fx.oauth.not-started')
},
handleAuthCallback: async () => {
throw new Error('fx.oauth.callback-rejected')
},
signOut: async () => undefined,
signInAnonymously: async () => {
throw new Error('fx.anon.not-supported')
},
on: () => undefined,
off: () => undefined,
}
}