feat: initial release v0.3.0
This commit is contained in:
166
tests/unit/generators/openai-compatible-video.test.ts
Normal file
166
tests/unit/generators/openai-compatible-video.test.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const openAIState = vi.hoisted(() => ({
|
||||
create: vi.fn(),
|
||||
toFile: vi.fn(async () => ({ name: 'reference-file' })),
|
||||
}))
|
||||
|
||||
const getProviderConfigMock = vi.hoisted(() => vi.fn(async () => ({
|
||||
id: 'openai-compatible:oa-1',
|
||||
apiKey: 'oa-key',
|
||||
baseUrl: 'https://oa.test/v1',
|
||||
})))
|
||||
|
||||
const normalizeToBase64ForGenerationMock = vi.hoisted(() => vi.fn(async () => 'data:image/png;base64,QQ=='))
|
||||
|
||||
vi.mock('openai', () => ({
|
||||
default: class OpenAI {
|
||||
videos = {
|
||||
create: openAIState.create,
|
||||
}
|
||||
},
|
||||
toFile: openAIState.toFile,
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/api-config', () => ({
|
||||
getProviderConfig: getProviderConfigMock,
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/media/outbound-image', () => ({
|
||||
normalizeToBase64ForGeneration: normalizeToBase64ForGenerationMock,
|
||||
}))
|
||||
|
||||
import { OpenAICompatibleVideoGenerator } from '@/lib/generators/video/openai-compatible'
|
||||
|
||||
describe('OpenAICompatibleVideoGenerator', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
getProviderConfigMock.mockResolvedValue({
|
||||
id: 'openai-compatible:oa-1',
|
||||
apiKey: 'oa-key',
|
||||
baseUrl: 'https://oa.test/v1',
|
||||
})
|
||||
})
|
||||
|
||||
it('submits official videos.create payload and returns OPENAI externalId', async () => {
|
||||
openAIState.create.mockResolvedValueOnce({ id: 'vid_123' })
|
||||
|
||||
const generator = new OpenAICompatibleVideoGenerator('openai-compatible:oa-1')
|
||||
const result = await generator.generate({
|
||||
userId: 'user-1',
|
||||
imageUrl: 'https://example.com/seed.png',
|
||||
prompt: 'animate this character',
|
||||
options: {
|
||||
modelId: 'sora-2',
|
||||
duration: 8,
|
||||
resolution: '720p',
|
||||
aspectRatio: '16:9',
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.async).toBe(true)
|
||||
expect(result.requestId).toBe('vid_123')
|
||||
|
||||
const expectedProviderToken = Buffer.from('openai-compatible:oa-1', 'utf8').toString('base64url')
|
||||
expect(result.externalId).toBe(`OPENAI:VIDEO:${expectedProviderToken}:vid_123`)
|
||||
|
||||
const createCall = openAIState.create.mock.calls[0]
|
||||
expect(createCall).toBeTruthy()
|
||||
if (!createCall) {
|
||||
throw new Error('videos.create should be called')
|
||||
}
|
||||
|
||||
expect(createCall[0]).toMatchObject({
|
||||
prompt: 'animate this character',
|
||||
model: 'sora-2',
|
||||
seconds: '8',
|
||||
size: '1280x720',
|
||||
})
|
||||
expect((createCall[0] as { input_reference?: unknown }).input_reference).toBeDefined()
|
||||
})
|
||||
|
||||
it('allows custom model ids for openai-compatible gateways', async () => {
|
||||
openAIState.create.mockResolvedValueOnce({ id: 'vid_custom' })
|
||||
|
||||
const generator = new OpenAICompatibleVideoGenerator('openai-compatible:oa-1')
|
||||
const result = await generator.generate({
|
||||
userId: 'user-1',
|
||||
imageUrl: 'https://example.com/seed.png',
|
||||
prompt: 'animate',
|
||||
options: {
|
||||
modelId: 'veo_3_1-fast-4K',
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
const createCall = openAIState.create.mock.calls.at(0)
|
||||
expect(createCall).toBeTruthy()
|
||||
if (!createCall) {
|
||||
throw new Error('videos.create should be called')
|
||||
}
|
||||
expect((createCall[0] as { model?: string }).model).toBe('veo_3_1-fast-4K')
|
||||
})
|
||||
|
||||
it('maps 3:2 to landscape size explicitly', async () => {
|
||||
openAIState.create.mockResolvedValueOnce({ id: 'vid_32' })
|
||||
|
||||
const generator = new OpenAICompatibleVideoGenerator('openai-compatible:oa-1')
|
||||
const result = await generator.generate({
|
||||
userId: 'user-1',
|
||||
imageUrl: 'https://example.com/seed.png',
|
||||
prompt: 'animate',
|
||||
options: {
|
||||
resolution: '1080p',
|
||||
aspectRatio: '3:2',
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
const createCall = openAIState.create.mock.calls.at(0)
|
||||
expect(createCall).toBeTruthy()
|
||||
if (!createCall) {
|
||||
throw new Error('videos.create should be called')
|
||||
}
|
||||
expect((createCall[0] as { size?: string }).size).toBe('1792x1024')
|
||||
})
|
||||
|
||||
it('maps 2:3 to portrait size explicitly', async () => {
|
||||
openAIState.create.mockResolvedValueOnce({ id: 'vid_23' })
|
||||
|
||||
const generator = new OpenAICompatibleVideoGenerator('openai-compatible:oa-1')
|
||||
const result = await generator.generate({
|
||||
userId: 'user-1',
|
||||
imageUrl: 'https://example.com/seed.png',
|
||||
prompt: 'animate',
|
||||
options: {
|
||||
resolution: '720p',
|
||||
aspectRatio: '2:3',
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
const createCall = openAIState.create.mock.calls.at(0)
|
||||
expect(createCall).toBeTruthy()
|
||||
if (!createCall) {
|
||||
throw new Error('videos.create should be called')
|
||||
}
|
||||
expect((createCall[0] as { size?: string }).size).toBe('720x1280')
|
||||
})
|
||||
|
||||
it('fails explicitly on unsupported aspect ratios', async () => {
|
||||
const generator = new OpenAICompatibleVideoGenerator('openai-compatible:oa-1')
|
||||
const result = await generator.generate({
|
||||
userId: 'user-1',
|
||||
imageUrl: 'https://example.com/seed.png',
|
||||
prompt: 'animate',
|
||||
options: {
|
||||
resolution: '720p',
|
||||
aspectRatio: '5:4',
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.success).toBe(false)
|
||||
expect(result.error).toContain('OPENAI_COMPAT_VIDEO_ASPECT_RATIO_UNSUPPORTED')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user