d3ro-voice/apps/desktop/tests/main/ipc/voice-handlers-redteam-r1-4.test.ts

81 lines
2.9 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
// ipcMain.handle(invoke 전용)과 ipcMain.on(send 전용)을 분리해 기록한다.
// Electron에서 ipcRenderer.send 메시지는 handle 등록에 절대 도달하지 않으므로
// 이 둘을 섞어 쓰는 하네스로는 RecordingTip 클릭 취소 회귀를 잡을 수 없다.
const invokeHandlers = vi.hoisted(
() => new Map<string, (event: unknown, ...args: unknown[]) => Promise<unknown>>()
)
const sendListeners = vi.hoisted(
() => new Map<string, Array<(event: unknown, ...args: unknown[]) => void>>()
)
const voice = vi.hoisted(() => ({
cancelSession: vi.fn(),
on: vi.fn(),
startSession: vi.fn(async () => undefined),
stopSession: vi.fn(async () => undefined),
getState: vi.fn(() => ({})),
currentSession: null
}))
vi.mock('electron', () => ({
ipcMain: {
handle: vi.fn((channel: string, handler: (event: unknown, ...args: unknown[]) => Promise<unknown>) => {
invokeHandlers.set(channel, handler)
}),
on: vi.fn((channel: string, listener: (event: unknown, ...args: unknown[]) => void) => {
const list = sendListeners.get(channel) ?? []
list.push(listener)
sendListeners.set(channel, list)
})
}
}))
vi.mock('../../../src/main/services/VoiceModeService', () => ({
getVoiceModeService: () => voice
}))
vi.mock('../../../src/main/windows/WindowManager', () => ({
getMainWindow: () => null
}))
/** ipcRenderer.send 의미론 — ipcMain.on 리스너에만 전달된다 */
function simulateRendererSend(channel: string, ...args: unknown[]): number {
const listeners = sendListeners.get(channel) ?? []
for (const listener of listeners) listener({}, ...args)
return listeners.length
}
beforeEach(async () => {
vi.resetModules()
vi.clearAllMocks()
invokeHandlers.clear()
sendListeners.clear()
const mod = await import('../../../src/main/ipc/voice-handlers')
mod.registerVoiceHandlers()
})
describe('VOICE.CANCEL_RECORDING — RecordingTip 팝업(send) 경로', () => {
it('popupAPI.send로 보낸 취소가 세션을 취소한다', () => {
const delivered = simulateRendererSend(IPC_CHANNELS.VOICE.CANCEL_RECORDING, {})
expect(delivered).toBe(1)
expect(voice.cancelSession).toHaveBeenCalledTimes(1)
})
it('RecordingTip 스크립트가 보내는 채널 문자열과 IPC 상수가 일치한다', () => {
expect(IPC_CHANNELS.VOICE.CANCEL_RECORDING).toBe('voice:cancelRecording')
})
})
describe('VOICE.CANCEL_RECORDING — 메인 윈도우(invoke) 경로', () => {
it('invoke 경로도 여전히 세션을 취소하고 성공을 반환한다', async () => {
const handler = invokeHandlers.get(IPC_CHANNELS.VOICE.CANCEL_RECORDING)
if (!handler) throw new Error('cancel invoke handler was not registered')
const result = await handler({}, {})
expect(voice.cancelSession).toHaveBeenCalledTimes(1)
expect(result).toMatchObject({ success: true })
})
})