phone-normalizer-supplement2.test.js
1 /** 2 * Phone Normalizer Supplement 2 — Cover isValidSmsNumber and 3 * remaining edge cases in stripDomesticZero / addCountryCode 4 * 5 * Existing tests cover: 6 * - normalizePhoneNumber (comprehensive) 7 * - normalizePhoneNumbers (comprehensive) 8 * - addCountryCode (basic) 9 * - isFakeNumber (supplement 1) 10 * 11 * This supplement covers: 12 * - isValidSmsNumber (lines 205-245) — completely untested 13 * - stripDomesticZero for various country codes (44, 64, 33, 353, etc.) 14 * - addCountryCode with UK, NZ, GB, IE country codes 15 * - Edge cases: 3-digit calling codes (353/IE), empty string, etc. 16 */ 17 18 import { test, describe } from 'node:test'; 19 import assert from 'node:assert/strict'; 20 import { 21 normalizePhoneNumber, 22 addCountryCode, 23 isFakeNumber, 24 isValidSmsNumber, 25 } from '../../src/utils/phone-normalizer.js'; 26 27 // ── isValidSmsNumber ──────────────────────────────────────────────────────── 28 29 describe('isValidSmsNumber', () => { 30 test('returns null for valid AU mobile', () => { 31 assert.equal(isValidSmsNumber('+61412345678'), null); 32 }); 33 34 test('returns null for valid US mobile', () => { 35 assert.equal(isValidSmsNumber('+12125551234'), null); 36 }); 37 38 test('returns null for valid UK mobile', () => { 39 assert.equal(isValidSmsNumber('+447821234567'), null); 40 }); 41 42 test('returns null for valid NZ mobile', () => { 43 assert.equal(isValidSmsNumber('+64211234567'), null); 44 }); 45 46 test('returns error for null input', () => { 47 const result = isValidSmsNumber(null); 48 assert.ok(result, 'should return error string'); 49 assert.match(result, /Empty or non-string/i); 50 }); 51 52 test('returns error for undefined', () => { 53 assert.ok(isValidSmsNumber(undefined)); 54 }); 55 56 test('returns error for empty string', () => { 57 assert.ok(isValidSmsNumber('')); 58 }); 59 60 test('returns error for non-string (number)', () => { 61 assert.ok(isValidSmsNumber(61412345678)); 62 }); 63 64 test('returns error for too-short number', () => { 65 const result = isValidSmsNumber('+1234'); 66 assert.ok(result); 67 assert.match(result, /too short/i); 68 }); 69 70 test('returns error for too-long number (>15 digits)', () => { 71 const result = isValidSmsNumber('+1234567890123456'); 72 assert.ok(result); 73 assert.match(result, /too many digits/i); 74 }); 75 76 test('returns error for +0 prefix', () => { 77 const result = isValidSmsNumber('+0412345678'); 78 assert.ok(result); 79 assert.match(result, /starts with \+0/i); 80 }); 81 82 test('returns error for missing + (bare number)', () => { 83 const result = isValidSmsNumber('61412345678'); 84 assert.ok(result); 85 assert.match(result, /missing country code/i); 86 }); 87 88 test('returns error for US 555 placeholder', () => { 89 const result = isValidSmsNumber('+15551234567'); 90 assert.ok(result); 91 assert.match(result, /555 placeholder/i); 92 }); 93 94 test('returns error for repeated-digit placeholder', () => { 95 const result = isValidSmsNumber('+11111111'); 96 assert.ok(result); 97 assert.match(result, /repeated-digit/i); 98 }); 99 100 test('returns error for sequential placeholder 1234567890', () => { 101 const result = isValidSmsNumber('+11234567890'); 102 assert.ok(result); 103 assert.match(result, /sequential placeholder/i); 104 }); 105 106 test('returns error for reverse sequential 0987654321', () => { 107 const result = isValidSmsNumber('+10987654321'); 108 assert.ok(result); 109 assert.match(result, /sequential placeholder/i); 110 }); 111 112 test('returns error for AU toll-free 1800', () => { 113 const result = isValidSmsNumber('+611800123456'); 114 assert.ok(result); 115 assert.match(result, /AU toll-free/i); 116 }); 117 118 test('returns error for AU toll-free 1300', () => { 119 const result = isValidSmsNumber('+611300123456'); 120 assert.ok(result); 121 assert.match(result, /AU toll-free/i); 122 }); 123 124 test('returns error for AU toll-free 1900', () => { 125 const result = isValidSmsNumber('+611900123456'); 126 assert.ok(result); 127 assert.match(result, /AU toll-free/i); 128 }); 129 130 test('returns error for US toll-free 800', () => { 131 const result = isValidSmsNumber('+18001234567'); 132 assert.ok(result); 133 assert.match(result, /US\/CA toll-free/i); 134 }); 135 136 test('returns error for US toll-free 888', () => { 137 const result = isValidSmsNumber('+18881234567'); 138 assert.ok(result); 139 assert.match(result, /US\/CA toll-free/i); 140 }); 141 142 test('returns error for US toll-free 877', () => { 143 const result = isValidSmsNumber('+18771234567'); 144 assert.ok(result); 145 assert.match(result, /US\/CA toll-free/i); 146 }); 147 148 test('returns error for US toll-free 866', () => { 149 const result = isValidSmsNumber('+18661234567'); 150 assert.ok(result); 151 assert.match(result, /US\/CA toll-free/i); 152 }); 153 154 test('returns error for US toll-free 855', () => { 155 const result = isValidSmsNumber('+18551234567'); 156 assert.ok(result); 157 assert.match(result, /US\/CA toll-free/i); 158 }); 159 160 test('returns error for US toll-free 844', () => { 161 const result = isValidSmsNumber('+18441234567'); 162 assert.ok(result); 163 assert.match(result, /US\/CA toll-free/i); 164 }); 165 166 test('returns error for US toll-free 833', () => { 167 const result = isValidSmsNumber('+18331234567'); 168 assert.ok(result); 169 assert.match(result, /US\/CA toll-free/i); 170 }); 171 172 test('returns error for short code (few subscriber digits)', () => { 173 // +44 followed by 4 digits (after stripping 44) → short code 174 const result = isValidSmsNumber('+441234'); 175 assert.ok(result); 176 // Could match "too short" or "short code" 177 assert.ok(result.length > 0); 178 }); 179 180 test('boundary: exactly 8 digits is valid', () => { 181 // +65 91234567 (Singapore, 8 digits total with country code) 182 assert.equal(isValidSmsNumber('+6591234567'), null); 183 }); 184 185 test('boundary: exactly 15 digits is valid (E.164 max)', () => { 186 // 15 digits that don't contain sequential placeholder patterns (1234567890 or 0987654321) 187 assert.equal(isValidSmsNumber('+614239876512340'), null); 188 }); 189 }); 190 191 // ── stripDomesticZero — via normalizePhoneNumber ──────────────────────────── 192 193 describe('stripDomesticZero — country-specific cases', () => { 194 test('strips trunk 0 from UK number +440XXXXXXXXX', () => { 195 assert.equal(normalizePhoneNumber('+4407821234567'), '+447821234567'); 196 }); 197 198 test('strips trunk 0 from NZ number +640XXXXXXXX', () => { 199 assert.equal(normalizePhoneNumber('+640274282748'), '+64274282748'); 200 }); 201 202 test('strips trunk 0 from French number +330XXXXXXXXX', () => { 203 assert.equal(normalizePhoneNumber('+330612345678'), '+33612345678'); 204 }); 205 206 test('strips trunk 0 from German number +490XXXXXXXXX', () => { 207 assert.equal(normalizePhoneNumber('+4901701234567'), '+491701234567'); 208 }); 209 210 test('strips trunk 0 from Irish number +3530XXXXXXXX', () => { 211 // 353 is a 3-digit calling code 212 assert.equal(normalizePhoneNumber('+353087123456'), '+35387123456'); 213 }); 214 215 test('strips trunk 0 from South African number +270XXXXXXXXX', () => { 216 assert.equal(normalizePhoneNumber('+270821234567'), '+27821234567'); 217 }); 218 219 test('strips trunk 0 from Japanese number +810XXXXXXXXX', () => { 220 assert.equal(normalizePhoneNumber('+8109012345678'), '+819012345678'); 221 }); 222 223 test('strips trunk 0 from Indian number +910XXXXXXXXX', () => { 224 assert.equal(normalizePhoneNumber('+9109876543210'), '+919876543210'); 225 }); 226 227 test('does NOT strip 0 when subscriber length would be out of range', () => { 228 // +44 followed by 0 then only 5 digits — stripping would give 5 digits, below range [10,11] 229 assert.equal(normalizePhoneNumber('+44012345'), '+44012345'); 230 }); 231 232 test('does NOT strip 0 from US numbers (no trunk prefix in US)', () => { 233 // +1 is not in TRUNK_ZERO_COUNTRIES 234 assert.equal(normalizePhoneNumber('+10123456789'), '+10123456789'); 235 }); 236 237 test('handles number that already has no trunk 0', () => { 238 assert.equal(normalizePhoneNumber('+447821234567'), '+447821234567'); 239 }); 240 }); 241 242 // ── addCountryCode — additional countries ─────────────────────────────────── 243 244 describe('addCountryCode — additional country codes', () => { 245 test('UK (GB): adds +44 and strips leading 0', () => { 246 assert.equal(addCountryCode('07821234567', 'GB'), '+447821234567'); 247 }); 248 249 test('NZ: adds +64 and strips leading 0', () => { 250 assert.equal(addCountryCode('0211234567', 'NZ'), '+64211234567'); 251 }); 252 253 test('IE: adds +353 and strips leading 0', () => { 254 assert.equal(addCountryCode('0871234567', 'IE'), '+353871234567'); 255 }); 256 257 test('CA: adds +1 (same as US, no trunk 0 stripping)', () => { 258 assert.equal(addCountryCode('4161234567', 'CA'), '+14161234567'); 259 }); 260 261 test('handles empty string phone number', () => { 262 const result = addCountryCode('', 'AU'); 263 assert.equal(result, ''); 264 }); 265 266 test('handles phone number that is just a 0', () => { 267 // Stripping the 0 leaves empty string, then prepending calling code 268 const result = addCountryCode('0', 'AU'); 269 assert.equal(result, '+61'); 270 }); 271 272 test('handles UK alias — getCountryByCode accepts GB', () => { 273 assert.equal(addCountryCode('07911123456', 'GB'), '+447911123456'); 274 }); 275 }); 276 277 // ── isFakeNumber — additional edge cases ──────────────────────────────────── 278 279 describe('isFakeNumber — additional patterns', () => { 280 test('returns false for number with some repeated digits but not 7+', () => { 281 assert.equal(isFakeNumber('+611111234'), false); 282 }); 283 284 test('returns true for +15550100 (US 555-01xx pattern)', () => { 285 // The pattern is +1 + (area code) + 55501xx 286 // +1 613 555 0100 → digits = 16135550100 287 assert.equal(isFakeNumber('+16135550100'), true); 288 }); 289 290 test('returns false for legitimate +15551234 (not enough digits for 555 pattern)', () => { 291 // Only 8 digits total — too short for the 555 area code pattern 292 assert.equal(isFakeNumber('+15551234'), false); 293 }); 294 295 test('returns true for +1 (any area) 555-01xx', () => { 296 assert.equal(isFakeNumber('+12125550199'), true); 297 }); 298 299 test('returns false for all-zeros short string (less than 7)', () => { 300 // 6 zeros — doesn't match {6,} which requires 7+ 301 assert.equal(isFakeNumber('+000000'), false); 302 }); 303 });