Skip to content

Commit a0ed34b

Browse files
author
kali
committed
fix(api): classify compare route GitHub errors
1 parent e6b5723 commit a0ed34b

2 files changed

Lines changed: 68 additions & 12 deletions

File tree

app/api/compare/route.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,38 @@ describe('GET /api/compare', () => {
9090
const res = await GET(makeRequest('user1=octocat&user2=ghost123'));
9191
expect(res.status).toBe(404);
9292
});
93+
94+
it('returns 403 when the user1 fetch hits a GitHub rate limit', async () => {
95+
vi.mocked(getFullDashboardData).mockRejectedValueOnce(new Error('API Rate Limit Exceeded'));
96+
97+
const res = await GET(makeRequest('user1=octocat&user2=torvalds'));
98+
99+
expect(res.status).toBe(403);
100+
const data = await res.json();
101+
expect(data.error).toBe('GitHub API rate limit reached. Please configure GITHUB_TOKEN.');
102+
});
103+
104+
it('returns 403 when the user2 fetch hits a GitHub rate limit', async () => {
105+
vi.mocked(getFullDashboardData)
106+
.mockResolvedValueOnce({ calendar: { totalContributions: 0, weeks: [] } } as never)
107+
.mockRejectedValueOnce(new Error('GitHub GraphQL API returned status 403'));
108+
109+
const res = await GET(makeRequest('user1=octocat&user2=torvalds'));
110+
111+
expect(res.status).toBe(403);
112+
const data = await res.json();
113+
expect(data.error).toBe('GitHub API rate limit reached. Please configure GITHUB_TOKEN.');
114+
});
115+
116+
it('returns 500 for non-rate-limit upstream failures instead of reporting not found', async () => {
117+
vi.mocked(getFullDashboardData).mockRejectedValueOnce(
118+
new Error('GitHub GraphQL API returned status 500')
119+
);
120+
121+
const res = await GET(makeRequest('user1=octocat&user2=torvalds'));
122+
123+
expect(res.status).toBe(500);
124+
const data = await res.json();
125+
expect(data.error).toContain('GitHub GraphQL API returned status 500');
126+
});
93127
});

app/api/compare/route.ts

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,38 @@ import { compareParamsSchema } from '@/lib/validations';
44

55
export const revalidate = 3600;
66

7+
function buildCompareFetchErrorResponse(user: string, reason: unknown): NextResponse {
8+
const message = reason instanceof Error ? reason.message : 'Unknown error';
9+
const lowerMessage = message.toLowerCase();
10+
11+
if (lowerMessage.includes('not found') || lowerMessage.includes('could not resolve')) {
12+
return NextResponse.json(
13+
{
14+
error: `Failed to fetch data for "${user}": ${message}`,
15+
},
16+
{ status: 404 }
17+
);
18+
}
19+
20+
if (
21+
lowerMessage.includes('rate limit') ||
22+
message.includes('API limit reached') ||
23+
message.includes('status 403')
24+
) {
25+
return NextResponse.json(
26+
{ error: 'GitHub API rate limit reached. Please configure GITHUB_TOKEN.' },
27+
{ status: 403 }
28+
);
29+
}
30+
31+
return NextResponse.json(
32+
{
33+
error: `Failed to fetch data for "${user}": ${message}`,
34+
},
35+
{ status: 500 }
36+
);
37+
}
38+
739
export async function GET(request: Request) {
840
const { searchParams } = new URL(request.url);
941

@@ -26,21 +58,11 @@ export async function GET(request: Request) {
2658
]);
2759

2860
if (result1.status === 'rejected') {
29-
return NextResponse.json(
30-
{
31-
error: `Failed to fetch data for "${user1}": ${result1.reason?.message || 'Unknown error'}`,
32-
},
33-
{ status: 404 }
34-
);
61+
return buildCompareFetchErrorResponse(user1, result1.reason);
3562
}
3663

3764
if (result2.status === 'rejected') {
38-
return NextResponse.json(
39-
{
40-
error: `Failed to fetch data for "${user2}": ${result2.reason?.message || 'Unknown error'}`,
41-
},
42-
{ status: 404 }
43-
);
65+
return buildCompareFetchErrorResponse(user2, result2.reason);
4466
}
4567

4668
return NextResponse.json({

0 commit comments

Comments
 (0)