@@ -349,6 +349,162 @@ describe('RunpodImageModel', () => {
349349 } ) ;
350350 } ) ;
351351
352+ describe ( 'Z-Image Turbo size validation' , ( ) => {
353+ let zImageModel : RunpodImageModel ;
354+
355+ beforeEach ( ( ) => {
356+ zImageModel = new RunpodImageModel ( 'tongyi-mai/z-image-turbo' , {
357+ provider : 'runpod' ,
358+ baseURL : 'https://api.runpod.ai/v2/z-image-turbo' ,
359+ headers : ( ) => ( { Authorization : 'Bearer test-key' } ) ,
360+ fetch : mockFetch ,
361+ } ) ;
362+ } ) ;
363+
364+ it ( 'should accept supported sizes' , async ( ) => {
365+ const supportedSizes = [
366+ '1328x1328' ,
367+ '1472x1140' ,
368+ '1140x1472' ,
369+ '512x512' ,
370+ '768x768' ,
371+ '1024x1024' ,
372+ '1280x1280' ,
373+ '1536x1536' ,
374+ '512x768' ,
375+ '768x512' ,
376+ '1024x768' ,
377+ '768x1024' ,
378+ '768x432' ,
379+ '1024x576' ,
380+ '1280x720' ,
381+ '1536x864' ,
382+ '432x768' ,
383+ '576x1024' ,
384+ '720x1280' ,
385+ '864x1536' ,
386+ ] ;
387+
388+ mockFetch . mockImplementation ( async ( input : any , _init ?: any ) => {
389+ const url = typeof input === 'string' ? input : input ?. url ;
390+
391+ if ( url ?. includes ( '/runsync' ) ) {
392+ return new Response (
393+ JSON . stringify ( {
394+ id : 'test' ,
395+ status : 'COMPLETED' ,
396+ output : { result : 'https://cdn.test/z-image.png' } ,
397+ } ) ,
398+ { headers : { 'content-type' : 'application/json' } }
399+ ) ;
400+ }
401+
402+ if ( url === 'https://cdn.test/z-image.png' ) {
403+ return new Response ( new Uint8Array ( [ 1 , 2 , 3 ] ) , {
404+ headers : { 'content-type' : 'image/png' } ,
405+ } ) ;
406+ }
407+
408+ throw new Error ( `Unexpected fetch url: ${ String ( url ) } ` ) ;
409+ } ) ;
410+
411+ for ( const size of supportedSizes ) {
412+ await expect (
413+ zImageModel . doGenerate ( {
414+ prompt : 'Test' ,
415+ n : 1 ,
416+ size,
417+ aspectRatio : undefined ,
418+ seed : undefined ,
419+ providerOptions : { } ,
420+ headers : { } ,
421+ abortSignal : undefined ,
422+ } )
423+ ) . resolves . toBeDefined ( ) ;
424+ }
425+ } ) ;
426+
427+ it ( 'should reject unsupported sizes' , async ( ) => {
428+ await expect (
429+ zImageModel . doGenerate ( {
430+ prompt : 'Test' ,
431+ n : 1 ,
432+ size : '2048x2048' ,
433+ aspectRatio : undefined ,
434+ seed : undefined ,
435+ providerOptions : { } ,
436+ headers : { } ,
437+ abortSignal : undefined ,
438+ } )
439+ ) . rejects . toThrow ( InvalidArgumentError ) ;
440+ } ) ;
441+
442+ it ( 'should map supported aspect ratios to sizes' , async ( ) => {
443+ const aspectRatioToSize = {
444+ '1:1' : '1328*1328' ,
445+ '4:3' : '1472*1140' ,
446+ '3:4' : '1140*1472' ,
447+ '3:2' : '768*512' ,
448+ '2:3' : '512*768' ,
449+ '16:9' : '1280*720' ,
450+ '9:16' : '720*1280' ,
451+ } ;
452+
453+ for ( const [ aspectRatio , expectedSize ] of Object . entries (
454+ aspectRatioToSize
455+ ) ) {
456+ let capturedBody : any ;
457+
458+ mockFetch . mockImplementationOnce ( async ( _input : any , init ?: any ) => {
459+ capturedBody = JSON . parse ( init ?. body ?? '{}' ) ;
460+ return new Response (
461+ JSON . stringify ( {
462+ id : 'test' ,
463+ status : 'COMPLETED' ,
464+ output : { result : 'https://cdn.test/z-image.png' } ,
465+ } ) ,
466+ { headers : { 'content-type' : 'application/json' } }
467+ ) ;
468+ } ) ;
469+ mockFetch . mockImplementationOnce ( ( ) =>
470+ Promise . resolve (
471+ new Response ( new Uint8Array ( [ 1 , 2 , 3 ] ) , {
472+ headers : { 'content-type' : 'image/png' } ,
473+ } )
474+ )
475+ ) ;
476+
477+ await zImageModel . doGenerate ( {
478+ prompt : 'Test' ,
479+ n : 1 ,
480+ size : undefined ,
481+ aspectRatio,
482+ seed : undefined ,
483+ providerOptions : { } ,
484+ headers : { } ,
485+ abortSignal : undefined ,
486+ } ) ;
487+
488+ expect ( capturedBody ?. input ?. size ) . toBe ( expectedSize ) ;
489+ }
490+ } ) ;
491+
492+ it ( 'should reject unsupported aspect ratios' , async ( ) => {
493+ await expect (
494+ zImageModel . doGenerate ( {
495+ prompt : 'Test' ,
496+ n : 1 ,
497+ size : undefined ,
498+ aspectRatio : '21:9' ,
499+ seed : undefined ,
500+ providerOptions : { } ,
501+ headers : { } ,
502+ abortSignal : undefined ,
503+ } )
504+ ) . rejects . toThrow ( InvalidArgumentError ) ;
505+ } ) ;
506+ } ) ;
507+
352508 describe ( 'parameter conversion' , ( ) => {
353509 it ( 'should build correct payload for Qwen models' , ( ) => {
354510 const qwenModel = new RunpodImageModel ( 'qwen/qwen-image' , {
@@ -375,6 +531,32 @@ describe('RunpodImageModel', () => {
375531 } ) ;
376532 } ) ;
377533
534+ it ( 'should build correct payload for Z-Image Turbo' , ( ) => {
535+ const zImageModel = new RunpodImageModel ( 'tongyi-mai/z-image-turbo' , {
536+ provider : 'runpod' ,
537+ baseURL : 'https://api.runpod.ai/v2/z-image-turbo' ,
538+ headers : ( ) => ( { Authorization : 'Bearer test-key' } ) ,
539+ fetch : mockFetch ,
540+ } ) ;
541+
542+ const payload = ( zImageModel as any ) . buildInputPayload (
543+ 'Test prompt' ,
544+ '1024*1024' ,
545+ 42 ,
546+ { strength : 0.8 , output_format : 'png' , enable_safety_checker : true }
547+ ) ;
548+
549+ expect ( payload ) . toMatchObject ( {
550+ prompt : 'Test prompt' ,
551+ size : '1024*1024' ,
552+ seed : 42 ,
553+ strength : 0.8 ,
554+ output_format : 'png' ,
555+ enable_safety_checker : true ,
556+ } ) ;
557+ expect ( payload ) . not . toHaveProperty ( 'negative_prompt' ) ;
558+ } ) ;
559+
378560 it ( 'should build correct payload for Flux standard models' , ( ) => {
379561 const fluxModel = new RunpodImageModel (
380562 'black-forest-labs/flux-1-schnell' ,
0 commit comments