Skip to content

Commit d6388b3

Browse files
authored
test(api): create specific integration test for streak views parameter JhaSourav07#2343 (JhaSourav07#2410)
## Description Fixes JhaSourav07#2343 Program: GSSoC 2026 This PR introduces an isolated integration test suite tracking the behavior of the `view` parameter group within the `/api/streak` router engine. Previously, view testing was lumped broadly under general validation suites, which created gaps when verifying rendering outputs and responsive dimensions tied to dynamic query configurations. ### Changes Made * Created a brand new integration test file at `app/api/streak/tests/views.test.ts`. * Written 5 highly focused test cases utilizing mocked router payloads checking valid inputs (`default`, `monthly`), fallback defaults on omission or invalid inputs (`radar`), and response header verification. * Asserted SVG formatting rules, content type alignments, and execution handling bounds under Vitest. ### Why this matters Isolates configuration interface bugs early before rendering workflows kick off, guaranteeing that breaking style changes do not cascade into downstream UI layout or parsing engines. ## Pillar - [ ] 🎨 Pillar 1 — New Theme Design - [ ] 📐 Pillar 2 — Geometric SVG Improvement - [ ] 🕐 Pillar 3 — Timezone Logic Optimization - [x] 🛠️ Other (Bug fix, refactoring, docs) ## Checklist before requesting a review: - [x] I have read the `CONTRIBUTING.md` file. - [x] I have tested these changes locally (`npm run test`). - [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. - [x] The SVG output matches the CommitPulse "premium quality" aesthetic standard (no raw elements, smooth animations, correct fonts). - [x] (Recommended) I joined the CommitPulse Discord server for faster collaboration, mentorship, and PR support.
2 parents 27fc9ff + 2a5326e commit d6388b3

1 file changed

Lines changed: 157 additions & 0 deletions

File tree

app/api/streak/tests/views.test.ts

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
import { NextRequest } from 'next/server';
3+
import { GET } from '../route';
4+
import { streakParamsSchema } from '@/lib/validations';
5+
6+
vi.mock('@/lib/github', () => ({
7+
fetchGitHubContributions: vi.fn(),
8+
getOrgDashboardData: vi.fn(),
9+
}));
10+
11+
vi.mock('@/utils/time', () => ({
12+
getSecondsUntilUTCMidnight: vi.fn(),
13+
getSecondsUntilMidnightInTimezone: vi.fn(),
14+
}));
15+
16+
import { fetchGitHubContributions, getOrgDashboardData } from '@/lib/github';
17+
import { getSecondsUntilUTCMidnight, getSecondsUntilMidnightInTimezone } from '@/utils/time';
18+
import type { ContributionCalendar, ExtendedContributionData } from '@/types';
19+
20+
const mockCalendar: ContributionCalendar = {
21+
totalContributions: 10,
22+
weeks: [
23+
{
24+
contributionDays: [
25+
{ contributionCount: 1, date: '2024-06-10' },
26+
{ contributionCount: 2, date: '2024-06-11' },
27+
{ contributionCount: 0, date: '2024-06-12' },
28+
{ contributionCount: 3, date: '2024-06-13' },
29+
{ contributionCount: 1, date: '2024-06-14' },
30+
{ contributionCount: 0, date: '2024-06-15' },
31+
{ contributionCount: 3, date: '2024-06-16' },
32+
],
33+
},
34+
{
35+
contributionDays: [
36+
{ contributionCount: 0, date: '2024-06-17' },
37+
{ contributionCount: 0, date: '2024-06-18' },
38+
{ contributionCount: 0, date: '2024-06-19' },
39+
{ contributionCount: 0, date: '2024-06-20' },
40+
{ contributionCount: 0, date: '2024-06-21' },
41+
{ contributionCount: 0, date: '2024-06-22' },
42+
{ contributionCount: 0, date: '2024-06-23' },
43+
],
44+
},
45+
],
46+
};
47+
48+
function makeRequest(params: Record<string, string> = {}): NextRequest {
49+
const url = new URL('http://localhost:3000/api/streak');
50+
51+
for (const [key, value] of Object.entries(params)) {
52+
url.searchParams.set(key, value);
53+
}
54+
55+
return new NextRequest(url.toString());
56+
}
57+
58+
describe('GET /api/streak view parameter integration', () => {
59+
beforeEach(() => {
60+
vi.clearAllMocks();
61+
62+
vi.mocked(fetchGitHubContributions).mockResolvedValue({
63+
calendar: mockCalendar,
64+
repoContributions: [],
65+
} as unknown as ExtendedContributionData);
66+
67+
vi.mocked(getOrgDashboardData).mockResolvedValue({
68+
profile: {
69+
username: 'octocat',
70+
name: 'The Octocat',
71+
avatarUrl: 'https://github.com/octocat.png',
72+
isPro: false,
73+
bio: 'Testing organization mock pipelines',
74+
location: 'San Francisco, CA',
75+
joinedDate: '2011-01-25',
76+
developerScore: 85,
77+
stats: { repositories: 10, followers: 2500, following: 9, stars: 450 },
78+
},
79+
stats: {
80+
totalCommits: 10,
81+
totalIssues: 2,
82+
totalPRs: 5,
83+
totalReviews: 1,
84+
totalDiscussions: 0,
85+
contributedTo: 3,
86+
},
87+
calendar: mockCalendar,
88+
} as unknown as Awaited<ReturnType<typeof getOrgDashboardData>>);
89+
90+
vi.mocked(getSecondsUntilUTCMidnight).mockReturnValue(3600);
91+
vi.mocked(getSecondsUntilMidnightInTimezone).mockReturnValue(7200);
92+
});
93+
94+
it('returns 200 and the standard SVG headers for view=default', async () => {
95+
const response = await GET(makeRequest({ user: 'octocat', view: 'default' }));
96+
97+
expect(response.status).toBe(200);
98+
expect(response.headers.get('Content-Type')).toBe('image/svg+xml');
99+
expect(response.headers.get('Content-Security-Policy')).toContain("default-src 'none'");
100+
101+
const body = await response.text();
102+
expect(body).toContain('CURRENT_STREAK');
103+
});
104+
105+
it('returns 200 and renders monthly dimensions for view=monthly', async () => {
106+
const response = await GET(makeRequest({ user: 'octocat', view: 'monthly' }));
107+
108+
expect(response.status).toBe(200);
109+
110+
const body = await response.text();
111+
expect(body).toContain('COMMITS THIS MONTH');
112+
expect(body).toContain('width="300"');
113+
expect(body).toContain('height="120"');
114+
expect(body).toContain('viewBox="0 0 300 120"');
115+
});
116+
117+
it('falls back to the default layout when view is omitted', async () => {
118+
const parsed = streakParamsSchema.safeParse({ user: 'octocat' });
119+
120+
expect(parsed.success).toBe(true);
121+
expect(parsed.success && parsed.data.view).toBe('default');
122+
123+
const response = await GET(makeRequest({ user: 'octocat' }));
124+
125+
expect(response.status).toBe(200);
126+
127+
const body = await response.text();
128+
expect(body).toContain('CURRENT_STREAK');
129+
expect(body).not.toContain('COMMITS THIS MONTH');
130+
});
131+
132+
it('treats an invalid view value as default without crashing', async () => {
133+
const parsed = streakParamsSchema.safeParse({ user: 'octocat', view: 'radar' });
134+
135+
expect(parsed.success).toBe(true);
136+
expect(parsed.success && parsed.data.view).toBe('default');
137+
138+
const response = await GET(makeRequest({ user: 'octocat', view: 'radar' }));
139+
140+
expect(response.status).toBe(200);
141+
142+
const body = await response.text();
143+
expect(body).toContain('CURRENT_STREAK');
144+
expect(body).not.toContain('COMMITS THIS MONTH');
145+
});
146+
147+
it('exposes stable caching headers on the SVG response', async () => {
148+
const response = await GET(makeRequest({ user: 'octocat', view: 'default' }));
149+
150+
expect(response.status).toBe(200);
151+
expect(response.headers.get('Content-Type')).toBe('image/svg+xml');
152+
expect(response.headers.get('Cache-Control')).toBe(
153+
'public, s-maxage=3600, stale-while-revalidate=86400'
154+
);
155+
expect(response.headers.get('X-Cache-Status')).toBe('HIT');
156+
});
157+
});

0 commit comments

Comments
 (0)