import { beforeEach, describe, expect, it, vi } from 'vitest' const getProviderConfigMock = vi.hoisted(() => vi.fn(async () => ({ id: 'bailian', apiKey: 'bl-key', })), ) vi.mock('@/lib/api-config', () => ({ getProviderConfig: getProviderConfigMock, })) import { generateBailianVideo } from '@/lib/providers/bailian/video' describe('bailian video provider', () => { beforeEach(() => { vi.clearAllMocks() }) it('submits i2v task and returns async externalId', async () => { const fetchMock = vi.fn(async () => ({ ok: true, status: 200, text: async () => JSON.stringify({ request_id: 'req-1', output: { task_id: 'task-123', task_status: 'PENDING', }, }), })) vi.stubGlobal('fetch', fetchMock as unknown as typeof fetch) const result = await generateBailianVideo({ userId: 'user-1', imageUrl: 'https://example.com/frame.png', prompt: '让人物向前走', options: { provider: 'bailian', modelId: 'wan2.6-i2v-flash', modelKey: 'bailian::wan2.6-i2v-flash', duration: 5, resolution: '720P', promptExtend: true, }, }) expect(getProviderConfigMock).toHaveBeenCalledWith('user-1', 'bailian') expect(fetchMock).toHaveBeenCalledTimes(1) const firstCall = fetchMock.mock.calls[0] as unknown as [RequestInfo | URL, RequestInit] | undefined expect(firstCall).toBeDefined() if (!firstCall) { throw new Error('missing fetch call') } const requestUrl = firstCall[0] const requestInit = firstCall[1] expect(requestUrl).toBe('https://dashscope.aliyuncs.com/api/v1/services/aigc/video-generation/video-synthesis') expect(requestInit.method).toBe('POST') expect(requestInit.headers).toEqual({ Authorization: 'Bearer bl-key', 'Content-Type': 'application/json', 'X-DashScope-Async': 'enable', }) expect(requestInit.body).toBe(JSON.stringify({ model: 'wan2.6-i2v-flash', input: { img_url: 'https://example.com/frame.png', prompt: '让人物向前走', }, parameters: { resolution: '720P', prompt_extend: true, duration: 5, }, })) expect(result).toEqual({ success: true, async: true, requestId: 'task-123', externalId: 'BAILIAN:VIDEO:task-123', }) }) it('submits wan2.7 i2v task without last frame', async () => { const fetchMock = vi.fn(async () => ({ ok: true, status: 200, text: async () => JSON.stringify({ output: { task_id: 'task-wan27-i2v', task_status: 'PENDING', }, }), })) vi.stubGlobal('fetch', fetchMock as unknown as typeof fetch) const result = await generateBailianVideo({ userId: 'user-1', imageUrl: 'https://example.com/frame.png', prompt: '让人物转身看向镜头', options: { provider: 'bailian', modelId: 'wan2.7-i2v', modelKey: 'bailian::wan2.7-i2v', }, }) const firstCall = fetchMock.mock.calls[0] as unknown as [RequestInfo | URL, RequestInit] | undefined expect(firstCall).toBeDefined() if (!firstCall) { throw new Error('missing fetch call') } expect(firstCall[0]).toBe('https://dashscope.aliyuncs.com/api/v1/services/aigc/video-generation/video-synthesis') expect(firstCall[1].body).toBe(JSON.stringify({ model: 'wan2.7-i2v', input: { img_url: 'https://example.com/frame.png', prompt: '让人物转身看向镜头', }, })) expect(result).toEqual({ success: true, async: true, requestId: 'task-wan27-i2v', externalId: 'BAILIAN:VIDEO:task-wan27-i2v', }) }) it('submits kf2v task with first and last frame', async () => { const fetchMock = vi.fn(async () => ({ ok: true, status: 200, text: async () => JSON.stringify({ output: { task_id: 'task-kf2v-1', task_status: 'PENDING', }, }), })) vi.stubGlobal('fetch', fetchMock as unknown as typeof fetch) const result = await generateBailianVideo({ userId: 'user-1', imageUrl: 'https://example.com/first.png', prompt: '让人物从左走到右', options: { provider: 'bailian', modelId: 'wan2.2-kf2v-flash', modelKey: 'bailian::wan2.2-kf2v-flash', lastFrameImageUrl: 'https://example.com/last.png', duration: 5, }, }) expect(fetchMock).toHaveBeenCalledTimes(1) const firstCall = fetchMock.mock.calls[0] as unknown as [RequestInfo | URL, RequestInit] | undefined expect(firstCall).toBeDefined() if (!firstCall) { throw new Error('missing fetch call') } const requestUrl = firstCall[0] const requestInit = firstCall[1] expect(requestUrl).toBe('https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis') expect(requestInit.body).toBe(JSON.stringify({ model: 'wan2.2-kf2v-flash', input: { first_frame_url: 'https://example.com/first.png', last_frame_url: 'https://example.com/last.png', prompt: '让人物从左走到右', }, parameters: { duration: 5, }, })) expect(result).toEqual({ success: true, async: true, requestId: 'task-kf2v-1', externalId: 'BAILIAN:VIDEO:task-kf2v-1', }) }) it('submits wan2.7 i2v task with first and last frame', async () => { const fetchMock = vi.fn(async () => ({ ok: true, status: 200, text: async () => JSON.stringify({ output: { task_id: 'task-wan27-flf', task_status: 'PENDING', }, }), })) vi.stubGlobal('fetch', fetchMock as unknown as typeof fetch) const result = await generateBailianVideo({ userId: 'user-1', imageUrl: 'https://example.com/first.png', prompt: '从清晨过渡到夜晚', options: { provider: 'bailian', modelId: 'wan2.7-i2v', modelKey: 'bailian::wan2.7-i2v', lastFrameImageUrl: 'https://example.com/last.png', }, }) const firstCall = fetchMock.mock.calls[0] as unknown as [RequestInfo | URL, RequestInit] | undefined expect(firstCall).toBeDefined() if (!firstCall) { throw new Error('missing fetch call') } expect(firstCall[0]).toBe('https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis') expect(firstCall[1].body).toBe(JSON.stringify({ model: 'wan2.7-i2v', input: { first_frame_url: 'https://example.com/first.png', last_frame_url: 'https://example.com/last.png', prompt: '从清晨过渡到夜晚', }, })) expect(result).toEqual({ success: true, async: true, requestId: 'task-wan27-flf', externalId: 'BAILIAN:VIDEO:task-wan27-flf', }) }) it('fails fast when kf2v model misses last frame', async () => { const fetchMock = vi.fn() vi.stubGlobal('fetch', fetchMock as unknown as typeof fetch) await expect( generateBailianVideo({ userId: 'user-1', imageUrl: 'https://example.com/first.png', prompt: 'test', options: { provider: 'bailian', modelId: 'wanx2.1-kf2v-plus', modelKey: 'bailian::wanx2.1-kf2v-plus', }, }), ).rejects.toThrow(/BAILIAN_VIDEO_LAST_FRAME_IMAGE_URL_REQUIRED/) expect(fetchMock).not.toHaveBeenCalled() }) it('fails fast when options contain unsupported field', async () => { const fetchMock = vi.fn() vi.stubGlobal('fetch', fetchMock as unknown as typeof fetch) await expect( generateBailianVideo({ userId: 'user-1', imageUrl: 'https://example.com/frame.png', prompt: 'test', options: { provider: 'bailian', modelId: 'wan2.6-i2v', modelKey: 'bailian::wan2.6-i2v', fps: 24, }, }), ).rejects.toThrow(/BAILIAN_VIDEO_OPTION_UNSUPPORTED: fps/) expect(fetchMock).not.toHaveBeenCalled() }) })