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 });