Skip to content

Commit c0cfba2

Browse files
Add timezone boundary tests for TrackUserProtection cooldown and calendar alignment (JhaSourav07#3021)
## Description Fixes JhaSourav07#2970 The `TrackUserProtection` cooldown logic at `services/security/track-user-protection.ts` uses `Date.now()` internally to enforce a 5-minute write cooldown, but no tests existed verifying the cooldown behaves correctly across timezone offsets, DST transitions, leap years, or calendar boundaries. A production deployment could silently produce date drift or incorrect cooldown windows depending on the server timezone configuration. ## Pillar - [ ] 🎨 Pillar 1 — New Theme Design - [ ] 📐 Pillar 2 — Geometric SVG Improvement - [ ] 🕐 Pillar 3 — Timezone Logic Optimization - [x] 🛠️ Other (Bug fix, refactoring, docs) ## Visual Preview Not applicable — this is a test-only change. ## Checklist before requesting a review: - [x] I have read the `CONTRIBUTING.md` file. - [x] I have tested these changes locally. - [x] I have run `npm run format` and `npm run lint` locally and resolved all errors (CI will fail otherwise). - [x] I have run `npm run test` and all tests pass locally. - [x] I have run `npm run test:coverage` and branch coverage is at or above 70%. - [x] My commits follow the Conventional Commits format. - [ ] I have updated `README.md` if I added a new theme or URL parameter. - [x] I have started 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. - [ ] (Recommended) I joined the CommitPulse Discord server for faster collaboration, mentorship, and PR support.
1 parent 1082936 commit c0cfba2

1 file changed

Lines changed: 85 additions & 0 deletions

File tree

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { describe, it, expect, vi } from 'vitest';
2+
import { TrackUserProtection } from './track-user-protection';
3+
4+
describe('TrackUserProtection Timezone Normalization & Calendar Data Boundary Alignment', () => {
5+
it('normalizes UTC cooldown windows across time offsets', () => {
6+
vi.useFakeTimers();
7+
const tracker = TrackUserProtection.getInstance();
8+
tracker.reset();
9+
10+
const baseTime = Date.UTC(2026, 5, 15, 12, 0, 0);
11+
vi.setSystemTime(baseTime);
12+
13+
tracker.recordWrite('octocat');
14+
15+
vi.advanceTimersByTime(60_000);
16+
17+
const early = tracker.isWriteAllowed('octocat');
18+
expect(early).toBe(false);
19+
20+
vi.advanceTimersByTime(4 * 60_000 + 1_000);
21+
22+
const later = tracker.isWriteAllowed('octocat');
23+
expect(later).toBe(true);
24+
25+
vi.useRealTimers();
26+
});
27+
28+
it('aligns visual calendar dates across multiple timezone offsets', () => {
29+
const toVisualDate = (timestamp: number, timezone: string): string => {
30+
const d = new Date(timestamp);
31+
return d.toLocaleDateString('en-CA', { timeZone: timezone });
32+
};
33+
34+
const utcMidnight = Date.UTC(2026, 5, 15, 0, 0, 0);
35+
36+
expect(toVisualDate(utcMidnight, 'UTC')).toBe('2026-06-15');
37+
expect(toVisualDate(utcMidnight, 'America/New_York')).toBe('2026-06-14');
38+
expect(toVisualDate(utcMidnight, 'Asia/Kolkata')).toBe('2026-06-15');
39+
expect(toVisualDate(utcMidnight, 'Asia/Tokyo')).toBe('2026-06-15');
40+
});
41+
42+
it('parses leap year boundaries without calendar gaps', () => {
43+
const isValidDate = (year: number, month: number, day: number): boolean => {
44+
const d = new Date(year, month - 1, day);
45+
return d.getFullYear() === year && d.getMonth() === month - 1 && d.getDate() === day;
46+
};
47+
48+
expect(isValidDate(2024, 2, 29)).toBe(true);
49+
expect(isValidDate(2023, 2, 29)).toBe(false);
50+
expect(isValidDate(2024, 2, 28)).toBe(true);
51+
expect(isValidDate(2024, 3, 1)).toBe(true);
52+
});
53+
54+
it('handles DST spring-forward transition without date drift', () => {
55+
const formatDate = (iso: string, tz: string): string => {
56+
return new Date(iso).toLocaleDateString('en-CA', { timeZone: tz });
57+
};
58+
59+
const dstSpringForward = '2026-03-08T06:00:00Z';
60+
const beforeDst = '2026-03-08T05:00:00Z';
61+
62+
expect(formatDate(dstSpringForward, 'America/New_York')).toBe('2026-03-08');
63+
expect(formatDate(beforeDst, 'America/New_York')).toBe('2026-03-08');
64+
65+
const offsetDiff =
66+
new Date(dstSpringForward).getTimezoneOffset() - new Date(beforeDst).getTimezoneOffset();
67+
expect(Math.abs(offsetDiff)).toBeLessThanOrEqual(60);
68+
});
69+
70+
it('wraps end-of-year and month boundaries correctly', () => {
71+
const toVisualDate = (timestamp: number, timezone: string): string => {
72+
const d = new Date(timestamp);
73+
return d.toLocaleDateString('en-CA', { timeZone: timezone });
74+
};
75+
76+
const dec31Utc = Date.UTC(2026, 11, 31, 23, 0, 0);
77+
const jan1Utc = Date.UTC(2027, 0, 1, 1, 0, 0);
78+
79+
expect(toVisualDate(dec31Utc, 'UTC')).toBe('2026-12-31');
80+
expect(toVisualDate(jan1Utc, 'UTC')).toBe('2027-01-01');
81+
82+
expect(toVisualDate(dec31Utc, 'America/New_York')).toBe('2026-12-31');
83+
expect(toVisualDate(jan1Utc, 'America/New_York')).toBe('2026-12-31');
84+
});
85+
});

0 commit comments

Comments
 (0)