timezone-detector.test.js
1 /** 2 * Timezone Detector Tests 3 * 4 * Tests for detectTimezone() and getSiteTimezone(). 5 * Uses mock DB for getSiteTimezone to avoid SQLite I/O. 6 */ 7 8 import { test, describe } from 'node:test'; 9 import assert from 'node:assert/strict'; 10 11 import { detectTimezone, getSiteTimezone } from '../../src/utils/timezone-detector.js'; 12 13 // ─── detectTimezone ──────────────────────────────────────────────────────────── 14 15 describe('detectTimezone', () => { 16 test('defaults to America/New_York when no location data', () => { 17 assert.equal(detectTimezone(null, null), 'America/New_York'); 18 assert.equal(detectTimezone(undefined, undefined), 'America/New_York'); 19 }); 20 21 test('exact city match for US cities', () => { 22 assert.equal(detectTimezone('new york', 'US'), 'America/New_York'); 23 assert.equal(detectTimezone('chicago', 'US'), 'America/Chicago'); 24 assert.equal(detectTimezone('denver', 'US'), 'America/Denver'); 25 assert.equal(detectTimezone('los angeles', 'US'), 'America/Los_Angeles'); 26 assert.equal(detectTimezone('phoenix', 'US'), 'America/Phoenix'); 27 assert.equal(detectTimezone('honolulu', 'US'), 'Pacific/Honolulu'); 28 assert.equal(detectTimezone('anchorage', 'US'), 'America/Anchorage'); 29 }); 30 31 test('case-insensitive city lookup', () => { 32 assert.equal(detectTimezone('New York', 'US'), 'America/New_York'); 33 assert.equal(detectTimezone('LOS ANGELES', 'US'), 'America/Los_Angeles'); 34 assert.equal(detectTimezone('Chicago', 'US'), 'America/Chicago'); 35 }); 36 37 test('case-insensitive country code', () => { 38 assert.equal(detectTimezone('sydney', 'au'), 'Australia/Sydney'); 39 assert.equal(detectTimezone('toronto', 'ca'), 'America/Toronto'); 40 }); 41 42 test('city lookup for Australian cities', () => { 43 assert.equal(detectTimezone('sydney', 'AU'), 'Australia/Sydney'); 44 assert.equal(detectTimezone('melbourne', 'AU'), 'Australia/Melbourne'); 45 assert.equal(detectTimezone('perth', 'AU'), 'Australia/Perth'); 46 assert.equal(detectTimezone('adelaide', 'AU'), 'Australia/Adelaide'); 47 assert.equal(detectTimezone('darwin', 'AU'), 'Australia/Darwin'); 48 assert.equal(detectTimezone('brisbane', 'AU'), 'Australia/Brisbane'); 49 }); 50 51 test('city lookup for Canadian cities', () => { 52 assert.equal(detectTimezone('toronto', 'CA'), 'America/Toronto'); 53 assert.equal(detectTimezone('vancouver', 'CA'), 'America/Vancouver'); 54 assert.equal(detectTimezone('calgary', 'CA'), 'America/Edmonton'); 55 assert.equal(detectTimezone("st. john's", 'CA'), 'America/St_Johns'); 56 }); 57 58 test('partial city name match', () => { 59 // "San Francisco Bay Area" should match "san francisco" 60 assert.equal(detectTimezone('San Francisco Bay Area', 'US'), 'America/Los_Angeles'); 61 }); 62 63 test('country fallback when city not in CITY_TIMEZONES', () => { 64 // Obscure US city → country fallback 65 assert.equal(detectTimezone('Springfield', 'US'), 'America/New_York'); // US country default 66 }); 67 68 test('country-level timezone for single-timezone countries', () => { 69 assert.equal(detectTimezone(null, 'AU'), 'Australia/Sydney'); 70 assert.equal(detectTimezone(null, 'UK'), 'Europe/London'); // UK alias for GB (migration added to COUNTRY_TIMEZONES) 71 assert.equal(detectTimezone(null, 'GB'), 'Europe/London'); 72 assert.equal(detectTimezone(null, 'DE'), 'Europe/Berlin'); 73 assert.equal(detectTimezone(null, 'JP'), 'Asia/Tokyo'); 74 assert.equal(detectTimezone(null, 'IN'), 'Asia/Kolkata'); 75 assert.equal(detectTimezone(null, 'NZ'), 'Pacific/Auckland'); 76 assert.equal(detectTimezone(null, 'ZA'), 'Africa/Johannesburg'); 77 }); 78 79 test('defaults to America/New_York when country not in map', () => { 80 assert.equal(detectTimezone(null, 'UNKNOWN'), 'America/New_York'); 81 assert.equal(detectTimezone('SomeCity', 'XX'), 'America/New_York'); 82 }); 83 84 test('Mexico city-specific timezones', () => { 85 assert.equal(detectTimezone('cancun', 'MX'), 'America/Cancun'); 86 assert.equal(detectTimezone('tijuana', 'MX'), 'America/Tijuana'); 87 assert.equal(detectTimezone('mexico city', 'MX'), 'America/Mexico_City'); 88 }); 89 }); 90 91 // ─── getSiteTimezone ─────────────────────────────────────────────────────────── 92 93 describe('getSiteTimezone', () => { 94 test('returns timezone from site city and country_code', () => { 95 const db = { 96 prepare: () => ({ 97 get: () => ({ city: 'sydney', country_code: 'AU' }), 98 }), 99 }; 100 const result = getSiteTimezone(1, db); 101 assert.equal(result, 'Australia/Sydney'); 102 }); 103 104 test('returns America/New_York when site not found', () => { 105 const db = { 106 prepare: () => ({ 107 get: () => undefined, 108 }), 109 }; 110 const result = getSiteTimezone(999, db); 111 assert.equal(result, 'America/New_York'); 112 }); 113 114 test('handles site with no city (country fallback)', () => { 115 const db = { 116 prepare: () => ({ 117 get: () => ({ city: null, country_code: 'DE' }), 118 }), 119 }; 120 const result = getSiteTimezone(1, db); 121 assert.equal(result, 'Europe/Berlin'); 122 }); 123 124 test('passes siteId to SQL query', () => { 125 let capturedArgs = null; 126 const db = { 127 prepare: () => ({ 128 get: (...args) => { 129 capturedArgs = args; 130 return { city: 'paris', country_code: 'FR' }; 131 }, 132 }), 133 }; 134 getSiteTimezone(42, db); 135 assert.equal(capturedArgs[0], 42); 136 }); 137 });