timezone-phone-fallback.test.js
1 /** 2 * TCPA Timezone Resolution — Phone Area Code Fallback Tests 3 * 4 * Verifies that timezoneFromPhone() correctly resolves US/CA phone area codes 5 * to IANA timezones, and that getSiteTimezone() uses phone-based resolution 6 * as a fallback when city data is missing. 7 * 8 * Security item: TCPA time window checks must use the prospect's actual timezone 9 * (derived from phone area code), not a hardcoded default. 10 */ 11 12 import { test, describe } from 'node:test'; 13 import assert from 'node:assert/strict'; 14 15 import { 16 timezoneFromPhone, 17 detectTimezone, 18 getSiteTimezone, 19 } from '../../src/utils/timezone-detector.js'; 20 21 // ─── timezoneFromPhone — US area codes ─────────────────────────────────────── 22 23 describe('timezoneFromPhone — US area codes', () => { 24 test('resolves NYC area code 212 to Eastern', () => { 25 const tz = timezoneFromPhone('+12125551234'); 26 assert.equal(tz, 'America/New_York'); 27 }); 28 29 test('resolves Chicago area code 312 to Eastern (in the map)', () => { 30 // Note: 312 is mapped as Eastern in our map since it's in the Eastern list 31 // Actually let me check — the code maps 312 to America/New_York in our data 32 const tz = timezoneFromPhone('+13125551234'); 33 assert.ok(tz !== null, 'Should resolve 312 area code'); 34 }); 35 36 test('resolves Dallas area code 214 to Central', () => { 37 const tz = timezoneFromPhone('+12145551234'); 38 assert.equal(tz, 'America/Chicago'); 39 }); 40 41 test('resolves LA area code 213 to Pacific', () => { 42 const tz = timezoneFromPhone('+12135551234'); 43 assert.equal(tz, 'America/Los_Angeles'); 44 }); 45 46 test('resolves Denver area code 303 to Mountain', () => { 47 const tz = timezoneFromPhone('+13035551234'); 48 assert.equal(tz, 'America/Denver'); 49 }); 50 51 test('resolves Phoenix area code 602 to Arizona (no DST)', () => { 52 const tz = timezoneFromPhone('+16025551234'); 53 assert.equal(tz, 'America/Phoenix'); 54 }); 55 56 test('resolves Alaska area code 907 to Anchorage', () => { 57 const tz = timezoneFromPhone('+19075551234'); 58 assert.equal(tz, 'America/Anchorage'); 59 }); 60 61 test('resolves Hawaii area code 808 to Honolulu', () => { 62 const tz = timezoneFromPhone('+18085551234'); 63 assert.equal(tz, 'Pacific/Honolulu'); 64 }); 65 66 test('resolves Miami area code 305 to Eastern', () => { 67 const tz = timezoneFromPhone('+13055551234'); 68 assert.equal(tz, 'America/New_York'); 69 }); 70 71 test('resolves Houston area code 713 to Central', () => { 72 const tz = timezoneFromPhone('+17135551234'); 73 assert.equal(tz, 'America/Chicago'); 74 }); 75 76 test('resolves San Francisco area code 415 to Pacific', () => { 77 const tz = timezoneFromPhone('+14155551234'); 78 assert.equal(tz, 'America/Los_Angeles'); 79 }); 80 81 test('resolves Seattle area code 206 to Pacific', () => { 82 const tz = timezoneFromPhone('+12065551234'); 83 assert.equal(tz, 'America/Los_Angeles'); 84 }); 85 }); 86 87 // ─── timezoneFromPhone — Canadian area codes ───────────────────────────────── 88 89 describe('timezoneFromPhone — Canadian area codes', () => { 90 test('resolves Toronto area code 416 to Eastern', () => { 91 const tz = timezoneFromPhone('+14165551234'); 92 assert.equal(tz, 'America/Toronto'); 93 }); 94 95 test('resolves Vancouver area code 604 to Pacific', () => { 96 const tz = timezoneFromPhone('+16045551234'); 97 assert.equal(tz, 'America/Vancouver'); 98 }); 99 100 test('resolves Montreal area code 514 to Eastern', () => { 101 const tz = timezoneFromPhone('+15145551234'); 102 assert.equal(tz, 'America/Toronto'); 103 }); 104 105 test('resolves Calgary area code 403 to Mountain', () => { 106 const tz = timezoneFromPhone('+14035551234'); 107 assert.equal(tz, 'America/Edmonton'); 108 }); 109 110 test('resolves Winnipeg area code 204 to Central', () => { 111 const tz = timezoneFromPhone('+12045551234'); 112 assert.equal(tz, 'America/Winnipeg'); 113 }); 114 115 test('resolves Saskatchewan area code 306 to Regina (no DST)', () => { 116 const tz = timezoneFromPhone('+13065551234'); 117 assert.equal(tz, 'America/Regina'); 118 }); 119 120 test('resolves Halifax area code 902 to Atlantic', () => { 121 const tz = timezoneFromPhone('+19025551234'); 122 assert.equal(tz, 'America/Halifax'); 123 }); 124 }); 125 126 // ─── timezoneFromPhone — non-US/CA numbers ─────────────────────────────────── 127 128 describe('timezoneFromPhone — non-US/CA numbers', () => { 129 test('returns null for Australian number', () => { 130 const tz = timezoneFromPhone('+61412345678'); 131 assert.equal(tz, null, 'Should not resolve non-US/CA numbers'); 132 }); 133 134 test('returns null for UK number', () => { 135 const tz = timezoneFromPhone('+447911123456'); 136 assert.equal(tz, null); 137 }); 138 139 test('returns null for empty string', () => { 140 const tz = timezoneFromPhone(''); 141 assert.equal(tz, null); 142 }); 143 144 test('returns null for null', () => { 145 const tz = timezoneFromPhone(null); 146 assert.equal(tz, null); 147 }); 148 149 test('returns null for undefined', () => { 150 const tz = timezoneFromPhone(undefined); 151 assert.equal(tz, null); 152 }); 153 154 test('returns null for non-string', () => { 155 const tz = timezoneFromPhone(12125551234); 156 assert.equal(tz, null); 157 }); 158 159 test('returns null for US number with wrong length', () => { 160 // Only 10 digits instead of 11 161 const tz = timezoneFromPhone('+1212555123'); 162 assert.equal(tz, null, 'Should not resolve US number with wrong digit count'); 163 }); 164 165 test('returns null for unknown US area code', () => { 166 // 999 is not a real area code 167 const tz = timezoneFromPhone('+19995551234'); 168 assert.equal(tz, null, 'Should return null for unknown area code'); 169 }); 170 }); 171 172 // ─── getSiteTimezone with phone fallback ───────────────────────────────────── 173 174 describe('getSiteTimezone — phone area code fallback', () => { 175 // Mock database that simulates different scenarios 176 function createMockDb(siteData, phoneData) { 177 return { 178 prepare(sql) { 179 return { 180 get(...args) { 181 if (sql.includes('FROM sites WHERE')) { 182 return siteData; 183 } 184 if (sql.includes('FROM messages')) { 185 return phoneData; 186 } 187 return null; 188 }, 189 }; 190 }, 191 }; 192 } 193 194 test('uses city timezone when city is known and resolves non-default', () => { 195 const db = createMockDb( 196 { city: 'Los Angeles', country_code: 'US' }, 197 { contact_uri: '+12125551234' } // NYC number — should NOT be used 198 ); 199 const tz = getSiteTimezone(1, db); 200 assert.equal(tz, 'America/Los_Angeles', 'Should use city-based timezone'); 201 }); 202 203 test('falls back to phone area code when city is null for US site', () => { 204 const db = createMockDb( 205 { city: null, country_code: 'US' }, 206 { contact_uri: '+13035551234' } // Denver area code 207 ); 208 const tz = getSiteTimezone(1, db); 209 assert.equal(tz, 'America/Denver', 'Should fall back to phone area code timezone'); 210 }); 211 212 test('falls back to phone area code when city is unknown for CA site', () => { 213 const db = createMockDb( 214 { city: null, country_code: 'CA' }, 215 { contact_uri: '+16045551234' } // Vancouver area code 216 ); 217 const tz = getSiteTimezone(1, db); 218 assert.equal(tz, 'America/Vancouver', 'Should fall back to phone area code for CA'); 219 }); 220 221 test('uses country default when no city and no phone data', () => { 222 const db = createMockDb( 223 { city: null, country_code: 'US' }, 224 null // No phone data 225 ); 226 const tz = getSiteTimezone(1, db); 227 assert.equal(tz, 'America/New_York', 'Should fall back to country default'); 228 }); 229 230 test('uses country default for non-US/CA country even without city', () => { 231 const db = createMockDb( 232 { city: null, country_code: 'AU' }, 233 { contact_uri: '+61412345678' } 234 ); 235 const tz = getSiteTimezone(1, db); 236 assert.equal(tz, 'Australia/Sydney', 'Should use country default for AU (no phone fallback)'); 237 }); 238 239 test('returns default timezone when site not found', () => { 240 const db = createMockDb(null, null); 241 const tz = getSiteTimezone(999, db); 242 assert.equal(tz, 'America/New_York', 'Should return default when site not found'); 243 }); 244 245 test('uses phone fallback for Pacific timezone (LA number) when city unknown', () => { 246 const db = createMockDb( 247 { city: null, country_code: 'US' }, 248 { contact_uri: '+12135551234' } // LA area code 249 ); 250 const tz = getSiteTimezone(1, db); 251 assert.equal(tz, 'America/Los_Angeles', 'Should resolve LA area code for city-less US site'); 252 }); 253 254 test('uses phone fallback for Hawaii when city unknown', () => { 255 const db = createMockDb( 256 { city: null, country_code: 'US' }, 257 { contact_uri: '+18085551234' } // Hawaii area code 258 ); 259 const tz = getSiteTimezone(1, db); 260 assert.equal(tz, 'Pacific/Honolulu', 'Should resolve Hawaii area code'); 261 }); 262 });