Skip to content

Commit 21d406f

Browse files
committed
test(api): covers nameWithOwner, endCursor, fork fallback
1 parent 1b8c761 commit 21d406f

File tree

1 file changed

+90
-0
lines changed

1 file changed

+90
-0
lines changed

tests/services/api.test.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)