272 lines
8.0 KiB
TypeScript
272 lines
8.0 KiB
TypeScript
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()
|
|
})
|
|
})
|