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
181 lines
7 KiB
TypeScript
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,
|
|
}
|
|
}
|