diff --git a/.env.example b/.env.example index 38e357a..5679146 100644 --- a/.env.example +++ b/.env.example @@ -4,10 +4,8 @@ APPLICATIONINSIGHTS_CONNECTION_STRING= BASIC_GITHUB_TOKEN= -REDIS_CACHE_SEC=60 REDIS_PORT=6379 -REDIS_KEY= -REDIS_HOSTNAME= +REDIS_HOSTNAME=redis # replace with localhost if you run outside docker VALIDATOR_SERVICES_URL=https://dev-func-validator-services.azurewebsites.net/api VALIDATOR_SERVICES_KEY_NAME=x-functions-key diff --git a/.github/workflows/develop-func-ci.yml b/.github/workflows/develop-func-ci.yml index 4e20cb6..682795b 100644 --- a/.github/workflows/develop-func-ci.yml +++ b/.github/workflows/develop-func-ci.yml @@ -31,7 +31,7 @@ jobs: - name: 'Install Dependencies with Npm' run: | - npm i -g npm + npm i -g npm@8.19.4 npm ci - name: 'Run unit tests' diff --git a/Dockerfile b/Dockerfile index 87643af..e7f21dd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,6 @@ COPY . /home/site/wwwroot # Install node_modules WORKDIR /home/site/wwwroot RUN \ - npm i -g npm && \ + npm i -g npm@8.19.4 && \ npm pkg delete scripts.prepare && \ npm ci --production diff --git a/README.md b/README.md index ae57d1f..8c43ae5 100644 --- a/README.md +++ b/README.md @@ -87,12 +87,11 @@ BASIC_GITHUB_TOKEN - GitHub personal access token. This is needed to pull in the Codelists from the `IATI/IATI-Validator-Codelists` repository. Note that you cannot use a "Personal Access Token (Classic)"; you must generate a fine-grained access token. -REDIS_CACHE_SEC=60 REDIS_PORT=6379 -REDIS_KEY= -REDIS_HOSTNAME= +REDIS_HOSTNAME=redis -- Redis connection, leaving the default will connect to a locally installed instance if you have one. +- Redis connection, configured for `docker compose use`. If you develop outside + docker, change these settings as appropriate. VALIDATOR_SERVICES_URL=https://func-validator-services-dev.azurewebsites.net/api VALIDATOR_SERVICES_KEY_NAME=x-functions-key diff --git a/config/redis.js b/config/redis.js index 3c64d21..6b78194 100644 --- a/config/redis.js +++ b/config/redis.js @@ -26,12 +26,16 @@ if (config.REDIS_KEY && config.REDIS_HOSTNAME) { }, }, }; +} else { + connectionOptions = { + url: `redis://${config.REDIS_HOSTNAME}:${config.REDIS_PORT}` + }; } const client = createClient(connectionOptions); client.on('ready', () => { console.log({ name: 'redisConnect', - value: `Redis: Connection to ${config.REDIS_HOSTNAME || 'local'} ready`, + value: `Redis: Connection to ${config.REDIS_HOSTNAME} ready`, }); }); client.on('error', (err) => { diff --git a/integration-tests/js-validator-api-tests.postman_collection.json b/integration-tests/js-validator-api-tests.postman_collection.json index e9be251..4a6d029 100644 --- a/integration-tests/js-validator-api-tests.postman_collection.json +++ b/integration-tests/js-validator-api-tests.postman_collection.json @@ -3,7 +3,8 @@ "_postman_id": "4a0049ac-ff32-4d7a-ac58-7149a7b2f704", "name": "js-validator-api-tests", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "13097277" + "_exporter_id": "29844336", + "_collection_link": "https://iatisecretariat.postman.co/workspace/IATI~c354ba4e-15bf-41ee-9d69-a5a5360da160/collection/13097277-4a0049ac-ff32-4d7a-ac58-7149a7b2f704?action=share&source=collection_link&creator=29844336" }, "item": [ { @@ -1572,7 +1573,7 @@ "response": [] }, { - "name": "iati-act-no-errors", + "name": "iati-act-no-errors - group false meta true", "event": [ { "listen": "test", @@ -1660,7 +1661,189 @@ "response": [] }, { - "name": "iati-org-no-errors", + "name": "iati-activity-line-endings-cr-and-crlf", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });", + "const jsonData = pm.response.json();", + "pm.test(\"The file is valid\", function () {", + " pm.expect(jsonData.valid).to.eql(true);", + "});", + "pm.test(\"fileType value is iati-activities\", function () {", + " pm.expect(jsonData.fileType).to.eql('iati-activities');", + "});", + "pm.test(\"version is 2.02\", function () {", + " pm.expect(jsonData.iatiVersion).to.eql('2.03');", + "});", + "pm.test(\"rulesetCommitSha is present\", function () {", + " pm.expect(jsonData.rulesetCommitSha).to.be.a('string').with.length.greaterThan(0);", + "});", + "pm.test(\"codelistCommitSha is present\", function () {", + " pm.expect(jsonData.codelistCommitSha).to.be.a('string').with.length.greaterThan(0);", + "});", + "pm.test(\"orgIdPrefixFileName is present\", function () {", + " pm.expect(jsonData.orgIdPrefixFileName).to.be.a('string').with.length.greaterThan(0);", + "});", + "pm.test(\"No file level errors are present\", function () {", + " pm.expect(jsonData.errors.map(obj => (obj.title))).to.not.include('file');", + "});", + "pm.test(\"Activities identifier is equal to AA-AAA-123456789-ABC123 and contains identifier error 1.14.8\", function () {", + " pm.expect(jsonData.errors.map(obj => (obj.id))).to.include('1.14.8');", + "});", + "pm.test(\"Response containes expected activities object\", function () {", + " pm.expect(jsonData['iati-activities'][0]).to.eql({", + " \"identifier\": \"AA-AAA-123456789-ABC123\",", + " \"valid\": true,", + " \"index\": 0", + " });", + "});", + "", + "", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/xml", + "type": "text" + }, + { + "key": "Accept", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "file", + "file": { + "src": "iati-act-line-endings-cr-and-crlf.xml" + } + }, + "url": { + "raw": "{{baseURL}}/pub/validate?group=false&meta=true", + "host": [ + "{{baseURL}}" + ], + "path": [ + "pub", + "validate" + ], + "query": [ + { + "key": "group", + "value": "false" + }, + { + "key": "meta", + "value": "true" + } + ] + } + }, + "response": [] + }, + { + "name": "iati-activity-line-endings-cr", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () { pm.response.to.have.status(200); });", + "const jsonData = pm.response.json();", + "pm.test(\"The file is valid\", function () {", + " pm.expect(jsonData.valid).to.eql(true);", + "});", + "pm.test(\"fileType value is iati-activities\", function () {", + " pm.expect(jsonData.fileType).to.eql('iati-activities');", + "});", + "pm.test(\"version is 2.02\", function () {", + " pm.expect(jsonData.iatiVersion).to.eql('2.03');", + "});", + "pm.test(\"rulesetCommitSha is present\", function () {", + " pm.expect(jsonData.rulesetCommitSha).to.be.a('string').with.length.greaterThan(0);", + "});", + "pm.test(\"codelistCommitSha is present\", function () {", + " pm.expect(jsonData.codelistCommitSha).to.be.a('string').with.length.greaterThan(0);", + "});", + "pm.test(\"orgIdPrefixFileName is present\", function () {", + " pm.expect(jsonData.orgIdPrefixFileName).to.be.a('string').with.length.greaterThan(0);", + "});", + "pm.test(\"No file level errors are present\", function () {", + " pm.expect(jsonData.errors.map(obj => (obj.title))).to.not.include('file');", + "});", + "pm.test(\"Activities identifier is equal to AA-AAA-123456789-ABC123 and contains identifier error 1.14.8\", function () {", + " pm.expect(jsonData.errors.map(obj => (obj.id))).to.include('1.14.8');", + "});", + "pm.test(\"Response containes expected activities object\", function () {", + " pm.expect(jsonData['iati-activities'][0]).to.eql({", + " \"identifier\": \"AA-AAA-123456789-ABC123\",", + " \"valid\": true,", + " \"index\": 0", + " });", + "});", + "", + "", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/xml", + "type": "text" + }, + { + "key": "Accept", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "file", + "file": { + "src": "iati-act-line-endings-cr.xml" + } + }, + "url": { + "raw": "{{baseURL}}/pub/validate?group=false&meta=true", + "host": [ + "{{baseURL}}" + ], + "path": [ + "pub", + "validate" + ], + "query": [ + { + "key": "group", + "value": "false" + }, + { + "key": "meta", + "value": "true" + } + ] + } + }, + "response": [] + }, + { + "name": "iati-org-no-errors - meta true", "event": [ { "listen": "test", @@ -3591,7 +3774,7 @@ ] }, { - "name": "iati-act-atLeastOne-error", + "name": "iati-act-atLeastOne-error-01", "event": [ { "listen": "test", @@ -3627,7 +3810,74 @@ "body": { "mode": "file", "file": { - "src": "iati-act-atLeastOne-error.xml" + "src": "iati-act-atLeastOne-error-01.xml" + } + }, + "url": { + "raw": "{{baseURL}}/pub/validate?details=true&group=false", + "host": [ + "{{baseURL}}" + ], + "path": [ + "pub", + "validate" + ], + "query": [ + { + "key": "details", + "value": "true" + }, + { + "key": "group", + "value": "false" + } + ] + } + }, + "response": [] + }, + { + "name": "iati-act-atLeastOne-error-02-transaction-desc", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Errors Array contains error id 4.4.1\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.errors.map(obj => (obj.id))).to.include('4.4.1');", + "});", + "pm.test(\"Errors contain a context Array with at least 1 string\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.errors.map(obj => (obj.context)).map((con) => con.text).length).to.be.greaterThan(0);", + "});", + "pm.test(\"Context text correctly identifies transaction/description at line 268 as the problem\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.errors[0].context[0].text).to.equal(\"For transaction/description at line: 268, column: 4\");", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/xml", + "type": "text" + }, + { + "key": "Accept", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "file", + "file": { + "src": "iati-act-atLeastOne-error-02.xml" } }, "url": { @@ -4569,6 +4819,255 @@ } ] }, + { + "name": "Rule Specific Tests - non 200", + "item": [ + { + "name": "Activities", + "item": [ + { + "name": "iati-act-atLeastOne-error-03-iati-activity-desc", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 422\", function () { pm.response.to.have.status(422); });", + "pm.test(\"The file is not valid\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.valid).to.eql(false);", + "});", + "pm.test(\"Errors Array contains error id 4.4.1\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.errors.map(obj => (obj.id))).to.include('4.4.1');", + "});", + "pm.test(\"Errors contain a context Array with at least 1 string\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.errors.map(obj => (obj.context)).map((con) => con.text).length).to.be.greaterThan(0);", + "});", + "pm.test(\"Context text correctly identifies the iati-activity/description as the problem element\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.errors[1].context[0].text).to.be.equal(\"For iati-activity/description at line: 29, column: 3\");", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/xml", + "type": "text" + }, + { + "key": "Accept", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "file", + "file": { + "src": "iati-act-atLeastOne-error-03.xml" + } + }, + "url": { + "raw": "{{baseURL}}/pub/validate?details=true&group=false", + "host": [ + "{{baseURL}}" + ], + "path": [ + "pub", + "validate" + ], + "query": [ + { + "key": "details", + "value": "true" + }, + { + "key": "group", + "value": "false" + } + ] + } + }, + "response": [] + }, + { + "name": "iati-act-atLeastOne-error-04-transaction-desc", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 422\", function () { pm.response.to.have.status(422); });", + "pm.test(\"The file is not valid\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.valid).to.eql(false);", + "});", + "pm.test(\"Errors Array contains error id 4.4.1\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.errors.map(obj => (obj.id))).to.include('4.4.1');", + "});", + "pm.test(\"Errors contain a context Array with at least 1 string\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.errors.map(obj => (obj.context)).map((con) => con.text).length).to.be.greaterThan(0);", + "});", + "pm.test(\"Context text correctly identifies transaction/description at line 268 as the problem\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.errors[1].context[0].text).to.equal(\"For transaction/description at line: 268, column: 4\");", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/xml", + "type": "text" + }, + { + "key": "Accept", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "file", + "file": { + "src": "iati-act-atLeastOne-error-04.xml" + } + }, + "url": { + "raw": "{{baseURL}}/pub/validate?details=true&group=false", + "host": [ + "{{baseURL}}" + ], + "path": [ + "pub", + "validate" + ], + "query": [ + { + "key": "details", + "value": "true" + }, + { + "key": "group", + "value": "false" + } + ] + } + }, + "response": [] + }, + { + "name": "iati-act-atLeastOne-error-05-result-title", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 422\", function () { pm.response.to.have.status(422); });", + "pm.test(\"The file is not valid\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.valid).to.eql(false);", + "});", + "pm.test(\"Errors Array contains error id 4.3.1\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.errors.map(obj => (obj.id))).to.include('4.3.1');", + "});", + "pm.test(\"Errors contain a context Array with at least 1 string\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.errors.map(obj => (obj.context)).map((con) => con.text).length).to.be.greaterThan(0);", + "});", + "pm.test(\"Context text correctly identifies the result/title as the problem element\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.errors[1].context[0].text).to.be.equal(\"For result/title at line: 322, column: 4\");", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/xml", + "type": "text" + }, + { + "key": "Accept", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "file", + "file": { + "src": "iati-act-atLeastOne-error-05.xml" + } + }, + "url": { + "raw": "{{baseURL}}/pub/validate?details=true&group=false", + "host": [ + "{{baseURL}}" + ], + "path": [ + "pub", + "validate" + ], + "query": [ + { + "key": "details", + "value": "true" + }, + { + "key": "group", + "value": "false" + } + ] + } + }, + "response": [] + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test(\"Validation Report contains API version\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.apiVersion).to.exist;", + "});" + ] + } + } + ] + }, { "name": "File Schema Validation", "item": [ diff --git a/integration-tests/test-files/iati-act-atLeastOne-error.xml b/integration-tests/test-files/iati-act-atLeastOne-error-01.xml similarity index 100% rename from integration-tests/test-files/iati-act-atLeastOne-error.xml rename to integration-tests/test-files/iati-act-atLeastOne-error-01.xml diff --git a/integration-tests/test-files/iati-act-atLeastOne-error-02.xml b/integration-tests/test-files/iati-act-atLeastOne-error-02.xml new file mode 100644 index 0000000..266e518 --- /dev/null +++ b/integration-tests/test-files/iati-act-atLeastOne-error-02.xml @@ -0,0 +1,470 @@ + + + + + + + + + + + XM-DAC-41127-123 + + + + + Organisation name + Nom de l'organisme + + + + + + <narrative>Activity title</narrative> + <narrative xml:lang="fr">Titre de l'activité</narrative> + <narrative xml:lang="es">Título de la actividad</narrative> + + + + + + Objectives for the activity, for example from a logical framework. + + + Objectives for the activity, for example from a logical framework. + Objectifs de l'activité, par exemple à partir d'un cadre logique. + + + Statement of groups targeted to benefit from the activity. + Déclaration de groupes ciblés pour bénéficier de l'activité. + + + + + + Name of Agency B + + + Name of Agency C + + + Name of Agency A + Nom de l'agence A + + + + + + + Organisation name + + + + + + + + + + + Planned start date of the activity + Date prévue de début de l'activité + + + + + + + + Agency A + + + Department B + + + A. Example + + + Transparency Lead + + 0044111222333444 + transparency@example.org + http://www.example.org + + Transparency House, The Street, Town, City, Postcode + + + + + + + + + + + + + + + + + + + + + + + + Location name + + + Location description + + + A description that qualifies the activity taking place at the location. + + + + 31.616944 65.716944 + + + + + + + + + + + + + Location #2 name + + + Location #2 description + + + A description that qualifies the activity taking place at location #2 + + + + 11.5500 104.9167 + + + + + + + + + + + + Code A1 + + + + + + A description of the tag + + + A description of the tag + + + + + + + + Description text + + + + + Description text + + + + + + + + Nepal Earthquake April 2015 + Népal Earthquake Avril 2015 + + + Nepal Earthquake April 2015 + + + + + + + + Code A1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3000 + + + + + + + + 3000 + + Agency B + + + Agency A + + + + + + + + + 3000 + + + + + + + + + + + + 1000 + + + + + Agency B + + + Agency A + + + + + + + + + + + + + + + <narrative>Project Report 2013</narrative> + <narrative xml:lang="fr">Rapport de projet 2013</narrative> + + + Description of the content of the project report or guidance on where to access the relevant information in this report + + + + + + + + + + + + + + + + + + + + Conditions text + Conditions texte + + + + + + + + <narrative>Result title</narrative> + + + Result description text + + + + + <narrative>Results Report 2013</narrative> + + + Report of results + + + + + + + + + + + + + <narrative>Indicator title</narrative> + + + Indicator description text + + + + <narrative>Results Indicator Report 2013</narrative> + + + Report of results indicator + + + + + + + + + + + + + + + + + <narrative>Results Baseline Report 2013</narrative> + + + Report of results baseline + + + + + + + Baseline comment text + + + + + + + + + + + + + + Target comment text + + + + <narrative>Results Period Target Report 2013</narrative> + + + Report of results period target + + + + + + + + + + + + + Actual comment text + + + + <narrative>Results Period Actual Report 2013</narrative> + + + Report of results period actual + + + + + + + + + + + + + + + + + + + + + + + + + 200000 + 1500000 + 0 + 0 + + 21039 + + + + + + 10000 + + + + + + + + diff --git a/integration-tests/test-files/iati-act-atLeastOne-error-03.xml b/integration-tests/test-files/iati-act-atLeastOne-error-03.xml new file mode 100644 index 0000000..2b13491 --- /dev/null +++ b/integration-tests/test-files/iati-act-atLeastOne-error-03.xml @@ -0,0 +1,469 @@ + + + + + + + + + + + XM-DAC-41127-123 + + + + + Organisation name + Nom de l'organisme + + + + + + <narrative>Activity title</narrative> + <narrative xml:lang="fr">Titre de l'activité</narrative> + <narrative xml:lang="es">Título de la actividad</narrative> + + + + + + + + Objectives for the activity, for example from a logical framework. + Objectifs de l'activité, par exemple à partir d'un cadre logique. + + + Statement of groups targeted to benefit from the activity. + Déclaration de groupes ciblés pour bénéficier de l'activité. + + + + + + Name of Agency B + + + Name of Agency C + + + Name of Agency A + Nom de l'agence A + + + + + + + Organisation name + + + + + + + + + + + Planned start date of the activity + Date prévue de début de l'activité + + + + + + + + Agency A + + + Department B + + + A. Example + + + Transparency Lead + + 0044111222333444 + transparency@example.org + http://www.example.org + + Transparency House, The Street, Town, City, Postcode + + + + + + + + + + + + + + + + + + + + + + + + Location name + + + Location description + + + A description that qualifies the activity taking place at the location. + + + + 31.616944 65.716944 + + + + + + + + + + + + + Location #2 name + + + Location #2 description + + + A description that qualifies the activity taking place at location #2 + + + + 11.5500 104.9167 + + + + + + + + + + + + Code A1 + + + + + + A description of the tag + + + A description of the tag + + + + + + + + Description text + + + + + Description text + + + + + + + + Nepal Earthquake April 2015 + Népal Earthquake Avril 2015 + + + Nepal Earthquake April 2015 + + + + + + + + Code A1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3000 + + + + + + + + 3000 + + Agency B + + + Agency A + + + + + + + + + 3000 + + + + + + + + + + + + 1000 + + Transaction description text + + + Agency B + + + Agency A + + + + + + + + + + + + + + + <narrative>Project Report 2013</narrative> + <narrative xml:lang="fr">Rapport de projet 2013</narrative> + + + Description of the content of the project report or guidance on where to access the relevant information in this report + + + + + + + + + + + + + + + + + + + + Conditions text + Conditions texte + + + + + + + + <narrative>Result title</narrative> + + + Result description text + + + + + <narrative>Results Report 2013</narrative> + + + Report of results + + + + + + + + + + + + + <narrative>Indicator title</narrative> + + + Indicator description text + + + + <narrative>Results Indicator Report 2013</narrative> + + + Report of results indicator + + + + + + + + + + + + + + + + + <narrative>Results Baseline Report 2013</narrative> + + + Report of results baseline + + + + + + + Baseline comment text + + + + + + + + + + + + + + Target comment text + + + + <narrative>Results Period Target Report 2013</narrative> + + + Report of results period target + + + + + + + + + + + + + Actual comment text + + + + <narrative>Results Period Actual Report 2013</narrative> + + + Report of results period actual + + + + + + + + + + + + + + + + + + + + + + + + + 200000 + 1500000 + 0 + 0 + + 21039 + + + + + + 10000 + + + + + + + + diff --git a/integration-tests/test-files/iati-act-atLeastOne-error-04.xml b/integration-tests/test-files/iati-act-atLeastOne-error-04.xml new file mode 100644 index 0000000..f2bfacf --- /dev/null +++ b/integration-tests/test-files/iati-act-atLeastOne-error-04.xml @@ -0,0 +1,469 @@ + + + + + + + + + + + XM-DAC-41127-123 + + + + + Organisation name + Nom de l'organisme + + + + + + <narrative>Activity title</narrative> + <narrative xml:lang="fr">Titre de l'activité</narrative> + <narrative xml:lang="es">Título de la actividad</narrative> + + + + + + Objectives for the activity, for example from a logical framework. + + + Objectives for the activity, for example from a logical framework. + Objectifs de l'activité, par exemple à partir d'un cadre logique. + + + Statement of groups targeted to benefit from the activity. + Déclaration de groupes ciblés pour bénéficier de l'activité. + + + + + + Name of Agency B + + + Name of Agency C + + + Name of Agency A + Nom de l'agence A + + + + + + + Organisation name + + + + + + + + + + + Planned start date of the activity + Date prévue de début de l'activité + + + + + + + + Agency A + + + Department B + + + A. Example + + + Transparency Lead + + 0044111222333444 + transparency@example.org + http://www.example.org + + Transparency House, The Street, Town, City, Postcode + + + + + + + + + + + + + + + + + + + + + + + + Location name + + + Location description + + + A description that qualifies the activity taking place at the location. + + + + 31.616944 65.716944 + + + + + + + + + + + + + Location #2 name + + + Location #2 description + + + A description that qualifies the activity taking place at location #2 + + + + 11.5500 104.9167 + + + + + + + + + + + + Code A1 + + + + + + A description of the tag + + + A description of the tag + + + + + + + + Description text + + + + + Description text + + + + + + + + Nepal Earthquake April 2015 + Népal Earthquake Avril 2015 + + + Nepal Earthquake April 2015 + + + + + + + + Code A1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3000 + + + + + + + + 3000 + + Agency B + + + Agency A + + + + + + + + + 3000 + + + + + + + + + + + + 1000 + + + + Agency B + + + Agency A + + + + + + + + + + + + + + + <narrative>Project Report 2013</narrative> + <narrative xml:lang="fr">Rapport de projet 2013</narrative> + + + Description of the content of the project report or guidance on where to access the relevant information in this report + + + + + + + + + + + + + + + + + + + + Conditions text + Conditions texte + + + + + + + + <narrative>Result title</narrative> + + + Result description text + + + + + <narrative>Results Report 2013</narrative> + + + Report of results + + + + + + + + + + + + + <narrative>Indicator title</narrative> + + + Indicator description text + + + + <narrative>Results Indicator Report 2013</narrative> + + + Report of results indicator + + + + + + + + + + + + + + + + + <narrative>Results Baseline Report 2013</narrative> + + + Report of results baseline + + + + + + + Baseline comment text + + + + + + + + + + + + + + Target comment text + + + + <narrative>Results Period Target Report 2013</narrative> + + + Report of results period target + + + + + + + + + + + + + Actual comment text + + + + <narrative>Results Period Actual Report 2013</narrative> + + + Report of results period actual + + + + + + + + + + + + + + + + + + + + + + + + + 200000 + 1500000 + 0 + 0 + + 21039 + + + + + + 10000 + + + + + + + + diff --git a/integration-tests/test-files/iati-act-atLeastOne-error-05.xml b/integration-tests/test-files/iati-act-atLeastOne-error-05.xml new file mode 100644 index 0000000..4655bfd --- /dev/null +++ b/integration-tests/test-files/iati-act-atLeastOne-error-05.xml @@ -0,0 +1,469 @@ + + + + + + + + + + + XM-DAC-41127-123 + + + + + Organisation name + Nom de l'organisme + + + + + + <narrative>Activity title</narrative> + <narrative xml:lang="fr">Titre de l'activité</narrative> + <narrative xml:lang="es">Título de la actividad</narrative> + + + + + + Objectives for the activity, for example from a logical framework. + + + Objectives for the activity, for example from a logical framework. + Objectifs de l'activité, par exemple à partir d'un cadre logique. + + + Statement of groups targeted to benefit from the activity. + Déclaration de groupes ciblés pour bénéficier de l'activité. + + + + + + Name of Agency B + + + Name of Agency C + + + Name of Agency A + Nom de l'agence A + + + + + + + Organisation name + + + + + + + + + + + Planned start date of the activity + Date prévue de début de l'activité + + + + + + + + Agency A + + + Department B + + + A. Example + + + Transparency Lead + + 0044111222333444 + transparency@example.org + http://www.example.org + + Transparency House, The Street, Town, City, Postcode + + + + + + + + + + + + + + + + + + + + + + + + Location name + + + Location description + + + A description that qualifies the activity taking place at the location. + + + + 31.616944 65.716944 + + + + + + + + + + + + + Location #2 name + + + Location #2 description + + + A description that qualifies the activity taking place at location #2 + + + + 11.5500 104.9167 + + + + + + + + + + + + Code A1 + + + + + + A description of the tag + + + A description of the tag + + + + + + + + Description text + + + + + Description text + + + + + + + + Nepal Earthquake April 2015 + Népal Earthquake Avril 2015 + + + Nepal Earthquake April 2015 + + + + + + + + Code A1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3000 + + + + + + + + 3000 + + Agency B + + + Agency A + + + + + + + + + 3000 + + + + + + + + + + + + 1000 + + A description here. + + + Agency B + + + Agency A + + + + + + + + + + + + + + + <narrative>Project Report 2013</narrative> + <narrative xml:lang="fr">Rapport de projet 2013</narrative> + + + Description of the content of the project report or guidance on where to access the relevant information in this report + + + + + + + + + + + + + + + + + + + + Conditions text + Conditions texte + + + + + + + + + + Result description text + + + + + <narrative>Results Report 2013</narrative> + + + Report of results + + + + + + + + + + + + + <narrative>Indicator title</narrative> + + + Indicator description text + + + + <narrative>Results Indicator Report 2013</narrative> + + + Report of results indicator + + + + + + + + + + + + + + + + + <narrative>Results Baseline Report 2013</narrative> + + + Report of results baseline + + + + + + + Baseline comment text + + + + + + + + + + + + + + Target comment text + + + + <narrative>Results Period Target Report 2013</narrative> + + + Report of results period target + + + + + + + + + + + + + Actual comment text + + + + <narrative>Results Period Actual Report 2013</narrative> + + + Report of results period actual + + + + + + + + + + + + + + + + + + + + + + + + + 200000 + 1500000 + 0 + 0 + + 21039 + + + + + + 10000 + + + + + + + + diff --git a/integration-tests/test-files/iati-act-line-endings-cr-and-crlf.xml b/integration-tests/test-files/iati-act-line-endings-cr-and-crlf.xml new file mode 100644 index 0000000..fd7db47 --- /dev/null +++ b/integration-tests/test-files/iati-act-line-endings-cr-and-crlf.xml @@ -0,0 +1,469 @@ + + + + + + + + + AA-AAA-123456789-ABC123 + + + + Organisation name + Nom de l'organisme + + + + + + <narrative>Activity title</narrative> + <narrative xml:lang="fr">Titre de l'activité</narrative> + <narrative xml:lang="es">Título de la actividad</narrative> + + + + + + General activity description text. Long description of the activity with no particular structure. + Activité générale du texte de description. Longue description de l'activité sans structure particulière. + + + Objectives for the activity, for example from a logical framework. + Objectifs de l'activité, par exemple à partir d'un cadre logique. + + + Statement of groups targeted to benefit from the activity. + Déclaration de groupes ciblés pour bénéficier de l'activité. + + + + + + Name of Agency B + + + Name of Agency C + + + Name of Agency A + Nom de l'agence A + + + + + + + Organisation name + + + + + + + + + + + Planned start date of the activity + Date prévue de début de l'activité + + + + + + + + + Agency A + + + Department B + + + A. Example + + + Transparency Lead + + 0044111222333444 + transparency@example.org + http://www.example.org + + Transparency House, The Street, Town, City, Postcode + + + + + + + + + + + + + + + + + + + + + + + + Location name + + + Location description + + + A description that qualifies the activity taking place at the location. + + + + 31.616944 65.716944 + + + + + + + + + + + + + Location #2 name + + + Location #2 description + + + A description that qualifies the activity taking place at location #2 + + + + 11.5500 104.9167 + + + + + + + + + + + + Code A1 + + + + + + A description of the tag + + + A description of the tag + + + + + + + + Description text + + + + + Description text + + + + + + + + Nepal Earthquake April 2015 + Népal Earthquake Avril 2015 + + + Nepal Earthquake April 2015 + + + + + + + + Code A1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3000 + + + + + + + + 3000 + + Agency B + + + Agency A + + + + + + + + + 3000 + + + + + + + + + + + + 1000 + + Transaction description text + + + Agency B + + + Agency A + + + + + + + + + + + + + + + <narrative>Project Report 2013</narrative> + <narrative xml:lang="fr">Rapport de projet 2013</narrative> + + + Description of the content of the project report or guidance on where to access the relevant information in this report + + + + + + + + + + + + + + + + + + + + Conditions text + Conditions texte + + + + + + + + <narrative>Result title</narrative> + + + Result description text + + + + + <narrative>Results Report 2013</narrative> + + + Report of results + + + + + + + + + + + + + <narrative>Indicator title</narrative> + + + Indicator description text + + + + <narrative>Results Indicator Report 2013</narrative> + + + Report of results indicator + + + + + + + + + + + + + + + + + <narrative>Results Baseline Report 2013</narrative> + + + Report of results baseline + + + + + + + Baseline comment text + + + + + + + + + + + + + + Target comment text + + + + <narrative>Results Period Target Report 2013</narrative> + + + Report of results period target + + + + + + + + + + + + + Actual comment text + + + + <narrative>Results Period Actual Report 2013</narrative> + + + Report of results period actual + + + + + + + + + + + + + + + + + + + + + + + + + 200000 + 1500000 + 0 + 0 + + 21039 + + + + + + 10000 + + + + + + + + diff --git a/integration-tests/test-files/iati-act-line-endings-cr.xml b/integration-tests/test-files/iati-act-line-endings-cr.xml new file mode 100644 index 0000000..6edccd4 --- /dev/null +++ b/integration-tests/test-files/iati-act-line-endings-cr.xml @@ -0,0 +1 @@ + AA-AAA-123456789-ABC123 Organisation name Nom de l'organisme <narrative>Activity title</narrative> <narrative xml:lang="fr">Titre de l'activité</narrative> <narrative xml:lang="es">Título de la actividad</narrative> General activity description text. Long description of the activity with no particular structure. Activité générale du texte de description. Longue description de l'activité sans structure particulière. Objectives for the activity, for example from a logical framework. Objectifs de l'activité, par exemple à partir d'un cadre logique. Statement of groups targeted to benefit from the activity. Déclaration de groupes ciblés pour bénéficier de l'activité. Name of Agency B Name of Agency C Name of Agency A Nom de l'agence A Organisation name Planned start date of the activity Date prévue de début de l'activité Agency A Department B A. Example Transparency Lead 0044111222333444 transparency@example.org http://www.example.org Transparency House, The Street, Town, City, Postcode Location name Location description A description that qualifies the activity taking place at the location. 31.616944 65.716944 Location #2 name Location #2 description A description that qualifies the activity taking place at location #2 11.5500 104.9167 Code A1 A description of the tag A description of the tag Description text Description text Nepal Earthquake April 2015 Népal Earthquake Avril 2015 Nepal Earthquake April 2015 Code A1 3000 3000 Agency B Agency A 3000 1000 Transaction description text Agency B Agency A <narrative>Project Report 2013</narrative> <narrative xml:lang="fr">Rapport de projet 2013</narrative> Description of the content of the project report or guidance on where to access the relevant information in this report Conditions text Conditions texte <narrative>Result title</narrative> Result description text <narrative>Results Report 2013</narrative> Report of results <narrative>Indicator title</narrative> Indicator description text <narrative>Results Indicator Report 2013</narrative> Report of results indicator <narrative>Results Baseline Report 2013</narrative> Report of results baseline Baseline comment text Target comment text <narrative>Results Period Target Report 2013</narrative> Report of results period target Actual comment text <narrative>Results Period Actual Report 2013</narrative> Report of results period actual 200000 1500000 0 0 21039 10000 \ No newline at end of file diff --git a/services/rulesValidator.js b/services/rulesValidator.js index 6133514..007a094 100644 --- a/services/rulesValidator.js +++ b/services/rulesValidator.js @@ -197,6 +197,9 @@ class Rules { } atLeastOne() { + if (this.pathMatches.length === 0) { + this.addFailureContext(this.element); + } return this.pathMatches.length >= 1; } @@ -561,18 +564,24 @@ const standardiseResultFormat = (result, showDetails, xml, lineCount) => { let severity; let category; let message; + let elementContext; const { xpathContext, ruleName, ruleCase, caseContext, failContext } = result; if ('ruleInfo' in ruleCase) { ({ id, severity, category, message } = ruleCase.ruleInfo); } switch (ruleName) { case 'atLeastOne': + if (xpathContext.xpath === '//description' || xpathContext.xpath === '//title') { + if (failContext.length === 1) { + elementContext = `${failContext[0].parent}/${failContext[0].element}`; + } else { + elementContext = getFullContext(xml, xpathContext, lineCount); + } + } else { + elementContext = `<${xpathContext.xpath.split('/').pop()}>`; + } context.push({ - text: `For ${ - xpathContext.xpath === '//description' || xpathContext.xpath === '//title' - ? `${getFullContext(xml, xpathContext, lineCount)}` - : `<${xpathContext.xpath.split('/').pop()}>` - } at line: ${xpathContext.lineNumber}, column: ${xpathContext.columnNumber}`, + text: `For ${elementContext} at line: ${xpathContext.lineNumber}, column: ${xpathContext.columnNumber}` }); break; case 'dateNow': @@ -847,26 +856,26 @@ const validateIATI = async ( const processActivity = () => new Transform({ - transform(chunk, enc, next) { + transform(oneIatiActivity, enc, next) { let newSchemaErrors = []; - const docString = chunk.toString(); + const docString = oneIatiActivity.toString(); // adjust lineCount to account for added wrapper of root element around each activity lineCount -= getLineStart(docString, fileDefinition[fileType].subRoot); // build single activity or org document - const singleElementDoc = new DOMParser().parseFromString(docString, 'text/xml'); + const singleActivityDoc = new DOMParser().parseFromString(docString, 'text/xml'); // parse identifier and title let identifier = select( `string(/${fileType}/${fileDefinition[fileType].subRoot}/${fileDefinition[fileType].identifier})`, - singleElementDoc + singleActivityDoc, ) || 'noIdentifier'; const title = select( `string(/${fileType}/${fileDefinition[fileType].subRoot}/${fileDefinition[fileType].titleLocation})`, - singleElementDoc + singleActivityDoc, ) || ''; // track and duplicate check identifier @@ -902,7 +911,7 @@ const validateIATI = async ( identifier, title, showDetails, - lineCount + lineCount, ); schemaErrors = [...schemaErrors, ...newSchemaErrors]; } @@ -916,15 +925,15 @@ const validateIATI = async ( }); } - // validate against ruleset - const errors = testRuleset(ruleset, singleElementDoc, idSets, lineCount).reduce( + // this validates a single IATI Activity (wrapped in an ) against the rulesets + const errors = testRuleset(ruleset, singleActivityDoc, idSets, lineCount).reduce( (acc, result) => { if (result.result === false) { acc.push( standardiseResultFormat( result, showDetails, - singleElementDoc, + singleActivityDoc, lineCount ) ); @@ -941,7 +950,7 @@ const validateIATI = async ( index += 1; let idx = -1; do { - idx = chunk.indexOf(10, idx + 1); + idx = oneIatiActivity.indexOf(10, idx + 1); lineCount += 1; } while (idx !== -1); diff --git a/services/validationService.js b/services/validationService.js index 232bf97..7c94128 100644 --- a/services/validationService.js +++ b/services/validationService.js @@ -167,7 +167,7 @@ export default async function validate(context, req) { // Clean input with xmllint for future steps, only need to replace body with this if no XML errors. const { output, error: xmlError } = await validateXMLrecover(body); - if (!xmlError) { + if (xmlError === undefined || xmlError === null || xmlError === '') { body = output; } } catch (error) { @@ -358,7 +358,6 @@ export default async function validate(context, req) { body: JSON.stringify(validationReport), }; body = null; - return; } catch (error) { context.log(error);