mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-14 17:43:04 +00:00
126 lines
4.6 KiB
TypeScript
126 lines
4.6 KiB
TypeScript
// @vitest-environment node
|
|
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const { generateObjectMock, deepSeekMock } = vi.hoisted(() => ({
|
|
generateObjectMock: vi.fn(),
|
|
deepSeekMock: vi.fn(() => 'mock-model'),
|
|
}))
|
|
|
|
vi.mock('ai', () => ({ generateObject: generateObjectMock }))
|
|
vi.mock('@ai-sdk/deepseek', () => ({ deepSeek: deepSeekMock }))
|
|
|
|
import {
|
|
expandContentPieceSearchTheme,
|
|
expandRecordingSearchTheme,
|
|
expandSearchTheme,
|
|
expandSpeechSearchTheme,
|
|
} from '@/utilities/ai/expandSpeechSearchTheme'
|
|
|
|
beforeEach(() => {
|
|
process.env.DEEPSEEK_API_KEY = 'test-deepseek-key'
|
|
generateObjectMock.mockReset()
|
|
deepSeekMock.mockClear()
|
|
})
|
|
|
|
afterEach(() => {
|
|
delete process.env.DEEPSEEK_API_KEY
|
|
})
|
|
|
|
describe('expandSpeechSearchTheme (C192)', () => {
|
|
it('returns the sanitized terms the model produced', async () => {
|
|
generateObjectMock.mockResolvedValue({
|
|
object: { terms: ['SUS%', 'saúde pública', 'sus', 'ab'] },
|
|
})
|
|
|
|
await expect(expandSpeechSearchTheme('defesa do SUS')).resolves.toEqual({
|
|
terms: ['SUS', 'saúde pública'],
|
|
})
|
|
})
|
|
|
|
it('honra a lista vazia como resposta legítima (não é indisponibilidade)', async () => {
|
|
generateObjectMock.mockResolvedValue({ object: { terms: [] } })
|
|
|
|
await expect(expandSpeechSearchTheme('tema qualquer')).resolves.toEqual({ terms: [] })
|
|
})
|
|
|
|
it('curto-circuita sem chave sem chamar o provedor', async () => {
|
|
delete process.env.DEEPSEEK_API_KEY
|
|
|
|
await expect(expandSpeechSearchTheme('SUS')).resolves.toBeNull()
|
|
expect(generateObjectMock).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('curto-circuita um tema vazio', async () => {
|
|
await expect(expandSpeechSearchTheme(' ')).resolves.toBeNull()
|
|
expect(generateObjectMock).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('degrada para null em erro do provedor — nunca lança', async () => {
|
|
generateObjectMock.mockRejectedValue(new Error('provider down'))
|
|
|
|
await expect(expandSpeechSearchTheme('SUS')).resolves.toBeNull()
|
|
})
|
|
|
|
it('envia o tema e um abortSignal limitado ao modelo', async () => {
|
|
generateObjectMock.mockResolvedValue({ object: { terms: ['SUS'] } })
|
|
|
|
await expandSpeechSearchTheme('defesa do SUS')
|
|
|
|
const call = generateObjectMock.mock.calls[0]![0] as { prompt: string; abortSignal: unknown }
|
|
expect(call.prompt).toBe('defesa do SUS')
|
|
expect(call.abortSignal).toBeInstanceOf(AbortSignal)
|
|
})
|
|
|
|
it('desliga o thinking e mantém teto de saída acima do orçamento de raciocínio', async () => {
|
|
generateObjectMock.mockResolvedValue({ object: { terms: ['SUS'] } })
|
|
|
|
await expandSpeechSearchTheme('defesa do SUS')
|
|
|
|
const call = generateObjectMock.mock.calls[0]![0] as {
|
|
maxOutputTokens?: number
|
|
providerOptions?: unknown
|
|
}
|
|
expect(call.providerOptions).toEqual({ deepseek: { thinking: { type: 'disabled' } } })
|
|
expect(call.maxOutputTokens).toBeGreaterThanOrEqual(1000)
|
|
})
|
|
})
|
|
|
|
describe('expandSearchTheme (S28 — corpus)', () => {
|
|
it('uses the public Central prompt for contentPiece and keeps the acervo prompt', async () => {
|
|
generateObjectMock.mockResolvedValue({ object: { terms: ['escala 6x1'] } })
|
|
|
|
await expandContentPieceSearchTheme('fim da escala')
|
|
await expandSpeechSearchTheme('defesa do SUS')
|
|
|
|
const publicCall = generateObjectMock.mock.calls[0]![0] as { system: string }
|
|
const speechCall = generateObjectMock.mock.calls[1]![0] as { system: string }
|
|
expect(publicCall.system).toContain('Central de Conteúdos')
|
|
expect(publicCall.system).not.toContain('acervo de discursos')
|
|
expect(speechCall.system).toContain('acervo de discursos')
|
|
expect(speechCall.system).not.toContain('Central de Conteúdos')
|
|
})
|
|
|
|
it('shares the sanitization and the failure contract with the acervo', async () => {
|
|
generateObjectMock.mockResolvedValue({ object: { terms: ['SUS%', 'saúde pública', 'sus'] } })
|
|
|
|
await expect(expandSearchTheme('defesa do SUS', 'contentPiece')).resolves.toEqual({
|
|
terms: ['SUS', 'saúde pública'],
|
|
})
|
|
|
|
delete process.env.DEEPSEEK_API_KEY
|
|
await expect(expandSearchTheme('defesa do SUS', 'contentPiece')).resolves.toBeNull()
|
|
expect(generateObjectMock).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('uses the recordings prompt for the enviadas corpus (C219)', async () => {
|
|
generateObjectMock.mockResolvedValue({ object: { terms: ['merenda escolar'] } })
|
|
|
|
await expandRecordingSearchTheme('merenda')
|
|
|
|
const call = generateObjectMock.mock.calls[0]![0] as { system: string }
|
|
expect(call.system).toContain('gravações enviadas pela equipe')
|
|
expect(call.system).not.toContain('acervo de discursos')
|
|
expect(call.system).not.toContain('Central de Conteúdos')
|
|
})
|
|
})
|