Files
goodbuddy/src/main/local-inference-service.test.ts
T

62 lines
3.3 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import { LocalInferenceService } from './local-inference-service'
import type { InferenceService } from '../shared/local-inference-contracts'
const service: InferenceService = { id: 'embedding', name: 'Embedding', engine: 'ONNX', ownership: 'managed-process', state: 'running', detail: '', actions: ['stop', 'restart'] }
describe('local inference management', () => {
it('does not infer service readiness from an active ASR request', async () => {
const registry = new LocalInferenceService()
registry.register('asr', { snapshot: () => ({ ...service, id: 'asr', state: 'unknown', actions: [] }) })
let finish!: () => void
const work = registry.run('asr', 'speech', () => new Promise<void>((resolve) => { finish = resolve }))
expect((await registry.snapshot()).services.find((entry) => entry.id === 'asr')?.state).toBe('unknown')
finish()
await work
})
it('requires confirmation of every current task and blocks new work during stop', async () => {
const registry = new LocalInferenceService()
let finish!: () => void
const work = registry.run('embedding', 'index', () => new Promise<void>((resolve) => { finish = resolve }))
const act = vi.fn(async () => {
await expect(registry.run('embedding', 'new query', async () => [])).rejects.toThrow('切换')
finish()
})
registry.register('embedding', { snapshot: () => service, act })
await expect(registry.act({ serviceId: 'embedding', action: 'stop', confirmedTaskIds: [] })).rejects.toThrow('重新确认')
expect(act).not.toHaveBeenCalled()
const task = (await registry.snapshot()).tasks[0]!
await registry.act({ serviceId: 'embedding', action: 'stop', confirmedTaskIds: [task.id] })
await work
expect(act).toHaveBeenCalledOnce()
})
it('waits for cancellation acknowledgement and preserves natural completion', async () => {
const registry = new LocalInferenceService()
let finish!: () => void
const cancel = vi.fn()
const work = registry.run('asr', 'speech', () => new Promise<void>((resolve) => { finish = resolve }), { cancel })
const id = (await registry.snapshot()).tasks[0]!.id
registry.cancel(id)
registry.cancel(id)
expect(cancel).toHaveBeenCalledOnce()
expect((await registry.snapshot()).tasks[0]!.state).toBe('cancelling')
finish()
await work
expect((await registry.snapshot()).tasks[0]!.state).toBe('completed')
})
it('records real failures, bounds history, and isolates unavailable engines', async () => {
const registry = new LocalInferenceService()
registry.register('asr', { snapshot: () => { throw new Error('missing model') } })
for (let index = 0; index < 103; index++) await registry.run('embedding', 'query', async () => [])
await expect(registry.run('ocr', 'document', async () => { throw new Error('OCR failed') })).rejects.toThrow('OCR failed')
const snapshot = await registry.snapshot()
expect(snapshot.tasks).toHaveLength(100)
expect(snapshot.tasks[0]).toMatchObject({ state: 'failed', error: 'OCR failed' })
expect(snapshot.services.find((entry) => entry.id === 'asr')).toMatchObject({ state: 'error', error: 'Error: missing model' })
expect(snapshot.services.find((entry) => entry.id === 'tts')).toMatchObject({ state: 'unavailable', actions: [] })
})
})