Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions lib/github-data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,26 @@ describe('fetchContributionData', () => {
]);
});

it('flags capped results when total_count exceeds 1000', async () => {
it('flags capped results when total_count exceeds 1000 and still paginates to the 1000-item cap', async () => {
// Page 1: 100 items in external repos
const page1Items = Array.from({ length: 100 }, (_, i) => ({
number: i + 1,
title: `PR ${i + 1}`,
html_url: `https://github.com/external/popular/pull/${i + 1}`,
repository_url: 'https://api.github.com/repos/external/popular',
closed_at: '2026-03-01T00:00:00Z',
pull_request: { merged_at: '2026-03-01T00:00:00Z' },
}));
searchMock.mockResolvedValueOnce({
data: { total_count: 1500, items: [] },
data: { total_count: 1500, items: page1Items },
});
// Pages 2–10: empty (still must be requested, proving pagination continues past the cap guard)
for (let p = 2; p <= 10; p++) {
searchMock.mockResolvedValueOnce({
data: { total_count: 1500, items: [] },
});
}
// Open + closed-unmerged
searchMock.mockResolvedValueOnce({ data: { total_count: 10, items: [] } });
searchMock.mockResolvedValueOnce({ data: { total_count: 20, items: [] } });

Expand All @@ -87,6 +103,10 @@ describe('fetchContributionData', () => {
if ('error' in result && result.error) throw new Error('unexpected error');
expect(result.merged).toBe(1500);
expect(result.cappedMerged).toBe(true);
// 10 merged pages + open + closed = 12 search calls. Pre-fix behavior stopped at 3.
expect(searchMock).toHaveBeenCalledTimes(12);
// topRepos must reflect the paginated items, not just page 1's snapshot
expect(result.topRepos).toEqual([{ repo: 'external/popular', count: 100 }]);
});

it('returns error state for non-existent user', async () => {
Expand Down
9 changes: 3 additions & 6 deletions lib/github-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,9 @@ async function paginateSearch(
const totalCount = firstPage.data.total_count;
const items = [...firstPage.data.items];

// GitHub caps search results at 1000; if we're at or above that, don't paginate
if (totalCount >= maxItems) {
return { totalCount, items };
}

const totalPages = Math.min(Math.ceil(totalCount / perPage), 10);
// GitHub Search API caps results at 1000 (10 pages × 100). Paginate up to that cap.
const effectiveCount = Math.min(totalCount, maxItems);
const totalPages = Math.min(Math.ceil(effectiveCount / perPage), 10);

for (let page = 2; page <= totalPages; page++) {
const res = await octokit.rest.search.issuesAndPullRequests({
Expand Down
Loading