@@ -702,6 +702,96 @@ describe("fetchPullRequests", () => {
702702 expect ( pullRequests [ 0 ] . checkStatus ) . toBeNull ( ) ;
703703 } ) ;
704704
705+ it ( "preserves PR when headRepository.nameWithOwner is malformed" , async ( ) => {
706+ const malformedNode = {
707+ ...graphqlPRNode ,
708+ databaseId : 700 ,
709+ headRepository : {
710+ owner : { login : "fork-owner" } ,
711+ nameWithOwner : "no-slash-here" , // malformed — missing "/"
712+ } ,
713+ commits : { nodes : [ { commit : { statusCheckRollup : null } } ] } ,
714+ } as unknown as typeof graphqlPRNode ;
715+ const octokit = makePROctokit ( async ( ) => makeGraphqlPRResponse ( [ malformedNode ] ) ) ;
716+
717+ const { pullRequests } = await fetchPullRequests (
718+ octokit as unknown as ReturnType < typeof import ( "../../src/app/services/github" ) . getClient > ,
719+ [ testRepo ] ,
720+ "octocat"
721+ ) ;
722+
723+ // PR is NOT silently dropped — it's returned with null checkStatus
724+ expect ( pullRequests . length ) . toBe ( 1 ) ;
725+ expect ( pullRequests [ 0 ] . id ) . toBe ( 700 ) ;
726+ expect ( pullRequests [ 0 ] . checkStatus ) . toBeNull ( ) ;
727+ } ) ;
728+
729+ it ( "stops pagination when hasNextPage is true but endCursor is null" , async ( ) => {
730+ let callCount = 0 ;
731+ const octokit = makePROctokit ( async ( ) => {
732+ callCount ++ ;
733+ return {
734+ search : {
735+ issueCount : 100 ,
736+ pageInfo : { hasNextPage : true , endCursor : null } , // degenerate response
737+ nodes : [ { ...graphqlPRNode , databaseId : callCount } ] ,
738+ } ,
739+ rateLimit : { remaining : 4999 , resetAt : new Date ( Date . now ( ) + 3600000 ) . toISOString ( ) } ,
740+ } ;
741+ } ) ;
742+
743+ const { pullRequests } = await fetchPullRequests (
744+ octokit as unknown as ReturnType < typeof import ( "../../src/app/services/github" ) . getClient > ,
745+ [ testRepo ] ,
746+ "octocat"
747+ ) ;
748+
749+ // Should NOT loop infinitely — breaks after first page per query type
750+ expect ( pullRequests . length ) . toBeGreaterThan ( 0 ) ;
751+ // 2 query types × 1 page each (stopped by null endCursor) = 2 calls
752+ expect ( callCount ) . toBe ( 2 ) ;
753+ } ) ;
754+
755+ it ( "surfaces pushNotification when fork fallback query fails" , async ( ) => {
756+ vi . mocked ( pushNotification ) . mockClear ( ) ;
757+
758+ // PR with fork head that differs from base owner
759+ const forkNode = {
760+ ...graphqlPRNode ,
761+ databaseId : 800 ,
762+ headRepository : {
763+ owner : { login : "fork-user" } ,
764+ nameWithOwner : "fork-user/some-repo" ,
765+ } ,
766+ commits : { nodes : [ { commit : { statusCheckRollup : null } } ] } ,
767+ } as unknown as typeof graphqlPRNode ;
768+
769+ let callCount = 0 ;
770+ const octokit = makePROctokit ( async ( ) => {
771+ callCount ++ ;
772+ if ( callCount <= 2 ) {
773+ // First 2 calls: involves + review-requested search queries
774+ return makeGraphqlPRResponse ( [ forkNode ] ) ;
775+ }
776+ // 3rd call: fork fallback query — throw to simulate failure
777+ throw new Error ( "Fork repo not accessible" ) ;
778+ } ) ;
779+
780+ const { pullRequests } = await fetchPullRequests (
781+ octokit as unknown as ReturnType < typeof import ( "../../src/app/services/github" ) . getClient > ,
782+ [ testRepo ] ,
783+ "octocat"
784+ ) ;
785+
786+ expect ( pullRequests . length ) . toBe ( 1 ) ;
787+ expect ( pullRequests [ 0 ] . checkStatus ) . toBeNull ( ) ; // fallback failed, stays null
788+ expect ( pushNotification ) . toHaveBeenCalledWith (
789+ "graphql" ,
790+ expect . stringContaining ( "Fork PR check status unavailable" ) ,
791+ "warning"
792+ ) ;
793+ } ) ;
794+
705795 it ( "returns partial results and an error when second page throws mid-pagination" , async ( ) => {
706796 vi . mocked ( pushNotification ) . mockClear ( ) ;
707797
0 commit comments