Skip to content

Commit ad0f996

Browse files
authored
test(github): add fetchWithRetry unit tests for 429 and 5xx retry logic (JhaSourav07#603)
## Description Fixes JhaSourav07#326 ## Pillar - [ ] 🎨 Pillar 1 — New Theme Design - [ ] 📐 Pillar 2 — Geometric SVG Improvement - [ ] 🕐 Pillar 3 — Timezone Logic Optimization - [x] 🔧 Other (Bug fix, refactoring, docs) ## Visual Preview N/A — this PR adds unit tests only, no visual changes. ## Checklist before requesting a review: - [x] I have read the `CONTRIBUTING.md` file. - [x] I have tested these changes locally (`localhost:3000/api/streak?user=YOUR_USERNAME`). - [x] I have run `npm run format` and `npm run lint` locally and resolved all errors (CI will fail otherwise). - [x] My commits follow the Conventional Commits format (e.g., `feat(themes): ...`, `fix(calculate): ...`). - [ ] I have updated `README.md` if I added a new theme or URL parameter. - [x] I have starred the repo. - [x] I have made sure that i have only one commit to merge in this PR. - [ ] The SVG output matches the CommitPulse "premium quality" aesthetic standard (no raw elements, smooth animations, correct fonts). - [ ] (Recommended) I joined the CommitPulse Discord community for contributor discussions, mentorship, and faster PR support.
2 parents 5ceb811 + 7d41b24 commit ad0f996

1 file changed

Lines changed: 71 additions & 0 deletions

File tree

lib/github.test.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
clearGitHubApiCacheForTests,
1010
GITHUB_CACHE_TTL_MS,
1111
validateGitHubUsername,
12+
fetchWithRetry,
1213
} from './github';
1314
import type { ContributionCalendar } from '../types';
1415

@@ -475,3 +476,73 @@ describe('validateGitHubUsername', () => {
475476
expect(validateGitHubUsername('in--valid')).toBe(false);
476477
});
477478
});
479+
describe('fetchWithRetry', () => {
480+
beforeEach(() => {
481+
vi.spyOn(global, 'fetch');
482+
});
483+
484+
afterEach(() => {
485+
vi.restoreAllMocks();
486+
});
487+
488+
it('resolves immediately on first attempt when fetch returns 200', async () => {
489+
vi.mocked(fetch).mockResolvedValueOnce(mockResponse({ ok: true }, 200));
490+
491+
const res = await fetchWithRetry('https://api.github.com/rest', {});
492+
493+
expect(res.status).toBe(200);
494+
expect(fetch).toHaveBeenCalledTimes(1);
495+
});
496+
497+
it('retries once on 429 (rate limit) and resolves on second attempt', async () => {
498+
vi.useFakeTimers();
499+
vi.mocked(fetch)
500+
.mockResolvedValueOnce(mockResponse({}, 429))
501+
.mockResolvedValueOnce(mockResponse({}, 200));
502+
503+
const promise = fetchWithRetry('https://api.github.com/rest', {});
504+
await vi.runAllTimersAsync();
505+
const res = await promise;
506+
507+
expect(res.status).toBe(200);
508+
expect(fetch).toHaveBeenCalledTimes(2);
509+
vi.useRealTimers();
510+
});
511+
512+
it('retries once on 500 (server error) and resolves on second attempt', async () => {
513+
vi.useFakeTimers();
514+
vi.mocked(fetch)
515+
.mockResolvedValueOnce(mockResponse({}, 500))
516+
.mockResolvedValueOnce(mockResponse({}, 200));
517+
518+
const promise = fetchWithRetry('https://api.github.com/rest', {});
519+
await vi.runAllTimersAsync();
520+
const res = await promise;
521+
522+
expect(res.status).toBe(200);
523+
expect(fetch).toHaveBeenCalledTimes(2);
524+
vi.useRealTimers();
525+
});
526+
527+
it('returns the bad response after exhausting all retries on 429', async () => {
528+
vi.useFakeTimers();
529+
vi.mocked(fetch).mockResolvedValue(mockResponse({}, 429));
530+
531+
const promise = fetchWithRetry('https://api.github.com/rest', {});
532+
await vi.runAllTimersAsync();
533+
const res = await promise;
534+
535+
expect(res.status).toBe(429);
536+
vi.useRealTimers();
537+
});
538+
539+
it('propagates network error after exhausting all retries', async () => {
540+
// Use real timers so retry delays actually fire and resolve cleanly
541+
vi.useRealTimers();
542+
vi.mocked(fetch).mockRejectedValue(new Error('network failure'));
543+
544+
await expect(fetchWithRetry('https://api.github.com/rest', {})).rejects.toThrow(
545+
'network failure'
546+
);
547+
});
548+
});

0 commit comments

Comments
 (0)