/ tests / utils / phone-normalizer-supplement2.test.js
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  });