import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { callRoute } from '../integration/api/helpers/call-route' import { installAuthMocks, mockAuthenticated, resetAuthMockState } from '../helpers/auth' import { resetSystemState } from '../helpers/db-reset' import { prisma } from '../helpers/prisma' import { seedMinimalDomainState } from './helpers/seed' import { expectLifecycleEvents, listTaskEventTypes, waitForTaskTerminalState } from './helpers/tasks' import { startSystemWorkers, stopSystemWorkers, type SystemWorkers } from './helpers/workers' const voiceState = vi.hoisted(() => ({ audioUrl: 'voice/system-line.wav', audioDuration: 1200, })) vi.mock('@/lib/api-config', async () => { const actual = await vi.importActual('@/lib/api-config') return { ...actual, resolveModelSelectionOrSingle: vi.fn(async () => ({ provider: 'fal', modelId: 'fal-audio-model', modelKey: 'fal::audio-model', mediaType: 'audio', })), getProviderKey: vi.fn((providerId: string) => providerId), } }) vi.mock('@/lib/voice/generate-voice-line', async () => { const actual = await vi.importActual('@/lib/voice/generate-voice-line') return { ...actual, generateVoiceLine: vi.fn(async (params: { lineId: string }) => { await prisma.novelPromotionVoiceLine.update({ where: { id: params.lineId }, data: { audioUrl: voiceState.audioUrl, audioDuration: voiceState.audioDuration, }, }) return { lineId: params.lineId, audioUrl: voiceState.audioUrl, storageKey: voiceState.audioUrl, audioDuration: voiceState.audioDuration, } }), } }) describe('system - voice generate', () => { let workers: SystemWorkers = {} beforeEach(async () => { vi.resetModules() vi.clearAllMocks() voiceState.audioUrl = 'voice/system-line.wav' voiceState.audioDuration = 1200 await resetSystemState() installAuthMocks() }) afterEach(async () => { await stopSystemWorkers(workers) workers = {} resetAuthMockState() }) it('route -> voice worker -> line audio persisted', async () => { const seeded = await seedMinimalDomainState() mockAuthenticated(seeded.user.id) workers = await startSystemWorkers(['voice']) const mod = await import('@/app/api/novel-promotion/[projectId]/voice-generate/route') const response = await callRoute( mod.POST, 'POST', { locale: 'zh', episodeId: seeded.episode.id, lineId: seeded.voiceLine.id, audioModel: 'fal::audio-model', }, { params: { projectId: seeded.project.id } }, ) expect(response.status).toBe(200) const json = await response.json() as { success: boolean; async: boolean; taskId: string } expect(json.success).toBe(true) const task = await waitForTaskTerminalState(json.taskId) expect(task.status).toBe('completed') expect(task.type).toBe('voice_line') const voiceLine = await prisma.novelPromotionVoiceLine.findUnique({ where: { id: seeded.voiceLine.id }, select: { audioUrl: true, audioDuration: true }, }) expect(voiceLine).toEqual({ audioUrl: voiceState.audioUrl, audioDuration: voiceState.audioDuration, }) const eventTypes = await listTaskEventTypes(json.taskId) expectLifecycleEvents(eventTypes, 'completed') }) })