diff --git a/api/resources/translations/messages-en.properties b/api/resources/translations/messages-en.properties index b0c0dc83b31..a3ad79b0b4e 100644 --- a/api/resources/translations/messages-en.properties +++ b/api/resources/translations/messages-en.properties @@ -841,6 +841,7 @@ messages.f.report_accepted = Thank you for flagging {{patient_name}} ({{patient_ messages.f.report_accepted_parent = {{patient_name}} ({{patient_id}}) was flagged by {{contact.name}} ({{contact.phone}}) for further attention. messages.generic.no_provided_patient_id = External ID number support is configured, but is not found on document in the configured location. messages.generic.provided_patient_id_not_unique = The provided ID number is already used. +messages.generic.provided_phone_not_valid = The provided phone is not valid. messages.generic.registration_accepted = Thank you {{contact.name}} for registering {{patient_name}}. Their ID is {{patient_id}}. messages.generic.registration_not_found = No person with ID number '{{patient_id}}' found. Verify the ID and resend the message. messages.generic.report_accepted = Thank you {{contact.name}}, {{form}} for {{patient_name}} ({{patient_id}}) has been recorded. diff --git a/api/resources/translations/messages-ne.properties b/api/resources/translations/messages-ne.properties index cff6e69188e..32f1a8c13bf 100644 --- a/api/resources/translations/messages-ne.properties +++ b/api/resources/translations/messages-ne.properties @@ -764,7 +764,8 @@ messages.errors.patient.missing = messages.f.report_accepted = messages.f.report_accepted_parent = messages.generic.no_provided_patient_id = -messages.generic.provided_patient_id_not_unique = +messages.generic.provided_patient_id_not_unique = तपाइँले पठाउनुभएको आईडी पहिल्यै प्रयोगमा छ। +messages.generic.provided_phone_not_valid = तपाइँले पठाउनुभएको फोन नम्बर मिलेन । फेरी प्रयास गर्नुहोस्। messages.generic.registration_accepted = messages.generic.registration_not_found = messages.generic.report_accepted = diff --git a/api/src/services/report/smsparser.js b/api/src/services/report/smsparser.js index dba2a4d7164..ae47ee51a1f 100644 --- a/api/src/services/report/smsparser.js +++ b/api/src/services/report/smsparser.js @@ -8,6 +8,7 @@ const textformsParser = require('./textforms-parser'); const logger = require('../../logger'); const moment = require('moment'); const bs = require('bikram-sambat'); +const phoneNumberParser = require('@medic/phone-number'); const MUVUKU_REGEX = /^\s*([A-Za-z]?\d)!.+!.+/; // matches invisible characters that can mess up our parsing @@ -210,6 +211,20 @@ const fieldParsers = { // keep months integers, not their list value. return parseNum(stripInvisibleCharacters(raw)); }, + phone_number: (raw) => { + //standardiseDigits ensures that Nepali digits are also supported. + raw = standardiseDigits(raw); + const formattedAndValidatedPhone = phoneNumberParser.normalize(config.getAll(), raw); + if (formattedAndValidatedPhone) { + return formattedAndValidatedPhone; + } + logger.warn(`The provided phone number ${raw} is invalid`); + + // Returning raw here becuase what to do with invalid phone + // is defined in transitions so error will be thrown there if required. + // Warning is logged just in case. + return raw; + }, bsYear: (raw) => { return standardiseDigits(raw); }, @@ -221,6 +236,7 @@ const fieldParsers = { } }; +//selects parser by field type and parses and validates the given data. exports.parseField = (field, raw, key) => { const parser = fieldParsers[field.type]; if (!parser) { diff --git a/api/tests/form-definitions.js b/api/tests/form-definitions.js index 955c49bb83b..0ca2c33a995 100644 --- a/api/tests/form-definitions.js +++ b/api/tests/form-definitions.js @@ -791,5 +791,18 @@ exports.forms = { required: true } } + }, + NP: { + meta: { + code: 'NP' + }, + fields: { + patient_age: { + type: 'integer' + }, + phone_number: { + type: 'phone_number' + } + } } }; diff --git a/api/tests/mocha/services/report/smsparser.spec.js b/api/tests/mocha/services/report/smsparser.spec.js index 88608b4b559..5d2eccecda4 100644 --- a/api/tests/mocha/services/report/smsparser.spec.js +++ b/api/tests/mocha/services/report/smsparser.spec.js @@ -97,10 +97,123 @@ describe('sms parser', () => { chai.expect(smsparser.getFormCode('द CDT33')).to.equal('द'); }); + + ['full', 'none', 'partial'].forEach(validationtype => { + it('supports all type of phone validation', () => { + const doc = { message: 'NP 20 +9779841202020' }; + const def = definitions.forms.NP; + sinon.stub(config, 'getAll').returns({ + default_country_code: 977, + phone_validation: validationtype + }); + const data = smsparser.parse(def, doc); + chai.expect(data.phone_number).to.equal('+9779841202020'); + }); + }); + + it('accepts phone number with extension', () => { + const doc = { message: 'NP 20 +9779841202020' }; + const def = definitions.forms.NP; + sinon.stub(config, 'getAll').returns({ + default_country_code: 977, + phone_validation: 'full' + }); + const data = smsparser.parse(def, doc); + chai.expect(data.phone_number).to.equal('+9779841202020'); + }); + + it('accepts valid phone number in Nepali langauge and saves by converting it to english', () => { + const doc = { message: 'NP 20 ९८४१२३२३२३' }; + const def = definitions.forms.NP; + sinon.stub(config, 'getAll').returns({ + default_country_code: 977, + phone_validation: 'full' + }); + const data = smsparser.parse(def, doc); + chai.expect(data.phone_number).to.equal('+9779841232323'); + }); + + it('accepts valid phone number with extension in Nepali langauge', () => { + const doc = { message: 'NP 20 +९७७९८४१२३२३२३' }; + const def = definitions.forms.NP; + sinon.stub(config, 'getAll').returns({ + default_country_code: 977, + phone_validation: 'full' + }); + const data = smsparser.parse(def, doc); + chai.expect(data.phone_number).to.equal('+9779841232323'); + }); + + it('returns provided Nepali number converted ot english if number is invalid', () => { + // Looks counter intuitive but validation needs to be done agian in transition so it can + // act against the valid or invalid result. Warning is logged just in case. + const doc = { message: 'NP 20 ९८४१२३' }; + const def = definitions.forms.NP; + sinon.stub(config, 'getAll').returns({ + default_country_code: 977, + phone_validation: 'full' + }); + const data = smsparser.parse(def, doc); + chai.expect(data.phone_number).to.equal('984123'); + }); + + + it('accepts correct phone number without extension', () => { + const doc = { message: 'NP 20 9841202020' }; + const def = definitions.forms.NP; + sinon.stub(config, 'getAll').returns({ + default_country_code: 977, + phone_validation: 'full' + }); + const data = smsparser.parse(def, doc); + chai.expect(data.phone_number).to.equal('+9779841202020'); + }); + + it('returns the exact invalid number if phone number is invalid for the region', () => { + // Looks counter intuitive but validation needs to be done agian in transition so it can + // act against the valid or invalid result. Warning is logged just in case. + const doc = { message: 'NP 20 +97712312' }; + const def = definitions.forms.NP; + sinon.stub(config, 'getAll').returns({ + default_country_code: 977, + phone_validation: 'full' + }); + const data = smsparser.parse(def, doc); + chai.expect(data.phone_number).to.equal('+97712312'); + }); + + it('should add extension to given number if not provided', () => { + const doc = { message: 'NP 20 9841202020' }; + const def = definitions.forms.NP; + sinon.stub(config, 'getAll').returns({ + default_country_code: 977, + phone_validation: 'full' + }); + const data = smsparser.parse(def, doc); + chai.expect(data.phone_number).to.equal('+9779841202020'); + }); + + //India , Kenya, Tanzania Phone is accepted as contact info in Nepal region. + //Just in case we make cross region/borders tool + [['NP 20 +918750660880', '+918750660880'], + ['NP 20 +254773087889', '+254773087889'], + ['NP 20 +255712262987', '+255712262987']].forEach(phoneNumerWithParsed => { + it(`returns parsed number if valid phone of another the region ${phoneNumerWithParsed[1]}`, () => { + const doc = { message: phoneNumerWithParsed[0] }; + const def = definitions.forms.NP; + sinon.stub(config, 'getAll').returns({ + default_country_code: 977, + phone_validation: 'full' + }); + const data = smsparser.parse(def, doc); + chai.expect(data.phone_number).to.equal(phoneNumerWithParsed[1]); + }); + }); + it('parse month type', () => { const doc = { message: '1!FOO!10' }; const def = { - meta: {code: 'FOO', label: 'Test Monthly Report'}, + meta: { code: 'FOO', label: 'Test Monthly Report' }, fields: { month: { labels: { @@ -118,9 +231,9 @@ describe('sms parser', () => { it('form not found', () => { const doc = { - message:'1!X0X0!facility#2011#11#1#2#3#4#5#6#9#8#7#6#5#4', - type:'sms_message', - form:'X0X0' + message: '1!X0X0!facility#2011#11#1#2#3#4#5#6#9#8#7#6#5#4', + type: 'sms_message', + form: 'X0X0' }; const def = {}; const data = smsparser.parse(def, doc); @@ -129,9 +242,9 @@ describe('sms parser', () => { it('wrong field type', () => { const doc = { - message:'1!YYYY!facility#2011#11#yyyyy#zzzz#2#3#4#5#6#9#8#7#6#5#4', - type:'sms_message', - form:'YYYY' + message: '1!YYYY!facility#2011#11#yyyyy#zzzz#2#3#4#5#6#9#8#7#6#5#4', + type: 'sms_message', + form: 'YYYY' }; const def = definitions.forms.YYYY; const data = smsparser.parse(def, doc); @@ -161,9 +274,9 @@ describe('sms parser', () => { it('missing fields', () => { const doc = { - message:'1!YYYY!facility#2011#11#1#1#2#3', - type:'sms_message', - form:'YYYY' + message: '1!YYYY!facility#2011#11#1#1#2#3', + type: 'sms_message', + form: 'YYYY' }; const def = definitions.forms.YYYY; const data = smsparser.parse(def, doc); @@ -193,9 +306,9 @@ describe('sms parser', () => { it('extra fields', () => { const doc = { - message:'1!YYYY!facility#2011#11#0#1#2#3#1#1#1#1#1#1#1#1#1#1#####77#', - type:'sms_message', - form:'YYYY' + message: '1!YYYY!facility#2011#11#0#1#2#3#1#1#1#1#1#1#1#1#1#1#####77#', + type: 'sms_message', + form: 'YYYY' }; const def = definitions.forms.YYYY; const data = smsparser.parse(def, doc); @@ -383,7 +496,7 @@ describe('sms parser', () => { }; const data = smsparser.parse(def, sms); // q2 should be null. empty string attempted to be parsed as number. - chai.expect(data).to.deep.equal({q1: 'No', q2: null}); + chai.expect(data).to.deep.equal({ q1: 'No', q2: null }); }); it('parse zero value list field', () => { @@ -400,7 +513,7 @@ describe('sms parser', () => { } }; const data = smsparser.parse(def, sms); - chai.expect(data).to.deep.equal({q1: 'Yes'}); + chai.expect(data).to.deep.equal({ q1: 'Yes' }); }); it('ignore whitespace in list field textforms', () => { @@ -525,7 +638,7 @@ describe('sms parser', () => { } }; const data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({testdate: 1331510400000}); + chai.expect(data).to.deep.equal({ testdate: 1331510400000 }); }); it('parse date field yyyz: muvuku', () => { @@ -543,7 +656,7 @@ describe('sms parser', () => { const doc = { message: 'YYYZ BIR2012-03-12' }; const def = definitions.forms.YYYZ; const data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({birthdate: 1331510400000}); + chai.expect(data).to.deep.equal({ birthdate: 1331510400000 }); }); it('parse bsDate field: muvuku', () => { @@ -563,7 +676,7 @@ describe('sms parser', () => { } }; const data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({testdate: moment('2012-03-12').valueOf()}); + chai.expect(data).to.deep.equal({ testdate: moment('2012-03-12').valueOf() }); }); it('parse bsDate field: compact textforms', () => { @@ -583,7 +696,7 @@ describe('sms parser', () => { } }; const data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({testdate: moment('2012-03-12').valueOf()}); + chai.expect(data).to.deep.equal({ testdate: moment('2012-03-12').valueOf() }); }); it('parse bsDate field yyyt: muvuku', () => { @@ -630,7 +743,7 @@ describe('sms parser', () => { const doc = { message: '12345 2068-11-32' }; const def = definitions.forms.YYYT; const data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({patient_id: '12345', lmp_date: null}); + chai.expect(data).to.deep.equal({ patient_id: '12345', lmp_date: null }); }); it('parse patient id in nepali correctly', () => { @@ -735,7 +848,7 @@ describe('sms parser', () => { } }; const data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({testbool: true}); + chai.expect(data).to.deep.equal({ testbool: true }); }); it('parse boolean field: false', () => { @@ -749,7 +862,7 @@ describe('sms parser', () => { } }; const data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({testbool: false}); + chai.expect(data).to.deep.equal({ testbool: false }); }); it('parse string field mixed: muvuku', () => { @@ -769,7 +882,7 @@ describe('sms parser', () => { } }; const data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({foo: '16A'}); + chai.expect(data).to.deep.equal({ foo: '16A' }); }); it('parse string field mixed: textforms', () => { @@ -789,7 +902,7 @@ describe('sms parser', () => { } }; const data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({foo: '16A'}); + chai.expect(data).to.deep.equal({ foo: '16A' }); }); it('parse string field with exlamation mark: textforms', () => { @@ -809,7 +922,7 @@ describe('sms parser', () => { } }; const data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({foo: '16A!'}); + chai.expect(data).to.deep.equal({ foo: '16A!' }); }); it('parse string field with exlamation mark: muvuku', () => { @@ -829,7 +942,7 @@ describe('sms parser', () => { } }; const data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({foo: '16A!'}); + chai.expect(data).to.deep.equal({ foo: '16A!' }); }); it('parse string field leading zero: textforms', () => { @@ -849,7 +962,7 @@ describe('sms parser', () => { } }; const data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({foo: '012345'}); + chai.expect(data).to.deep.equal({ foo: '012345' }); }); it('parse string field leading zero: muvuku', () => { @@ -869,14 +982,14 @@ describe('sms parser', () => { } }; const data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({foo: '012345'}); + chai.expect(data).to.deep.equal({ foo: '012345' }); }); it('smsformats unstructured', () => { const docs = [ { message: 'testing one two three.' }, { message: 'HELP ME' }, - { message: ''} // blank message + { message: '' } // blank message ]; docs.forEach(doc => { chai.expect(smsparser.parse(null, doc)).to.deep.equal({}); @@ -885,8 +998,8 @@ describe('sms parser', () => { it('smsformats structured but no form', () => { const docs = [ - {message: '1!0000!1'}, - {message: '0000 ABC 123-123-123'} + { message: '1!0000!1' }, + { message: '0000 ABC 123-123-123' } ]; docs.forEach(doc => { chai.expect(smsparser.parse(null, doc)).to.deep.equal({}); @@ -986,7 +1099,7 @@ describe('sms parser', () => { sent_timestamp: '12-11-11 15:00', from: '+15551212', message: 'J1!YYYY!EDO#4#ODT#5#RPM#11#L2T#2#HFI#facility#CDT#3#CDO#7#MSP#0#ZDO#6#L1O#9#RPY#2011#EOT#6#ODO#' + - '5#L1T#1#ZDT#4#L2O#8' + '5#L1T#1#ZDT#4#L2O#8' }; const actual = smsparser.parse(def, doc); chai.expect(actual).to.deep.equal({ @@ -1269,7 +1382,7 @@ describe('sms parser', () => { } }, position: 1, - length: [ 3, 100 ], + length: [3, 100], type: 'string' } } @@ -1299,17 +1412,17 @@ describe('sms parser', () => { // textforms let doc = { message: 'ग foo 16A' }; let data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({foo: '16A'}); + chai.expect(data).to.deep.equal({ foo: '16A' }); // compact doc = { message: 'ग 16A' }; data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({foo: '16A'}); + chai.expect(data).to.deep.equal({ foo: '16A' }); // muvuku doc = { message: '1!ग!16A' }; data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({foo: '16A'}); + chai.expect(data).to.deep.equal({ foo: '16A' }); }); it('support textforms locale on tiny labels', () => { @@ -1338,7 +1451,7 @@ describe('sms parser', () => { locale: 'sw' }; let data = smsparser.parse(def, doc, doc.locale); - chai.expect(data).to.deep.equal({name: 'n jane'}); + chai.expect(data).to.deep.equal({ name: 'n jane' }); // textforms with locale match parses correctly doc = { @@ -1346,7 +1459,7 @@ describe('sms parser', () => { locale: 'sw' }; data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({name: 'jane'}); + chai.expect(data).to.deep.equal({ name: 'jane' }); // same thing but case insensitive check doc = { @@ -1354,21 +1467,21 @@ describe('sms parser', () => { locale: 'sw' }; data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({name: 'jane'}); + chai.expect(data).to.deep.equal({ name: 'jane' }); // compact parses correctly doc = { message: 'R jane' }; data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({name: 'jane'}); + chai.expect(data).to.deep.equal({ name: 'jane' }); // muvuku parses correctly doc = { message: '1!R!jane' }; data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({name: 'jane'}); + chai.expect(data).to.deep.equal({ name: 'jane' }); }); it('support translation keys on tiny labels', () => { @@ -1399,7 +1512,7 @@ describe('sms parser', () => { locale: 'sw' }; let data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({name: 'jane'}); + chai.expect(data).to.deep.equal({ name: 'jane' }); // same thing but case insensitive check doc = { @@ -1407,21 +1520,21 @@ describe('sms parser', () => { locale: 'sw' }; data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({name: 'jane'}); + chai.expect(data).to.deep.equal({ name: 'jane' }); // compact parses correctly doc = { message: 'R jane' }; data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({name: 'jane'}); + chai.expect(data).to.deep.equal({ name: 'jane' }); // muvuku parses correctly doc = { message: '1!R!jane' }; data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({name: 'jane'}); + chai.expect(data).to.deep.equal({ name: 'jane' }); }); it('support mixed case field keys', () => { @@ -1445,21 +1558,21 @@ describe('sms parser', () => { message: 'R n jane', }; let data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({ooOoo: 'jane'}); + chai.expect(data).to.deep.equal({ ooOoo: 'jane' }); // compact textforms doc = { message: 'R jane' }; data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({ooOoo: 'jane'}); + chai.expect(data).to.deep.equal({ ooOoo: 'jane' }); // muvuku doc = { message: '1!R!jane' }; data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({ooOoo: 'jane'}); + chai.expect(data).to.deep.equal({ ooOoo: 'jane' }); }); it('support uppercase field keys', () => { @@ -1483,21 +1596,21 @@ describe('sms parser', () => { message: 'R n jane', }; let data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({OOOOO: 'jane'}); + chai.expect(data).to.deep.equal({ OOOOO: 'jane' }); // compact textforms doc = { message: 'R jane' }; data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({OOOOO: 'jane'}); + chai.expect(data).to.deep.equal({ OOOOO: 'jane' }); // muvuku doc = { message: '1!R!jane' }; data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({OOOOO: 'jane'}); + chai.expect(data).to.deep.equal({ OOOOO: 'jane' }); }); it('support regex chars in form code, parser escapes them', () => { @@ -1521,7 +1634,7 @@ describe('sms parser', () => { message: '.*.* n jane' }; const data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({name: 'jane'}); + chai.expect(data).to.deep.equal({ name: 'jane' }); }); it('stop input values from getting translated', () => { @@ -1553,7 +1666,7 @@ describe('sms parser', () => { message: 'J1!c_imm!bcg#yes#ch#no' }; const data = smsparser.parse(def, doc); - chai.expect(data).to.deep.equal({bcg: 'yes', ch:'no'}); + chai.expect(data).to.deep.equal({ bcg: 'yes', ch: 'no' }); chai.expect(config.translate.callCount).to.equal(2); }); diff --git a/shared-libs/.eslintrc b/shared-libs/.eslintrc index 148e99b20c1..5fdb8f63d8d 100644 --- a/shared-libs/.eslintrc +++ b/shared-libs/.eslintrc @@ -3,6 +3,6 @@ "node": true }, "parserOptions": { - "ecmaVersion": 2018 + "ecmaVersion": 2020 } } diff --git a/shared-libs/phone-number/src/phone-number.js b/shared-libs/phone-number/src/phone-number.js index 8d1a43c1329..179aff14158 100644 --- a/shared-libs/phone-number/src/phone-number.js +++ b/shared-libs/phone-number/src/phone-number.js @@ -5,7 +5,7 @@ const phonenumber = require('google-libphonenumber'); const CHARACTER_REGEX = /[a-z]/i; -const _init = function(settings, phone) { +const _init = function (settings, phone) { const instance = phonenumber.PhoneNumberUtil.getInstance(); const shortInfo = phonenumber.ShortNumberInfo.getInstance(); const countryCode = settings && settings.default_country_code; @@ -15,11 +15,13 @@ const _init = function(settings, phone) { const validPhone = () => { if (validationType === 'partial') { + // Quickly guesses whether a number is a possible phone number by using only the length information, return instance.isPossibleNumber(parsed); } - if(validationType === 'none') { + if (validationType === 'none') { return true; } + // Does full validation of a phone number for a region using length and prefix information. return instance.isValidNumber(parsed); }; @@ -27,7 +29,7 @@ const _init = function(settings, phone) { if (shortInfo.isValidShortNumber(parsed)) { return phonenumber.PhoneNumberFormat.NATIONAL; } - if (typeof(given) !== 'undefined') { + if (typeof (given) !== 'undefined') { return given; } if (parsed.getCountryCode() + '' === countryCode) { @@ -37,13 +39,13 @@ const _init = function(settings, phone) { }; return { - format: function(scheme) { + format: function (scheme) { if (!this.validate()) { return false; } return instance.format(parsed, getScheme(scheme)).toString(); }, - validate: function() { + validate: function () { return validPhone() && // Disallow alpha numbers which libphonenumber considers valid, // e.g. 1-800-MICROSOFT. @@ -58,7 +60,7 @@ const _init = function(settings, phone) { * @param {String} phone The phone number to normalize. * @returns {(String|Boolean)} The normalized number or false if invalid. */ -exports.normalize = function(settings, phone) { +exports.normalize = function (settings, phone) { try { return _init(settings, phone).format(phonenumber.PhoneNumberFormat.E164); } catch (e) { @@ -73,7 +75,7 @@ exports.normalize = function(settings, phone) { * @param {String} phone The phone number to normalize. * @returns {(String|Boolean)} The formatted number or false if invalid. */ -exports.format = function(settings, phone) { +exports.format = function (settings, phone) { try { return _init(settings, phone).format(); } catch (e) { @@ -89,7 +91,7 @@ exports.format = function(settings, phone) { * @param {String} phone The phone number to normalize. * @returns {Boolean} Whether or not the number is valid. */ -exports.validate = function(settings, phone) { +exports.validate = function (settings, phone) { try { return _init(settings, phone).validate(); } catch (e) { @@ -104,11 +106,11 @@ exports.validate = function(settings, phone) { * @param {String} b The second phone number * @returns {Boolean} Whether or not the numbers match */ -exports.same = function(a, b) { +exports.same = function (a, b) { try { const match = phonenumber.PhoneNumberUtil.getInstance().isNumberMatch(a, b); return match === phonenumber.PhoneNumberUtil.MatchType.NSN_MATCH || - match === phonenumber.PhoneNumberUtil.MatchType.EXACT_MATCH; + match === phonenumber.PhoneNumberUtil.MatchType.EXACT_MATCH; } catch (e) { // exception thrown trying to match given numbers } diff --git a/shared-libs/transitions/src/transitions/registration.js b/shared-libs/transitions/src/transitions/registration.js index 83848a90c81..08ddb0df43d 100644 --- a/shared-libs/transitions/src/transitions/registration.js +++ b/shared-libs/transitions/src/transitions/registration.js @@ -10,6 +10,7 @@ const acceptPatientReports = require('./accept_patient_reports'); const moment = require('moment'); const config = require('../config'); const date = require('../date'); +const phoneNumberParser = require('@medic/phone-number'); const contactTypesUtils = require('@medic/contact-types-utils'); @@ -40,6 +41,17 @@ const getNameField = (params, prefix) => { return defaultNameField; }; +const getPatientPhoneField = (currentForm) => { + const formDef = utils.getForm(currentForm); + if (!formDef?.fields) { + return; + } + + return Object + .keys(formDef.fields) + .find(key => formDef.fields[key].type === 'phone_number'); +}; + const parseParams = params => { if (!params) { return {}; @@ -309,7 +321,7 @@ const addMessages = (config, doc) => { utils.getRegistrations({ id: patientId }), utils.getRegistrations({ id: placeId }), ]) - .then(([ patientRegistrations, placeRegistrations ]) => { + .then(([patientRegistrations, placeRegistrations]) => { const context = { patient: doc.patient, place: doc.place, @@ -337,7 +349,7 @@ const assignSchedule = (options) => { utils.getRegistrations({ id: patientId }), utils.getRegistrations({ id: placeId }), ]) - .then(([ patientRegistrations, placeRegistrations ]) => { + .then(([patientRegistrations, placeRegistrations]) => { options.params.forEach(scheduleName => { const schedule = schedules.getScheduleConfig(scheduleName); const context = { @@ -461,6 +473,7 @@ const addPatient = (options) => { const doc = options.doc; const patientShortcode = options.doc.patient_id; const patientNameField = getPatientNameField(options.params); + const patientPhoneField = getPatientPhoneField(doc.form); // create a new patient with this patient_id const patient = { @@ -477,6 +490,8 @@ const addPatient = (options) => { patient.type = 'person'; } + + return utils .getContactUuid(patientShortcode) .then(patientContactId => { @@ -505,6 +520,15 @@ const addPatient = (options) => { patient.date_of_birth = doc.birth_date; } + if (patientPhoneField && doc.fields[patientPhoneField]) { + const patientPhone = doc.fields[patientPhoneField]; + if (!phoneNumberParser.validate(config.getAll(), patientPhone)) { + transitionUtils.addRejectionMessage(doc, options.registrationConfig, 'provided_phone_not_valid'); + return; + } + patient.phone = patientPhone; + } + // assign patient in doc with full parent doc - to be used in messages doc.patient = Object.assign({ parent }, patient); patient.parent = lineage.minifyLineage(parent); diff --git a/shared-libs/transitions/src/transitions/utils.js b/shared-libs/transitions/src/transitions/utils.js index 1536e4a6ac4..7e26c82c0ab 100644 --- a/shared-libs/transitions/src/transitions/utils.js +++ b/shared-libs/transitions/src/transitions/utils.js @@ -58,9 +58,9 @@ module.exports = { }, isIdUnique: (id) => { return db.medic - .query('medic-client/contacts_by_reference', { key: [ 'shortcode', id ]}) + .query('medic-client/contacts_by_reference', { key: ['shortcode', id] }) .then(results => !(results && results.rows && results.rows.length)); - }, + }, addUniqueId: (doc) => { return idGenerator.next().value.then(patientId => { doc.patient_id = patientId; diff --git a/shared-libs/transitions/test/unit/transitions/registration.js b/shared-libs/transitions/test/unit/transitions/registration.js index 8409842c2fa..fabfa3be75d 100644 --- a/shared-libs/transitions/test/unit/transitions/registration.js +++ b/shared-libs/transitions/test/unit/transitions/registration.js @@ -7,6 +7,7 @@ const utils = require('../../../src/lib/utils'); const config = require('../../../src/config'); const validation = require('@medic/validation'); const contactTypeUtils = require('@medic/contact-types-utils'); +const phoneNumberParser = require('@medic/phone-number'); let schedules; let transitionUtils; @@ -64,6 +65,74 @@ describe('registration', () => { }); }); + describe('getPatientPhoneField', () => { + beforeEach(() => { + transition.getPatientPhoneField = transition.__get__('getPatientPhoneField'); + }); + + it('should return field name if form has field', () => { + const form = 'ph'; + const formDef = { + fields: { + patient_name: { + type: 'string' + }, + phone_number: { + type: 'phone_number' + } + } + }; + sinon.stub(utils, 'getForm').returns(formDef); + transition.getPatientPhoneField(form).should.equal('phone_number'); + }); + + it('should return undefined if form does not have phone_number field', () => { + const form = 'ph'; + const formDef = { + fields: { + patient_name: { + type: 'string' + } + } + }; + sinon.stub(utils, 'getForm').returns(formDef); + (typeof transition.getPatientPhoneField(form)).should.equal('undefined'); + }); + + it('should return undefined if form is not found', () => { + const form = 'ph'; + sinon.stub(utils, 'getForm').returns(undefined); + (typeof transition.getPatientPhoneField(form)).should.equal('undefined'); + }); + + it('should return undefined if form has no fields', () => { + const form = 'ph'; + const formDef = { + }; + sinon.stub(utils, 'getForm').returns(formDef); + (typeof transition.getPatientPhoneField(form)).should.equal('undefined'); + }); + + it('should return field by type not by name', () => { + const form = 'ph'; + const formDef = { + fields: { + patient_name: { + type: 'string' + }, + phone_number: { + type: 'not_phone_number_type' + }, + custom_name: { + type: 'phone_number' + } + } + }; + sinon.stub(utils, 'getForm').returns(formDef); + transition.getPatientPhoneField(form).should.equal('custom_name'); + }); + }); + describe('addPatient', () => { it('trigger creates a new patient', () => { const patientName = 'jack'; @@ -126,6 +195,223 @@ describe('registration', () => { }); }); + it('should only create a new patient with phone if form has phone field and phone is valid', () => { + // Form with phone field + const formDef = { + fields: { + patient_name: { + type: 'string' + }, + phone_number: { + type: 'phone_number' + } + } + }; + // Stubbing that the phone is valid + sinon.stub(phoneNumberParser, 'validate').returns(true); + const patientName = 'jack'; + const submitterId = 'abc'; + const parentId = 'papa'; + const patientId = '05649'; + const reportId = 'def'; + const senderPhoneNumber = '9841202020'; + const patientPhoneNumber = '9841000000'; + const dob = '2017-03-31T01:15:09.000Z'; + const change = { + doc: { + _id: reportId, + type: 'data_record', + form: 'R', + reported_date: 53, + from: senderPhoneNumber, + fields: { patient_name: patientName, phone_number: patientPhoneNumber }, + birth_date: dob, + }, + }; + const getContactUuid = sinon.stub(utils, 'getContactUuid').resolves(); + // return expected view results when searching for contacts_by_phone + const view = sinon.stub(db.medic, 'query').resolves({ + rows: [ + { + doc: { + _id: submitterId, + parent: { _id: parentId }, + }, + }, + ], + }); + sinon.stub(db.medic, 'get').withArgs('papa').resolves({ _id: parentId, type: 'contact', contact_type: 'place' }); + const saveDoc = sinon.stub(db.medic, 'post').resolves(); + + const eventConfig = { + form: 'R', + events: [{ name: 'on_create', trigger: 'add_patient' }], + }; + config.get.returns([eventConfig]); + sinon.stub(validation, 'validate').resolves(null); + sinon.stub(utils, 'getRegistrations').resolves([]); + sinon.stub(utils, 'getForm').returns(formDef); + sinon.stub(transitionUtils, 'getUniqueId').resolves(patientId); + config.getAll.returns(settings); + + return transition.onMatch(change).then(() => { + getContactUuid.callCount.should.equal(1); + view.callCount.should.equal(1); + view.args[0][0].should.equal('medic-client/contacts_by_phone'); + view.args[0][1].key.should.equal(senderPhoneNumber); + view.args[0][1].include_docs.should.equal(true); + saveDoc.callCount.should.equal(1); + saveDoc.args[0][0].name.should.equal(patientName); + saveDoc.args[0][0].phone.should.equal(patientPhoneNumber); + saveDoc.args[0][0].parent._id.should.equal(parentId); + saveDoc.args[0][0].reported_date.should.equal(53); + saveDoc.args[0][0].type.should.equal('person'); + saveDoc.args[0][0].patient_id.should.equal(patientId); + saveDoc.args[0][0].date_of_birth.should.equal(dob); + saveDoc.args[0][0].source_id.should.equal(reportId); + saveDoc.args[0][0].created_by.should.equal(submitterId); + }); + }); + + it('should not create patient if form has phone field and phone is invalid', () => { + // Form with phone field + const formDef = { + fields: { + patient_name: { + type: 'string' + }, + phone_number: { + type: 'phone_number' + } + } + }; + // Stubbing that phone is invalid + sinon.stub(phoneNumberParser, 'validate').returns(false); + + const patientName = 'jack'; + const submitterId = 'abc'; + const parentId = 'papa'; + const patientId = '05649'; + const reportId = 'def'; + const senderPhoneNumber = '9841202020'; + // We are stubbing that phone number validator returns false this is just a placeholder + const patientPhoneNumber = '98410'; + const dob = '2017-03-31T01:15:09.000Z'; + const change = { + doc: { + _id: reportId, + type: 'data_record', + form: 'R', + reported_date: 53, + from: senderPhoneNumber, + fields: { patient_name: patientName, phone_number: patientPhoneNumber }, + birth_date: dob, + }, + }; + sinon.stub(utils, 'getContactUuid').resolves(); + sinon.stub(db.medic, 'query').resolves({ + rows: [ + { + doc: { + _id: submitterId, + parent: { _id: parentId }, + }, + }, + ], + }); + sinon.stub(db.medic, 'get').withArgs('papa').resolves({ _id: parentId, type: 'contact', contact_type: 'place' }); + const saveDoc = sinon.stub(db.medic, 'post').resolves(); + + const eventConfig = { + form: 'R', + events: [{ name: 'on_create', trigger: 'add_patient' }], + }; + + config.get.returns([eventConfig]); + sinon.stub(validation, 'validate').resolves(null); + sinon.stub(utils, 'getRegistrations').resolves([]); + sinon.stub(utils, 'getForm').returns(formDef); + sinon.stub(transitionUtils, 'getUniqueId').resolves(patientId); + config.getAll.returns(settings); + + return transition.onMatch(change).then(() => { + saveDoc.callCount.should.equal(0); + }); + }); + + it('should not add patient phone if form does not have phone field', () => { + // Form without phone field + const formDef = { + fields: { + patient_name: { + type: 'string' + } + } + }; + const patientName = 'jack'; + const submitterId = 'abc'; + const parentId = 'papa'; + const patientId = '05649'; + const reportId = 'def'; + const senderPhoneNumber = '9841202020'; + const patientPhoneNumber = '9841000000'; + const dob = '2017-03-31T01:15:09.000Z'; + const change = { + doc: { + _id: reportId, + type: 'data_record', + form: 'R', + reported_date: 53, + from: senderPhoneNumber, + fields: { patient_name: patientName, phone_number: patientPhoneNumber }, + birth_date: dob, + }, + }; + const getContactUuid = sinon.stub(utils, 'getContactUuid').resolves(); + // return expected view results when searching for contacts_by_phone + const view = sinon.stub(db.medic, 'query').resolves({ + rows: [ + { + doc: { + _id: submitterId, + parent: { _id: parentId }, + }, + }, + ], + }); + sinon.stub(db.medic, 'get').withArgs('papa').resolves({ _id: parentId, type: 'contact', contact_type: 'place' }); + const saveDoc = sinon.stub(db.medic, 'post').resolves(); + + const eventConfig = { + form: 'R', + events: [{ name: 'on_create', trigger: 'add_patient' }], + }; + config.get.returns([eventConfig]); + sinon.stub(validation, 'validate').resolves(null); + sinon.stub(utils, 'getRegistrations').resolves([]); + sinon.stub(utils, 'getForm').returns(formDef); + sinon.stub(transitionUtils, 'getUniqueId').resolves(patientId); + config.getAll.returns(settings); + + return transition.onMatch(change).then(() => { + getContactUuid.callCount.should.equal(1); + view.callCount.should.equal(1); + view.args[0][0].should.equal('medic-client/contacts_by_phone'); + view.args[0][1].key.should.equal(senderPhoneNumber); + view.args[0][1].include_docs.should.equal(true); + saveDoc.callCount.should.equal(1); + saveDoc.args[0][0].name.should.equal(patientName); + (typeof saveDoc.args[0][0].phone).should.be.equal('undefined'); + saveDoc.args[0][0].parent._id.should.equal(parentId); + saveDoc.args[0][0].reported_date.should.equal(53); + saveDoc.args[0][0].type.should.equal('person'); + saveDoc.args[0][0].patient_id.should.equal(patientId); + saveDoc.args[0][0].date_of_birth.should.equal(dob); + saveDoc.args[0][0].source_id.should.equal(reportId); + saveDoc.args[0][0].created_by.should.equal(submitterId); + }); + }); + it('does nothing when patient already added', () => { const patientId = '05649'; const change = { @@ -535,7 +821,7 @@ describe('registration', () => { const eventConfig = { form: 'R', events: [{ name: 'on_create', trigger: 'add_patient', params: { contact_type: 'patient' } }], - messages: [ { + messages: [{ recipient: 'reporting_unit', event_type: 'report_accepted', bool_expr: 'doc.patient', @@ -631,7 +917,7 @@ describe('registration', () => { name: 'on_create', trigger: 'add_patient', params: { contact_type: 'buddy', patient_name_field: 'buddy_name', parent_id: 'parent' } }], - messages: [ { + messages: [{ recipient: 'reporting_unit', event_type: 'report_accepted', bool_expr: 'doc.patient', @@ -723,7 +1009,7 @@ describe('registration', () => { events: [{ name: 'on_create', trigger: 'add_patient', params: { contact_type: 'patient', parent_id: 'parent' } }], - messages: [ { + messages: [{ recipient: 'reporting_unit', event_type: 'parent_field_not_provided', message: [ @@ -991,7 +1277,7 @@ describe('registration', () => { events: [{ name: 'on_create', trigger: 'add_place', params: { contact_type: 'clinic', parent_id: 'parent_id' } }], - messages: [ { + messages: [{ recipient: 'reporting_unit', event_type: 'report_accepted', bool_expr: 'doc.place', @@ -1071,8 +1357,8 @@ describe('registration', () => { sinon.stub(db.medic, 'post').resolves(); const eventConfig = { form: 'R', - events: [{ name: 'on_create', trigger: 'add_place', params: { contact_type: 'clinic_1' }}], - messages: [ { + events: [{ name: 'on_create', trigger: 'add_place', params: { contact_type: 'clinic_1' } }], + messages: [{ recipient: 'reporting_unit', event_type: 'report_accepted', bool_expr: 'doc.place', @@ -1140,8 +1426,8 @@ describe('registration', () => { sinon.stub(db.medic, 'post').resolves(); const eventConfig = { form: 'R', - events: [{ name: 'on_create', trigger: 'add_place', params: { contact_type: 'clinic_1' }}], - messages: [ { + events: [{ name: 'on_create', trigger: 'add_place', params: { contact_type: 'clinic_1' } }], + messages: [{ recipient: 'reporting_unit', event_type: 'report_accepted', bool_expr: 'doc.place', @@ -1156,18 +1442,20 @@ describe('registration', () => { }; sinon.stub(db.medic, 'query') .withArgs('medic-client/contacts_by_phone') - .resolves({ rows: [ - { - doc: { - _id: 'supervisor', - name: 'Frank', - contact_type: 'supervisor', - type: 'contact', - phone: '+111222', - parent: { _id: 'west_hc' } + .resolves({ + rows: [ + { + doc: { + _id: 'supervisor', + name: 'Frank', + contact_type: 'supervisor', + type: 'contact', + phone: '+111222', + parent: { _id: 'west_hc' } + } } - } - ]}); + ] + }); sinon.stub(db.medic, 'get').withArgs('west_hc').resolves({ _id: 'west_hc', name: 'west hc', @@ -1265,7 +1553,7 @@ describe('registration', () => { name: 'on_create', trigger: 'add_place', params: { contact_type: 'clinic_1', parent_id: 'parent_id', place_name_field: 'doodle' } }], - messages: [ { + messages: [{ recipient: 'reporting_unit', event_type: 'report_accepted', bool_expr: 'doc.place', @@ -1363,7 +1651,7 @@ describe('registration', () => { name: 'on_create', trigger: 'add_place', params: { contact_type: 'clinic', parent_id: 'fiddle', place_name_field: 'doodle' } }], - messages: [ { + messages: [{ recipient: 'reporting_unit', event_type: 'report_accepted', bool_expr: 'doc.place', @@ -1431,7 +1719,7 @@ describe('registration', () => { const eventConfig = { form: 'R', events: [{ name: 'on_create', trigger: 'add_place', params: { contact_type: 'clinic' } }], - messages: [ { + messages: [{ recipient: 'reporting_unit', event_type: 'parent_not_found', message: [ @@ -1492,7 +1780,7 @@ describe('registration', () => { const eventConfig = { form: 'R', events: [{ name: 'on_create', trigger: 'add_place', params: { contact_type: 'clinic', parent_id: 'some_id' } }], - messages: [ { + messages: [{ recipient: 'reporting_unit', event_type: 'parent_field_not_provided', message: [ @@ -1547,7 +1835,7 @@ describe('registration', () => { const eventConfig = { form: 'R', events: [{ name: 'on_create', trigger: 'add_place', params: { contact_type: 'clinic', parent_id: 'some_id' } }], - messages: [ { + messages: [{ recipient: 'reporting_unit', event_type: 'parent_field_not_provided', message: [ @@ -1616,7 +1904,7 @@ describe('registration', () => { const eventConfig = { form: 'R', events: [{ name: 'on_create', trigger: 'add_place', params: { contact_type: 'area', parent_id: 'parent_id' } }], - messages: [ { + messages: [{ recipient: 'reporting_unit', event_type: 'parent_invalid', message: [ @@ -2469,7 +2757,7 @@ describe('registration', () => { patient: testPatient, }; - return transition.addMessages(testConfig, testDoc).then( () => { + return transition.addMessages(testConfig, testDoc).then(() => { addMessage.callCount.should.equal(1); const expectedContext = { patient: testPatient, @@ -2518,10 +2806,10 @@ describe('registration', () => { utils.getSubjectIds.args[0].should.deep.equal([doc.patient]); utils.getSubjectIds.args[1].should.deep.equal([doc.place]); utils.getReportsBySubject.callCount.should.equal(1); - utils.getReportsBySubject.args[0].should.deep.equal([ { + utils.getReportsBySubject.args[0].should.deep.equal([{ ids: ['uuid', 'patient_id'], registrations: true - } ]); + }]); }); }); @@ -2538,10 +2826,10 @@ describe('registration', () => { utils.getSubjectIds.args[0].should.deep.equal([doc.patient]); utils.getSubjectIds.args[1].should.deep.equal([doc.place]); utils.getReportsBySubject.callCount.should.equal(1); - utils.getReportsBySubject.args[0].should.deep.equal([ { + utils.getReportsBySubject.args[0].should.deep.equal([{ ids: ['uuid', 'place_id'], registrations: true - } ]); + }]); }); }); @@ -2561,10 +2849,10 @@ describe('registration', () => { utils.getSubjectIds.args[0].should.deep.equal([doc.patient]); utils.getSubjectIds.args[1].should.deep.equal([doc.place]); utils.getReportsBySubject.callCount.should.equal(1); - utils.getReportsBySubject.args[0].should.deep.equal([ { + utils.getReportsBySubject.args[0].should.deep.equal([{ ids: ['uuid', 'patient_id', 'place_uuid', 'place_id'], registrations: true - } ]); + }]); }); }); @@ -2653,7 +2941,7 @@ describe('registration', () => { result.should.equal(true); fireConfiguredTriggers.callCount.should.equal(0); validation.validate.callCount.should.equal(1); - validation.validate.args[0].slice(0, 2).should.deep.equal([ doc, ['validation!!'] ]); + validation.validate.args[0].slice(0, 2).should.deep.equal([doc, ['validation!!']]); messages.addErrors.callCount.should.equal(1); messages.addErrors.args[0].should.deep.equal([ registrationConfig, @@ -2798,7 +3086,9 @@ describe('registration', () => { transitionUtils.addRegistrationNotFoundError.callCount.should.equal(1); transitionUtils.addRegistrationNotFoundError.args[0].should.deep.equal([doc, registrationConfig]); contactTypeUtils.isPlace.callCount.should.equal(1); - contactTypeUtils.isPlace.args[0].should.deep.equal([{}, {_id: 'person', patient_id: '56987', type: 'person' }]); + contactTypeUtils.isPlace.args[0].should.deep.equal( + [{}, { _id: 'person', patient_id: '56987', type: 'person' }] + ); }); }); diff --git a/shared-libs/validation/src/validation.js b/shared-libs/validation/src/validation.js index 248b10a9751..e0bcae2abe0 100644 --- a/shared-libs/validation/src/validation.js +++ b/shared-libs/validation/src/validation.js @@ -163,6 +163,11 @@ module.exports = { }) .then(result => !result); }, + uniquePhone: (doc, validation) => { + return db.medic + .query('medic-client/contacts_by_phone', { key: doc[validation.field] }) + .then(results => !(results && results.rows && results.rows.length)); + }, uniqueWithin: (doc, validation) => { const fields = [...validation.funcArgs]; const duration = _parseDuration(fields.pop()); @@ -196,13 +201,13 @@ module.exports = { const year = yearFieldName ? doc[yearFieldName] : new Date().getFullYear(); const isValidISOWeek = - /^\d{1,2}$/.test(doc[weekFieldName]) && - /^\d{4}$/.test(year) && - doc[weekFieldName] >= 1 && - doc[weekFieldName] <= - moment() - .year(year) - .isoWeeksInYear(); + /^\d{1,2}$/.test(doc[weekFieldName]) && + /^\d{4}$/.test(year) && + doc[weekFieldName] >= 1 && + doc[weekFieldName] <= + moment() + .year(year) + .isoWeeksInYear(); if (isValidISOWeek) { return Promise.resolve(true); } @@ -259,7 +264,7 @@ module.exports = { * @param {String[]} [ignores=[]] Keys of doc that is always considered valid * @returns {Promise} Array of errors if validation failed, empty array otherwise. */ - validate: (doc, validations=[], ignores=[]) => { + validate: (doc, validations = [], ignores = []) => { if (!inited) { throw new Error('Validation module not initialized'); } diff --git a/shared-libs/validation/test/validations.js b/shared-libs/validation/test/validations.js index de8cb5806c5..a1964bc813f 100644 --- a/shared-libs/validation/test/validations.js +++ b/shared-libs/validation/test/validations.js @@ -248,6 +248,59 @@ describe('validations', () => { }); }); + it('unique phone validation should fail if db query for phone returns doc', () => { + sinon.stub(db.medic, 'query').resolves({ + rows: [ + { + id: 'original', + phone: '+9779841111111' + } + ] + }); + const validations = [ + { + property: 'phone_number', + rule: 'uniquePhone("phone_number")', + message: [ + { + content: 'Duplicate phone', + locale: 'en', + }, + ], + }, + ]; + const doc = { + _id: 'duplicate', + xyz: '+9779841111111', + }; + return validation.validate(doc, validations).then(errors => { + assert.equal(errors.length, 1); + }); + }); + + it('unique phone validation should pass if db query for phone does not return any doc', () => { + sinon.stub(db.medic, 'query').resolves({ undefined }); + const validations = [ + { + property: 'phone_number', + rule: 'uniquePhone("phone_number")', + message: [ + { + content: 'unique phone', + locale: 'en', + }, + ], + }, + ]; + const doc = { + _id: 'unique', + xyz: '+9779841111111', + }; + return validation.validate(doc, validations).then(errors => { + assert.equal(errors.length, 0); + }); + }); + it('pass uniqueWithin validation on old doc', () => { clock = sinon.useFakeTimers(); sinon.stub(db.medic, 'query').resolves({ @@ -532,7 +585,7 @@ describe('validations', () => { ]; const doc = { _id: 'same', - lmp_date: moment().subtract({weeks: 4, days: 1}).valueOf(), + lmp_date: moment().subtract({ weeks: 4, days: 1 }).valueOf(), reported_date: moment().valueOf() }; return validation.validate(doc, validations).then(errors => { @@ -548,9 +601,9 @@ describe('validations', () => { }, ]; const doc = { - _id: 'same', + _id: 'same', fields: { - lmp_date: moment().subtract({weeks: 4, days: 1}).valueOf() + lmp_date: moment().subtract({ weeks: 4, days: 1 }).valueOf() }, reported_date: moment().valueOf() }; @@ -568,7 +621,7 @@ describe('validations', () => { ]; const doc = { _id: 'same', - lmp_date: moment().subtract({weeks: 4}).valueOf(), + lmp_date: moment().subtract({ weeks: 4 }).valueOf(), reported_date: moment().valueOf() }; return validation.validate(doc, validations).then(errors => { @@ -591,7 +644,7 @@ describe('validations', () => { ]; const doc = { _id: 'same', - lmp_date: moment().subtract({weeks: 3, days: 6}).valueOf(), + lmp_date: moment().subtract({ weeks: 3, days: 6 }).valueOf(), reported_date: moment().valueOf() }; return validation.validate(doc, validations).then(errors => { @@ -647,7 +700,7 @@ describe('validations', () => { ]; const doc = { _id: 'same', - lmp_date: moment().subtract({weeks: 3, days: 6}).valueOf(), + lmp_date: moment().subtract({ weeks: 3, days: 6 }).valueOf(), reported_date: moment().valueOf() }; return validation.validate(doc, validations).then(errors => { @@ -669,7 +722,7 @@ describe('validations', () => { ]; const doc = { _id: 'same', - lmp_date: moment().subtract({weeks: 39, days: 6}).valueOf(), + lmp_date: moment().subtract({ weeks: 39, days: 6 }).valueOf(), reported_date: moment().valueOf() }; return validation.validate(doc, validations).then(errors => { @@ -686,7 +739,7 @@ describe('validations', () => { ]; const doc = { _id: 'same', - lmp_date: moment().subtract({weeks: 40}).valueOf(), + lmp_date: moment().subtract({ weeks: 40 }).valueOf(), reported_date: moment().valueOf() }; return validation.validate(doc, validations).then(errors => { @@ -709,7 +762,7 @@ describe('validations', () => { ]; const doc = { _id: 'same', - lmp_date: moment().subtract({weeks: 40, days: 1}).valueOf(), + lmp_date: moment().subtract({ weeks: 40, days: 1 }).valueOf(), reported_date: moment().valueOf() }; return validation.validate(doc, validations).then(errors => { diff --git a/tests/integration/sentinel/transitions/registration.spec.js b/tests/integration/sentinel/transitions/registration.spec.js index 02585ca376d..c25e52c3385 100644 --- a/tests/integration/sentinel/transitions/registration.spec.js +++ b/tests/integration/sentinel/transitions/registration.spec.js @@ -3,8 +3,8 @@ const sentinelUtils = require('@utils/sentinel'); const uuid = require('uuid').v4; const moment = require('moment'); const chai = require('chai'); - const defaultSettings = utils.getDefaultSettings(); +const testForm = require('./test-stubs'); const contacts = [ { @@ -32,7 +32,7 @@ const contacts = [ place_id: 'the_clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } }, contact: { - _id: 'person', parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + _id: 'person', parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } }, reported_date: new Date().getTime() }, @@ -98,6 +98,146 @@ describe('registration', () => { after(() => utils.revertDb([], true)); afterEach(() => utils.revertDb(getIds(contacts), true)); + it('should add valid phone to patient doc', () => { + const patientPhone = '+9779841123123'; + const patientNameAndPhone = { // has just the `patient_name`and phone so should create this person + _id: uuid(), + type: 'data_record', + form: 'FORM-A', + from: '+9779841212345', + fields: { + patient_name: 'Minerva', + phone_number: patientPhone + }, + reported_date: moment().valueOf(), + contact: { + _id: 'person', + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + } + }; + const docs = [ + patientNameAndPhone + ]; + const docIds = getIds(docs); + let newPatientId; + return utils + .updateSettings(testForm.forms.NP, 'sentinel') + .then(() => utils.saveDocs(docs)) + .then(() => sentinelUtils.waitForSentinel(docIds)) + .then(() => sentinelUtils.getInfoDocs(docIds)) + .then(infos => { + infos.forEach(info => { + chai.expect(info).to.deep.nested.include({ 'transitions.registration.ok': true }); + }); + }) + .then(() => utils.getDocs(docIds)) + .then(updated => { + chai.expect(updated[0].fields.phone_number).to.equal(patientPhone); + chai.expect(updated[0].patient_id).not.to.equal(undefined); + chai.expect(updated[0].tasks).to.have.lengthOf(1); + chai.expect(updated[0].tasks[0].messages[0]).to.include({ + to: '+9779841212345', + message: `Patient Minerva (${updated[0].patient_id}) added to Clinic` + }); + + newPatientId = updated[0].patient_id; + + return getContactsByReference([newPatientId, 'venus']); + }) + .then(patients => { + chai.expect(patients.rows[0].doc).to.deep.include({ + patient_id: newPatientId, + phone: patientPhone, + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } }, + name: 'Minerva', + type: 'person', + created_by: 'person', + source_id: patientNameAndPhone._id, + }); + }); + }); + + it('should not create patient from report doc when provided invalid phone', () => { + const patientPhone = '+97796666'; + const patient_id =uuid(); + + const patientNameAndInvalidPhone = { // has just the `patient_name` field, and should create this person + _id: patient_id, + type: 'data_record', + form: 'FORM-A', + from: '+9779841212345', + fields: { + patient_name: 'Minerva', + phone_number: patientPhone + }, + reported_date: moment().valueOf(), + contact: { + _id: 'person', + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + } + }; + + const docs = [ + patientNameAndInvalidPhone + ]; + const docIds = getIds(docs); + + return utils + .updateSettings(testForm.forms.NP, 'sentinel') + .then(() => utils.saveDocs(docs)) + .then(() => sentinelUtils.waitForSentinel(docIds)) + .then(() => sentinelUtils.getInfoDocs(docIds)) + .then(infos => { + infos.forEach(info => { + chai.expect(info).to.deep.nested.not.include({ 'transitions.registration.ok': false }); + }); + }).then(() => utils.getDocs(docIds)) + .then(updated => { + chai.expect(updated[0].fields.phone_number).to.equal(patientPhone); + chai.expect(updated[0].patient_id).to.not.be.null; + const newPatientId = updated[0].patient_id; + return getContactsByReference([newPatientId, 'venus']); + }) + .then(patients => { + chai.expect(patients.rows.length).to.equal(0); + }); + }); + + it('should fail transition on invalid phone', () => { + const patientPhone = '+97796666'; + const patientNameAndInvalidPhone = { // has just the `patient_name` field, and should create this person + _id: uuid(), + type: 'data_record', + form: 'FORM-A', + from: '+9779841212345', + fields: { + patient_name: 'Minerva', + phone_number: patientPhone + }, + reported_date: moment().valueOf(), + contact: { + _id: 'person', + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + } + }; + + const docs = [ + patientNameAndInvalidPhone + ]; + const docIds = getIds(docs); + + return utils + .updateSettings(testForm.forms.NP, 'sentinel') + .then(() => utils.saveDocs(docs)) + .then(() => sentinelUtils.waitForSentinel(docIds)) + .then(() => sentinelUtils.getInfoDocs(docIds)) + .then(infos => { + infos.forEach(info => { + chai.expect(info).to.deep.nested.not.include({ 'transitions.registration.ok': false }); + }); + }); + }); + it('should be skipped when transition is disabled', () => { const settings = { transitions: { registration: false }, @@ -113,7 +253,7 @@ describe('registration', () => { }], }] }], - forms: { FORM: { }} + forms: { FORM: {} } }; const doc = { @@ -123,7 +263,7 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -152,7 +292,7 @@ describe('registration', () => { }], }] }], - forms: { FORM: { }} + forms: { FORM: {} } }; const doc = { @@ -162,7 +302,7 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -211,7 +351,7 @@ describe('registration', () => { }] } }], - forms: { FORM: { }} + forms: { FORM: {} } }; const noSubjects = { // doesn't patient_id or place_id fields @@ -225,7 +365,7 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -241,7 +381,7 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -257,11 +397,11 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; - const docs = [ noSubjects, noPatient, noPlace ]; + const docs = [noSubjects, noPatient, noPlace]; const docIds = getIds(docs); return utils @@ -332,7 +472,7 @@ describe('registration', () => { }], }], }], - forms: { 'FORM-A': { }} + forms: { 'FORM-A': {} } }; const placeInsteadOfPatient = { // has a patient_id field containing shortcode corresponding to a place @@ -346,7 +486,7 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -361,7 +501,7 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -377,7 +517,7 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -393,7 +533,7 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -409,7 +549,7 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -429,14 +569,14 @@ describe('registration', () => { .then(() => sentinelUtils.getInfoDocs(allIds)) .then(infos => { infos.forEach((info, idx) => { - const errorMsg = `failed for doc${idx+1}`; + const errorMsg = `failed for doc${idx + 1}`; chai.expect(info).to.deep.nested.include({ 'transitions.registration.ok': true }, errorMsg); }); }) .then(() => utils.getDocs(allIds)) .then(updated => { updated.forEach((doc, idx) => { - const errorMsg = `failed for doc${idx+1}`; + const errorMsg = `failed for doc${idx + 1}`; chai.expect(doc.tasks).to.have.lengthOf(1, errorMsg); chai.expect(doc.tasks[0].messages[0]).to.include( { message: 'Subject not found or invalid', to: '+444999' }, @@ -486,7 +626,7 @@ describe('registration', () => { }], }], }], - forms: { 'FORM-A': { }, 'FORM-B': { }} + forms: { 'FORM-A': {}, 'FORM-B': {} } }; const justPatientName = { // has just the `patient_name` field, and should create this person @@ -500,7 +640,7 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -516,7 +656,7 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -532,7 +672,7 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -548,7 +688,7 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -711,7 +851,7 @@ describe('registration', () => { }], }], }], - forms: { 'FORM-A': { }, 'FORM-B': { }, 'FORM-CHW': {}, 'FORM-NURSE': {}}, + forms: { 'FORM-A': {}, 'FORM-B': {}, 'FORM-CHW': {}, 'FORM-NURSE': {} }, contact_types: [...defaultSettings.contact_types, chwContactType, nurseContactType] }; @@ -912,7 +1052,7 @@ describe('registration', () => { }] }], }], - forms: { 'FORM-PERSON': { }, 'FORM-CHW': {}, 'FORM-NURSE': {}}, + forms: { 'FORM-PERSON': {}, 'FORM-CHW': {}, 'FORM-NURSE': {} }, contact_types: [...defaultSettings.contact_types, chwContactType, nurseContactType] }; @@ -954,7 +1094,7 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -1045,7 +1185,7 @@ describe('registration', () => { }], messages: [], }], - forms: { FORM: { }} + forms: { FORM: {} } }; const withWeeksSinceLMP = { @@ -1060,7 +1200,7 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -1076,7 +1216,7 @@ describe('registration', () => { reported_date: moment().subtract(2, 'weeks').valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -1092,7 +1232,7 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -1144,7 +1284,7 @@ describe('registration', () => { }], messages: [], }], - forms: { FORM: { }} + forms: { FORM: {} } }; const withMonthsSinceBirth = { @@ -1159,7 +1299,7 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -1175,7 +1315,7 @@ describe('registration', () => { reported_date: moment().subtract(2, 'weeks').valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -1191,11 +1331,11 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; - const docs = [ withMonthsSinceBirth, withWeeksSinceBirth, withAgeInYears ]; + const docs = [withMonthsSinceBirth, withWeeksSinceBirth, withAgeInYears]; const docIds = getIds(docs); return utils @@ -1231,7 +1371,7 @@ describe('registration', () => { }], messages: [], }], - forms: { 'FORM-PLACE': { } }, + forms: { 'FORM-PLACE': {} }, }; const createPlace = { @@ -1345,7 +1485,7 @@ describe('registration', () => { }], }], }], - forms: { 'FORM-CLINIC_NO_PARENT': { }, 'FORM-CLINIC': { }, 'FORM-HEALTH_CENTER': {} }, + forms: { 'FORM-CLINIC_NO_PARENT': {}, 'FORM-CLINIC': {}, 'FORM-HEALTH_CENTER': {} }, }; const clinicNoParentUnderClinic = { @@ -1359,7 +1499,7 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -1486,34 +1626,34 @@ describe('registration', () => { chai.expect(updated[1].errors[0]).to.deep.equal({ code: 'parent_invalid', message: 'Cannot create a place type "clinic" under parent ' + - 'the_district_hospital(contact type district_hospital)', + 'the_district_hospital(contact type district_hospital)', }); chai.expect(updated[1].tasks[0].messages[0]).to.include({ to: '+00000000', message: 'Cannot create a place type "clinic" under parent ' + - 'the_district_hospital(contact type district_hospital)', + 'the_district_hospital(contact type district_hospital)', }); chai.expect(updated[2].errors[0]).to.deep.equal({ code: 'parent_invalid', message: 'Cannot create a place type "clinic" under parent ' + - 'the_clinic(contact type clinic)', + 'the_clinic(contact type clinic)', }); chai.expect(updated[2].tasks[0].messages[0]).to.include({ to: '+11111111', message: 'Cannot create a place type "clinic" under parent ' + - 'the_clinic(contact type clinic)', + 'the_clinic(contact type clinic)', }); chai.expect(updated[3].errors[0]).to.deep.equal({ code: 'parent_invalid', message: 'Cannot create a place type "clinic" under parent ' + - 'the_district_hospital(contact type district_hospital)', + 'the_district_hospital(contact type district_hospital)', }); chai.expect(updated[3].tasks[0].messages[0]).to.include({ to: '+11111111', message: 'Cannot create a place type "clinic" under parent ' + - 'the_district_hospital(contact type district_hospital)', + 'the_district_hospital(contact type district_hospital)', }); chai.expect(updated[4].errors[0]).to.deep.equal({ @@ -1537,12 +1677,12 @@ describe('registration', () => { chai.expect(updated[6].errors[0]).to.deep.equal({ code: 'parent_invalid', message: 'Cannot create a place type "health_center" under parent ' + - 'the_clinic(contact type clinic)', + 'the_clinic(contact type clinic)', }); chai.expect(updated[6].tasks[0].messages[0]).to.include({ to: '+11111111', message: 'Cannot create a place type "health_center" under parent ' + - 'the_clinic(contact type clinic)', + 'the_clinic(contact type clinic)', }); return getContactsByReference(updated.map(doc => doc.place_id)); @@ -1651,7 +1791,7 @@ describe('registration', () => { }], }], }], - forms: { 'FORM-CLINIC_NO_PARENT': { }, 'FORM-NURSING_HOME': { }, 'FORM-HEALTH_CENTER': {} }, + forms: { 'FORM-CLINIC_NO_PARENT': {}, 'FORM-NURSING_HOME': {}, 'FORM-HEALTH_CENTER': {} }, contact_types: [ ...defaultSettings.contact_types, nursingHomeType, @@ -1696,7 +1836,7 @@ describe('registration', () => { contact: { _id: 'supervisor', parent: { _id: 'district_hospital' } } }; - const docs = [ clinicNoParent, nursingHome, healthCenter ]; + const docs = [clinicNoParent, nursingHome, healthCenter]; const ids = getIds(docs); let updatedDocs; @@ -1807,7 +1947,7 @@ describe('registration', () => { message: [{ locale: 'en', content: 'Cannot create a place type "clinic" under parent ' + - '{{parent.place_id}}(contact type {{parent.contact_type}})' + '{{parent.place_id}}(contact type {{parent.contact_type}})' }], }], }, @@ -1833,12 +1973,12 @@ describe('registration', () => { message: [{ locale: 'en', content: 'Cannot create a person type "person" under parent ' + - '{{parent.place_id}}(contact type {{parent.contact_type}})' + '{{parent.place_id}}(contact type {{parent.contact_type}})' }], }], }, ], - forms: { 'FORM-CLINIC': { public_form: true }, 'FORM-PERSON': { public_form: true }}, + forms: { 'FORM-CLINIC': { public_form: true }, 'FORM-PERSON': { public_form: true } }, }; const createClinic = { @@ -1944,7 +2084,7 @@ describe('registration', () => { }], messages: [], }], - forms: { FORM: { }}, + forms: { FORM: {} }, schedules: [{ name: 'sch1', start_from: 'some_date_field', @@ -2010,7 +2150,7 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; @@ -2104,8 +2244,8 @@ describe('registration', () => { chai.expect(infoWithPatient).to.deep.nested.include({ 'transitions.registration.ok': true }); chai.expect(infoWithClinic).to.deep.nested.include({ 'transitions.registration.ok': true }); }) - .then(() => utils.getDocs([ withPatient1._id, withPatient2._id, withClinic1._id, withClinic2._id ])) - .then(([ updWithPatient1, updWithPatient2, updWithClinic1, updWithClinic2 ]) => { + .then(() => utils.getDocs([withPatient1._id, withPatient2._id, withClinic1._id, withClinic2._id])) + .then(([updWithPatient1, updWithPatient2, updWithClinic1, updWithClinic2]) => { //1st doc has cleared schedules chai.expect(updWithPatient1.scheduled_tasks).to.be.ok; chai.expect(updWithPatient1.scheduled_tasks).to.have.lengthOf(3); @@ -2140,8 +2280,8 @@ describe('registration', () => { .then((infodoc) => { chai.expect(infodoc).to.deep.nested.include({ 'transitions.registration.ok': true }); }) - .then(() => utils.getDocs([ withPatient2._id, withClinic2._id, withClinicAndPatient1._id])) - .then(([ updWithPatient2, updWithClinic2, withClinicAndPatient ]) => { + .then(() => utils.getDocs([withPatient2._id, withClinic2._id, withClinicAndPatient1._id])) + .then(([updWithPatient2, updWithClinic2, withClinicAndPatient]) => { // cleared schedules for the withPatient doc chai.expect(updWithPatient2.scheduled_tasks).to.be.ok; chai.expect(updWithPatient2.scheduled_tasks).to.have.lengthOf(3); @@ -2175,7 +2315,7 @@ describe('registration', () => { withClinic1._id, withClinic2._id, withClinicAndPatient1._id, ])) - .then(([updWithClinicAndPatient2, ...docsWithClearedTasks ]) => { + .then(([updWithClinicAndPatient2, ...docsWithClearedTasks]) => { // withPatientAndClinic has schedules chai.expect(updWithClinicAndPatient2.scheduled_tasks).to.be.ok; chai.expect(updWithClinicAndPatient2.scheduled_tasks).to.have.lengthOf(3); @@ -2229,7 +2369,7 @@ describe('registration', () => { }], }], }], - forms: { FORM: { }}, + forms: { FORM: {} }, }; const doc = { @@ -2245,7 +2385,7 @@ describe('registration', () => { reported_date: moment().valueOf(), contact: { _id: 'person', - parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } + parent: { _id: 'clinic', parent: { _id: 'health_center', parent: { _id: 'district_hospital' } } } } }; diff --git a/tests/integration/sentinel/transitions/test-stubs.js b/tests/integration/sentinel/transitions/test-stubs.js new file mode 100644 index 00000000000..961c53aa02e --- /dev/null +++ b/tests/integration/sentinel/transitions/test-stubs.js @@ -0,0 +1,70 @@ +exports.forms = { + NP: { + transitions: { registration: true }, + registrations: [{ + form: 'FORM-A', + events: [{ + name: 'on_create', + trigger: 'add_patient', + params: '', + bool_expr: '' + }], + messages: [{ + recipient: 'reporting_unit', + event_type: 'report_accepted', + message: [{ + locale: 'en', + content: 'Patient {{patient_name}} ({{patient_id}}) added to {{clinic.name}}' + }], + }], + }], + forms: { + 'FORM-A': { + fields: { + phone_number: { + labels: { + tiny: { + en: 'phone number' + }, + description: { + en: 'phone number' + }, + short: { + en: 'phone number' + } + }, + position: 0, + flags: { + allow_duplicate: false + }, + type: 'phone_number', + required: true + }, + patient_name: { + labels: { + tiny: { + en: 'patient_name' + }, + description: { + en: 'Patient name' + }, + short: { + 'en': 'Patient name' + } + }, + position: 1, + type: 'string', + length: [ + 3, + 30 + ], + required: true + } + }, + public_form: false, + use_sentinel: true + } + } + } +}; +