// tests/main/services/audio-capture-redteam-r1-3.test.ts // SoX 가 장치를 못 열고 끝났을 때 'error' 리스너가 없어 ERR_UNHANDLED_ERROR 가 나고 // 뒤이은 'stopped' 가 영영 나가지 않던 버그의 회귀 테스트. import { describe, it, expect, beforeEach, vi } from 'vitest' import { EventEmitter } from 'events' vi.mock('../../../src/main/services/LoggerService', () => ({ getLogger: () => ({ info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }), })) vi.mock('../../../src/main/services/ConfigService', () => ({ configGet: vi.fn(() => 'usb-headset-gone'), })) vi.mock('../../../src/main/utils/paths', () => ({ getSoxPath: () => 'sox', })) class FakeProcess extends EventEmitter { stdout = new EventEmitter() stderr = new EventEmitter() kill = vi.fn() } const spawned = vi.hoisted(() => ({ list: [] as unknown[] })) vi.mock('child_process', async (importOriginal) => { const actual = await importOriginal() return { ...actual, spawn: vi.fn(() => { const proc = new FakeProcess() spawned.list.push(proc) return proc }), } }) type Module = typeof import('../../../src/main/services/AudioCaptureService') let mod: Module beforeEach(async () => { vi.resetModules() spawned.list = [] mod = await import('../../../src/main/services/AudioCaptureService') mod.resetAudioCaptureServiceForTests() }) describe('AudioCaptureService — 캡처 프로세스 종료', () => { it("SoX 가 녹음 중 끝나도 예외 없이 'stopped'(device-lost) 를 알린다", async () => { const audio = mod.getAudioCaptureService() const stopped: string[] = [] audio.on('stopped', ({ reason }) => stopped.push(reason)) await audio.start() expect(audio.state).toBe('capturing') const proc = spawned.list[0] as FakeProcess expect(() => proc.emit('close', 1)).not.toThrow() expect(stopped).toEqual(['device-lost']) expect(audio.state).toBe('error') }) it('실패 뒤에도 다시 시작할 수 있다', async () => { const audio = mod.getAudioCaptureService() await audio.start() ;(spawned.list[0] as FakeProcess).emit('close', 1) await audio.start() expect(audio.state).toBe('capturing') expect(spawned.list).toHaveLength(2) }) })