/ tests / config / countries.test.js
countries.test.js
  1  /**
  2   * Unit tests for src/config/countries.js
  3   *
  4   * Tests country configuration data integrity, lookup functions,
  5   * phone validation, GDPR filtering, and free email provider checks.
  6   */
  7  
  8  import { test, describe } from 'node:test';
  9  import assert from 'node:assert/strict';
 10  import {
 11    COUNTRIES,
 12    getCountryByCode,
 13    getCountryByGoogleDomain,
 14    isMobileNumber,
 15    getSupportedCountries,
 16    getGDPRCountries,
 17    FREE_EMAIL_PROVIDERS,
 18    isFreeEmailProvider,
 19  } from '../../src/config/countries.js';
 20  
 21  // ---------------------------------------------------------------------------
 22  // 1. COUNTRIES data integrity
 23  // ---------------------------------------------------------------------------
 24  describe('COUNTRIES data integrity', () => {
 25    const requiredFields = [
 26      'code',
 27      'name',
 28      'googleDomain',
 29      'language',
 30      'currency',
 31      'currencySymbol',
 32      'dateFormat',
 33      'phoneFormat',
 34      'mobilePattern',
 35      'commonCities',
 36      'requiresGDPRCheck',
 37      'timezone',
 38    ];
 39  
 40    test('contains exactly 26 country entries', () => {
 41      assert.equal(Object.keys(COUNTRIES).length, 26);
 42    });
 43  
 44    test('every country has all required fields', () => {
 45      for (const [key, country] of Object.entries(COUNTRIES)) {
 46        for (const field of requiredFields) {
 47          assert.ok(
 48            country[field] !== undefined && country[field] !== null,
 49            `${key} is missing required field "${field}"`
 50          );
 51        }
 52      }
 53    });
 54  
 55    test('all country codes are 2-letter uppercase strings', () => {
 56      for (const key of Object.keys(COUNTRIES)) {
 57        assert.match(key, /^[A-Z]{2}$/, `Key "${key}" is not a 2-letter uppercase code`);
 58      }
 59    });
 60  
 61    test('each country code field matches its object key', () => {
 62      for (const [key, country] of Object.entries(COUNTRIES)) {
 63        assert.equal(
 64          country.code,
 65          key,
 66          `${key}.code should equal "${key}" but got "${country.code}"`
 67        );
 68      }
 69    });
 70  
 71    test('all googleDomains start with "google."', () => {
 72      for (const [key, country] of Object.entries(COUNTRIES)) {
 73        assert.ok(
 74          country.googleDomain.startsWith('google.'),
 75          `${key}.googleDomain "${country.googleDomain}" does not start with "google."`
 76        );
 77      }
 78    });
 79  
 80    test('all googleDomains are unique', () => {
 81      const domains = Object.values(COUNTRIES).map(c => c.googleDomain);
 82      const uniqueDomains = new Set(domains);
 83      assert.equal(domains.length, uniqueDomains.size, 'Duplicate Google domains found');
 84    });
 85  
 86    test('all commonCities arrays are non-empty', () => {
 87      for (const [key, country] of Object.entries(COUNTRIES)) {
 88        assert.ok(Array.isArray(country.commonCities), `${key}.commonCities is not an array`);
 89        assert.ok(country.commonCities.length > 0, `${key}.commonCities is empty`);
 90      }
 91    });
 92  
 93    test('all mobilePatterns are RegExp instances', () => {
 94      for (const [key, country] of Object.entries(COUNTRIES)) {
 95        assert.ok(country.mobilePattern instanceof RegExp, `${key}.mobilePattern is not a RegExp`);
 96      }
 97    });
 98  
 99    test('all requiresGDPRCheck values are boolean', () => {
100      for (const [key, country] of Object.entries(COUNTRIES)) {
101        assert.equal(
102          typeof country.requiresGDPRCheck,
103          'boolean',
104          `${key}.requiresGDPRCheck is not a boolean`
105        );
106      }
107    });
108  
109    test('all phoneFormat values start with "+"', () => {
110      for (const [key, country] of Object.entries(COUNTRIES)) {
111        assert.ok(
112          country.phoneFormat.startsWith('+'),
113          `${key}.phoneFormat "${country.phoneFormat}" does not start with "+"`
114        );
115      }
116    });
117  
118    test('all language codes are lowercase strings', () => {
119      for (const [key, country] of Object.entries(COUNTRIES)) {
120        assert.match(
121          country.language,
122          /^[a-z]{2}$/,
123          `${key}.language "${country.language}" is not a valid 2-letter lowercase code`
124        );
125      }
126    });
127  
128    test('all currency codes are 3-letter uppercase strings', () => {
129      for (const [key, country] of Object.entries(COUNTRIES)) {
130        assert.match(
131          country.currency,
132          /^[A-Z]{3}$/,
133          `${key}.currency "${country.currency}" is not a valid 3-letter ISO currency code`
134        );
135      }
136    });
137  
138    test('all timezone values contain a "/" separator', () => {
139      for (const [key, country] of Object.entries(COUNTRIES)) {
140        assert.ok(
141          country.timezone.includes('/'),
142          `${key}.timezone "${country.timezone}" does not contain a "/" separator`
143        );
144      }
145    });
146  
147    test('specific countries are present', () => {
148      const expectedCodes = [
149        'US',
150        'CN',
151        'JP',
152        'DE',
153        'UK',
154        'IN',
155        'FR',
156        'IT',
157        'CA',
158        'KR',
159        'ES',
160        'AU',
161        'MX',
162        'ID',
163        'NL',
164        'CH',
165        'PL',
166        'BE',
167        'SE',
168        'AT',
169        'NO',
170        'DK',
171        'SG',
172        'NZ',
173        'IE',
174        'ZA',
175      ];
176      for (const code of expectedCodes) {
177        assert.ok(COUNTRIES[code], `Expected country "${code}" not found`);
178      }
179    });
180  });
181  
182  // ---------------------------------------------------------------------------
183  // 2. getCountryByCode
184  // ---------------------------------------------------------------------------
185  describe('getCountryByCode', () => {
186    test('returns correct country for US', () => {
187      const us = getCountryByCode('US');
188      assert.equal(us.code, 'US');
189      assert.equal(us.name, 'United States');
190      assert.equal(us.googleDomain, 'google.com');
191      assert.equal(us.currency, 'USD');
192    });
193  
194    test('returns correct country for AU', () => {
195      const au = getCountryByCode('AU');
196      assert.equal(au.code, 'AU');
197      assert.equal(au.name, 'Australia');
198      assert.equal(au.googleDomain, 'google.com.au');
199    });
200  
201    test('returns correct country for UK', () => {
202      const uk = getCountryByCode('UK');
203      assert.equal(uk.code, 'UK');
204      assert.equal(uk.name, 'United Kingdom');
205    });
206  
207    test('returns correct country for DE', () => {
208      const de = getCountryByCode('DE');
209      assert.equal(de.code, 'DE');
210      assert.equal(de.name, 'Germany');
211      assert.equal(de.requiresGDPRCheck, true);
212    });
213  
214    test('returns correct country for JP', () => {
215      const jp = getCountryByCode('JP');
216      assert.equal(jp.code, 'JP');
217      assert.equal(jp.name, 'Japan');
218      assert.equal(jp.currency, 'JPY');
219    });
220  
221    test('returns null for unknown code EU', () => {
222      assert.equal(getCountryByCode('EU'), null);
223    });
224  
225    test('returns null for unknown code MC', () => {
226      assert.equal(getCountryByCode('MC'), null);
227    });
228  
229    test('returns null for unknown code CO', () => {
230      assert.equal(getCountryByCode('CO'), null);
231    });
232  
233    test('returns null for unknown code AR', () => {
234      assert.equal(getCountryByCode('AR'), null);
235    });
236  
237    test('handles GB alias to UK', () => {
238      const gb = getCountryByCode('GB');
239      assert.equal(gb.code, 'UK');
240      assert.equal(gb.name, 'United Kingdom');
241    });
242  
243    test('GB alias returns the same object as UK lookup', () => {
244      const gb = getCountryByCode('GB');
245      const uk = getCountryByCode('UK');
246      assert.strictEqual(gb, uk);
247    });
248  
249    test('handles lowercase input "us"', () => {
250      const us = getCountryByCode('us');
251      assert.equal(us.code, 'US');
252    });
253  
254    test('handles mixed case input "Us"', () => {
255      const us = getCountryByCode('Us');
256      assert.equal(us.code, 'US');
257    });
258  
259    test('handles mixed case input "uS"', () => {
260      const us = getCountryByCode('uS');
261      assert.equal(us.code, 'US');
262    });
263  
264    test('handles lowercase "gb" alias', () => {
265      const gb = getCountryByCode('gb');
266      assert.equal(gb.code, 'UK');
267    });
268  
269    test('throws for null input', () => {
270      assert.throws(() => getCountryByCode(null), {
271        message: 'Country code is required',
272      });
273    });
274  
275    test('throws for undefined input', () => {
276      assert.throws(() => getCountryByCode(undefined), {
277        message: 'Country code is required',
278      });
279    });
280  
281    test('throws for empty string input', () => {
282      assert.throws(() => getCountryByCode(''), {
283        message: 'Country code is required',
284      });
285    });
286  
287    test('returns null for random string', () => {
288      assert.equal(getCountryByCode('ZZ'), null);
289    });
290  
291    test('returns null for numeric string', () => {
292      assert.equal(getCountryByCode('12'), null);
293    });
294  });
295  
296  // ---------------------------------------------------------------------------
297  // 3. getCountryByGoogleDomain
298  // ---------------------------------------------------------------------------
299  describe('getCountryByGoogleDomain', () => {
300    test('returns US for google.com', () => {
301      const country = getCountryByGoogleDomain('google.com');
302      assert.equal(country.code, 'US');
303    });
304  
305    test('returns AU for google.com.au', () => {
306      const country = getCountryByGoogleDomain('google.com.au');
307      assert.equal(country.code, 'AU');
308    });
309  
310    test('returns UK for google.co.uk', () => {
311      const country = getCountryByGoogleDomain('google.co.uk');
312      assert.equal(country.code, 'UK');
313    });
314  
315    test('returns DE for google.de', () => {
316      const country = getCountryByGoogleDomain('google.de');
317      assert.equal(country.code, 'DE');
318    });
319  
320    test('returns JP for google.co.jp', () => {
321      const country = getCountryByGoogleDomain('google.co.jp');
322      assert.equal(country.code, 'JP');
323    });
324  
325    test('returns FR for google.fr', () => {
326      const country = getCountryByGoogleDomain('google.fr');
327      assert.equal(country.code, 'FR');
328    });
329  
330    test('returns CN for google.com.hk', () => {
331      const country = getCountryByGoogleDomain('google.com.hk');
332      assert.equal(country.code, 'CN');
333    });
334  
335    test('returns NZ for google.co.nz', () => {
336      const country = getCountryByGoogleDomain('google.co.nz');
337      assert.equal(country.code, 'NZ');
338    });
339  
340    test('returns IE for google.ie', () => {
341      const country = getCountryByGoogleDomain('google.ie');
342      assert.equal(country.code, 'IE');
343    });
344  
345    test('returns ZA for google.co.za', () => {
346      const country = getCountryByGoogleDomain('google.co.za');
347      assert.equal(country.code, 'ZA');
348    });
349  
350    test('every country can be looked up by its googleDomain', () => {
351      for (const [key, country] of Object.entries(COUNTRIES)) {
352        const result = getCountryByGoogleDomain(country.googleDomain);
353        assert.equal(
354          result.code,
355          key,
356          `googleDomain "${country.googleDomain}" did not resolve to ${key}`
357        );
358      }
359    });
360  
361    test('throws for unknown domain', () => {
362      assert.throws(() => getCountryByGoogleDomain('google.xyz'), {
363        message: /Unknown Google domain: google\.xyz/,
364      });
365    });
366  
367    test('throws for partially matching domain', () => {
368      assert.throws(() => getCountryByGoogleDomain('google.com.br'), {
369        message: /Unknown Google domain/,
370      });
371    });
372  
373    test('throws for null input', () => {
374      assert.throws(() => getCountryByGoogleDomain(null), {
375        message: 'Google domain is required',
376      });
377    });
378  
379    test('throws for undefined input', () => {
380      assert.throws(() => getCountryByGoogleDomain(undefined), {
381        message: 'Google domain is required',
382      });
383    });
384  
385    test('throws for empty string input', () => {
386      assert.throws(() => getCountryByGoogleDomain(''), {
387        message: 'Google domain is required',
388      });
389    });
390  });
391  
392  // ---------------------------------------------------------------------------
393  // 4. isMobileNumber
394  // ---------------------------------------------------------------------------
395  describe('isMobileNumber', () => {
396    describe('US mobile numbers', () => {
397      const us = COUNTRIES.US;
398  
399      test('valid US mobile with +1 prefix', () => {
400        assert.equal(isMobileNumber('+12025551234', us), true);
401      });
402  
403      test('valid US mobile with 1 prefix', () => {
404        assert.equal(isMobileNumber('12025551234', us), true);
405      });
406  
407      test('valid US mobile without prefix', () => {
408        assert.equal(isMobileNumber('2025551234', us), true);
409      });
410  
411      test('US number with dashes', () => {
412        assert.equal(isMobileNumber('+1-202-555-1234', us), true);
413      });
414  
415      test('US number with spaces', () => {
416        assert.equal(isMobileNumber('+1 202 555 1234', us), true);
417      });
418  
419      test('US number with parentheses', () => {
420        assert.equal(isMobileNumber('(202) 555-1234', us), true);
421      });
422  
423      test('rejects US number starting with 0', () => {
424        assert.equal(isMobileNumber('0025551234', us), false);
425      });
426  
427      test('rejects US number starting with 1 in area code', () => {
428        assert.equal(isMobileNumber('1025551234', us), false);
429      });
430    });
431  
432    describe('AU mobile numbers', () => {
433      const au = COUNTRIES.AU;
434  
435      test('valid AU mobile with +61 prefix', () => {
436        assert.equal(isMobileNumber('+61412345678', au), true);
437      });
438  
439      test('valid AU mobile with local prefix 04', () => {
440        assert.equal(isMobileNumber('0412345678', au), true);
441      });
442  
443      test('AU mobile with spaces', () => {
444        assert.equal(isMobileNumber('+61 412 345 678', au), true);
445      });
446  
447      test('AU mobile with dashes', () => {
448        assert.equal(isMobileNumber('0412-345-678', au), true);
449      });
450  
451      test('rejects AU landline (02)', () => {
452        assert.equal(isMobileNumber('+61212345678', au), false);
453      });
454  
455      test('rejects AU landline (03)', () => {
456        assert.equal(isMobileNumber('0312345678', au), false);
457      });
458    });
459  
460    describe('UK mobile numbers', () => {
461      const uk = COUNTRIES.UK;
462  
463      test('valid UK mobile with +44 prefix', () => {
464        assert.equal(isMobileNumber('+447911123456', uk), true);
465      });
466  
467      test('valid UK mobile with local 07 prefix', () => {
468        assert.equal(isMobileNumber('07911123456', uk), true);
469      });
470  
471      test('UK mobile with spaces', () => {
472        assert.equal(isMobileNumber('+44 7911 123 456', uk), true);
473      });
474  
475      test('rejects UK landline (020)', () => {
476        assert.equal(isMobileNumber('+442012345678', uk), false);
477      });
478  
479      test('rejects UK landline with local prefix', () => {
480        assert.equal(isMobileNumber('02012345678', uk), false);
481      });
482    });
483  
484    describe('DE mobile numbers', () => {
485      const de = COUNTRIES.DE;
486  
487      test('valid DE mobile with +49 prefix (015x)', () => {
488        assert.equal(isMobileNumber('+4915112345678', de), true);
489      });
490  
491      test('valid DE mobile with +49 prefix (016x)', () => {
492        assert.equal(isMobileNumber('+4916212345678', de), true);
493      });
494  
495      test('valid DE mobile with +49 prefix (017x)', () => {
496        assert.equal(isMobileNumber('+4917612345678', de), true);
497      });
498  
499      test('valid DE mobile with local 0 prefix', () => {
500        assert.equal(isMobileNumber('015112345678', de), true);
501      });
502  
503      test('DE mobile with spaces', () => {
504        assert.equal(isMobileNumber('+49 151 1234 5678', de), true);
505      });
506  
507      test('rejects DE landline (030 Berlin)', () => {
508        assert.equal(isMobileNumber('+493012345678', de), false);
509      });
510    });
511  
512    describe('JP mobile numbers', () => {
513      const jp = COUNTRIES.JP;
514  
515      test('valid JP mobile with +81 prefix (090)', () => {
516        assert.equal(isMobileNumber('+819012345678', jp), true);
517      });
518  
519      test('valid JP mobile with +81 prefix (080)', () => {
520        assert.equal(isMobileNumber('+818012345678', jp), true);
521      });
522  
523      test('valid JP mobile with +81 prefix (070)', () => {
524        assert.equal(isMobileNumber('+817012345678', jp), true);
525      });
526  
527      test('valid JP mobile with local 0 prefix', () => {
528        assert.equal(isMobileNumber('09012345678', jp), true);
529      });
530  
531      test('rejects JP landline (03 Tokyo)', () => {
532        assert.equal(isMobileNumber('+81312345678', jp), false);
533      });
534    });
535  
536    describe('FR mobile numbers', () => {
537      const fr = COUNTRIES.FR;
538  
539      test('valid FR mobile with +33 prefix (06)', () => {
540        assert.equal(isMobileNumber('+33612345678', fr), true);
541      });
542  
543      test('valid FR mobile with +33 prefix (07)', () => {
544        assert.equal(isMobileNumber('+33712345678', fr), true);
545      });
546  
547      test('valid FR mobile with local 0 prefix', () => {
548        assert.equal(isMobileNumber('0612345678', fr), true);
549      });
550  
551      test('rejects FR landline (01)', () => {
552        assert.equal(isMobileNumber('+33112345678', fr), false);
553      });
554    });
555  
556    describe('IN mobile numbers', () => {
557      const india = COUNTRIES.IN;
558  
559      test('valid IN mobile with +91 prefix', () => {
560        assert.equal(isMobileNumber('+919876543210', india), true);
561      });
562  
563      test('valid IN mobile without prefix', () => {
564        assert.equal(isMobileNumber('9876543210', india), true);
565      });
566  
567      test('valid IN mobile starting with 6', () => {
568        assert.equal(isMobileNumber('+916123456789', india), true);
569      });
570  
571      test('rejects IN number starting with 5', () => {
572        assert.equal(isMobileNumber('+915123456789', india), false);
573      });
574    });
575  
576    describe('NZ mobile numbers', () => {
577      const nz = COUNTRIES.NZ;
578  
579      test('valid NZ mobile with +64 prefix', () => {
580        assert.equal(isMobileNumber('+64211234567', nz), true);
581      });
582  
583      test('valid NZ mobile with local 0 prefix', () => {
584        assert.equal(isMobileNumber('0211234567', nz), true);
585      });
586  
587      test('rejects NZ landline (09 Auckland)', () => {
588        assert.equal(isMobileNumber('+6491234567', nz), false);
589      });
590    });
591  
592    describe('IE mobile numbers', () => {
593      const ie = COUNTRIES.IE;
594  
595      test('valid IE mobile with +353 prefix (083)', () => {
596        assert.equal(isMobileNumber('+353831234567', ie), true);
597      });
598  
599      test('valid IE mobile with +353 prefix (085)', () => {
600        assert.equal(isMobileNumber('+353851234567', ie), true);
601      });
602  
603      test('valid IE mobile with +353 prefix (086)', () => {
604        assert.equal(isMobileNumber('+353861234567', ie), true);
605      });
606  
607      test('valid IE mobile with +353 prefix (087)', () => {
608        assert.equal(isMobileNumber('+353871234567', ie), true);
609      });
610  
611      test('valid IE mobile with +353 prefix (089)', () => {
612        assert.equal(isMobileNumber('+353891234567', ie), true);
613      });
614  
615      test('valid IE mobile with local 0 prefix', () => {
616        assert.equal(isMobileNumber('0851234567', ie), true);
617      });
618  
619      test('rejects IE landline (01 Dublin)', () => {
620        assert.equal(isMobileNumber('+35311234567', ie), false);
621      });
622    });
623  
624    describe('ZA mobile numbers', () => {
625      const za = COUNTRIES.ZA;
626  
627      test('valid ZA mobile with +27 prefix (06x)', () => {
628        assert.equal(isMobileNumber('+27612345678', za), true);
629      });
630  
631      test('valid ZA mobile with +27 prefix (07x)', () => {
632        assert.equal(isMobileNumber('+27712345678', za), true);
633      });
634  
635      test('valid ZA mobile with local 0 prefix', () => {
636        assert.equal(isMobileNumber('0712345678', za), true);
637      });
638  
639      test('rejects ZA landline (011)', () => {
640        assert.equal(isMobileNumber('+27111234567', za), false);
641      });
642    });
643  
644    describe('edge cases', () => {
645      test('returns false for null phone', () => {
646        assert.equal(isMobileNumber(null, COUNTRIES.US), false);
647      });
648  
649      test('returns false for undefined phone', () => {
650        assert.equal(isMobileNumber(undefined, COUNTRIES.US), false);
651      });
652  
653      test('returns false for empty string phone', () => {
654        assert.equal(isMobileNumber('', COUNTRIES.US), false);
655      });
656  
657      test('returns false for null country', () => {
658        assert.equal(isMobileNumber('+12025551234', null), false);
659      });
660  
661      test('returns false for undefined country', () => {
662        assert.equal(isMobileNumber('+12025551234', undefined), false);
663      });
664  
665      test('returns false for country object without mobilePattern', () => {
666        assert.equal(isMobileNumber('+12025551234', { code: 'XX' }), false);
667      });
668  
669      test('strips dots from phone numbers', () => {
670        assert.equal(isMobileNumber('+1.202.555.1234', COUNTRIES.US), true);
671      });
672  
673      test('strips mixed separators', () => {
674        assert.equal(isMobileNumber('+1 (202) 555-1234', COUNTRIES.US), true);
675      });
676  
677      test('returns false for completely invalid phone', () => {
678        assert.equal(isMobileNumber('not-a-phone', COUNTRIES.US), false);
679      });
680  
681      test('returns false for too-short number', () => {
682        assert.equal(isMobileNumber('12345', COUNTRIES.US), false);
683      });
684    });
685  });
686  
687  // ---------------------------------------------------------------------------
688  // 5. getSupportedCountries
689  // ---------------------------------------------------------------------------
690  describe('getSupportedCountries', () => {
691    test('returns an array', () => {
692      const codes = getSupportedCountries();
693      assert.ok(Array.isArray(codes));
694    });
695  
696    test('returns exactly 26 country codes', () => {
697      assert.equal(getSupportedCountries().length, 26);
698    });
699  
700    test('all elements are strings', () => {
701      for (const code of getSupportedCountries()) {
702        assert.equal(typeof code, 'string');
703      }
704    });
705  
706    test('all codes are 2-letter uppercase', () => {
707      for (const code of getSupportedCountries()) {
708        assert.match(code, /^[A-Z]{2}$/);
709      }
710    });
711  
712    test('includes US, AU, UK, DE', () => {
713      const codes = getSupportedCountries();
714      assert.ok(codes.includes('US'));
715      assert.ok(codes.includes('AU'));
716      assert.ok(codes.includes('UK'));
717      assert.ok(codes.includes('DE'));
718    });
719  
720    test('matches COUNTRIES object keys', () => {
721      const codes = getSupportedCountries();
722      const keys = Object.keys(COUNTRIES);
723      assert.deepEqual(codes.sort(), keys.sort());
724    });
725  });
726  
727  // ---------------------------------------------------------------------------
728  // 6. getGDPRCountries
729  // ---------------------------------------------------------------------------
730  describe('getGDPRCountries', () => {
731    test('returns an array', () => {
732      const gdprCountries = getGDPRCountries();
733      assert.ok(Array.isArray(gdprCountries));
734    });
735  
736    test('all returned countries have requiresGDPRCheck=true', () => {
737      for (const country of getGDPRCountries()) {
738        assert.equal(country.requiresGDPRCheck, true, `${country.code} should require GDPR check`);
739      }
740    });
741  
742    test('includes DE (Germany)', () => {
743      const codes = getGDPRCountries().map(c => c.code);
744      assert.ok(codes.includes('DE'));
745    });
746  
747    test('includes UK (United Kingdom)', () => {
748      const codes = getGDPRCountries().map(c => c.code);
749      assert.ok(codes.includes('UK'));
750    });
751  
752    test('includes FR (France)', () => {
753      const codes = getGDPRCountries().map(c => c.code);
754      assert.ok(codes.includes('FR'));
755    });
756  
757    test('includes IT (Italy)', () => {
758      const codes = getGDPRCountries().map(c => c.code);
759      assert.ok(codes.includes('IT'));
760    });
761  
762    test('includes ES (Spain)', () => {
763      const codes = getGDPRCountries().map(c => c.code);
764      assert.ok(codes.includes('ES'));
765    });
766  
767    test('includes NL (Netherlands)', () => {
768      const codes = getGDPRCountries().map(c => c.code);
769      assert.ok(codes.includes('NL'));
770    });
771  
772    test('includes PL (Poland)', () => {
773      const codes = getGDPRCountries().map(c => c.code);
774      assert.ok(codes.includes('PL'));
775    });
776  
777    test('includes BE (Belgium)', () => {
778      const codes = getGDPRCountries().map(c => c.code);
779      assert.ok(codes.includes('BE'));
780    });
781  
782    test('includes SE (Sweden)', () => {
783      const codes = getGDPRCountries().map(c => c.code);
784      assert.ok(codes.includes('SE'));
785    });
786  
787    test('includes AT (Austria)', () => {
788      const codes = getGDPRCountries().map(c => c.code);
789      assert.ok(codes.includes('AT'));
790    });
791  
792    test('includes NO (Norway)', () => {
793      const codes = getGDPRCountries().map(c => c.code);
794      assert.ok(codes.includes('NO'));
795    });
796  
797    test('includes DK (Denmark)', () => {
798      const codes = getGDPRCountries().map(c => c.code);
799      assert.ok(codes.includes('DK'));
800    });
801  
802    test('includes IE (Ireland)', () => {
803      const codes = getGDPRCountries().map(c => c.code);
804      assert.ok(codes.includes('IE'));
805    });
806  
807    test('does NOT include US', () => {
808      const codes = getGDPRCountries().map(c => c.code);
809      assert.ok(!codes.includes('US'), 'US should not require GDPR');
810    });
811  
812    test('does NOT include AU', () => {
813      const codes = getGDPRCountries().map(c => c.code);
814      assert.ok(!codes.includes('AU'), 'AU should not require GDPR');
815    });
816  
817    test('does NOT include CA', () => {
818      const codes = getGDPRCountries().map(c => c.code);
819      assert.ok(!codes.includes('CA'), 'CA should not require GDPR');
820    });
821  
822    test('does NOT include JP', () => {
823      const codes = getGDPRCountries().map(c => c.code);
824      assert.ok(!codes.includes('JP'), 'JP should not require GDPR');
825    });
826  
827    test('does NOT include CN', () => {
828      const codes = getGDPRCountries().map(c => c.code);
829      assert.ok(!codes.includes('CN'), 'CN should not require GDPR');
830    });
831  
832    test('does NOT include IN', () => {
833      const codes = getGDPRCountries().map(c => c.code);
834      assert.ok(!codes.includes('IN'), 'IN should not require GDPR');
835    });
836  
837    test('does NOT include MX', () => {
838      const codes = getGDPRCountries().map(c => c.code);
839      assert.ok(!codes.includes('MX'), 'MX should not require GDPR');
840    });
841  
842    test('count matches countries with requiresGDPRCheck=true', () => {
843      const gdprCount = Object.values(COUNTRIES).filter(c => c.requiresGDPRCheck).length;
844      assert.equal(getGDPRCountries().length, gdprCount);
845    });
846  });
847  
848  // ---------------------------------------------------------------------------
849  // 7. FREE_EMAIL_PROVIDERS
850  // ---------------------------------------------------------------------------
851  describe('FREE_EMAIL_PROVIDERS', () => {
852    test('is a non-empty array', () => {
853      assert.ok(Array.isArray(FREE_EMAIL_PROVIDERS));
854      assert.ok(FREE_EMAIL_PROVIDERS.length > 0);
855    });
856  
857    test('all entries are lowercase strings', () => {
858      for (const provider of FREE_EMAIL_PROVIDERS) {
859        assert.equal(typeof provider, 'string');
860        assert.equal(provider, provider.toLowerCase(), `Provider "${provider}" should be lowercase`);
861      }
862    });
863  
864    test('includes common free providers', () => {
865      assert.ok(FREE_EMAIL_PROVIDERS.includes('gmail.com'));
866      assert.ok(FREE_EMAIL_PROVIDERS.includes('outlook.com'));
867      assert.ok(FREE_EMAIL_PROVIDERS.includes('hotmail.com'));
868      assert.ok(FREE_EMAIL_PROVIDERS.includes('yahoo.com'));
869      assert.ok(FREE_EMAIL_PROVIDERS.includes('icloud.com'));
870      assert.ok(FREE_EMAIL_PROVIDERS.includes('protonmail.com'));
871      assert.ok(FREE_EMAIL_PROVIDERS.includes('aol.com'));
872    });
873  
874    test('includes international providers', () => {
875      assert.ok(FREE_EMAIL_PROVIDERS.includes('yahoo.co.uk'));
876      assert.ok(FREE_EMAIL_PROVIDERS.includes('yahoo.de'));
877      assert.ok(FREE_EMAIL_PROVIDERS.includes('gmx.de'));
878      assert.ok(FREE_EMAIL_PROVIDERS.includes('web.de'));
879      assert.ok(FREE_EMAIL_PROVIDERS.includes('mail.ru'));
880      assert.ok(FREE_EMAIL_PROVIDERS.includes('qq.com'));
881    });
882  
883    test('has no duplicate entries', () => {
884      const uniqueSet = new Set(FREE_EMAIL_PROVIDERS);
885      assert.equal(
886        FREE_EMAIL_PROVIDERS.length,
887        uniqueSet.size,
888        'Duplicate free email providers found'
889      );
890    });
891  });
892  
893  // ---------------------------------------------------------------------------
894  // 8. isFreeEmailProvider
895  // ---------------------------------------------------------------------------
896  describe('isFreeEmailProvider', () => {
897    test('returns true for gmail.com email', () => {
898      assert.equal(isFreeEmailProvider('user@gmail.com'), true);
899    });
900  
901    test('returns true for outlook.com email', () => {
902      assert.equal(isFreeEmailProvider('user@outlook.com'), true);
903    });
904  
905    test('returns true for yahoo.com email', () => {
906      assert.equal(isFreeEmailProvider('user@yahoo.com'), true);
907    });
908  
909    test('returns true for hotmail.com email', () => {
910      assert.equal(isFreeEmailProvider('user@hotmail.com'), true);
911    });
912  
913    test('returns true for icloud.com email', () => {
914      assert.equal(isFreeEmailProvider('user@icloud.com'), true);
915    });
916  
917    test('returns true for protonmail.com email', () => {
918      assert.equal(isFreeEmailProvider('user@protonmail.com'), true);
919    });
920  
921    test('returns true for proton.me email', () => {
922      assert.equal(isFreeEmailProvider('user@proton.me'), true);
923    });
924  
925    test('returns true for international yahoo domain', () => {
926      assert.equal(isFreeEmailProvider('user@yahoo.co.uk'), true);
927    });
928  
929    test('returns true for gmx.de email', () => {
930      assert.equal(isFreeEmailProvider('user@gmx.de'), true);
931    });
932  
933    test('returns true for qq.com email', () => {
934      assert.equal(isFreeEmailProvider('user@qq.com'), true);
935    });
936  
937    test('returns false for custom business domain', () => {
938      assert.equal(isFreeEmailProvider('ceo@mybusiness.com'), false);
939    });
940  
941    test('returns false for corporate domain', () => {
942      assert.equal(isFreeEmailProvider('info@acme-corp.co.uk'), false);
943    });
944  
945    test('returns false for custom .org domain', () => {
946      assert.equal(isFreeEmailProvider('contact@nonprofit.org'), false);
947    });
948  
949    test('returns false for null input', () => {
950      assert.equal(isFreeEmailProvider(null), false);
951    });
952  
953    test('returns false for undefined input', () => {
954      assert.equal(isFreeEmailProvider(undefined), false);
955    });
956  
957    test('returns false for empty string', () => {
958      assert.equal(isFreeEmailProvider(''), false);
959    });
960  
961    test('returns false for non-string input (number)', () => {
962      assert.equal(isFreeEmailProvider(12345), false);
963    });
964  
965    test('returns false for non-string input (object)', () => {
966      assert.equal(isFreeEmailProvider({}), false);
967    });
968  
969    test('handles uppercase email (Gmail.com)', () => {
970      assert.equal(isFreeEmailProvider('User@Gmail.com'), true);
971    });
972  
973    test('handles uppercase email (OUTLOOK.COM)', () => {
974      assert.equal(isFreeEmailProvider('USER@OUTLOOK.COM'), true);
975    });
976  
977    test('handles mixed case email', () => {
978      assert.equal(isFreeEmailProvider('User@Yahoo.COM'), true);
979    });
980  
981    test('returns false for email without @ sign', () => {
982      assert.equal(isFreeEmailProvider('user-at-gmail.com'), false);
983    });
984  
985    test('returns false for subdomain of free provider', () => {
986      assert.equal(isFreeEmailProvider('user@mail.gmail.com'), false);
987    });
988  });