import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { generateVideoViaOpenAICompatTemplate } from '@/lib/model-gateway/openai-compat/template-video' import { pollAsyncTask } from '@/lib/async-poll' import { startScenarioServer } from '../../helpers/fakes/scenario-server' const getProviderConfigMock = vi.hoisted(() => vi.fn()) const getUserModelsMock = vi.hoisted(() => vi.fn()) vi.mock('@/lib/api-config', () => ({ getProviderConfig: getProviderConfigMock, getUserModels: getUserModelsMock, })) function encode(value: string): string { return Buffer.from(value, 'utf8').toString('base64url') } describe('provider contract - openai compatible media template', () => { let server: Awaited> | null = null beforeEach(async () => { server = await startScenarioServer() vi.clearAllMocks() getProviderConfigMock.mockResolvedValue({ id: 'openai-compatible:provider-local', apiKey: 'sk-local', baseUrl: `${server.baseUrl}/compat/v1`, }) }) afterEach(async () => { await server?.close() server = null }) it('renders create request against provider baseUrl and returns OCOMPAT externalId', async () => { server!.defineScenario({ method: 'POST', path: '/compat/v1/video/create', mode: 'success', submitResponse: { status: 200, body: { status: 'queued', task_id: 'task_local_1' }, }, }) const result = await generateVideoViaOpenAICompatTemplate({ userId: 'user-local', providerId: 'openai-compatible:provider-local', modelId: 'veo-local', modelKey: 'openai-compatible:provider-local::veo-local', imageUrl: 'data:image/png;base64,AAAA', prompt: 'animate this frame', options: { duration: 5, aspectRatio: '9:16', }, profile: 'openai-compatible', template: { version: 1, mediaType: 'video', mode: 'async', create: { method: 'POST', path: '/video/create', bodyTemplate: { model: '{{model}}', prompt: '{{prompt}}', image: '{{image}}', duration: '{{duration}}', }, }, status: { method: 'GET', path: '/video/status/{{task_id}}' }, response: { taskIdPath: '$.task_id', statusPath: '$.status', }, polling: { intervalMs: 1000, timeoutMs: 30_000, doneStates: ['done'], failStates: ['failed'], }, }, }) expect(result).toMatchObject({ success: true, async: true, requestId: 'task_local_1', externalId: `OCOMPAT:VIDEO:b64_${encode('openai-compatible:provider-local')}:${encode('veo-local')}:task_local_1`, }) const requests = server!.getRequests('POST', '/compat/v1/video/create') expect(requests).toHaveLength(1) expect(requests[0]?.headers.authorization).toBe('Bearer sk-local') expect(JSON.parse(requests[0]?.bodyText || '{}')).toEqual({ model: 'veo-local', prompt: 'animate this frame', image: 'data:image/png;base64,AAAA', duration: 5, }) }) it('polls localhost provider status and falls back to content endpoint when output url is missing', async () => { getUserModelsMock.mockResolvedValue([ { modelKey: 'openai-compatible:provider-local::veo-local', modelId: 'veo-local', name: 'Local Veo', type: 'video', provider: 'openai-compatible:provider-local', price: 0, compatMediaTemplate: { version: 1, mediaType: 'video', mode: 'async', create: { method: 'POST', path: '/video/create' }, status: { method: 'GET', path: '/video/status/{{task_id}}' }, content: { method: 'GET', path: '/video/content/{{task_id}}' }, response: { statusPath: '$.status', }, polling: { intervalMs: 1000, timeoutMs: 30_000, doneStates: ['done'], failStates: ['failed'], }, }, }, ]) server!.defineScenario({ method: 'GET', path: '/compat/v1/video/status/task_local_2', mode: 'queued_then_success', pollSequence: [ { status: 200, body: { status: 'running' } }, { status: 200, body: { status: 'done' } }, ], }) const first = await pollAsyncTask( `OCOMPAT:VIDEO:${encode('openai-compatible:provider-local')}:${encode('openai-compatible:provider-local::veo-local')}:task_local_2`, 'user-local', ) const second = await pollAsyncTask( `OCOMPAT:VIDEO:${encode('openai-compatible:provider-local')}:${encode('openai-compatible:provider-local::veo-local')}:task_local_2`, 'user-local', ) expect(first).toEqual({ status: 'pending' }) expect(second).toEqual({ status: 'completed', resultUrl: `${server!.baseUrl}/compat/v1/video/content/task_local_2`, videoUrl: `${server!.baseUrl}/compat/v1/video/content/task_local_2`, downloadHeaders: { Authorization: 'Bearer sk-local', }, }) }) it('fails explicitly when async create response omits task id', async () => { server!.defineScenario({ method: 'POST', path: '/compat/v1/video/create', mode: 'malformed_response', submitResponse: { status: 200, body: { status: 'queued' }, }, }) await expect( generateVideoViaOpenAICompatTemplate({ userId: 'user-local', providerId: 'openai-compatible:provider-local', modelId: 'veo-local', modelKey: 'openai-compatible:provider-local::veo-local', imageUrl: 'data:image/png;base64,AAAA', prompt: 'bad create payload', profile: 'openai-compatible', template: { version: 1, mediaType: 'video', mode: 'async', create: { method: 'POST', path: '/video/create', bodyTemplate: { prompt: '{{prompt}}' }, }, status: { method: 'GET', path: '/video/status/{{task_id}}' }, response: { taskIdPath: '$.task_id', statusPath: '$.status', }, polling: { intervalMs: 1000, timeoutMs: 30_000, doneStates: ['done'], failStates: ['failed'], }, }, }), ).rejects.toThrow('OPENAI_COMPAT_VIDEO_TEMPLATE_TASK_ID_NOT_FOUND') }) })