Skip to content

Commit 732aee3

Browse files
authored
Merge branch 'main' into test-non-serializable-json-payload
2 parents 6e5361d + e6b5723 commit 732aee3

10 files changed

Lines changed: 163 additions & 149 deletions

File tree

app/api/streak/route.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,14 @@ describe('GET /api/streak', () => {
672672
expect(response.status).toBe(400);
673673
expect(body.details.fieldErrors.date[0]).toContain('Invalid "date" format');
674674
});
675+
676+
it('returns 400 when an invalid ISO8601 calendar date format like "2026-15-40" is supplied (Variation 4)', async () => {
677+
const response = await GET(makeRequest({ user: 'octocat', date: '2026-15-40' }));
678+
const body = await response.json();
679+
680+
expect(response.status).toBe(400);
681+
expect(body.details.fieldErrors.date[0]).toContain('Invalid "date" format. Use ISO 8601.');
682+
});
675683
});
676684
});
677685

app/api/streak/route.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { getSecondsUntilUTCMidnight, getSecondsUntilMidnightInTimezone } from '@
1616
import type { BadgeParams } from '@/types';
1717
import { themes } from '@/lib/svg/themes';
1818
import { streakParamsSchema } from '@/lib/validations';
19+
import { sanitizeHexColor } from '@/lib/svg/sanitizer';
1920

2021
const SVG_CSP_HEADER =
2122
"default-src 'none'; style-src 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src https://fonts.gstatic.com;";
@@ -299,15 +300,15 @@ function buildErrorResponse(error: unknown, parseResult: ParseResult): NextRespo
299300
message.toLowerCase().includes('validation') ||
300301
message.toLowerCase().includes('strictly for organizations');
301302

302-
const errBg = `#${(parseResult.success && parseResult.data.bg) || '0d1117'}`;
303-
const errAccent = `#${
303+
const errBg = `#${sanitizeHexColor(parseResult.success ? parseResult.data.bg : undefined, '0d1117')}`;
304+
const errAccentRaw =
304305
(parseResult.success &&
305306
(Array.isArray(parseResult.data.accent)
306307
? parseResult.data.accent[parseResult.data.accent.length - 1]
307308
: parseResult.data.accent)) ||
308-
'58a6ff'
309-
}`;
310-
const errText = `#${(parseResult.success && parseResult.data.text) || 'c9d1d9'}`;
309+
undefined;
310+
const errAccent = `#${sanitizeHexColor(errAccentRaw, '58a6ff')}`;
311+
const errText = `#${sanitizeHexColor(parseResult.success ? parseResult.data.text : undefined, 'c9d1d9')}`;
311312
const errRadius = parseResult.success
312313
? (() => {
313314
const r = Number(parseResult.data.radius);

app/components/navbar.test.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,17 @@ vi.mock('lucide-react', () => ({
5858

5959
describe('Navbar mobile menu', () => {
6060
beforeEach(() => {
61+
Object.defineProperty(window, 'localStorage', {
62+
value: {
63+
getItem: vi.fn(),
64+
setItem: vi.fn(),
65+
clear: vi.fn(),
66+
},
67+
writable: true,
68+
});
6169
window.innerWidth = 500;
6270
mockMatchMedia(false);
63-
window.localStorage.clear();
71+
window.localStorage?.clear();
6472
document.documentElement.className = '';
6573
});
6674

@@ -106,8 +114,16 @@ describe('Navbar mobile menu', () => {
106114

107115
describe('Navbar responsive breakpoints', () => {
108116
beforeEach(() => {
117+
Object.defineProperty(window, 'localStorage', {
118+
value: {
119+
getItem: vi.fn(),
120+
setItem: vi.fn(),
121+
clear: vi.fn(),
122+
},
123+
writable: true,
124+
});
109125
window.innerWidth = 500;
110-
window.localStorage.clear();
126+
window.localStorage?.clear();
111127
document.documentElement.className = '';
112128
});
113129

app/page.test.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ vi.mock('next/link', () => ({
2929
),
3030
}));
3131

32+
vi.mock('@/utils/tracking', () => ({
33+
trackUser: vi.fn(),
34+
}));
35+
3236
// Mock GSAP so FeatureCards don't break in JSDOM
3337
vi.mock('gsap', () => {
3438
const tween = { kill: vi.fn() };

lib/calculate.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,37 @@ describe('calculateStreak', () => {
735735
expect(result.longestStreak).toBe(15);
736736
});
737737

738+
it('verify streak formulas for different starting days of the week timeline (Variation 3)', () => {
739+
// Week 1: 0, 0, 0, 0, 1, 1, 1 (Starts on Friday, 3 days)
740+
// Week 2: 1, 1, 1, 1, 1, 1, 1 (7 days)
741+
// Week 3: 1, 1, 1, 1, 1 // Ends on Friday (5 days)
742+
// Total continuous streak = 15 days, ending on the last day.
743+
const calendar = buildCalendar([
744+
0,
745+
0,
746+
0,
747+
0,
748+
1,
749+
1,
750+
1, // Week 1 (Starts Fri)
751+
1,
752+
1,
753+
1,
754+
1,
755+
1,
756+
1,
757+
1, // Week 2
758+
1,
759+
1,
760+
1,
761+
1,
762+
1, // Week 3 (Ends Fri)
763+
]);
764+
const result = calculateStreak(calendar);
765+
expect(result.currentStreak).toBe(15);
766+
expect(result.longestStreak).toBe(15);
767+
});
768+
738769
it('verify streak formulas for multiple weeks gaps timeline (Variation 3)', () => {
739770
// Streak 1: 5 days
740771
// Gap 1: 14 days (2 weeks of zeros)

lib/github.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
} from './github';
2525
import type { ContributionCalendar } from '../types';
2626

27+
vi.mock('server-only', () => ({}));
28+
2729
const mockCalendar: ContributionCalendar = {
2830
totalContributions: 42,
2931
weeks: [

lib/github.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { DistributedCache } from '@/lib/cache';
1212
import { LANGUAGE_COLORS } from '@/lib/svg/languageColors';
1313
import { CONTRIBUTION_MILESTONES, STREAK_MILESTONES } from './svg/constants';
1414

15+
import 'server-only';
16+
1517
interface GitHubRepo {
1618
name: string;
1719
stargazers_count: number;

lib/validations.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,3 +1090,31 @@ describe('streakParamsSchema user maxLength validation boundaries (Variation 3)'
10901090
expect(parseResult.success).toBe(true);
10911091
});
10921092
});
1093+
1094+
/* ==========================================================================
1095+
* DATE PARAMETER — QUERY VALIDATION BOUNDARIES (VARIATION 4)
1096+
* ========================================================================== */
1097+
1098+
describe('streakParamsSchema — date query validation boundaries (Variation 4)', () => {
1099+
it('rejects an invalid date format like "2026-15-40"', () => {
1100+
const result = streakParamsSchema.safeParse({
1101+
user: 'octocat',
1102+
date: '2026-15-40',
1103+
});
1104+
1105+
expect(result.success).toBe(false);
1106+
if (!result.success) {
1107+
const messages = result.error.issues.map((i) => i.message).join(' ');
1108+
expect(messages).toContain('Invalid "date" format. Use ISO 8601.');
1109+
}
1110+
});
1111+
1112+
it('accepts a valid ISO8601 date', () => {
1113+
const result = streakParamsSchema.safeParse({
1114+
user: 'octocat',
1115+
date: '2026-05-30',
1116+
});
1117+
1118+
expect(result.success).toBe(true);
1119+
});
1120+
});

0 commit comments

Comments
 (0)