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
+
+
+
+
+
+ Activity title
+ Titre de l'activité
+ Título de la actividad
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Project Report 2013
+ Rapport de projet 2013
+
+
+ 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 title
+
+
+ Result description text
+
+
+
+
+ Results Report 2013
+
+
+ Report of results
+
+
+
+
+
+
+
+
+
+
+
+
+ Indicator title
+
+
+ Indicator description text
+
+
+
+ Results Indicator Report 2013
+
+
+ Report of results indicator
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Results Baseline Report 2013
+
+
+ Report of results baseline
+
+
+
+
+
+
+ Baseline comment text
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Target comment text
+
+
+
+ Results Period Target Report 2013
+
+
+ Report of results period target
+
+
+
+
+
+
+
+
+
+
+
+
+ Actual comment text
+
+
+
+ Results Period Actual Report 2013
+
+
+ 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
+
+
+
+
+
+ Activity title
+ Titre de l'activité
+ Título de la actividad
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Project Report 2013
+ Rapport de projet 2013
+
+
+ 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 title
+
+
+ Result description text
+
+
+
+
+ Results Report 2013
+
+
+ Report of results
+
+
+
+
+
+
+
+
+
+
+
+
+ Indicator title
+
+
+ Indicator description text
+
+
+
+ Results Indicator Report 2013
+
+
+ Report of results indicator
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Results Baseline Report 2013
+
+
+ Report of results baseline
+
+
+
+
+
+
+ Baseline comment text
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Target comment text
+
+
+
+ Results Period Target Report 2013
+
+
+ Report of results period target
+
+
+
+
+
+
+
+
+
+
+
+
+ Actual comment text
+
+
+
+ Results Period Actual Report 2013
+
+
+ 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
+
+
+
+
+
+ Activity title
+ Titre de l'activité
+ Título de la actividad
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Project Report 2013
+ Rapport de projet 2013
+
+
+ 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 title
+
+
+ Result description text
+
+
+
+
+ Results Report 2013
+
+
+ Report of results
+
+
+
+
+
+
+
+
+
+
+
+
+ Indicator title
+
+
+ Indicator description text
+
+
+
+ Results Indicator Report 2013
+
+
+ Report of results indicator
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Results Baseline Report 2013
+
+
+ Report of results baseline
+
+
+
+
+
+
+ Baseline comment text
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Target comment text
+
+
+
+ Results Period Target Report 2013
+
+
+ Report of results period target
+
+
+
+
+
+
+
+
+
+
+
+
+ Actual comment text
+
+
+
+ Results Period Actual Report 2013
+
+
+ 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
+
+
+
+
+
+ Activity title
+ Titre de l'activité
+ Título de la actividad
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Project Report 2013
+ Rapport de projet 2013
+
+
+ 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
+
+
+
+
+ Results Report 2013
+
+
+ Report of results
+
+
+
+
+
+
+
+
+
+
+
+
+ Indicator title
+
+
+ Indicator description text
+
+
+
+ Results Indicator Report 2013
+
+
+ Report of results indicator
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Results Baseline Report 2013
+
+
+ Report of results baseline
+
+
+
+
+
+
+ Baseline comment text
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Target comment text
+
+
+
+ Results Period Target Report 2013
+
+
+ Report of results period target
+
+
+
+
+
+
+
+
+
+
+
+
+ Actual comment text
+
+
+
+ Results Period Actual Report 2013
+
+
+ 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
+
+
+
+
+
+ Activity title
+ Titre de l'activité
+ Título de la actividad
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Project Report 2013
+ Rapport de projet 2013
+
+
+ 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 title
+
+
+ Result description text
+
+
+
+
+ Results Report 2013
+
+
+ Report of results
+
+
+
+
+
+
+
+
+
+
+
+
+ Indicator title
+
+
+ Indicator description text
+
+
+
+ Results Indicator Report 2013
+
+
+ Report of results indicator
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Results Baseline Report 2013
+
+
+ Report of results baseline
+
+
+
+
+
+
+ Baseline comment text
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Target comment text
+
+
+
+ Results Period Target Report 2013
+
+
+ Report of results period target
+
+
+
+
+
+
+
+
+
+
+
+
+ Actual comment text
+
+
+
+ Results Period Actual Report 2013
+
+
+ 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
Activity title
Titre de l'activité
Título de la actividad
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
Project Report 2013
Rapport de projet 2013
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 title
Result description text
Results Report 2013
Report of results
Indicator title
Indicator description text
Results Indicator Report 2013
Report of results indicator
Results Baseline Report 2013
Report of results baseline
Baseline comment text
Target comment text
Results Period Target Report 2013
Report of results period target
Actual comment text
Results Period Actual Report 2013
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);