From c6c9edbff89035b67dea6152fcc6ff2c07af2f9b Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Tue, 28 Jan 2025 19:21:36 +0000 Subject: [PATCH 1/3] ghana-bdr: add json schema validator --- packages/ghana-bdr/package.json | 1 + packages/ghana-bdr/src/mock.js | 73 +---- packages/ghana-bdr/src/schema/request.json | 313 +++++++++++++++++++++ pnpm-lock.yaml | 7 +- 4 files changed, 326 insertions(+), 68 deletions(-) create mode 100644 packages/ghana-bdr/src/schema/request.json diff --git a/packages/ghana-bdr/package.json b/packages/ghana-bdr/package.json index ac88363c1..c3d2e9834 100644 --- a/packages/ghana-bdr/package.json +++ b/packages/ghana-bdr/package.json @@ -29,6 +29,7 @@ ], "dependencies": { "@openfn/language-common": "workspace:*", + "ajv": "^6.12.6", "undici": "^5.22.1" }, "devDependencies": { diff --git a/packages/ghana-bdr/src/mock.js b/packages/ghana-bdr/src/mock.js index 62940d622..369a2ffa1 100644 --- a/packages/ghana-bdr/src/mock.js +++ b/packages/ghana-bdr/src/mock.js @@ -1,71 +1,12 @@ import { MockAgent } from 'undici'; import { validateRequestBody } from '../../../tools/mock-validator.js'; -const sampleRequestBody = { - child: { - first_name: 'Nana', - middle_name: 'Nana', - Surname: 'Kumasi', - birth_date: '2023/10/22', - national_id_number: '90282901892', - place_of_delivery_code: '2', - place_of_delivery_other: null, - birth_type_code: '1', - gender_code: '2', - attendant_at_birth_code: '1', - attendant_at_birth_other: null, - bith_attendant_full_name: 'Dr Lawrence Kwame', - bith_attendant_phone: '0556969609', - }, - mother: { - first_name: 'Nana', - middle_name: 'Nana', - maiden_name: 'Nana', - surname_or_marriage_name: 'Nana', - phone: '05673673673', - age: 35, - national_id_number: '90-030-003', - nationality_code: '131', - house_number: 'No 4536', - street_name: 'Accra city Ghana', - district_code: '10', - region_code: '10', - town_or_village: 'Ajiringanor', - number_of_children_ever_born_alive: 5, - born_alive_are_living: 5, - live_birth_order: 5, - born_alive_are_dead: 0, - education_level_code: '6', - education_level_other: null, - occupation_code: '13', - occupation_other: null, - }, - father: { - first_name: 'Kufor', - middle_name: 'Kufor', - surname: 'Mensa', - nationality_code: '131', - national_id_number: '88-9292-92092', - phone: '05673673737', - age: 38, - education_level_code: '13', - occupation_code: '13', - religion_code: '1', - education_level_other: null, - occupation_other: null, - religion_other: null, - is_gainfully_employed_code: '1', - }, - health_facility: { - name: 'Korlebu General Hospital', - house_number: 'No 4536', - street_name: 'Adjiringano', - town_code: '12', - town_other: null, - district_code: '11', - region_code: '12', - }, -}; +// Generated from sample with https://www.jsongenerator.io/schema +// But!! I had to replace $schema with $id +import reqSchema from './schema/request.json' assert { type: 'json' }; +import Ajv from 'ajv'; + +const validate = new Ajv().compile(reqSchema); const birthNotificationResponse = { birth_certificate_number: '000000-00-2024', @@ -112,7 +53,7 @@ export function createServer(url = 'http://tracker.chimgh.org') { const mockPool = agent.get(url); const sendBirthNotification = req => { - if (validateRequestBody(JSON.parse(req.body), sampleRequestBody)) { + if (validate(JSON.parse(req.body))) { return { statusCode: 200, responseOptions: { diff --git a/packages/ghana-bdr/src/schema/request.json b/packages/ghana-bdr/src/schema/request.json new file mode 100644 index 000000000..03ce645e1 --- /dev/null +++ b/packages/ghana-bdr/src/schema/request.json @@ -0,0 +1,313 @@ +{ + "$id": "http://json-schema.org/draft-04/schema#", + "description": "", + "type": "object", + "properties": { + "child": { + "type": "object", + "properties": { + "first_name": { + "type": "string", + "minLength": 1 + }, + "middle_name": { + "type": "string", + "minLength": 1 + }, + "Surname": { + "type": "string", + "minLength": 1 + }, + "birth_date": { + "type": "string", + "minLength": 1 + }, + "national_id_number": { + "type": "string", + "minLength": 1 + }, + "place_of_delivery_code": { + "type": "string", + "minLength": 1 + }, + "place_of_delivery_other": { + "type": "object", + "properties": {}, + "required": [] + }, + "birth_type_code": { + "type": "string", + "minLength": 1 + }, + "gender_code": { + "type": "string", + "minLength": 1 + }, + "attendant_at_birth_code": { + "type": "string", + "minLength": 1 + }, + "attendant_at_birth_other": { + "type": "object", + "properties": {}, + "required": [] + }, + "bith_attendant_full_name": { + "type": "string", + "minLength": 1 + }, + "bith_attendant_phone": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "first_name", + "middle_name", + "Surname", + "birth_date", + "national_id_number", + "place_of_delivery_code", + "place_of_delivery_other", + "birth_type_code", + "gender_code", + "attendant_at_birth_code", + "attendant_at_birth_other", + "bith_attendant_full_name", + "bith_attendant_phone" + ] + }, + "mother": { + "type": "object", + "properties": { + "first_name": { + "type": "string", + "minLength": 1 + }, + "middle_name": { + "type": "string", + "minLength": 1 + }, + "maiden_name": { + "type": "string", + "minLength": 1 + }, + "surname_or_marriage_name": { + "type": "string", + "minLength": 1 + }, + "phone": { + "type": "string", + "minLength": 1 + }, + "age": { + "type": "number" + }, + "national_id_number": { + "type": "string", + "minLength": 1 + }, + "nationality_code": { + "type": "string", + "minLength": 1 + }, + "house_number": { + "type": "string", + "minLength": 1 + }, + "street_name": { + "type": "string", + "minLength": 1 + }, + "district_code": { + "type": "string", + "minLength": 1 + }, + "region_code": { + "type": "string", + "minLength": 1 + }, + "town_or_village": { + "type": "string", + "minLength": 1 + }, + "number_of_children_ever_born_alive": { + "type": "number" + }, + "born_alive_are_living": { + "type": "number" + }, + "live_birth_order": { + "type": "number" + }, + "born_alive_are_dead": { + "type": "number" + }, + "education_level_code": { + "type": "string", + "minLength": 1 + }, + "education_level_other": { + "type": "object", + "properties": {}, + "required": [] + }, + "occupation_code": { + "type": "string", + "minLength": 1 + }, + "occupation_other": { + "type": "object", + "properties": {}, + "required": [] + } + }, + "required": [ + "first_name", + "middle_name", + "maiden_name", + "surname_or_marriage_name", + "phone", + "age", + "national_id_number", + "nationality_code", + "house_number", + "street_name", + "district_code", + "region_code", + "town_or_village", + "number_of_children_ever_born_alive", + "born_alive_are_living", + "live_birth_order", + "born_alive_are_dead", + "education_level_code", + "education_level_other", + "occupation_code", + "occupation_other" + ] + }, + "father": { + "type": "object", + "properties": { + "first_name": { + "type": "string", + "minLength": 1 + }, + "middle_name": { + "type": "string", + "minLength": 1 + }, + "surname": { + "type": "string", + "minLength": 1 + }, + "nationality_code": { + "type": "string", + "minLength": 1 + }, + "national_id_number": { + "type": "string", + "minLength": 1 + }, + "phone": { + "type": "string", + "minLength": 1 + }, + "age": { + "type": "number" + }, + "education_level_code": { + "type": "string", + "minLength": 1 + }, + "occupation_code": { + "type": "string", + "minLength": 1 + }, + "religion_code": { + "type": "string", + "minLength": 1 + }, + "education_level_other": { + "type": "object", + "properties": {}, + "required": [] + }, + "occupation_other": { + "type": "object", + "properties": {}, + "required": [] + }, + "religion_other": { + "type": "object", + "properties": {}, + "required": [] + }, + "is_gainfully_employed_code": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "first_name", + "middle_name", + "surname", + "nationality_code", + "national_id_number", + "phone", + "age", + "education_level_code", + "occupation_code", + "religion_code", + "education_level_other", + "occupation_other", + "religion_other", + "is_gainfully_employed_code" + ] + }, + "health_facility": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "house_number": { + "type": "string", + "minLength": 1 + }, + "street_name": { + "type": "string", + "minLength": 1 + }, + "town_code": { + "type": "string", + "minLength": 1 + }, + "town_other": { + "type": "object", + "properties": {}, + "required": [] + }, + "district_code": { + "type": "string", + "minLength": 1 + }, + "region_code": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "name", + "house_number", + "street_name", + "town_code", + "town_other", + "district_code", + "region_code" + ] + } + }, + "required": ["child", "mother", "father", "health_facility"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a7876925c..2903c4650 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -581,6 +581,9 @@ importers: '@openfn/language-common': specifier: workspace:* version: link:../common + ajv: + specifier: ^6.12.6 + version: 6.12.6 undici: specifier: ^5.22.1 version: 5.28.5 @@ -11970,11 +11973,11 @@ packages: /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} + dev: false /punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - dev: false /q@1.5.1: resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} @@ -14173,7 +14176,7 @@ packages: /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: - punycode: 2.3.0 + punycode: 2.3.1 /urix@0.1.0: resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==} From 1f7ace3a9c3c461e583cb4063d85299f3cfb4d3b Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Tue, 28 Jan 2025 19:28:00 +0000 Subject: [PATCH 2/3] ghana-nia: add schema validation --- packages/ghana-bdr/src/mock.js | 1 - packages/ghana-bdr/src/schema/request.json | 82 +++++++-------- packages/ghana-nia/package.json | 1 + packages/ghana-nia/src/mock.js | 38 ++----- packages/ghana-nia/src/schema/request.json | 116 +++++++++++++++++++++ pnpm-lock.yaml | 3 + 6 files changed, 172 insertions(+), 69 deletions(-) create mode 100644 packages/ghana-nia/src/schema/request.json diff --git a/packages/ghana-bdr/src/mock.js b/packages/ghana-bdr/src/mock.js index 369a2ffa1..5aae94307 100644 --- a/packages/ghana-bdr/src/mock.js +++ b/packages/ghana-bdr/src/mock.js @@ -1,5 +1,4 @@ import { MockAgent } from 'undici'; -import { validateRequestBody } from '../../../tools/mock-validator.js'; // Generated from sample with https://www.jsongenerator.io/schema // But!! I had to replace $schema with $id diff --git a/packages/ghana-bdr/src/schema/request.json b/packages/ghana-bdr/src/schema/request.json index 03ce645e1..e3eb6bb0f 100644 --- a/packages/ghana-bdr/src/schema/request.json +++ b/packages/ghana-bdr/src/schema/request.json @@ -8,27 +8,27 @@ "properties": { "first_name": { "type": "string", - "minLength": 1 + "minLength": 0 }, "middle_name": { "type": "string", - "minLength": 1 + "minLength": 0 }, "Surname": { "type": "string", - "minLength": 1 + "minLength": 0 }, "birth_date": { "type": "string", - "minLength": 1 + "minLength": 0 }, "national_id_number": { "type": "string", - "minLength": 1 + "minLength": 0 }, "place_of_delivery_code": { "type": "string", - "minLength": 1 + "minLength": 0 }, "place_of_delivery_other": { "type": "object", @@ -37,15 +37,15 @@ }, "birth_type_code": { "type": "string", - "minLength": 1 + "minLength": 0 }, "gender_code": { "type": "string", - "minLength": 1 + "minLength": 0 }, "attendant_at_birth_code": { "type": "string", - "minLength": 1 + "minLength": 0 }, "attendant_at_birth_other": { "type": "object", @@ -54,11 +54,11 @@ }, "bith_attendant_full_name": { "type": "string", - "minLength": 1 + "minLength": 0 }, "bith_attendant_phone": { "type": "string", - "minLength": 1 + "minLength": 0 } }, "required": [ @@ -82,54 +82,54 @@ "properties": { "first_name": { "type": "string", - "minLength": 1 + "minLength": 0 }, "middle_name": { "type": "string", - "minLength": 1 + "minLength": 0 }, "maiden_name": { "type": "string", - "minLength": 1 + "minLength": 0 }, "surname_or_marriage_name": { "type": "string", - "minLength": 1 + "minLength": 0 }, "phone": { "type": "string", - "minLength": 1 + "minLength": 0 }, "age": { "type": "number" }, "national_id_number": { "type": "string", - "minLength": 1 + "minLength": 0 }, "nationality_code": { "type": "string", - "minLength": 1 + "minLength": 0 }, "house_number": { "type": "string", - "minLength": 1 + "minLength": 0 }, "street_name": { "type": "string", - "minLength": 1 + "minLength": 0 }, "district_code": { "type": "string", - "minLength": 1 + "minLength": 0 }, "region_code": { "type": "string", - "minLength": 1 + "minLength": 0 }, "town_or_village": { "type": "string", - "minLength": 1 + "minLength": 0 }, "number_of_children_ever_born_alive": { "type": "number" @@ -145,7 +145,7 @@ }, "education_level_code": { "type": "string", - "minLength": 1 + "minLength": 0 }, "education_level_other": { "type": "object", @@ -154,7 +154,7 @@ }, "occupation_code": { "type": "string", - "minLength": 1 + "minLength": 0 }, "occupation_other": { "type": "object", @@ -191,42 +191,42 @@ "properties": { "first_name": { "type": "string", - "minLength": 1 + "minLength": 0 }, "middle_name": { "type": "string", - "minLength": 1 + "minLength": 0 }, "surname": { "type": "string", - "minLength": 1 + "minLength": 0 }, "nationality_code": { "type": "string", - "minLength": 1 + "minLength": 0 }, "national_id_number": { "type": "string", - "minLength": 1 + "minLength": 0 }, "phone": { "type": "string", - "minLength": 1 + "minLength": 0 }, "age": { "type": "number" }, "education_level_code": { "type": "string", - "minLength": 1 + "minLength": 0 }, "occupation_code": { "type": "string", - "minLength": 1 + "minLength": 0 }, "religion_code": { "type": "string", - "minLength": 1 + "minLength": 0 }, "education_level_other": { "type": "object", @@ -245,7 +245,7 @@ }, "is_gainfully_employed_code": { "type": "string", - "minLength": 1 + "minLength": 0 } }, "required": [ @@ -270,19 +270,19 @@ "properties": { "name": { "type": "string", - "minLength": 1 + "minLength": 0 }, "house_number": { "type": "string", - "minLength": 1 + "minLength": 0 }, "street_name": { "type": "string", - "minLength": 1 + "minLength": 0 }, "town_code": { "type": "string", - "minLength": 1 + "minLength": 0 }, "town_other": { "type": "object", @@ -291,11 +291,11 @@ }, "district_code": { "type": "string", - "minLength": 1 + "minLength": 0 }, "region_code": { "type": "string", - "minLength": 1 + "minLength": 0 } }, "required": [ diff --git a/packages/ghana-nia/package.json b/packages/ghana-nia/package.json index fcc279c78..77ebb87be 100644 --- a/packages/ghana-nia/package.json +++ b/packages/ghana-nia/package.json @@ -29,6 +29,7 @@ ], "dependencies": { "@openfn/language-common": "workspace:*", + "ajv": "6", "undici": "^5.22.1" }, "devDependencies": { diff --git a/packages/ghana-nia/src/mock.js b/packages/ghana-nia/src/mock.js index ef8650b69..fe544d9a7 100644 --- a/packages/ghana-nia/src/mock.js +++ b/packages/ghana-nia/src/mock.js @@ -1,31 +1,11 @@ import { MockAgent } from 'undici'; -import { validateRequestBody } from '../../../tools/mock-validator.js'; -const sampleRequestBody = { - merchantKey: '89487284-9083-4015-9128-91d8db7e023e', - babyData: { - dateOfBirth: '2024-03-05', - fatherName: 'Nyarkoa Osei-Akoto', - forenames: 'Kharis', - gender: 'Female', - lightwaveETrackerID: '00313180/24-03', - motherName: 'Gifty Osei-Akoto', - noSiblingsInDelivery: '0', - placeOfBirth: 'New Market Health Centre', - surname: 'Osei', - timeOfbirth: '09:34', - weightAtBirth: '2.7', - birthCertificateNumber: '011803-48-2024', - babyPicture: '...base64 encoded image goes here...', - }, - personVouching: { - etrackerLightwaveID: '00313180/24-03', - ghanaCardPIN: 'GHA-001097272-4', - relationToBaby: 'Mother', - relativePhone: '0248403076', - relativePicture: '...base64 encoded image goes here...', - }, -}; +// Generated from sample with https://www.jsongenerator.io/schema +// But!! I had to replace $schema with $id +import reqSchema from './schema/request.json' assert { type: 'json' }; +import Ajv from 'ajv'; + +const validate = new Ajv().compile(reqSchema); const nationalIdSampleResponse = { data: { @@ -47,7 +27,8 @@ export function createServer(url = 'https://selfie.imsgh.org:2035') { const mockPool = agent.get(url); const sendNiaData = req => { - if (validateRequestBody(JSON.parse(req.body), sampleRequestBody)) { + const valid = validate(JSON.parse(req.body)); + if (valid) { return { statusCode: 200, responseOptions: { @@ -56,6 +37,7 @@ export function createServer(url = 'https://selfie.imsgh.org:2035') { data: JSON.stringify(nationalIdSampleResponse), }; } else { + console.log('Validation errors:', validate.errors); return { // Why is this a 404 coming from NIA? statusCode: 404, @@ -68,6 +50,8 @@ export function createServer(url = 'https://selfie.imsgh.org:2035') { data: null, // sic msg: 'Error in Verifiation Process', + // from the mock: + _errors: validate.errors, }, }; } diff --git a/packages/ghana-nia/src/schema/request.json b/packages/ghana-nia/src/schema/request.json new file mode 100644 index 000000000..dd672f622 --- /dev/null +++ b/packages/ghana-nia/src/schema/request.json @@ -0,0 +1,116 @@ +{ + "$id": "http://json-schema.org/draft-04/schema#", + "description": "", + "type": "object", + "properties": { + "merchantKey": { + "type": "string", + "minLength": 0 + }, + "babyData": { + "type": "object", + "properties": { + "dateOfBirth": { + "type": "string", + "minLength": 0 + }, + "fatherName": { + "type": "string", + "minLength": 0 + }, + "forenames": { + "type": "string", + "minLength": 0 + }, + "gender": { + "type": "string", + "minLength": 0 + }, + "lightwaveETrackerID": { + "type": "string", + "minLength": 0 + }, + "motherName": { + "type": "string", + "minLength": 0 + }, + "noSiblingsInDelivery": { + "type": "string", + "minLength": 0 + }, + "placeOfBirth": { + "type": "string", + "minLength": 0 + }, + "surname": { + "type": "string", + "minLength": 0 + }, + "timeOfbirth": { + "type": "string", + "minLength": 0 + }, + "weightAtBirth": { + "type": "string", + "minLength": 0 + }, + "birthCertificateNumber": { + "type": "string", + "minLength": 0 + }, + "babyPicture": { + "type": "string", + "minLength": 0 + } + }, + "required": [ + "dateOfBirth", + "fatherName", + "forenames", + "gender", + "lightwaveETrackerID", + "motherName", + "noSiblingsInDelivery", + "placeOfBirth", + "surname", + "timeOfbirth", + "weightAtBirth", + "birthCertificateNumber", + "babyPicture" + ] + }, + "personVouching": { + "type": "object", + "properties": { + "etrackerLightwaveID": { + "type": "string", + "minLength": 0 + }, + "ghanaCardPIN": { + "type": "string", + "minLength": 0 + }, + "relationToBaby": { + "type": "string", + "minLength": 0 + }, + "relativePhone": { + "type": "string", + "minLength": 0 + }, + "relativePicture": { + "type": "string", + "minLength": 0 + } + }, + "required": [ + "etrackerLightwaveID", + "ghanaCardPIN", + "relationToBaby", + "relativePhone", + "relativePicture" + ] + } + }, + "required": ["merchantKey", "babyData", "personVouching"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2903c4650..fded19a99 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -612,6 +612,9 @@ importers: '@openfn/language-common': specifier: workspace:* version: link:../common + ajv: + specifier: '6' + version: 6.12.6 undici: specifier: ^5.22.1 version: 5.28.5 From 1fe01172982371426b5b7d7aff212bd59b7f3cc4 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Tue, 28 Jan 2025 19:30:48 +0000 Subject: [PATCH 3/3] updates --- packages/ghana-bdr/src/mock.js | 3 +++ packages/ghana-nia/src/mock.js | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/ghana-bdr/src/mock.js b/packages/ghana-bdr/src/mock.js index 5aae94307..a5d65d74d 100644 --- a/packages/ghana-bdr/src/mock.js +++ b/packages/ghana-bdr/src/mock.js @@ -61,6 +61,7 @@ export function createServer(url = 'http://tracker.chimgh.org') { data: JSON.stringify(birthNotificationResponse), }; } else { + console.log('Validation errors:', validate.errors); return { statusCode: 417, responseOptions: { @@ -69,6 +70,8 @@ export function createServer(url = 'http://tracker.chimgh.org') { data: { Message: '{"issuccessful":false,"message":"Record failed to save with this error --> Modify the clause to make sure that a column is updated only once. If this statement updates or inserts columns into a view, column aliasing can conceal the duplication in your code.","messagecode":"210"} (Note that this is just a sample error from a mock endpoint.)', + // from the mock: + _errors: validate.errors, }, }; } diff --git a/packages/ghana-nia/src/mock.js b/packages/ghana-nia/src/mock.js index fe544d9a7..e2431edc1 100644 --- a/packages/ghana-nia/src/mock.js +++ b/packages/ghana-nia/src/mock.js @@ -27,8 +27,7 @@ export function createServer(url = 'https://selfie.imsgh.org:2035') { const mockPool = agent.get(url); const sendNiaData = req => { - const valid = validate(JSON.parse(req.body)); - if (valid) { + if (validate(JSON.parse(req.body))) { return { statusCode: 200, responseOptions: {