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
34 changes: 34 additions & 0 deletions app/api/compare/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,38 @@ describe('GET /api/compare', () => {
const res = await GET(makeRequest('user1=octocat&user2=ghost123'));
expect(res.status).toBe(404);
});

it('returns 403 when the user1 fetch hits a GitHub rate limit', async () => {
vi.mocked(getFullDashboardData).mockRejectedValueOnce(new Error('API Rate Limit Exceeded'));

const res = await GET(makeRequest('user1=octocat&user2=torvalds'));

expect(res.status).toBe(403);
const data = await res.json();
expect(data.error).toBe('GitHub API rate limit reached. Please configure GITHUB_TOKEN.');
});

it('returns 403 when the user2 fetch hits a GitHub rate limit', async () => {
vi.mocked(getFullDashboardData)
.mockResolvedValueOnce({ calendar: { totalContributions: 0, weeks: [] } } as never)
.mockRejectedValueOnce(new Error('GitHub GraphQL API returned status 403'));

const res = await GET(makeRequest('user1=octocat&user2=torvalds'));

expect(res.status).toBe(403);
const data = await res.json();
expect(data.error).toBe('GitHub API rate limit reached. Please configure GITHUB_TOKEN.');
});

it('returns 500 for non-rate-limit upstream failures instead of reporting not found', async () => {
vi.mocked(getFullDashboardData).mockRejectedValueOnce(
new Error('GitHub GraphQL API returned status 500')
);

const res = await GET(makeRequest('user1=octocat&user2=torvalds'));

expect(res.status).toBe(500);
const data = await res.json();
expect(data.error).toContain('GitHub GraphQL API returned status 500');
});
});
46 changes: 34 additions & 12 deletions app/api/compare/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,38 @@ import { compareParamsSchema } from '@/lib/validations';

export const revalidate = 3600;

function buildCompareFetchErrorResponse(user: string, reason: unknown): NextResponse {
const message = reason instanceof Error ? reason.message : 'Unknown error';
const lowerMessage = message.toLowerCase();

if (lowerMessage.includes('not found') || lowerMessage.includes('could not resolve')) {
return NextResponse.json(
{
error: `Failed to fetch data for "${user}": ${message}`,
},
{ status: 404 }
);
}

if (
lowerMessage.includes('rate limit') ||
message.includes('API limit reached') ||
message.includes('status 403')
) {
return NextResponse.json(
{ error: 'GitHub API rate limit reached. Please configure GITHUB_TOKEN.' },
{ status: 403 }
);
}

return NextResponse.json(
{
error: `Failed to fetch data for "${user}": ${message}`,
},
{ status: 500 }
);
}

export async function GET(request: Request) {
const { searchParams } = new URL(request.url);

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

if (result1.status === 'rejected') {
return NextResponse.json(
{
error: `Failed to fetch data for "${user1}": ${result1.reason?.message || 'Unknown error'}`,
},
{ status: 404 }
);
return buildCompareFetchErrorResponse(user1, result1.reason);
}

if (result2.status === 'rejected') {
return NextResponse.json(
{
error: `Failed to fetch data for "${user2}": ${result2.reason?.message || 'Unknown error'}`,
},
{ status: 404 }
);
return buildCompareFetchErrorResponse(user2, result2.reason);
}

return NextResponse.json({
Expand Down
Loading