Skip to content

Commit 4608499

Browse files
committed
fix: implement timezone-safe testing patterns for 24/7 CI/CD
- Fix failing enhancedContextTimelineHandler tests that expected 6 periods but got 5 - Add comprehensive timezone-safe-dates utility for consistent UTC-based testing - Update all timeline and date-sensitive tests to use fixed UTC reference dates - Replace timezone-dependent new Date() patterns with createUTCDate() helper - Ensure tests pass consistently across all timezones (UTC, PST, EST, JST, etc) This resolves test failures in GitHub Actions running at UTC 02:37 when local development uses different timezones. Tests now produce consistent timeline groupings regardless of system timezone or DST transitions. Fixes the CI failure where timeline tests failed due to timezone boundaries affecting day grouping calculations.
1 parent 03dff01 commit 4608499

File tree

4 files changed

+415
-131
lines changed

4 files changed

+415
-131
lines changed

src/__tests__/integration/contextDiff.test.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as os from 'os';
66
import * as path from 'path';
77
import * as fs from 'fs';
88
import { v4 as uuidv4 } from 'uuid';
9+
import { createUTCDateByHours, validateTimezoneSafety } from '../utils/timezone-safe-dates';
910

1011
describe('Context Diff Integration Tests', () => {
1112
let dbManager: DatabaseManager;
@@ -52,22 +53,23 @@ describe('Context Diff Integration Tests', () => {
5253

5354
describe('Basic Diff Operations', () => {
5455
it('should detect added items since timestamp', () => {
55-
const baseTime = new Date();
56-
baseTime.setHours(baseTime.getHours() - 2);
56+
// TIMEZONE-SAFE: Use UTC-based date calculations
57+
validateTimezoneSafety();
58+
const baseTime = createUTCDateByHours(-2);
5759

5860
// Add items at different times
5961
const oldItem = {
6062
id: uuidv4(),
6163
key: 'old_item',
6264
value: 'This existed before',
63-
created_at: new Date(baseTime.getTime() - 1000).toISOString(),
65+
created_at: createUTCDateByHours(-2.1).toISOString(), // Slightly before base time
6466
};
6567

6668
const newItem = {
6769
id: uuidv4(),
6870
key: 'new_item',
6971
value: 'This was added recently',
70-
created_at: new Date().toISOString(),
72+
created_at: createUTCDateByHours(0).toISOString(), // Now
7173
};
7274

7375
db.prepare(

src/__tests__/integration/enhanced-context-operations.test.ts

Lines changed: 60 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ import * as os from 'os';
55
import * as path from 'path';
66
import * as fs from 'fs';
77
import { v4 as uuidv4 } from 'uuid';
8+
import {
9+
createTimelineTestDates,
10+
createUTCDate,
11+
createUTCDateByHours,
12+
validateTimezoneSafety,
13+
} from '../utils/timezone-safe-dates';
814

915
describe('Enhanced Context Operations Integration Tests', () => {
1016
let dbManager: DatabaseManager;
@@ -658,48 +664,34 @@ describe('Enhanced Context Operations Integration Tests', () => {
658664

659665
describe('Enhanced context_timeline', () => {
660666
beforeEach(() => {
661-
// Create items across different time periods
662-
const now = new Date();
667+
// TIMEZONE-SAFE: Use fixed UTC dates to ensure consistent behavior across all environments
668+
validateTimezoneSafety();
663669

664-
// Calculate proper timestamps for yesterday to ensure they fall within yesterday's date range
665-
const yesterday = new Date();
666-
yesterday.setDate(yesterday.getDate() - 1);
667-
yesterday.setHours(10, 0, 0, 0); // 10 AM yesterday
670+
const { today: _today, yesterday: _yesterday } = createTimelineTestDates();
668671

669-
const yesterdayAfternoon = new Date();
670-
yesterdayAfternoon.setDate(yesterdayAfternoon.getDate() - 1);
671-
yesterdayAfternoon.setHours(15, 0, 0, 0); // 3 PM yesterday
672-
673-
// Ensure we have items that are definitely "today" - create them at specific times today
674-
// Use UTC to match the test query
675-
const todayMorning = new Date();
676-
todayMorning.setUTCHours(9, 0, 0, 0); // 9 AM today UTC
677-
678-
const todayAfternoon = new Date();
679-
todayAfternoon.setUTCHours(14, 0, 0, 0); // 2 PM today UTC
680-
681-
// Create more "today" items ensuring they stay within today's boundaries
682-
const todayEarlyMorning = new Date();
683-
todayEarlyMorning.setUTCHours(7, 0, 0, 0); // 7 AM today UTC
684-
685-
const todayEarlyMorning2 = new Date();
686-
todayEarlyMorning2.setUTCHours(5, 0, 0, 0); // 5 AM today UTC
672+
// Create timezone-safe timestamps at specific hours
673+
const yesterdayMorning = createUTCDate(-1, 10, 0, 0); // 10 AM yesterday UTC
674+
const yesterdayAfternoon = createUTCDate(-1, 15, 0, 0); // 3 PM yesterday UTC
675+
const todayEarlyMorning = createUTCDate(0, 5, 0, 0); // 5 AM today UTC
676+
const todayEarlyMorning2 = createUTCDate(0, 7, 0, 0); // 7 AM today UTC
677+
const todayMorning = createUTCDate(0, 9, 0, 0); // 9 AM today UTC
678+
const todayAfternoon = createUTCDate(0, 14, 0, 0); // 2 PM today UTC
687679

688680
const timeOffsets = [
689-
// Use absolute timestamps for "today" items to ensure they're always in today
681+
// Use timezone-safe absolute timestamps
690682
{ timestamp: todayMorning, key: 'recent_1', category: 'task' }, // 9 AM today
691683
{ timestamp: todayAfternoon, key: 'recent_2', category: 'note' }, // 2 PM today
692-
{ timestamp: todayEarlyMorning, key: 'today_1', category: 'task' }, // 7 AM today
693-
{ timestamp: todayEarlyMorning2, key: 'today_2', category: 'decision' }, // 5 AM today
694-
{ timestamp: yesterday, key: 'yesterday_1', category: 'task' }, // Yesterday 10 AM
684+
{ timestamp: todayEarlyMorning2, key: 'today_1', category: 'task' }, // 7 AM today
685+
{ timestamp: todayEarlyMorning, key: 'today_2', category: 'decision' }, // 5 AM today
686+
{ timestamp: yesterdayMorning, key: 'yesterday_1', category: 'task' }, // Yesterday 10 AM
695687
{ timestamp: yesterdayAfternoon, key: 'yesterday_2', category: 'note' }, // Yesterday 3 PM
696-
{ hours: -72, key: 'days_ago_1', category: 'progress' }, // 3 days ago
697-
{ hours: -168, key: 'week_ago_1', category: 'task' }, // 1 week ago
698-
{ hours: -336, key: 'weeks_ago_1', category: 'error' }, // 2 weeks ago
688+
{ timestamp: createUTCDateByHours(-72), key: 'days_ago_1', category: 'progress' }, // 3 days ago
689+
{ timestamp: createUTCDateByHours(-168), key: 'week_ago_1', category: 'task' }, // 1 week ago
690+
{ timestamp: createUTCDateByHours(-336), key: 'weeks_ago_1', category: 'error' }, // 2 weeks ago
699691
];
700692

701-
timeOffsets.forEach(({ hours, timestamp, key, category }) => {
702-
const createdAt = timestamp || new Date(now.getTime() + hours * 3600000);
693+
timeOffsets.forEach(({ timestamp, key, category }) => {
694+
const createdAt = timestamp;
703695
db.prepare(
704696
`
705697
INSERT INTO context_items (id, session_id, key, value, category, priority, created_at)
@@ -724,7 +716,7 @@ describe('Enhanced Context Operations Integration Tests', () => {
724716
];
725717

726718
journalEntries.forEach(({ hours, entry, mood }) => {
727-
const createdAt = new Date(now.getTime() + hours * 3600000);
719+
const createdAt = createUTCDateByHours(hours);
728720
db.prepare(
729721
`
730722
INSERT INTO journal_entries (id, session_id, entry, mood, created_at)
@@ -736,9 +728,10 @@ describe('Enhanced Context Operations Integration Tests', () => {
736728

737729
describe('Basic timeline functionality', () => {
738730
it('should group items by time period', () => {
739-
const now = new Date();
740-
const oneDayAgo = new Date(now.getTime() - 24 * 3600000);
741-
const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 3600000);
731+
// TIMEZONE-SAFE: Use UTC-based date calculations
732+
const { today: _today } = createTimelineTestDates();
733+
const oneDayAgo = createUTCDateByHours(-24);
734+
const oneWeekAgo = createUTCDateByHours(-7 * 24);
742735

743736
// Get items from different periods
744737
const todayItems = db
@@ -885,13 +878,12 @@ describe('Enhanced Context Operations Integration Tests', () => {
885878

886879
describe('relativeTime parameter', () => {
887880
it('should handle "today" relative time', () => {
888-
// Use UTC to ensure consistent behavior across timezones
889-
const todayUTC = new Date();
890-
todayUTC.setUTCHours(0, 0, 0, 0);
881+
// TIMEZONE-SAFE: Use UTC start of day
882+
const todayStart = createUTCDate(0, 0, 0, 0);
891883

892884
const items = db
893885
.prepare('SELECT * FROM context_items WHERE session_id = ? AND created_at >= ?')
894-
.all(testSessionId, todayUTC.toISOString()) as any[];
886+
.all(testSessionId, todayStart.toISOString()) as any[];
895887

896888
expect(items.length).toBeGreaterThan(0);
897889
// Check for items we know are created "today" based on our test setup
@@ -902,28 +894,25 @@ describe('Enhanced Context Operations Integration Tests', () => {
902894
});
903895

904896
it('should handle "yesterday" relative time', () => {
905-
const yesterday = new Date();
906-
yesterday.setDate(yesterday.getDate() - 1);
907-
yesterday.setHours(0, 0, 0, 0);
908-
909-
const today = new Date();
910-
today.setHours(0, 0, 0, 0);
897+
// TIMEZONE-SAFE: Use UTC dates for day boundaries
898+
const yesterdayStart = createUTCDate(-1, 0, 0, 0);
899+
const todayStart = createUTCDate(0, 0, 0, 0);
911900

912901
const items = db
913902
.prepare(
914903
'SELECT * FROM context_items WHERE session_id = ? AND created_at >= ? AND created_at < ?'
915904
)
916-
.all(testSessionId, yesterday.toISOString(), today.toISOString()) as any[];
905+
.all(testSessionId, yesterdayStart.toISOString(), todayStart.toISOString()) as any[];
917906

918907
expect(items.some(i => i.key.includes('yesterday'))).toBe(true);
919908
});
920909

921910
it('should handle "X hours ago" format', () => {
922-
// Create test items relative to current time for this specific test
923-
const now = new Date();
924-
const oneHourAgo = new Date(now.getTime() - 1 * 3600000);
925-
const twoHoursAgo = new Date(now.getTime() - 2 * 3600000);
926-
const fiveHoursAgo = new Date(now.getTime() - 5 * 3600000);
911+
// TIMEZONE-SAFE: Use UTC-based hour calculations
912+
const { today: _today } = createTimelineTestDates();
913+
const oneHourAgo = createUTCDateByHours(-1);
914+
const twoHoursAgo = createUTCDateByHours(-2);
915+
const fiveHoursAgo = createUTCDateByHours(-5);
927916

928917
// Add test items with specific relative timestamps
929918
db.prepare(
@@ -966,7 +955,7 @@ describe('Enhanced Context Operations Integration Tests', () => {
966955
);
967956

968957
// Query for items created 2 hours ago or less
969-
const queryTime = new Date(now.getTime() - 2.1 * 3600000); // 2.1 hours ago to ensure we catch 2 hour old items
958+
const queryTime = createUTCDateByHours(-2.1); // 2.1 hours ago to ensure we catch 2 hour old items
970959
const items = db
971960
.prepare('SELECT * FROM context_items WHERE session_id = ? AND created_at >= ?')
972961
.all(testSessionId, queryTime.toISOString()) as any[];
@@ -982,8 +971,8 @@ describe('Enhanced Context Operations Integration Tests', () => {
982971
});
983972

984973
it('should handle "X days ago" format', () => {
985-
const threeDaysAgo = new Date();
986-
threeDaysAgo.setDate(threeDaysAgo.getDate() - 3);
974+
// TIMEZONE-SAFE: Use UTC date arithmetic
975+
const threeDaysAgo = createUTCDate(-3);
987976

988977
const items = db
989978
.prepare('SELECT * FROM context_items WHERE session_id = ? AND created_at >= ?')
@@ -1003,11 +992,10 @@ describe('Enhanced Context Operations Integration Tests', () => {
1003992
});
1004993

1005994
it('should handle "this week" relative time', () => {
1006-
const startOfWeek = new Date();
1007-
const day = startOfWeek.getDay();
1008-
const diff = startOfWeek.getDate() - day;
1009-
startOfWeek.setDate(diff);
1010-
startOfWeek.setHours(0, 0, 0, 0);
995+
// TIMEZONE-SAFE: Calculate start of week in UTC
996+
const { today: _today } = createTimelineTestDates();
997+
const dayOfWeek = _today.getUTCDay();
998+
const startOfWeek = createUTCDate(-dayOfWeek, 0, 0, 0);
1011999

10121000
const items = db
10131001
.prepare('SELECT * FROM context_items WHERE session_id = ? AND created_at >= ?')
@@ -1017,14 +1005,11 @@ describe('Enhanced Context Operations Integration Tests', () => {
10171005
});
10181006

10191007
it('should handle "last week" relative time', () => {
1020-
const startOfLastWeek = new Date();
1021-
const day = startOfLastWeek.getDay();
1022-
const diff = startOfLastWeek.getDate() - day - 7;
1023-
startOfLastWeek.setDate(diff);
1024-
startOfLastWeek.setHours(0, 0, 0, 0);
1025-
1026-
const endOfLastWeek = new Date(startOfLastWeek);
1027-
endOfLastWeek.setDate(endOfLastWeek.getDate() + 7);
1008+
// TIMEZONE-SAFE: Calculate last week's boundaries in UTC
1009+
const { today: _today } = createTimelineTestDates();
1010+
const dayOfWeek = _today.getUTCDay();
1011+
const startOfLastWeek = createUTCDate(-dayOfWeek - 7, 0, 0, 0);
1012+
const endOfLastWeek = createUTCDate(-dayOfWeek, 0, 0, 0);
10281013

10291014
const items = db
10301015
.prepare(
@@ -1219,8 +1204,8 @@ describe('Enhanced Context Operations Integration Tests', () => {
12191204

12201205
describe('Combining timeline parameters', () => {
12211206
it('should combine categories and date filters', () => {
1222-
const twoDaysAgo = new Date();
1223-
twoDaysAgo.setDate(twoDaysAgo.getDate() - 2);
1207+
// TIMEZONE-SAFE: Use UTC date calculations
1208+
const twoDaysAgo = createUTCDate(-2);
12241209

12251210
const items = db
12261211
.prepare(
@@ -1297,9 +1282,10 @@ describe('Enhanced Context Operations Integration Tests', () => {
12971282
});
12981283

12991284
it('should work with only startDate and endDate as before', () => {
1300-
const endDate = new Date();
1301-
const startDate = new Date();
1302-
startDate.setDate(startDate.getDate() - 7);
1285+
// TIMEZONE-SAFE: Use UTC date range
1286+
const { today: _today } = createTimelineTestDates();
1287+
const startDate = createUTCDate(-7);
1288+
const endDate = _today;
13031289

13041290
const items = db
13051291
.prepare(

0 commit comments

Comments
 (0)