diff --git a/app/steps/ui/executors/contactdetails/index.js b/app/steps/ui/executors/contactdetails/index.js index 49a5920392..daf80db66a 100644 --- a/app/steps/ui/executors/contactdetails/index.js +++ b/app/steps/ui/executors/contactdetails/index.js @@ -49,14 +49,13 @@ class ExecutorContactDetails extends ValidationStep { if (executorsWrapper.executorEmailAlreadyUsed(ctx.email, executor.fullName, formdata.applicantEmail)) { errors.push(FieldError('email', 'duplicate', this.resourcePath, this.generateContent({}, {}, session.language), session.language)); } - if (!PhoneNumberValidator.validateMobilePhoneNumber(ctx.mobile)) { - errors.push(FieldError('mobile', 'invalid', this.resourcePath, this.generateContent({}, {}, session.language), session.language)); + const validationResult = PhoneNumberValidator.validateMobilePhoneNumber(ctx.mobile); + if (!validationResult.isValid) { + errors.push(FieldError('mobile', validationResult.errorType, this.resourcePath, this.generateContent({}, {}, session.language), session.language)); } - if (executorsWrapper.executorPhoneNumberAlreadyUsed(ctx.mobile, executor.fullName, formdata.applicant.phoneNumber)) { errors.push(FieldError('mobile', 'duplicate', this.resourcePath, this.generateContent({}, {}, session.language), session.language)); } - if (ctx.email !== executor.email && (executor.emailSent || executor.inviteId)) { executor.emailChanged = true; } diff --git a/app/steps/ui/executors/contactdetails/schema.json b/app/steps/ui/executors/contactdetails/schema.json index b9952d0f3b..1fd1094fc3 100644 --- a/app/steps/ui/executors/contactdetails/schema.json +++ b/app/steps/ui/executors/contactdetails/schema.json @@ -11,7 +11,7 @@ "mobile": { "type": "string", "minLength": 11, - "maxLength": 13 + "maxLength": 16 } }, "required": [ diff --git a/app/utils/PhoneNumberValidator.js b/app/utils/PhoneNumberValidator.js index e31431db58..4bc2a1c380 100644 --- a/app/utils/PhoneNumberValidator.js +++ b/app/utils/PhoneNumberValidator.js @@ -1,29 +1,16 @@ -class PhoneNumberValidator { - static validateMobilePhoneNumber(num) { - const ukNumberMatchRE = new RegExp(/^7[0-9]{9}$/); - const ukPrefix = '44'; - const internationalMatchRE = new RegExp(/^[0-9]*$/); - - if (num.startsWith('+')) { - let toValidate = num.slice(1); - if (toValidate.startsWith(ukPrefix)) { - toValidate = toValidate.slice(2); - if (toValidate.match(ukNumberMatchRE)) { - return true; - } - } else if (toValidate.match(internationalMatchRE)) { - return true; - } - } +const parsePhoneNumber = require('libphonenumber-js/mobile').parsePhoneNumber; - if (num.startsWith('07')) { - const toValidate = num.slice(1); - if (toValidate.match(ukNumberMatchRE)) { - return true; +class PhoneNumberValidator { + static validateMobilePhoneNumber(phoneNumber) { + try { + const parsedPhoneNumber = parsePhoneNumber(phoneNumber, 'GB'); + if (parsedPhoneNumber && parsedPhoneNumber.isValid()) { + return {isValid: true}; } + return {isValid: false, errorType: 'invalid'}; + } catch (error) { + return {isValid: false, errorType: 'invalid'}; } - - return false; } } diff --git a/package.json b/package.json index f18e08eb4c..fc710f5932 100644 --- a/package.json +++ b/package.json @@ -129,6 +129,7 @@ "js-yaml": "^4.0.0", "jsdom": "^26.0.0", "launchdarkly-node-server-sdk": "^7.0.0", + "libphonenumber-js": "^1.11.17", "lodash": "^4.17.21", "mocha-lcov-reporter": "^1.3.0", "moment": "^2.29.4", diff --git a/test/unit/utils/testPhoneNumberValidator.js b/test/unit/utils/testPhoneNumberValidator.js index 3beaafcfd4..37367efeb3 100644 --- a/test/unit/utils/testPhoneNumberValidator.js +++ b/test/unit/utils/testPhoneNumberValidator.js @@ -3,61 +3,147 @@ const expect = require('chai').expect; describe('PhoneNumberValidator.js', () => { describe('validateUKMobilePhoneNumber()', () => { - it('should return failure for invalid number', (done) => { - const phoneNumber = '0208 863 8689'; - expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.equal(false); - done(); + describe('Base Case', () => { + it('should return true to validate number for testing purposes', (done) => { + const phoneNumber = '07123456789'; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: true}); + done(); + }); }); - it('should return failure for invalid chars', (done) => { - const phoneNumber = 'abcdef'; - expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.equal(false); - done(); - }); - it('should return failure for invalid spces', (done) => { - const phoneNumber = ' 07958995330'; - expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.equal(false); - done(); - }); - it('should return failure for uk landline number', (done) => { - const phoneNumber = '02088638689'; - expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.equal(false); - done(); - }); - it('should return fail for uk mobile number INTL staring with 00', (done) => { - const phoneNumber = '00447958995330'; - expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.equal(false); - done(); - }); - it('should return failure for bad format uk mobile number', (done) => { - const phoneNumber = '+44 7958995330'; - expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.equal(false); - done(); - }); - it('should return failure for invalid uk mobile number', (done) => { - const phoneNumber = '08958995330'; - expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.equal(false); - done(); - }); - it('should return pass for uk mobile number INTL', (done) => { - const phoneNumber = '+447958995330'; - expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.equal(true); - done(); - }); - it('should return pass for overseas mobile number', (done) => { - const phoneNumber = '+337958995330'; - expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.equal(true); - done(); + + describe('Length Validation', () => { + it('should return false for not meeting minimum length', (done) => { + const phoneNumber = '020 1234'; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: false, errorType: 'invalid'}); + done(); + }); + it('should return false for exceeding maximum length', (done) => { + const phoneNumber = '020 12345 6789 10 11'; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: false, errorType: 'invalid'}); + done(); + }); + it('should return true for acceptable length (11 chars)', (done) => { + const phoneNumber = '07123456789'; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: true}); + done(); + }); + it('should return true for acceptable length (15 chars)', (done) => { + const phoneNumber = '+4917642010039'; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: true}); + done(); + }); + it('should return false for invalid mobile number', (done) => { + const phoneNumber = '+179589953302345234'; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: false, errorType: 'invalid'}); + done(); + }); }); - it('should return pass for uk mobile number', (done) => { - const phoneNumber = '07958995330'; - expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.equal(true); - done(); + + describe('Alphanumeric Input Validation', () => { + it('should return false for invalid input', (done) => { + const phoneNumber = 'INVALID_INPUT'; + const validationResult = PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber); + const expectedResult = {'isValid': false, 'errorType': 'invalid'}; + expect(validationResult).to.deep.equal(expectedResult); + done(); + }); + it('should return true for hyphens input', (done) => { + const phoneNumber = '07-1234-56789'; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: true}); + done(); + }); + it('should return true for spaces input', (done) => { + const phoneNumber = '07 1234 56789'; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: true}); + done(); + }); + it('should return false for invalid chars', (done) => { + const phoneNumber = 'abcdef'; + const validationResult = PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber); + const expectedResult = {'isValid': false, 'errorType': 'invalid'}; + expect(validationResult).to.deep.equal(expectedResult); + done(); + }); }); - it('should return pass for international mobile number', (done) => { - const phoneNumber = '+179589953302345234'; - expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.equal(true); - done(); + + describe('International Number Validation', () => { + it('should return true for international number (French)', (done) => { + const phoneNumber = '+33 6 12 34 56 78'; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: true}); + done(); + }); + it('should return true for overseas mobile number', (done) => { + const phoneNumber = '+33 7 58 99 53 30'; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: true}); + done(); + }); + it('should return true for British with country code', (done) => { + const phoneNumber = '+44 7911 123456'; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: true}); + done(); + }); + it('should return false for invalid country code', (done) => { + const phoneNumber = '+999 6 12 34 56 78'; + const validationResult = PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber); + const expectedResult = {'isValid': false, 'errorType': 'invalid'}; + expect(validationResult).to.deep.equal(expectedResult); + done(); + }); + it('should return false for missing country code', (done) => { + const phoneNumber = '+ 6 12 34 56 78'; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: false, errorType: 'invalid'}); + done(); + }); + it('should return false invalid international number', (done) => { + const phoneNumber = '+44-7911-12345'; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: false, errorType: 'invalid'}); + done(); + }); }); + describe('General Validation Tests', () => { + it('should return false for invalid number', (done) => { + const phoneNumber = '0208 863 8689'; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: false, errorType: 'invalid'}); + done(); + }); + it('should return false for uk landline number', (done) => { + const phoneNumber = '02088638689'; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: false, errorType: 'invalid'}); + done(); + }); + it('should return false for invalid uk mobile number', (done) => { + const phoneNumber = '08958995330'; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: false, errorType: 'invalid'}); + done(); + }); + it('should return true for uk mobile number INTL', (done) => { + const phoneNumber = '+447958995330'; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: true}); + done(); + }); + it('should return true for uk mobile number', (done) => { + const phoneNumber = '07958995330'; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: true}); + done(); + }); + //The following are edited tests which failed under previous validation + it('should return true for spaces', (done) => { + const phoneNumber = ' 07958995330 '; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: true}); + done(); + }); + it('should return true for uk mobile number INTL staring with 00', (done) => { + const phoneNumber = '00447958995330'; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: true}); + done(); + }); + it('should return true for spaced format uk mobile number', (done) => { + const phoneNumber = '+44 7958995330'; + expect(PhoneNumberValidator.validateMobilePhoneNumber(phoneNumber)).to.deep.equal({isValid: true}); + done(); + }); + + }); }); }); diff --git a/yarn.lock b/yarn.lock index 6eeb27e1f8..efd2539ea2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10340,6 +10340,13 @@ __metadata: languageName: node linkType: hard +"libphonenumber-js@npm:^1.11.17": + version: 1.11.17 + resolution: "libphonenumber-js@npm:1.11.17" + checksum: 10/e8e897834822060fe3067fc573a76f94eb0fbcc1ac075e3fdd9e1870b0c68f7c20f2bc50053ceeb69364fc3a8b881395bd121d904c38f716c4b69c3382b8f011 + languageName: node + linkType: hard + "lie@npm:~3.3.0": version: 3.3.0 resolution: "lie@npm:3.3.0" @@ -13489,6 +13496,7 @@ __metadata: js-yaml: "npm:^4.0.0" jsdom: "npm:^26.0.0" launchdarkly-node-server-sdk: "npm:^7.0.0" + libphonenumber-js: "npm:^1.11.17" lodash: "npm:^4.17.21" mocha: "npm:^11.0.0" mocha-jenkins-reporter: "npm:^0.4.8"