@@ -38,30 +38,52 @@ describe('Video provider client', () => {
3838 } )
3939
4040 it . each ( [
41- { model : 'gen4.5' , limit : 1000 } ,
42- { model : 'klingO3_4k' , limit : 2500 } ,
43- { model : 'kling_4' , limit : 26000 } ,
41+ { provider : 'runway' as const , model : 'gen4.5' , limit : 1000 } ,
42+ { provider : 'runway' as const , model : 'klingO3_4k' , limit : 2500 } ,
43+ { provider : 'runway' as const , model : 'kling_4' , limit : 26000 } ,
44+ { provider : 'luma' as const , model : 'ray-3.2' , limit : 6000 } ,
45+ { provider : 'minimax' as const , model : 'MiniMax-H3' , limit : 7000 } ,
4446 ] ) (
4547 'accepts the published prompt limit for $model and rejects overflow before submission' ,
46- async ( { model, limit } ) => {
48+ async ( { provider , model, limit } ) => {
4749 const mockFetch = vi
4850 . fn < typeof fetch > ( )
4951 . mockResolvedValueOnce ( jsonResponse ( { id : 'job' } ) )
5052 . mockResolvedValueOnce (
51- jsonResponse ( { status : 'SUCCEEDED' , output : [ 'data:video/mp4;base64,dmlkZW8=' ] } )
53+ jsonResponse (
54+ provider === 'luma'
55+ ? {
56+ state : 'completed' ,
57+ output : [ { type : 'video' , url : 'data:video/mp4;base64,dmlkZW8=' } ] ,
58+ }
59+ : provider === 'minimax'
60+ ? {
61+ task : {
62+ status : 'succeeded' ,
63+ content : { url : 'data:video/mp4;base64,dmlkZW8=' } ,
64+ } ,
65+ }
66+ : { status : 'SUCCEEDED' , output : [ 'data:video/mp4;base64,dmlkZW8=' ] }
67+ )
5268 )
5369 vi . stubGlobal ( 'fetch' , mockFetch )
5470 storageServiceMockFns . mockUploadFile . mockResolvedValue ( {
5571 path : '/api/files/video.mp4' ,
5672 size : 5 ,
5773 } )
58- const input = { provider : 'runway' as const , model, apiKey : 'key' , prompt : 'a' . repeat ( limit ) }
74+ const input = { provider, model, apiKey : 'key' , prompt : 'a' . repeat ( limit ) }
5975 const context = { requestId : 'prompt-boundary' , userId : 'user' , headers : new Headers ( ) }
6076 const completion = expect ( executeVideoOperation ( input , context ) ) . resolves . toMatchObject ( {
6177 model,
6278 } )
6379 await Promise . all ( [ completion , vi . advanceTimersByTimeAsync ( 5000 ) ] )
64- expect ( JSON . parse ( String ( mockFetch . mock . calls [ 0 ] ?. [ 1 ] ?. body ) ) . promptText ) . toBe ( input . prompt )
80+ expect ( JSON . parse ( String ( mockFetch . mock . calls [ 0 ] ?. [ 1 ] ?. body ) ) ) . toMatchObject (
81+ provider === 'runway'
82+ ? { promptText : input . prompt }
83+ : provider === 'luma'
84+ ? { prompt : input . prompt }
85+ : { content : [ { type : 'text' , text : input . prompt } ] }
86+ )
6587
6688 mockFetch . mockClear ( )
6789 await expect (
@@ -271,6 +293,32 @@ describe('Video provider client', () => {
271293 } )
272294 } )
273295
296+ it . each ( [ null , '' , false , 0 ] ) (
297+ 'ignores an empty camera control (%s) when submitting a Luma job' ,
298+ async ( cameraControl ) => {
299+ const fetchMock = vi
300+ . fn < typeof fetch > ( )
301+ . mockResolvedValueOnce ( jsonResponse ( { id : 'job' } ) )
302+ . mockResolvedValueOnce (
303+ jsonResponse ( { state : 'completed' , assets : { video : 'data:video/mp4;base64,dmlkZW8=' } } )
304+ )
305+ vi . stubGlobal ( 'fetch' , fetchMock )
306+ const result = generateVideo (
307+ {
308+ provider : 'luma' ,
309+ model : 'ray-2' ,
310+ apiKey : 'key' ,
311+ prompt : 'A cinematic sunrise' ,
312+ cameraControl,
313+ } ,
314+ { requestId : 'empty-camera' }
315+ )
316+ const completion = expect ( result ) . resolves . toMatchObject ( { buffer : Buffer . from ( 'video' ) } )
317+ await Promise . all ( [ completion , vi . advanceTimersByTimeAsync ( 5000 ) ] )
318+ expect ( JSON . parse ( String ( fetchMock . mock . calls [ 0 ] ?. [ 1 ] ?. body ) ) ) . not . toHaveProperty ( 'concepts' )
319+ }
320+ )
321+
274322 it ( 'rejects malformed camera controls before submitting a paid Luma job' , async ( ) => {
275323 const fetchMock = vi . fn ( ( ) => {
276324 throw new Error ( 'Unexpected paid request' )
0 commit comments