@@ -302,11 +302,15 @@ describe('testProvider success and error paths', () => {
302302 } ) ;
303303
304304 const response = await app . fetch ( request , env ) ;
305- const data = await response . json ( ) as { success : boolean ; message : string } ;
305+ const data = await response . json ( ) as any ;
306306
307307 expect ( response . status ) . toBe ( 200 ) ;
308308 expect ( data . success ) . toBe ( true ) ;
309- expect ( data . message ) . toContain ( '200' ) ;
309+ expect ( data . results ) . toHaveLength ( 4 ) ;
310+ data . results . forEach ( ( r : any ) => {
311+ expect ( r . success ) . toBe ( true ) ;
312+ expect ( r . message ) . toContain ( '200' ) ;
313+ } ) ;
310314
311315 globalThis . fetch = originalFetch ;
312316 } ) ;
@@ -332,21 +336,25 @@ describe('testProvider success and error paths', () => {
332336 } ) ;
333337
334338 const response = await app . fetch ( request , env ) ;
335- const data = await response . json ( ) as { success : boolean ; message : string } ;
339+ const data = await response . json ( ) as any ;
336340
337341 expect ( response . status ) . toBe ( 200 ) ;
338342 expect ( data . success ) . toBe ( true ) ;
339- expect ( data . message ) . toContain ( '201' ) ;
343+ expect ( data . results ) . toHaveLength ( 4 ) ;
344+ data . results . forEach ( ( r : any ) => {
345+ expect ( r . success ) . toBe ( true ) ;
346+ expect ( r . message ) . toContain ( '201' ) ;
347+ } ) ;
340348
341349 globalThis . fetch = originalFetch ;
342350 } ) ;
343351
344352 it ( 'handles HTTP error with JSON error message' , async ( ) => {
345- const mockFetch = vi . fn ( ) . mockResolvedValue (
346- new Response ( JSON . stringify ( { error : { message : 'Invalid API key' } } ) , {
353+ const mockFetch = vi . fn ( ) . mockImplementation ( ( ) =>
354+ Promise . resolve ( new Response ( JSON . stringify ( { error : { message : 'Invalid API key' } } ) , {
347355 status : 401 ,
348356 headers : { 'Content-Type' : 'application/json' } ,
349- } )
357+ } ) )
350358 ) ;
351359 globalThis . fetch = mockFetch as any ;
352360
@@ -362,21 +370,24 @@ describe('testProvider success and error paths', () => {
362370 } ) ;
363371
364372 const response = await app . fetch ( request , env ) ;
365- const data = await response . json ( ) as { success : boolean ; error : string } ;
373+ const data = await response . json ( ) as any ;
366374
367375 expect ( response . status ) . toBe ( 200 ) ;
368376 expect ( data . success ) . toBe ( false ) ;
369- expect ( data . error ) . toContain ( 'Invalid API key' ) ;
377+ data . results . forEach ( ( r : any ) => {
378+ expect ( r . success ) . toBe ( false ) ;
379+ expect ( r . error ) . toContain ( 'Invalid API key' ) ;
380+ } ) ;
370381
371382 globalThis . fetch = originalFetch ;
372383 } ) ;
373384
374385 it ( 'handles HTTP error with plain text response (short)' , async ( ) => {
375- const mockFetch = vi . fn ( ) . mockResolvedValue (
376- new Response ( 'Unauthorized' , {
386+ const mockFetch = vi . fn ( ) . mockImplementation ( ( ) =>
387+ Promise . resolve ( new Response ( 'Unauthorized' , {
377388 status : 401 ,
378389 headers : { 'Content-Type' : 'text/plain' } ,
379- } )
390+ } ) )
380391 ) ;
381392 globalThis . fetch = mockFetch as any ;
382393
@@ -392,22 +403,25 @@ describe('testProvider success and error paths', () => {
392403 } ) ;
393404
394405 const response = await app . fetch ( request , env ) ;
395- const data = await response . json ( ) as { success : boolean ; error : string } ;
406+ const data = await response . json ( ) as any ;
396407
397408 expect ( response . status ) . toBe ( 200 ) ;
398409 expect ( data . success ) . toBe ( false ) ;
399- expect ( data . error ) . toContain ( 'Unauthorized' ) ;
410+ data . results . forEach ( ( r : any ) => {
411+ expect ( r . success ) . toBe ( false ) ;
412+ expect ( r . error ) . toContain ( 'Unauthorized' ) ;
413+ } ) ;
400414
401415 globalThis . fetch = originalFetch ;
402416 } ) ;
403417
404418 it ( 'handles HTTP error with long text response (truncated)' , async ( ) => {
405419 const longText = 'A' . repeat ( 250 ) ;
406- const mockFetch = vi . fn ( ) . mockResolvedValue (
407- new Response ( longText , {
420+ const mockFetch = vi . fn ( ) . mockImplementation ( ( ) =>
421+ Promise . resolve ( new Response ( longText , {
408422 status : 500 ,
409423 headers : { 'Content-Type' : 'text/html' } ,
410- } )
424+ } ) )
411425 ) ;
412426 globalThis . fetch = mockFetch as any ;
413427
@@ -423,12 +437,15 @@ describe('testProvider success and error paths', () => {
423437 } ) ;
424438
425439 const response = await app . fetch ( request , env ) ;
426- const data = await response . json ( ) as { success : boolean ; error : string } ;
440+ const data = await response . json ( ) as any ;
427441
428442 expect ( response . status ) . toBe ( 200 ) ;
429443 expect ( data . success ) . toBe ( false ) ;
430- expect ( data . error ) . toContain ( 'HTTP 500' ) ;
431- expect ( data . error ) . not . toContain ( longText ) ; // Should not include long text
444+ data . results . forEach ( ( r : any ) => {
445+ expect ( r . success ) . toBe ( false ) ;
446+ expect ( r . error ) . toContain ( 'HTTP 500' ) ;
447+ expect ( r . error ) . not . toContain ( longText ) ;
448+ } ) ;
432449
433450 globalThis . fetch = originalFetch ;
434451 } ) ;
@@ -540,9 +557,13 @@ describe('testProvider success and error paths', () => {
540557 } ) ;
541558
542559 it ( 'applies model mapping when configured' , async ( ) => {
543- const mockFetch = vi . fn ( ) . mockResolvedValue (
544- new Response ( JSON . stringify ( { id : 'msg_123' } ) , { status : 200 } )
545- ) ;
560+ const fetchBodies : any [ ] = [ ] ;
561+ const mockFetch = vi . fn ( ) . mockImplementation ( ( _url : string , options : any ) => {
562+ fetchBodies . push ( JSON . parse ( options . body ) ) ;
563+ return Promise . resolve (
564+ new Response ( JSON . stringify ( { id : 'msg_123' } ) , { status : 200 } )
565+ ) ;
566+ } ) ;
546567 globalThis . fetch = mockFetch as any ;
547568
548569 const env = createMockBindings ( { adminToken : 'test-token' } ) ;
@@ -560,15 +581,23 @@ describe('testProvider success and error paths', () => {
560581 } ) ;
561582
562583 const response = await app . fetch ( request , env ) ;
563- const data = await response . json ( ) as { success : boolean } ;
584+ const data = await response . json ( ) as any ;
564585
565586 expect ( data . success ) . toBe ( true ) ;
566- expect ( mockFetch ) . toHaveBeenCalledWith (
567- expect . any ( String ) ,
568- expect . objectContaining ( {
569- body : expect . stringContaining ( 'anthropic/claude-sonnet-4' ) ,
570- } )
571- ) ;
587+ const models = fetchBodies . map ( ( b ) => b . model ) ;
588+ expect ( models ) . toContain ( 'anthropic/claude-sonnet-4' ) ;
589+ // Unmapped models use original IDs
590+ expect ( models ) . toContain ( 'claude-opus-4-20250514' ) ;
591+ expect ( models ) . toContain ( 'claude-3-5-haiku-20241022' ) ;
592+
593+ // Check mappedTo in results
594+ const sonnetResult = data . results . find ( ( r : any ) => r . model === 'claude-sonnet-4-20250514' ) ;
595+ expect ( sonnetResult . mappedTo ) . toBe ( 'anthropic/claude-sonnet-4' ) ;
596+ expect ( sonnetResult . hasMappingConfigured ) . toBe ( true ) ;
597+
598+ const opusResult = data . results . find ( ( r : any ) => r . model === 'claude-opus-4-20250514' ) ;
599+ expect ( opusResult . mappedTo ) . toBeUndefined ( ) ;
600+ expect ( opusResult . hasMappingConfigured ) . toBe ( false ) ;
572601
573602 globalThis . fetch = originalFetch ;
574603 } ) ;
@@ -589,18 +618,21 @@ describe('testProvider success and error paths', () => {
589618 } ) ;
590619
591620 const response = await app . fetch ( request , env ) ;
592- const data = await response . json ( ) as { success : boolean ; error : string } ;
621+ const data = await response . json ( ) as any ;
593622
594623 expect ( response . status ) . toBe ( 200 ) ;
595624 expect ( data . success ) . toBe ( false ) ;
596- expect ( data . error ) . toContain ( 'Network error' ) ;
625+ data . results . forEach ( ( r : any ) => {
626+ expect ( r . success ) . toBe ( false ) ;
627+ expect ( r . error ) . toContain ( 'Network error' ) ;
628+ } ) ;
597629
598630 globalThis . fetch = originalFetch ;
599631 } ) ;
600632
601633 it ( 'handles timeout (AbortError)' , async ( ) => {
602634 const mockFetch = vi . fn ( ) . mockImplementation ( ( ) => {
603- return new Promise ( ( resolve , reject ) => {
635+ return new Promise ( ( _resolve , reject ) => {
604636 setTimeout ( ( ) => {
605637 const error = new Error ( 'The operation was aborted' ) ;
606638 error . name = 'AbortError' ;
@@ -622,12 +654,146 @@ describe('testProvider success and error paths', () => {
622654 } ) ;
623655
624656 const response = await app . fetch ( request , env ) ;
625- const data = await response . json ( ) as { success : boolean ; error : string } ;
657+ const data = await response . json ( ) as any ;
626658
627659 expect ( response . status ) . toBe ( 200 ) ;
628660 expect ( data . success ) . toBe ( false ) ;
629- expect ( data . error ) . toContain ( 'timed out' ) ;
661+ data . results . forEach ( ( r : any ) => {
662+ expect ( r . success ) . toBe ( false ) ;
663+ expect ( r . error ) . toContain ( 'timed out' ) ;
664+ } ) ;
630665
631666 globalThis . fetch = originalFetch ;
632667 } , 15000 ) ; // Increase timeout for this test
668+
669+ it ( 'returns results array with 4 models' , async ( ) => {
670+ const mockFetch = vi . fn ( ) . mockResolvedValue (
671+ new Response ( JSON . stringify ( { id : 'msg_test' } ) , { status : 200 } )
672+ ) ;
673+ globalThis . fetch = mockFetch as any ;
674+
675+ const env = createMockBindings ( { adminToken : 'test-token' } ) ;
676+ const request = createRequest ( '/admin/test-provider' , {
677+ method : 'POST' ,
678+ token : 'test-token' ,
679+ body : {
680+ name : 'test-provider' ,
681+ baseUrl : 'https://api.example.com/v1/messages' ,
682+ apiKey : 'sk-test-key' ,
683+ } ,
684+ } ) ;
685+
686+ const response = await app . fetch ( request , env ) ;
687+ const data = await response . json ( ) as any ;
688+
689+ expect ( data . success ) . toBe ( true ) ;
690+ expect ( data . results ) . toHaveLength ( 4 ) ;
691+ expect ( data . results . map ( ( r : any ) => r . model ) ) . toEqual ( [
692+ 'claude-sonnet-4-20250514' ,
693+ 'claude-opus-4-20250514' ,
694+ 'claude-opus-4-6-20250415' ,
695+ 'claude-3-5-haiku-20241022' ,
696+ ] ) ;
697+ data . results . forEach ( ( r : any ) => {
698+ expect ( r . success ) . toBe ( true ) ;
699+ expect ( r . label ) . toBeDefined ( ) ;
700+ } ) ;
701+
702+ globalThis . fetch = originalFetch ;
703+ } ) ;
704+
705+ it ( 'reports per-model failures independently' , async ( ) => {
706+ let callCount = 0 ;
707+ const mockFetch = vi . fn ( ) . mockImplementation ( ( ) => {
708+ callCount ++ ;
709+ if ( callCount === 1 ) {
710+ return Promise . resolve (
711+ new Response ( JSON . stringify ( { id : 'msg_test' } ) , { status : 200 } )
712+ ) ;
713+ }
714+ return Promise . resolve (
715+ new Response ( JSON . stringify ( { error : { message : 'Model not found' } } ) , { status : 404 } )
716+ ) ;
717+ } ) ;
718+ globalThis . fetch = mockFetch as any ;
719+
720+ const env = createMockBindings ( { adminToken : 'test-token' } ) ;
721+ const request = createRequest ( '/admin/test-provider' , {
722+ method : 'POST' ,
723+ token : 'test-token' ,
724+ body : {
725+ name : 'test-provider' ,
726+ baseUrl : 'https://api.example.com/v1/messages' ,
727+ apiKey : 'sk-test-key' ,
728+ } ,
729+ } ) ;
730+
731+ const response = await app . fetch ( request , env ) ;
732+ const data = await response . json ( ) as any ;
733+
734+ expect ( data . success ) . toBe ( false ) ;
735+ expect ( data . results . filter ( ( r : any ) => r . success ) ) . toHaveLength ( 1 ) ;
736+ expect ( data . results . filter ( ( r : any ) => ! r . success ) ) . toHaveLength ( 3 ) ;
737+
738+ globalThis . fetch = originalFetch ;
739+ } ) ;
740+
741+ it ( 'includes suggestion when models fail without mapping' , async ( ) => {
742+ const mockFetch = vi . fn ( ) . mockImplementation ( ( ) =>
743+ Promise . resolve ( new Response ( JSON . stringify ( { error : { message : 'Not found' } } ) , { status : 404 } ) )
744+ ) ;
745+ globalThis . fetch = mockFetch as any ;
746+
747+ const env = createMockBindings ( { adminToken : 'test-token' } ) ;
748+ const request = createRequest ( '/admin/test-provider' , {
749+ method : 'POST' ,
750+ token : 'test-token' ,
751+ body : {
752+ name : 'test-provider' ,
753+ baseUrl : 'https://api.example.com/v1/messages' ,
754+ apiKey : 'sk-test-key' ,
755+ } ,
756+ } ) ;
757+
758+ const response = await app . fetch ( request , env ) ;
759+ const data = await response . json ( ) as any ;
760+
761+ expect ( data . success ) . toBe ( false ) ;
762+ expect ( data . suggestion ) . toBeDefined ( ) ;
763+ expect ( data . suggestion ) . toContain ( 'model mapping' ) ;
764+
765+ globalThis . fetch = originalFetch ;
766+ } ) ;
767+
768+ it ( 'does not include suggestion when failed models have mappings' , async ( ) => {
769+ const mockFetch = vi . fn ( ) . mockImplementation ( ( ) =>
770+ Promise . resolve ( new Response ( JSON . stringify ( { error : { message : 'Auth error' } } ) , { status : 401 } ) )
771+ ) ;
772+ globalThis . fetch = mockFetch as any ;
773+
774+ const env = createMockBindings ( { adminToken : 'test-token' } ) ;
775+ const request = createRequest ( '/admin/test-provider' , {
776+ method : 'POST' ,
777+ token : 'test-token' ,
778+ body : {
779+ name : 'test-provider' ,
780+ baseUrl : 'https://api.example.com/v1/messages' ,
781+ apiKey : 'sk-test-key' ,
782+ modelMapping : {
783+ 'claude-sonnet-4-20250514' : 'mapped-sonnet' ,
784+ 'claude-opus-4-20250514' : 'mapped-opus' ,
785+ 'claude-opus-4-6-20250415' : 'mapped-opus-46' ,
786+ 'claude-3-5-haiku-20241022' : 'mapped-haiku' ,
787+ } ,
788+ } ,
789+ } ) ;
790+
791+ const response = await app . fetch ( request , env ) ;
792+ const data = await response . json ( ) as any ;
793+
794+ expect ( data . success ) . toBe ( false ) ;
795+ expect ( data . suggestion ) . toBeUndefined ( ) ;
796+
797+ globalThis . fetch = originalFetch ;
798+ } ) ;
633799} ) ;
0 commit comments