Files
orca/src/main/ipc/speech.test.ts
T

160 lines
4.9 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
const {
handleMock,
fromWebContentsMock,
getSpeechModelManagerMock,
getSpeechSttServiceMock,
deleteLocalSpeechModelMock
} = vi.hoisted(() => ({
handleMock: vi.fn(),
fromWebContentsMock: vi.fn(),
getSpeechModelManagerMock: vi.fn(),
getSpeechSttServiceMock: vi.fn(),
deleteLocalSpeechModelMock: vi.fn()
}))
vi.mock('electron', () => ({
app: { getPath: vi.fn(() => '/tmp/orca-speech-test') },
BrowserWindow: { fromWebContents: fromWebContentsMock },
ipcMain: { handle: handleMock },
safeStorage: {
decryptString: vi.fn(),
encryptString: vi.fn(() => Buffer.from('encrypted')),
isEncryptionAvailable: vi.fn(() => true)
},
systemPreferences: {
getMediaAccessStatus: vi.fn(() => 'granted'),
askForMediaAccess: vi.fn(() => Promise.resolve(true))
}
}))
vi.mock('../speech/model-catalog', () => ({
SPEECH_MODEL_CATALOG: [],
getCatalogModel: vi.fn(() => ({ id: 'model-1' }))
}))
vi.mock('../speech/speech-runtime-service', () => ({
getSpeechModelManager: getSpeechModelManagerMock,
getSpeechSttService: getSpeechSttServiceMock
}))
vi.mock('../speech/speech-model-deletion', () => ({
deleteLocalSpeechModel: deleteLocalSpeechModelMock
}))
import { registerSpeechHandlers } from './speech'
type SpeechDownloadHandler = (event: { sender: { id: number } }, modelId: string) => Promise<void>
function getHandler(channel: string): SpeechDownloadHandler {
const call = handleMock.mock.calls.find((entry) => entry[0] === channel)
if (!call) {
throw new Error(`${channel} handler not registered`)
}
return call[1] as SpeechDownloadHandler
}
describe('registerSpeechHandlers', () => {
beforeEach(() => {
handleMock.mockReset()
fromWebContentsMock.mockReset()
getSpeechModelManagerMock.mockReset()
getSpeechSttServiceMock.mockReset()
deleteLocalSpeechModelMock.mockReset()
})
it('clears the model download progress callback after completion', async () => {
const clearProgressCallback = vi.fn()
const progressCallbacks: ((modelId: string, progress: number) => void)[] = []
let resolveDownload: () => void = () => {}
const manager = {
setProgressCallback: vi.fn((callback: (modelId: string, progress: number) => void) => {
progressCallbacks.push(callback)
return clearProgressCallback
}),
downloadModel: vi.fn(
() =>
new Promise<void>((resolve) => {
resolveDownload = resolve
})
)
}
const send = vi.fn()
const window = {
isDestroyed: vi.fn(() => false),
webContents: { send },
once: vi.fn(),
off: vi.fn()
}
getSpeechModelManagerMock.mockReturnValue(manager)
fromWebContentsMock.mockReturnValue(window)
registerSpeechHandlers({} as never)
const pending = getHandler('speech:downloadModel')({ sender: { id: 7 } }, 'model-1')
progressCallbacks[0]?.('model-1', 0.5)
resolveDownload()
await pending
expect(send).toHaveBeenCalledWith('speech:downloadProgress', {
modelId: 'model-1',
progress: 0.5
})
expect(clearProgressCallback).toHaveBeenCalledTimes(1)
expect(window.off).toHaveBeenCalledWith('closed', expect.any(Function))
})
it('clears the model download progress callback when the window closes', async () => {
const clearProgressCallback = vi.fn()
let resolveDownload: () => void = () => {}
const closeHandlers: (() => void)[] = []
const manager = {
setProgressCallback: vi.fn(() => clearProgressCallback),
downloadModel: vi.fn(
() =>
new Promise<void>((resolve) => {
resolveDownload = resolve
})
)
}
const window = {
isDestroyed: vi.fn(() => false),
webContents: { send: vi.fn() },
once: vi.fn((_event: string, handler: () => void) => {
closeHandlers.push(handler)
}),
off: vi.fn()
}
getSpeechModelManagerMock.mockReturnValue(manager)
fromWebContentsMock.mockReturnValue(window)
registerSpeechHandlers({} as never)
const pending = getHandler('speech:downloadModel')({ sender: { id: 7 } }, 'model-1')
closeHandlers[0]?.()
resolveDownload()
await pending
expect(clearProgressCallback).toHaveBeenCalledTimes(1)
expect(window.off).toHaveBeenCalledWith('closed', expect.any(Function))
})
it('routes desktop model deletion through the shared deletion helper', async () => {
const store = {} as never
const manager = { deleteModel: vi.fn() }
const sttService = { prepareModelForDeletion: vi.fn() }
getSpeechModelManagerMock.mockReturnValue(manager)
getSpeechSttServiceMock.mockReturnValue(sttService)
deleteLocalSpeechModelMock.mockResolvedValue(undefined)
registerSpeechHandlers(store)
await getHandler('speech:deleteModel')({ sender: { id: 7 } }, 'model-1')
expect(deleteLocalSpeechModelMock).toHaveBeenCalledWith({
store,
modelManager: manager,
sttService,
modelId: 'model-1'
})
})
})