/** * Static Node tests for timezone-aware date filtering in odata-helpers.js. * * These tests exercise buildDateFilter / localDateStringToInstant / processDateRange * with known local dates and assert that the produced UTC ISO boundaries are correct. * * Run with: * node tests/date-filter-timezone.test.js */ const assert = require('assert'); const { buildDateFilter, processDateRange, startOfDay, endOfDay, localDateStringToInstant, getEffectiveTimeZone } = require('../utils/odata-helpers'); function assertBoundary(actualISO, expectedISO, label) { if (actualISO !== expectedISO) { throw new Error(`${label}: expected ${expectedISO}, got ${actualISO}`); } } // We control the timezone via MS_TIMEZONE env var before requiring config. // For these tests we set America/New_York explicitly. assert.strictEqual(getEffectiveTimeZone('America/New_York'), 'America/New_York', 'IANA timezone should pass through'); assert.strictEqual(getEffectiveTimeZone('Eastern Standard Time'), 'America/New_York', 'Windows timezone should map to IANA'); // Test 1: dateFrom/dateTo as plain date strings in America/New_York (EDT). // 2024-06-21 EDT is UTC-4, so local midnight is 2024-06-21T04:00:00.000Z, // and end-of-day is 2024-06-22T03:59:59.999Z. { const conditions = buildDateFilter('2024-06-21', '2024-06-21', null, 'America/New_York'); console.log('Test 1 conditions (NY EDT):', conditions); assert.strictEqual(conditions.length, 2, 'should produce two conditions'); assertBoundary( conditions[0], 'receivedDateTime ge 2024-06-21T04:00:00.000Z', 'Test 1 start of local day in EDT' ); assertBoundary( conditions[1], 'receivedDateTime le 2024-06-22T03:59:59.999Z', 'Test 1 end of local day in EDT' ); } // Test 2: Plain date strings in America/New_York during EST (UTC-5). { const conditions = buildDateFilter('2024-01-15', '2024-01-15', null, 'America/New_York'); console.log('Test 2 conditions (NY EST):', conditions); assertBoundary( conditions[0], 'receivedDateTime ge 2024-01-15T05:00:00.000Z', 'Test 2 start of local day in EST' ); assertBoundary( conditions[1], 'receivedDateTime le 2024-01-16T04:59:59.999Z', 'Test 2 end of local day in EST' ); } // Test 3: Plain date strings in Pacific Time during PDT (UTC-7). { const conditions = buildDateFilter('2024-06-21', '2024-06-21', null, 'America/Los_Angeles'); console.log('Test 3 conditions (LA PDT):', conditions); assertBoundary( conditions[0], 'receivedDateTime ge 2024-06-21T07:00:00.000Z', 'Test 3 start of local day in PDT' ); assertBoundary( conditions[1], 'receivedDateTime le 2024-06-22T06:59:59.999Z', 'Test 3 end of local day in PDT' ); } // Test 4: ISO strings with explicit offsets are respected as-is. { const conditions = buildDateFilter('2024-06-21T08:00:00-04:00', '2024-06-21T18:00:00-04:00', null, 'America/New_York'); console.log('Test 4 conditions (offset provided):', conditions); assertBoundary( conditions[0], 'receivedDateTime ge 2024-06-21T12:00:00.000Z', 'Test 4 explicit offset fromDate' ); assertBoundary( conditions[1], 'receivedDateTime le 2024-06-21T22:00:00.000Z', 'Test 4 explicit offset toDate' ); } // Test 5: Windows timezone name should map correctly. { const conditions = buildDateFilter('2024-06-21', '2024-06-21', null, 'Pacific Standard Time'); console.log('Test 5 conditions (Windows PST in summer -> PDT):', conditions); assertBoundary( conditions[0], 'receivedDateTime ge 2024-06-21T07:00:00.000Z', 'Test 5 Windows Pacific start' ); } // Test 6: dateRange='today' boundary for a known UTC instant. // Force "now" by passing a specific date into processDateRange indirectly via startOfDay. // We instead test processDateRange by stubbing Date in a narrow scope. { const savedDate = Date; const fixedNow = new Date('2024-06-21T02:30:00.000Z'); // 2024-06-20 22:30 EDT global.Date = class extends savedDate { constructor(...args) { if (args.length === 0) { super(fixedNow); } else { super(...args); } } static now() { return fixedNow.getTime(); } }; // Reload helpers so the new Date constructor is used. delete require.cache[require.resolve('../utils/odata-helpers')]; delete require.cache[require.resolve('../utils/timezone-mapper')]; delete require.cache[require.resolve('../config')]; const helpers = require('../utils/odata-helpers'); const range = helpers.processDateRange('today', 'America/New_York'); console.log('Test 6 today range:', range); assertBoundary( range.from.toISOString(), '2024-06-20T04:00:00.000Z', 'Test 6 today start in EDT (local date is 2024-06-20)' ); assertBoundary( range.to.toISOString(), '2024-06-21T03:59:59.999Z', 'Test 6 today end in EDT' ); global.Date = savedDate; delete require.cache[require.resolve('../utils/odata-helpers')]; delete require.cache[require.resolve('../utils/timezone-mapper')]; delete require.cache[require.resolve('../config')]; } // Test 7: dateRange='yesterday' for the same fixed EDT instant. { const savedDate = Date; const fixedNow = new Date('2024-06-21T02:30:00.000Z'); global.Date = class extends savedDate { constructor(...args) { if (args.length === 0) { super(fixedNow); } else { super(...args); } } static now() { return fixedNow.getTime(); } }; delete require.cache[require.resolve('../utils/odata-helpers')]; delete require.cache[require.resolve('../utils/timezone-mapper')]; delete require.cache[require.resolve('../config')]; const helpers = require('../utils/odata-helpers'); const range = helpers.processDateRange('yesterday', 'America/New_York'); console.log('Test 7 yesterday range:', range); assertBoundary( range.from.toISOString(), '2024-06-19T04:00:00.000Z', 'Test 7 yesterday start in EDT' ); assertBoundary( range.to.toISOString(), '2024-06-20T03:59:59.999Z', 'Test 7 yesterday end in EDT' ); global.Date = savedDate; delete require.cache[require.resolve('../utils/odata-helpers')]; delete require.cache[require.resolve('../utils/timezone-mapper')]; delete require.cache[require.resolve('../config')]; } // Test 8: dateRange='thisweek' (Sunday-based) in EDT around the same instant. // Local date is Thursday 2024-06-20, so Sunday is 2024-06-16. { const savedDate = Date; const fixedNow = new Date('2024-06-21T02:30:00.000Z'); global.Date = class extends savedDate { constructor(...args) { if (args.length === 0) { super(fixedNow); } else { super(...args); } } static now() { return fixedNow.getTime(); } }; delete require.cache[require.resolve('../utils/odata-helpers')]; delete require.cache[require.resolve('../utils/timezone-mapper')]; delete require.cache[require.resolve('../config')]; const helpers = require('../utils/odata-helpers'); const range = helpers.processDateRange('thisweek', 'America/New_York'); console.log('Test 8 thisweek range:', range); assertBoundary( range.from.toISOString(), '2024-06-16T04:00:00.000Z', 'Test 8 thisweek start (Sunday 2024-06-16 EDT)' ); global.Date = savedDate; delete require.cache[require.resolve('../utils/odata-helpers')]; delete require.cache[require.resolve('../utils/timezone-mapper')]; delete require.cache[require.resolve('../config')]; } // Test 9: dateRange='thismonth' in EDT. { const savedDate = Date; const fixedNow = new Date('2024-06-21T02:30:00.000Z'); global.Date = class extends savedDate { constructor(...args) { if (args.length === 0) { super(fixedNow); } else { super(...args); } } static now() { return fixedNow.getTime(); } }; delete require.cache[require.resolve('../utils/odata-helpers')]; delete require.cache[require.resolve('../utils/timezone-mapper')]; delete require.cache[require.resolve('../config')]; const helpers = require('../utils/odata-helpers'); const range = helpers.processDateRange('thismonth', 'America/New_York'); console.log('Test 9 thismonth range:', range); assertBoundary( range.from.toISOString(), '2024-06-01T04:00:00.000Z', 'Test 9 thismonth start (June 1 EDT)' ); global.Date = savedDate; delete require.cache[require.resolve('../utils/odata-helpers')]; delete require.cache[require.resolve('../utils/timezone-mapper')]; delete require.cache[require.resolve('../config')]; } console.log('\nAll timezone-aware date filter tests passed.');