@@ -700,15 +700,13 @@ describe("provider-catalog-live-runtime", () => {
700700 expect ( fetchGuardMock ) . toHaveBeenCalledTimes ( 2 ) ;
701701 } ) ;
702702
703- it ( "gracefully ignores a malformed absolute next URL instead of crashing " , async ( ) => {
703+ it ( "reports incomplete pagination on malformed absolute next URL with no usable fallback " , async ( ) => {
704704 const release = vi . fn ( async ( ) => undefined ) ;
705705 const fetchGuardMock : MockedFunction < LiveModelCatalogFetchGuard > = vi . fn ( async ( ) => ( {
706706 response : new Response (
707707 JSON . stringify ( {
708708 data : [ { id : "model-a" , object : "model" } ] ,
709- // Space in hostname makes this a genuinely invalid absolute URL that
710- // throws TypeError from `new URL()`. Relative URLs like "?page=2" never
711- // throw and are handled by the existing pagination path.
709+ // Space in hostname makes this a genuinely invalid absolute URL.
712710 next : "http://exa mple.com/models?page=2" ,
713711 has_more : false ,
714712 } ) ,
@@ -717,18 +715,24 @@ describe("provider-catalog-live-runtime", () => {
717715 release,
718716 } ) ) ;
719717
718+ // The provider explicitly advertised a next page via the `next` field but
719+ // the URL is malformed and there is no cursor fallback. The controlled
720+ // incomplete-pagination error prevents silently returning a truncated
721+ // catalog.
720722 await expect (
721723 fetchLiveProviderModelIds ( {
722724 providerId : "provider" ,
723725 endpoint : "https://provider.example.test/v1/models" ,
724726 fetchGuard : fetchGuardMock ,
725727 } ) ,
726- ) . resolves . toEqual ( [ "model-a" ] ) ;
728+ ) . rejects . toThrow (
729+ "provider model discovery did not include a supported next page before the catalog completed" ,
730+ ) ;
727731
728732 expect ( fetchGuardMock ) . toHaveBeenCalledTimes ( 1 ) ;
729733 } ) ;
730734
731- it ( "gracefully ignores a malformed nested links.next URL" , async ( ) => {
735+ it ( "reports incomplete pagination on malformed nested links.next URL" , async ( ) => {
732736 const release = vi . fn ( async ( ) => undefined ) ;
733737 const fetchGuardMock : MockedFunction < LiveModelCatalogFetchGuard > = vi . fn ( async ( ) => ( {
734738 response : new Response (
@@ -748,11 +752,52 @@ describe("provider-catalog-live-runtime", () => {
748752 endpoint : "https://provider.example.test/v1/models" ,
749753 fetchGuard : fetchGuardMock ,
750754 } ) ,
751- ) . resolves . toEqual ( [ "model-a" ] ) ;
755+ ) . rejects . toThrow (
756+ "provider model discovery did not include a supported next page before the catalog completed" ,
757+ ) ;
752758
753759 expect ( fetchGuardMock ) . toHaveBeenCalledTimes ( 1 ) ;
754760 } ) ;
755761
762+ it ( "recovers malformed next URL via cursor fallback" , async ( ) => {
763+ const release = vi . fn ( async ( ) => undefined ) ;
764+ const fetchGuardMock : MockedFunction < LiveModelCatalogFetchGuard > = vi
765+ . fn ( )
766+ . mockResolvedValueOnce ( {
767+ response : new Response (
768+ JSON . stringify ( {
769+ data : [ { id : "model-a" , object : "model" } ] ,
770+ next : "http://exa mple.com/models?page=2" ,
771+ next_cursor : "cursor-2" ,
772+ has_more : true ,
773+ } ) ,
774+ ) ,
775+ finalUrl : "https://provider.example.test/v1/models" ,
776+ release,
777+ } )
778+ . mockResolvedValueOnce ( {
779+ response : new Response (
780+ JSON . stringify ( { data : [ { id : "model-b" , object : "model" } ] , has_more : false } ) ,
781+ ) ,
782+ finalUrl : "https://provider.example.test/v1/models?after=cursor-2" ,
783+ release,
784+ } ) ;
785+
786+ // The malformed next URL is ignored; cursor-based pagination takes over.
787+ await expect (
788+ fetchLiveProviderModelIds ( {
789+ providerId : "provider" ,
790+ endpoint : "https://provider.example.test/v1/models" ,
791+ fetchGuard : fetchGuardMock ,
792+ } ) ,
793+ ) . resolves . toEqual ( [ "model-a" , "model-b" ] ) ;
794+
795+ expect ( fetchGuardMock ) . toHaveBeenCalledTimes ( 2 ) ;
796+ expect ( fetchGuardMock . mock . calls [ 1 ] ?. [ 0 ] . url ) . toBe (
797+ "https://provider.example.test/v1/models?after=cursor-2" ,
798+ ) ;
799+ } ) ;
800+
756801 it ( "sets safe replay headers when final URL is unparseable" , async ( ) => {
757802 const release = vi . fn ( async ( ) => undefined ) ;
758803 const fetchGuardMock : MockedFunction < LiveModelCatalogFetchGuard > = vi . fn ( async ( ) => ( {
0 commit comments