diff --git a/.ncurc.yaml b/.ncurc.yaml index 10735f580..9f4ddec7b 100644 --- a/.ncurc.yaml +++ b/.ncurc.yaml @@ -12,4 +12,5 @@ reject: [ "sinon", # glob >= 11 requires node >= 20 "glob", + "@mojaloop/central-services-shared", ## todo: temporary!!!! ] diff --git a/audit-ci.jsonc b/audit-ci.jsonc index 9314e72e9..6915f272d 100644 --- a/audit-ci.jsonc +++ b/audit-ci.jsonc @@ -4,14 +4,19 @@ // Only use one of ["low": true, "moderate": true, "high": true, "critical": true] "moderate": true, "allowlist": [ // NOTE: Please add as much information as possible to any items added to the allowList - "GHSA-w5p7-h5w8-2hfq", // tap-spec>tap-out>trim; This has been analyzed and this is acceptable as it is used to run tests. - "GHSA-2mvq-xp48-4c77", // https://github.com/advisories/GHSA-2mvq-xp48-4c77 - "GHSA-5854-jvxx-2cg9", // https://github.com/advisories/GHSA-5854-jvxx-2cg9 - "GHSA-7hx8-2rxv-66xv", // https://github.com/advisories/GHSA-7hx8-2rxv-66xv - "GHSA-c429-5p7v-vgjp", // https://github.com/advisories/GHSA-c429-5p7v-vgjp - "GHSA-g64q-3vg8-8f93", // https://github.com/advisories/GHSA-g64q-3vg8-8f93 - "GHSA-mg85-8mv5-ffjr", // https://github.com/advisories/GHSA-mg85-8mv5-ffjr - "GHSA-8hc4-vh64-cxmj", // https://github.com/advisories/GHSA-8hc4-vh64-cxmj - "GHSA-952p-6rrq-rcjv" // https://github.com/advisories/GHSA-952p-6rrq-rcjv + "GHSA-w5p7-h5w8-2hfq", // tap-spec>tap-out>trim; This has been analyzed and this is acceptable as it is used to run tests. + "GHSA-2mvq-xp48-4c77", // https://github.com/advisories/GHSA-2mvq-xp48-4c77 + "GHSA-5854-jvxx-2cg9", // https://github.com/advisories/GHSA-5854-jvxx-2cg9 + "GHSA-7hx8-2rxv-66xv", // https://github.com/advisories/GHSA-7hx8-2rxv-66xv + "GHSA-c429-5p7v-vgjp", // https://github.com/advisories/GHSA-c429-5p7v-vgjp + "GHSA-g64q-3vg8-8f93", // https://github.com/advisories/GHSA-g64q-3vg8-8f93 + "GHSA-mg85-8mv5-ffjr", // https://github.com/advisories/GHSA-mg85-8mv5-ffjr + "GHSA-8hc4-vh64-cxmj", // https://github.com/advisories/GHSA-8hc4-vh64-cxmj + "GHSA-952p-6rrq-rcjv", // https://github.com/advisories/GHSA-952p-6rrq-rcjv + "GHSA-9wv6-86v2-598j", // https://github.com/advisories/GHSA-9wv6-86v2-598j + "GHSA-qwcr-r2fm-qrc7", // https://github.com/advisories/GHSA-qwcr-r2fm-qrc7 + "GHSA-cm22-4g7w-348p", // https://github.com/advisories/GHSA-cm22-4g7w-348p + "GHSA-m6fv-jmcg-4jfg", // https://github.com/advisories/GHSA-m6fv-jmcg-4jfg + "GHSA-qw6h-vgh9-j6wx" // https://github.com/advisories/GHSA-qw6h-vgh9-j6wx ] } diff --git a/migrations/960100_create_externalParticipant.js b/migrations/960100_create_externalParticipant.js new file mode 100644 index 000000000..a0f4ab5f7 --- /dev/null +++ b/migrations/960100_create_externalParticipant.js @@ -0,0 +1,47 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * Eugen Klymniuk + -------------- + **********/ + +exports.up = async (knex) => { + return knex.schema.hasTable('externalParticipant').then(function(exists) { + if (!exists) { + return knex.schema.createTable('externalParticipant', (t) => { + t.bigIncrements('externalParticipantId').primary().notNullable() + t.string('name', 30).notNullable() + t.unique('name') + t.dateTime('createdDate').defaultTo(knex.fn.now()).notNullable() + t.integer('proxyId').unsigned().notNullable() + t.foreign('proxyId').references('participantId').inTable('participant') + }) + } + }) +} + +exports.down = function (knex) { + return knex.schema.hasTable('externalParticipant').then(function(exists) { + if (!exists) { + return knex.schema.dropTableIfExists('externalParticipant') + } + }) +} diff --git a/migrations/960110_alter_transferParticipant__addFiled_externalParticipantId.js b/migrations/960110_alter_transferParticipant__addFiled_externalParticipantId.js new file mode 100644 index 000000000..13b01119e --- /dev/null +++ b/migrations/960110_alter_transferParticipant__addFiled_externalParticipantId.js @@ -0,0 +1,50 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * Eugen Klymniuk + -------------- + **********/ + +const EP_ID_FIELD = 'externalParticipantId' + +exports.up = async (knex) => { + return knex.schema.hasTable('transferParticipant').then(function(exists) { + if (exists) { + return knex.schema.alterTable('transferParticipant', (t) => { + t.bigint(EP_ID_FIELD).unsigned().nullable() + t.foreign(EP_ID_FIELD).references(EP_ID_FIELD).inTable('externalParticipant') + t.index(EP_ID_FIELD) + }) + } + }) +} + +exports.down = async (knex) => { + return knex.schema.hasTable('transferParticipant').then(function(exists) { + if (exists) { + return knex.schema.alterTable('transferParticipant', (t) => { + t.dropIndex(EP_ID_FIELD) + t.dropForeign(EP_ID_FIELD) + t.dropColumn(EP_ID_FIELD) + }) + } + }) +} diff --git a/migrations/960111_alter_fxTransferParticipant__addFiled_externalParticipantId.js b/migrations/960111_alter_fxTransferParticipant__addFiled_externalParticipantId.js new file mode 100644 index 000000000..ecf4adefd --- /dev/null +++ b/migrations/960111_alter_fxTransferParticipant__addFiled_externalParticipantId.js @@ -0,0 +1,50 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * Eugen Klymniuk + -------------- + **********/ + +const EP_ID_FIELD = 'externalParticipantId' + +exports.up = async (knex) => { + return knex.schema.hasTable('fxTransferParticipant').then((exists) => { + if (exists) { + return knex.schema.alterTable('fxTransferParticipant', (t) => { + t.bigint(EP_ID_FIELD).unsigned().nullable() + t.foreign(EP_ID_FIELD).references(EP_ID_FIELD).inTable('externalParticipant') + t.index(EP_ID_FIELD) + }) + } + }) +} + +exports.down = async (knex) => { + return knex.schema.hasTable('fxTransferParticipant').then((exists) => { + if (exists) { + return knex.schema.alterTable('fxTransferParticipant', (t) => { + t.dropIndex(EP_ID_FIELD) + t.dropForeign(EP_ID_FIELD) + t.dropColumn(EP_ID_FIELD) + }) + } + }) +} diff --git a/package-lock.json b/package-lock.json index bef1d808d..f58971f5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1684,12 +1684,6 @@ "@hapi/hoek": "9.x.x" } }, - "node_modules/@mojaloop/central-services-shared/node_modules/@hapi/boom/node_modules/@hapi/hoek": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.3.tgz", - "integrity": "sha512-jKtjLLDiH95b002sJVc5c74PE6KKYftuyVdVmsuYId5stTaWcRFqE+5ukZI4gDUKjGn8wv2C3zPn3/nyjEI7gg==", - "deprecated": "This version has been deprecated and is no longer supported or maintained" - }, "node_modules/@mojaloop/central-services-shared/node_modules/@hapi/catbox-memory": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@hapi/catbox-memory/-/catbox-memory-5.0.1.tgz", @@ -1699,25 +1693,10 @@ "@hapi/hoek": "9.x.x" } }, - "node_modules/@mojaloop/central-services-shared/node_modules/@hapi/catbox-memory/node_modules/@hapi/hoek": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.3.tgz", - "integrity": "sha512-jKtjLLDiH95b002sJVc5c74PE6KKYftuyVdVmsuYId5stTaWcRFqE+5ukZI4gDUKjGn8wv2C3zPn3/nyjEI7gg==", - "deprecated": "This version has been deprecated and is no longer supported or maintained" - }, - "node_modules/@mojaloop/central-services-shared/node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } + "node_modules/@mojaloop/central-services-shared/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" }, "node_modules/@mojaloop/central-services-stream": { "version": "11.3.1", @@ -2762,6 +2741,20 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -3011,20 +3004,24 @@ } }, "node_modules/cheerio": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", - "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", + "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "htmlparser2": "^8.0.1", - "parse5": "^7.0.0", - "parse5-htmlparser2-tree-adapter": "^7.0.0" + "domutils": "^3.1.0", + "encoding-sniffer": "^0.2.0", + "htmlparser2": "^9.1.0", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^7.0.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^6.19.5", + "whatwg-mimetype": "^4.0.0" }, "engines": { - "node": ">= 6" + "node": ">=18.17" }, "funding": { "url": "https://github.com/cheeriojs/cheerio?sponsor=1" @@ -4122,17 +4119,6 @@ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/domelementtype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", @@ -4431,14 +4417,16 @@ "node": ">= 0.8" } }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "optional": true, - "peer": true, + "node_modules/encoding-sniffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", + "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", "dependencies": { - "iconv-lite": "^0.6.2" + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" } }, "node_modules/end-of-stream": { @@ -4450,9 +4438,12 @@ } }, "node_modules/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } @@ -5497,17 +5488,6 @@ "node": ">=4.8" } }, - "node_modules/execa/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/execa/node_modules/is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -6326,6 +6306,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/get-symbol-description": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", @@ -7002,9 +6993,9 @@ "dev": true }, "node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -7015,19 +7006,8 @@ "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "domutils": "^3.1.0", + "entities": "^4.5.0" } }, "node_modules/http-errors": { @@ -7966,48 +7946,6 @@ "node": ">=8" } }, - "node_modules/istanbul-lib-processinfo/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/istanbul-lib-processinfo/node_modules/p-map": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", @@ -8020,21 +7958,6 @@ "node": ">=8" } }, - "node_modules/istanbul-lib-processinfo/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -8122,9 +8045,9 @@ } }, "node_modules/jake": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz", - "integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", "dependencies": { "async": "^3.2.3", "chalk": "^4.0.2", @@ -8575,6 +8498,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, "dependencies": { "uc.micro": "^2.0.0" } @@ -8582,7 +8506,8 @@ "node_modules/linkify-it/node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true }, "node_modules/load-json-file": { "version": "5.3.0", @@ -8798,6 +8723,7 @@ "version": "14.1.0", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", @@ -8820,17 +8746,6 @@ "markdown-it": "*" } }, - "node_modules/markdown-it-attrs": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/markdown-it-attrs/-/markdown-it-attrs-1.2.1.tgz", - "integrity": "sha512-EYYKLF9RvQJx1Etsb6EsBGWL7qNQLpg9BRej5f06+UdX75T5gvldEn7ts6bkLIQqugE15SGn4lw1CXDS1A+XUA==", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "markdown-it": ">=7.0.1" - } - }, "node_modules/markdown-it-emoji": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-1.4.0.tgz", @@ -8841,26 +8756,17 @@ "resolved": "https://registry.npmjs.org/markdown-it-lazy-headers/-/markdown-it-lazy-headers-0.1.3.tgz", "integrity": "sha512-65BxqvmYLpVifv6MvTElthY8zvZ/TpZBCdshr/mTpsFkqwcwWtfD3YoSE7RYSn7ugnEAAaj2gywszq+hI/Pxgg==" }, - "node_modules/markdown-it/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/markdown-it/node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true }, "node_modules/markdown-it/node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true }, "node_modules/marked": { "version": "4.3.0", @@ -9955,21 +9861,6 @@ "node": ">=8" } }, - "node_modules/nyc/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/nyc/node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -10331,9 +10222,9 @@ } }, "node_modules/openapi-sampler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.3.1.tgz", - "integrity": "sha512-Ert9mvc2tLPmmInwSyGZS+v4Ogu9/YoZuq9oP3EdUklg2cad6+IGndP9yqJJwbgdXwZibiq5fpv6vYujchdJFg==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.5.1.tgz", + "integrity": "sha512-tIWIrZUKNAsbqf3bd9U1oH6JEXo8LNYuDlXw26By67EygpjT+ArFnsxxyTMjFWRfbqo5ozkvgSQDK69Gd8CddA==", "dependencies": { "@types/json-schema": "^7.0.7", "json-pointer": "0.6.2" @@ -10553,15 +10444,15 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/parse5/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "engines": { - "node": ">=0.12" + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dependencies": { + "parse5": "^7.0.0" }, "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, "node_modules/parseurl": { @@ -10845,9 +10736,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.45", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz", + "integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==", "funding": [ { "type": "opencollective", @@ -10864,7 +10755,7 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "source-map-js": "^1.2.0" }, "engines": { @@ -11099,6 +10990,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, "engines": { "node": ">=6" } @@ -11169,30 +11061,19 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", - "iconv-lite": "0.4.24", + "iconv-lite": "0.6.3", "unpipe": "1.0.0" }, "engines": { "node": ">= 0.8" } }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -11970,6 +11851,65 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -12078,6 +12018,24 @@ "postcss": "^8.3.11" } }, + "node_modules/sanitize-html/node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -12290,6 +12248,14 @@ "wordwrap": "0.0.2" } }, + "node_modules/shins/node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/shins/node_modules/linkify-it": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", @@ -12313,6 +12279,17 @@ "markdown-it": "bin/markdown-it.js" } }, + "node_modules/shins/node_modules/markdown-it-attrs": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/markdown-it-attrs/-/markdown-it-attrs-1.2.1.tgz", + "integrity": "sha512-EYYKLF9RvQJx1Etsb6EsBGWL7qNQLpg9BRej5f06+UdX75T5gvldEn7ts6bkLIQqugE15SGn4lw1CXDS1A+XUA==", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "markdown-it": ">=7.0.1" + } + }, "node_modules/shins/node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -12529,9 +12506,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "engines": { "node": ">=0.10.0" } @@ -12573,16 +12550,6 @@ "node": ">=8" } }, - "node_modules/spawn-wrap/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/spawn-wrap/node_modules/foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", @@ -12596,53 +12563,6 @@ "node": ">=8.0.0" } }, - "node_modules/spawn-wrap/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/spawn-wrap/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/spawn-wrap/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/spawn-wrap/node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -14212,6 +14132,14 @@ "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "dev": true }, + "node_modules/undici": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz", + "integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -14347,6 +14275,25 @@ "node": ">=12" } }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "engines": { + "node": ">=18" + } + }, "node_modules/whatwg-url": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", @@ -14508,6 +14455,14 @@ "wrap-ansi": "^2.0.0" } }, + "node_modules/widdershins/node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/widdershins/node_modules/find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", diff --git a/src/handlers/transfers/createRemittanceEntity.js b/src/handlers/transfers/createRemittanceEntity.js index 1c35f18fa..c520ce3c5 100644 --- a/src/handlers/transfers/createRemittanceEntity.js +++ b/src/handlers/transfers/createRemittanceEntity.js @@ -1,6 +1,9 @@ const fxTransferModel = require('../../models/fxTransfer') const TransferService = require('../../domain/transfer') const cyril = require('../../domain/fx/cyril') +const { logger } = require('../../shared/logger') + +/** @import { ProxyObligation } from './prepare.js' */ // abstraction on transfer and fxTransfer const createRemittanceEntity = (isFx) => { @@ -18,6 +21,16 @@ const createRemittanceEntity = (isFx) => { : TransferService.saveTransferDuplicateCheck(id, hash) }, + /** + * Saves prepare transfer/fxTransfer details to DB. + * + * @param {Object} payload - Message payload. + * @param {string | null} reason - Validation failure reasons. + * @param {Boolean} isValid - isValid. + * @param {DeterminingTransferCheckResult} determiningTransferCheckResult - The determining transfer check result. + * @param {ProxyObligation} proxyObligation - The proxy obligation + * @returns {Promise} + */ async savePreparedRequest ( payload, reason, @@ -25,7 +38,6 @@ const createRemittanceEntity = (isFx) => { determiningTransferCheckResult, proxyObligation ) { - // todo: add histoTimer and try/catch here return isFx ? fxTransferModel.fxTransfer.savePreparedRequest( payload, @@ -49,16 +61,38 @@ const createRemittanceEntity = (isFx) => { : TransferService.getByIdLight(id) }, + /** + * A determiningTransferCheckResult. + * @typedef {Object} DeterminingTransferCheckResult + * @property {boolean} determiningTransferExists - Indicates if the determining transfer exists. + * @property {Array<{participantName, currencyId}>} participantCurrencyValidationList - List of validations for participant currencies. + * @property {Object} [transferRecord] - Determining transfer for the FX transfer (optional). + * @property {Array} [watchListRecords] - Records from fxWatchList-table for the transfer (optional). + */ + /** + * Checks if a determining transfer exists based on the payload and proxy obligation. + * The function determines which method to use based on whether it is an FX transfer. + * + * @param {Object} payload - The payload data required for the transfer check. + * @param {ProxyObligation} proxyObligation - The proxy obligation details. + * @returns {DeterminingTransferCheckResult} determiningTransferCheckResult + */ async checkIfDeterminingTransferExists (payload, proxyObligation) { - return isFx - ? cyril.checkIfDeterminingTransferExistsForFxTransferMessage(payload, proxyObligation) - : cyril.checkIfDeterminingTransferExistsForTransferMessage(payload, proxyObligation) + const result = isFx + ? await cyril.checkIfDeterminingTransferExistsForFxTransferMessage(payload, proxyObligation) + : await cyril.checkIfDeterminingTransferExistsForTransferMessage(payload, proxyObligation) + + logger.debug('cyril determiningTransferCheckResult:', { result }) + return result }, async getPositionParticipant (payload, determiningTransferCheckResult, proxyObligation) { - return isFx - ? cyril.getParticipantAndCurrencyForFxTransferMessage(payload, determiningTransferCheckResult) - : cyril.getParticipantAndCurrencyForTransferMessage(payload, determiningTransferCheckResult, proxyObligation) + const result = isFx + ? await cyril.getParticipantAndCurrencyForFxTransferMessage(payload, determiningTransferCheckResult) + : await cyril.getParticipantAndCurrencyForTransferMessage(payload, determiningTransferCheckResult, proxyObligation) + + logger.debug('cyril getPositionParticipant result:', { result }) + return result }, async logTransferError (id, errorCode, errorDescription) { diff --git a/src/handlers/transfers/dto.js b/src/handlers/transfers/dto.js index 6d4b5859f..1f1edcd41 100644 --- a/src/handlers/transfers/dto.js +++ b/src/handlers/transfers/dto.js @@ -16,10 +16,10 @@ const prepareInputDto = (error, messages) => { if (!message) throw new Error('No input kafka message') const payload = decodePayload(message.value.content.payload) - const isForwarded = message.value.metadata.event.action === Action.FORWARDED || message.value.metadata.event.action === Action.FX_FORWARDED const isFx = !payload.transferId const { action } = message.value.metadata.event + const isForwarded = [Action.FORWARDED, Action.FX_FORWARDED].includes(action) const isPrepare = [Action.PREPARE, Action.FX_PREPARE, Action.FORWARDED, Action.FX_FORWARDED].includes(action) const actionLetter = isPrepare diff --git a/src/handlers/transfers/prepare.js b/src/handlers/transfers/prepare.js index a6bfa9208..5809feb52 100644 --- a/src/handlers/transfers/prepare.js +++ b/src/handlers/transfers/prepare.js @@ -41,7 +41,7 @@ const ProxyCache = require('#src/lib/proxyCache') const FxTransferService = require('#src/domain/fx/index') const { Kafka, Comparators } = Util -const { TransferState } = Enum.Transfers +const { TransferState, TransferInternalState } = Enum.Transfers const { Action, Type } = Enum.Events.Event const { FSPIOPErrorCodes } = ErrorHandler.Enums const { createFSPIOPError, reformatFSPIOPError } = ErrorHandler.Factory @@ -51,6 +51,164 @@ const consumerCommit = true const fromSwitch = true const proxyEnabled = Config.PROXY_CACHE_CONFIG.enabled +const proceedForwardErrorMessage = async ({ fspiopError, isFx, params }) => { + const eventDetail = { + functionality: Type.NOTIFICATION, + action: isFx ? Action.FX_FORWARDED : Action.FORWARDED + } + await Kafka.proceed(Config.KAFKA_CONFIG, params, { + fspiopError, + eventDetail, + consumerCommit + }) + logger.warn('proceedForwardErrorMessage is done', { fspiopError, eventDetail }) +} + +// think better name +const forwardPrepare = async ({ isFx, params, ID }) => { + if (isFx) { + const fxTransfer = await FxTransferService.getByIdLight(ID) + if (!fxTransfer) { + const fspiopError = ErrorHandler.Factory.createFSPIOPError( + FSPIOPErrorCodes.ID_NOT_FOUND, + 'Forwarded fxTransfer could not be found.' + ).toApiErrorObject(Config.ERROR_HANDLING) + // IMPORTANT: This singular message is taken by the ml-api-adapter and used to + // notify the payerFsp and proxy of the error. + // As long as the `to` and `from` message values are the fsp and fxp, + // and the action is `fx-forwarded`, the ml-api-adapter will notify both. + await proceedForwardErrorMessage({ fspiopError, isFx, params }) + return true + } + + if (fxTransfer.fxTransferState === TransferInternalState.RESERVED) { + await FxTransferService.forwardedFxPrepare(ID) + } else { + const fspiopError = ErrorHandler.Factory.createInternalServerFSPIOPError( + `Invalid State: ${fxTransfer.fxTransferState} - expected: ${TransferInternalState.RESERVED}` + ).toApiErrorObject(Config.ERROR_HANDLING) + // IMPORTANT: This singular message is taken by the ml-api-adapter and used to + // notify the payerFsp and proxy of the error. + // As long as the `to` and `from` message values are the fsp and fxp, + // and the action is `fx-forwarded`, the ml-api-adapter will notify both. + await proceedForwardErrorMessage({ fspiopError, isFx, params }) + } + } else { + const transfer = await TransferService.getById(ID) + if (!transfer) { + const fspiopError = ErrorHandler.Factory.createFSPIOPError( + FSPIOPErrorCodes.ID_NOT_FOUND, + 'Forwarded transfer could not be found.' + ).toApiErrorObject(Config.ERROR_HANDLING) + // IMPORTANT: This singular message is taken by the ml-api-adapter and used to + // notify the payerFsp and proxy of the error. + // As long as the `to` and `from` message values are the payer and payee, + // and the action is `forwarded`, the ml-api-adapter will notify both. + await proceedForwardErrorMessage({ fspiopError, isFx, params }) + return true + } + + if (transfer.transferState === TransferInternalState.RESERVED) { + await TransferService.forwardedPrepare(ID) + } else { + const fspiopError = ErrorHandler.Factory.createInternalServerFSPIOPError( + `Invalid State: ${transfer.transferState} - expected: ${TransferInternalState.RESERVED}` + ).toApiErrorObject(Config.ERROR_HANDLING) + // IMPORTANT: This singular message is taken by the ml-api-adapter and used to + // notify the payerFsp and proxy of the error. + // As long as the `to` and `from` message values are the payer and payee, + // and the action is `forwarded`, the ml-api-adapter will notify both. + await proceedForwardErrorMessage({ fspiopError, isFx, params }) + } + } + + return true +} + +/** @import { ProxyOrParticipant } from '#src/lib/proxyCache.js' */ +/** + * @typedef {Object} ProxyObligation + * @property {boolean} isFx - Is FX transfer. + * @property {Object} payloadClone - A clone of the original payload. + * @property {ProxyOrParticipant} initiatingFspProxyOrParticipantId - initiating FSP: proxy or participant. + * @property {ProxyOrParticipant} counterPartyFspProxyOrParticipantId - counterparty FSP: proxy or participant. + * @property {boolean} isInitiatingFspProxy - initiatingFsp.(!inScheme && proxyId !== null). + * @property {boolean} isCounterPartyFspProxy - counterPartyFsp.(!inScheme && proxyId !== null). + */ + +/** + * Calculates proxyObligation. + * @returns {ProxyObligation} proxyObligation + */ +const calculateProxyObligation = async ({ payload, isFx, params, functionality, action }) => { + const proxyObligation = { + isFx, + payloadClone: { ...payload }, + isInitiatingFspProxy: false, + isCounterPartyFspProxy: false, + initiatingFspProxyOrParticipantId: null, + counterPartyFspProxyOrParticipantId: null + } + + if (proxyEnabled) { + const [initiatingFsp, counterPartyFsp] = isFx ? [payload.initiatingFsp, payload.counterPartyFsp] : [payload.payerFsp, payload.payeeFsp] + ;[proxyObligation.initiatingFspProxyOrParticipantId, proxyObligation.counterPartyFspProxyOrParticipantId] = await Promise.all([ + ProxyCache.getFSPProxy(initiatingFsp), + ProxyCache.getFSPProxy(counterPartyFsp) + ]) + logger.debug('Prepare proxy cache lookup results', { + initiatingFsp, + counterPartyFsp, + initiatingFspProxyOrParticipantId: proxyObligation.initiatingFspProxyOrParticipantId, + counterPartyFspProxyOrParticipantId: proxyObligation.counterPartyFspProxyOrParticipantId + }) + + proxyObligation.isInitiatingFspProxy = !proxyObligation.initiatingFspProxyOrParticipantId.inScheme && + proxyObligation.initiatingFspProxyOrParticipantId.proxyId !== null + proxyObligation.isCounterPartyFspProxy = !proxyObligation.counterPartyFspProxyOrParticipantId.inScheme && + proxyObligation.counterPartyFspProxyOrParticipantId.proxyId !== null + + if (isFx) { + proxyObligation.payloadClone.initiatingFsp = !proxyObligation.initiatingFspProxyOrParticipantId?.inScheme && + proxyObligation.initiatingFspProxyOrParticipantId?.proxyId + ? proxyObligation.initiatingFspProxyOrParticipantId.proxyId + : payload.initiatingFsp + proxyObligation.payloadClone.counterPartyFsp = !proxyObligation.counterPartyFspProxyOrParticipantId?.inScheme && + proxyObligation.counterPartyFspProxyOrParticipantId?.proxyId + ? proxyObligation.counterPartyFspProxyOrParticipantId.proxyId + : payload.counterPartyFsp + } else { + proxyObligation.payloadClone.payerFsp = !proxyObligation.initiatingFspProxyOrParticipantId?.inScheme && + proxyObligation.initiatingFspProxyOrParticipantId?.proxyId + ? proxyObligation.initiatingFspProxyOrParticipantId.proxyId + : payload.payerFsp + proxyObligation.payloadClone.payeeFsp = !proxyObligation.counterPartyFspProxyOrParticipantId?.inScheme && + proxyObligation.counterPartyFspProxyOrParticipantId?.proxyId + ? proxyObligation.counterPartyFspProxyOrParticipantId.proxyId + : payload.payeeFsp + } + + // If either debtor participant or creditor participant aren't in the scheme and have no proxy representative, then throw an error. + if ((proxyObligation.initiatingFspProxyOrParticipantId.inScheme === false && proxyObligation.initiatingFspProxyOrParticipantId.proxyId === null) || + (proxyObligation.counterPartyFspProxyOrParticipantId.inScheme === false && proxyObligation.counterPartyFspProxyOrParticipantId.proxyId === null)) { + const fspiopError = ErrorHandler.Factory.createFSPIOPError( + ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, + `Payer proxy or payee proxy not found: initiatingFsp: ${initiatingFsp} counterPartyFsp: ${counterPartyFsp}` + ).toApiErrorObject(Config.ERROR_HANDLING) + await Kafka.proceed(Config.KAFKA_CONFIG, params, { + consumerCommit, + fspiopError, + eventDetail: { functionality, action }, + fromSwitch, + hubName: Config.HUB_NAME + }) + throw fspiopError + } + } + + return proxyObligation +} + const checkDuplication = async ({ payload, isFx, ID, location }) => { const funcName = 'prepare_duplicateCheckComparator' const histTimerDuplicateCheckEnd = Metrics.getHistogram( @@ -80,7 +238,7 @@ const processDuplication = async ({ let error if (!duplication.hasDuplicateHash) { - logger.error(Util.breadcrumb(location, `callbackErrorModified1--${actionLetter}5`)) + logger.warn(Util.breadcrumb(location, `callbackErrorModified1--${actionLetter}5`)) error = createFSPIOPError(FSPIOPErrorCodes.MODIFIED_REQUEST) } else if (action === Action.BULK_PREPARE) { logger.info(Util.breadcrumb(location, `validationError1--${actionLetter}2`)) @@ -164,7 +322,7 @@ const savePreparedRequest = async ({ proxyObligation ) } catch (err) { - logger.error(`${logMessage} error - ${err.message}`) + logger.error(`${logMessage} error:`, err) const fspiopError = reformatFSPIOPError(err, FSPIOPErrorCodes.INTERNAL_SERVER_ERROR) await Kafka.proceed(Config.KAFKA_CONFIG, params, { consumerCommit, @@ -178,10 +336,9 @@ const savePreparedRequest = async ({ } const definePositionParticipant = async ({ isFx, payload, determiningTransferCheckResult, proxyObligation }) => { - console.log(determiningTransferCheckResult) const cyrilResult = await createRemittanceEntity(isFx) .getPositionParticipant(payload, determiningTransferCheckResult, proxyObligation) - console.log(cyrilResult) + let messageKey // On a proxied transfer prepare if there is a corresponding fx transfer `getPositionParticipant` // should return the fxp's proxy as the participantName since the fxp proxy would be saved as the counterPartyFsp @@ -192,8 +349,6 @@ const definePositionParticipant = async ({ isFx, payload, determiningTransferChe // Only check transfers that have a related fxTransfer if (determiningTransferCheckResult?.watchListRecords?.length > 0) { const counterPartyParticipantFXPProxy = cyrilResult.participantName - console.log(counterPartyParticipantFXPProxy) - console.log(proxyObligation?.counterPartyFspProxyOrParticipantId?.proxyId) isSameProxy = counterPartyParticipantFXPProxy && proxyObligation?.counterPartyFspProxyOrParticipantId?.proxyId ? counterPartyParticipantFXPProxy === proxyObligation.counterPartyFspProxyOrParticipantId.proxyId : false @@ -201,14 +356,14 @@ const definePositionParticipant = async ({ isFx, payload, determiningTransferChe if (isSameProxy) { messageKey = '0' } else { - const participantName = cyrilResult.participantName const account = await Participant.getAccountByNameAndCurrency( - participantName, + cyrilResult.participantName, cyrilResult.currencyId, Enum.Accounts.LedgerAccountType.POSITION ) messageKey = account.participantCurrencyId.toString() } + logger.info('prepare positionParticipant details:', { messageKey, isSameProxy, cyrilResult }) return { messageKey, @@ -218,7 +373,6 @@ const definePositionParticipant = async ({ isFx, payload, determiningTransferChe const sendPositionPrepareMessage = async ({ isFx, - payload, action, params, determiningTransferCheckResult, @@ -318,177 +472,14 @@ const prepare = async (error, messages) => { } if (proxyEnabled && isForwarded) { - if (isFx) { - const fxTransfer = await FxTransferService.getByIdLight(ID) - if (!fxTransfer) { - const eventDetail = { - functionality: Enum.Events.Event.Type.NOTIFICATION, - action: Enum.Events.Event.Action.FX_FORWARDED - } - const fspiopError = ErrorHandler.Factory.createFSPIOPError( - ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, - 'Forwarded fxTransfer could not be found.' - ).toApiErrorObject(Config.ERROR_HANDLING) - // IMPORTANT: This singular message is taken by the ml-api-adapter and used to - // notify the payerFsp and proxy of the error. - // As long as the `to` and `from` message values are the fsp and fxp, - // and the action is `fx-forwarded`, the ml-api-adapter will notify both. - await Kafka.proceed( - Config.KAFKA_CONFIG, - params, - { - consumerCommit, - fspiopError, - eventDetail - } - ) - return true - } else { - if (fxTransfer.fxTransferState === Enum.Transfers.TransferInternalState.RESERVED) { - await FxTransferService.forwardedFxPrepare(ID) - } else { - const eventDetail = { - functionality: Enum.Events.Event.Type.NOTIFICATION, - action: Enum.Events.Event.Action.FX_FORWARDED - } - const fspiopError = ErrorHandler.Factory.createInternalServerFSPIOPError( - `Invalid State: ${fxTransfer.fxTransferState} - expected: ${Enum.Transfers.TransferInternalState.RESERVED}` - ).toApiErrorObject(Config.ERROR_HANDLING) - // IMPORTANT: This singular message is taken by the ml-api-adapter and used to - // notify the payerFsp and proxy of the error. - // As long as the `to` and `from` message values are the fsp and fxp, - // and the action is `fx-forwarded`, the ml-api-adapter will notify both. - await Kafka.proceed( - Config.KAFKA_CONFIG, - params, - { - consumerCommit, - fspiopError, - eventDetail - } - ) - } - } - } else { - const transfer = await TransferService.getById(ID) - if (!transfer) { - const eventDetail = { - functionality: Enum.Events.Event.Type.NOTIFICATION, - action: Enum.Events.Event.Action.FORWARDED - } - const fspiopError = ErrorHandler.Factory.createFSPIOPError( - ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, - 'Forwarded transfer could not be found.' - ).toApiErrorObject(Config.ERROR_HANDLING) - // IMPORTANT: This singular message is taken by the ml-api-adapter and used to - // notify the payerFsp and proxy of the error. - // As long as the `to` and `from` message values are the payer and payee, - // and the action is `forwarded`, the ml-api-adapter will notify both. - await Kafka.proceed( - Config.KAFKA_CONFIG, - params, - { - consumerCommit, - fspiopError, - eventDetail - } - ) - return true - } - - if (transfer.transferState === Enum.Transfers.TransferInternalState.RESERVED) { - await TransferService.forwardedPrepare(ID) - } else { - const eventDetail = { - functionality: Enum.Events.Event.Type.NOTIFICATION, - action: Enum.Events.Event.Action.FORWARDED - } - const fspiopError = ErrorHandler.Factory.createInternalServerFSPIOPError( - `Invalid State: ${transfer.transferState} - expected: ${Enum.Transfers.TransferInternalState.RESERVED}` - ).toApiErrorObject(Config.ERROR_HANDLING) - // IMPORTANT: This singular message is taken by the ml-api-adapter and used to - // notify the payerFsp and proxy of the error. - // As long as the `to` and `from` message values are the payer and payee, - // and the action is `forwarded`, the ml-api-adapter will notify both. - await Kafka.proceed( - Config.KAFKA_CONFIG, - params, - { - consumerCommit, - fspiopError, - eventDetail - } - ) - } - } - return true - } - - let initiatingFspProxyOrParticipantId - let counterPartyFspProxyOrParticipantId - const proxyObligation = { - isInitiatingFspProxy: false, - isCounterPartyFspProxy: false, - initiatingFspProxyOrParticipantId: null, - counterPartyFspProxyOrParticipantId: null, - isFx, - payloadClone: { ...payload } + const isOk = await forwardPrepare({ isFx, params, ID }) + logger.info('forwardPrepare message is processed', { isOk, isFx, ID }) + return isOk } - if (proxyEnabled) { - const [initiatingFsp, counterPartyFsp] = isFx ? [payload.initiatingFsp, payload.counterPartyFsp] : [payload.payerFsp, payload.payeeFsp] - ;[proxyObligation.initiatingFspProxyOrParticipantId, proxyObligation.counterPartyFspProxyOrParticipantId] = await Promise.all([ - ProxyCache.getFSPProxy(initiatingFsp), - ProxyCache.getFSPProxy(counterPartyFsp) - ]) - logger.debug('Prepare proxy cache lookup results', { - initiatingFsp, - counterPartyFsp, - initiatingFspProxyOrParticipantId: proxyObligation.initiatingFspProxyOrParticipantId, - counterPartyFspProxyOrParticipantId: proxyObligation.counterPartyFspProxyOrParticipantId - }) - proxyObligation.isInitiatingFspProxy = !proxyObligation.initiatingFspProxyOrParticipantId.inScheme && - proxyObligation.initiatingFspProxyOrParticipantId.proxyId !== null - proxyObligation.isCounterPartyFspProxy = !proxyObligation.counterPartyFspProxyOrParticipantId.inScheme && - proxyObligation.counterPartyFspProxyOrParticipantId.proxyId !== null - - if (isFx) { - proxyObligation.payloadClone.initiatingFsp = !proxyObligation.initiatingFspProxyOrParticipantId?.inScheme && - proxyObligation.initiatingFspProxyOrParticipantId?.proxyId - ? proxyObligation.initiatingFspProxyOrParticipantId.proxyId - : payload.initiatingFsp - proxyObligation.payloadClone.counterPartyFsp = !proxyObligation.counterPartyFspProxyOrParticipantId?.inScheme && - proxyObligation.counterPartyFspProxyOrParticipantId?.proxyId - ? proxyObligation.counterPartyFspProxyOrParticipantId.proxyId - : payload.counterPartyFsp - } else { - proxyObligation.payloadClone.payerFsp = !proxyObligation.initiatingFspProxyOrParticipantId?.inScheme && - proxyObligation.initiatingFspProxyOrParticipantId?.proxyId - ? proxyObligation.initiatingFspProxyOrParticipantId.proxyId - : payload.payerFsp - proxyObligation.payloadClone.payeeFsp = !proxyObligation.counterPartyFspProxyOrParticipantId?.inScheme && - proxyObligation.counterPartyFspProxyOrParticipantId?.proxyId - ? proxyObligation.counterPartyFspProxyOrParticipantId.proxyId - : payload.payeeFsp - } - - // If either debtor participant or creditor participant aren't in the scheme and have no proxy representative, then throw an error. - if ((proxyObligation.initiatingFspProxyOrParticipantId.inScheme === false && proxyObligation.initiatingFspProxyOrParticipantId.proxyId === null) || - (proxyObligation.counterPartyFspProxyOrParticipantId.inScheme === false && proxyObligation.counterPartyFspProxyOrParticipantId.proxyId === null)) { - const fspiopError = ErrorHandler.Factory.createFSPIOPError( - ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, - `Payer proxy or payee proxy not found: initiatingFsp: ${initiatingFspProxyOrParticipantId} counterPartyFsp: ${counterPartyFspProxyOrParticipantId}` - ).toApiErrorObject(Config.ERROR_HANDLING) - await Kafka.proceed(Config.KAFKA_CONFIG, params, { - consumerCommit, - fspiopError, - eventDetail: { functionality, action }, - fromSwitch, - hubName: Config.HUB_NAME - }) - throw fspiopError - } - } + const proxyObligation = await calculateProxyObligation({ + payload, isFx, params, functionality, action + }) const duplication = await checkDuplication({ payload, isFx, ID, location }) if (duplication.hasDuplicateId) { @@ -499,10 +490,8 @@ const prepare = async (error, messages) => { return success } - const determiningTransferCheckResult = await createRemittanceEntity(isFx).checkIfDeterminingTransferExists( - proxyObligation.payloadClone, - proxyObligation - ) + const determiningTransferCheckResult = await createRemittanceEntity(isFx) + .checkIfDeterminingTransferExists(proxyObligation.payloadClone, proxyObligation) const { validationPassed, reasons } = await Validator.validatePrepare( payload, @@ -523,8 +512,9 @@ const prepare = async (error, messages) => { determiningTransferCheckResult, proxyObligation }) + if (!validationPassed) { - logger.error(Util.breadcrumb(location, { path: 'validationFailed' })) + logger.warn(Util.breadcrumb(location, { path: 'validationFailed' })) const fspiopError = createFSPIOPError(FSPIOPErrorCodes.VALIDATION_ERROR, reasons.toString()) await createRemittanceEntity(isFx) .logTransferError(ID, FSPIOPErrorCodes.VALIDATION_ERROR.code, reasons.toString()) @@ -546,7 +536,7 @@ const prepare = async (error, messages) => { logger.info(Util.breadcrumb(location, `positionTopic1--${actionLetter}7`)) const success = await sendPositionPrepareMessage({ - isFx, payload, action, params, determiningTransferCheckResult, proxyObligation + isFx, action, params, determiningTransferCheckResult, proxyObligation }) histTimerEnd({ success, fspId }) @@ -554,8 +544,7 @@ const prepare = async (error, messages) => { } catch (err) { histTimerEnd({ success: false, fspId }) const fspiopError = reformatFSPIOPError(err) - logger.error(`${Util.breadcrumb(location)}::${err.message}--P0`) - logger.error(err.stack) + logger.error(`${Util.breadcrumb(location)}::${err.message}`, err) const state = new EventSdk.EventStateMetadata(EventSdk.EventStatusType.failed, fspiopError.apiErrorCode.code, fspiopError.apiErrorCode.message) await span.error(fspiopError, state) await span.finish(fspiopError.message, state) @@ -569,6 +558,8 @@ const prepare = async (error, messages) => { module.exports = { prepare, + forwardPrepare, + calculateProxyObligation, checkDuplication, processDuplication, savePreparedRequest, diff --git a/src/lib/proxyCache.js b/src/lib/proxyCache.js index 21b4f6297..2413220c1 100644 --- a/src/lib/proxyCache.js +++ b/src/lib/proxyCache.js @@ -33,12 +33,26 @@ const getCache = () => { return proxyCache } +/** + * @typedef {Object} ProxyOrParticipant - An object containing the inScheme status, proxyId and FSP name + * @property {boolean} inScheme - Is FSP in the scheme. + * @property {string|null} proxyId - Proxy, associated with the FSP, if FSP is not in the scheme. + * @property {string} name - FSP name. + */ + +/** + * Checks if dfspId is in scheme or proxy. + * + * @param {string} dfspId - The DFSP ID to check. + * @returns {ProxyOrParticipant} proxyOrParticipant details + */ const getFSPProxy = async (dfspId) => { logger.debug('Checking if dfspId is in scheme or proxy', { dfspId }) const participant = await ParticipantService.getByName(dfspId) return { inScheme: !!participant, - proxyId: !participant ? await getCache().lookupProxyByDfspId(dfspId) : null + proxyId: !participant ? await getCache().lookupProxyByDfspId(dfspId) : null, + name: dfspId } } diff --git a/src/models/fxTransfer/fxTransfer.js b/src/models/fxTransfer/fxTransfer.js index 0e542f1c1..a691ea7d6 100644 --- a/src/models/fxTransfer/fxTransfer.js +++ b/src/models/fxTransfer/fxTransfer.js @@ -6,9 +6,10 @@ const TransferEventAction = Enum.Events.Event.Action const Db = require('../../lib/db') const participant = require('../participant/facade') +const ParticipantCachedModel = require('../participant/participantCached') +const externalParticipantModel = require('../participant/externalParticipant') const { TABLE_NAMES } = require('../../shared/constants') const { logger } = require('../../shared/logger') -const ParticipantCachedModel = require('../participant/participantCached') const { TransferInternalState } = Enum.Transfers @@ -199,6 +200,16 @@ const getAllDetailsByCommitRequestIdForProxiedFxTransfer = async (commitRequestI const getParticipant = async (name, currency) => participant.getByNameAndCurrency(name, currency, Enum.Accounts.LedgerAccountType.POSITION) +/** + * Saves prepare fxTransfer details to DB. + * + * @param {Object} payload - Message payload. + * @param {string | null} stateReason - Validation failure reasons. + * @param {Boolean} hasPassedValidation - Is fxTransfer prepare validation passed. + * @param {DeterminingTransferCheckResult} determiningTransferCheckResult - Determining transfer check result. + * @param {ProxyObligation} proxyObligation - The proxy obligation + * @returns {Promise} + */ const savePreparedRequest = async ( payload, stateReason, @@ -214,10 +225,10 @@ const savePreparedRequest = async ( // Substitute out of scheme participants with their proxy representatives const initiatingFsp = proxyObligation.isInitiatingFspProxy - ? proxyObligation.initiatingFspProxyOrParticipantId?.proxyId + ? proxyObligation.initiatingFspProxyOrParticipantId.proxyId : payload.initiatingFsp const counterPartyFsp = proxyObligation.isCounterPartyFspProxy - ? proxyObligation.counterPartyFspProxyOrParticipantId?.proxyId + ? proxyObligation.counterPartyFspProxyOrParticipantId.proxyId : payload.counterPartyFsp // If creditor(counterPartyFsp) is a proxy in a jurisdictional scenario, @@ -257,6 +268,10 @@ const savePreparedRequest = async ( transferParticipantRoleTypeId: Enum.Accounts.TransferParticipantRoleType.INITIATING_FSP, ledgerEntryTypeId: Enum.Accounts.LedgerEntryType.PRINCIPLE_VALUE } + if (proxyObligation.isInitiatingFspProxy) { + initiatingParticipantRecord.externalParticipantId = await externalParticipantModel + .getIdByNameOrCreate(proxyObligation.initiatingFspProxyOrParticipantId) + } const counterPartyParticipantRecord1 = { commitRequestId: payload.commitRequestId, @@ -267,6 +282,10 @@ const savePreparedRequest = async ( fxParticipantCurrencyTypeId: Enum.Fx.FxParticipantCurrencyType.SOURCE, ledgerEntryTypeId: Enum.Accounts.LedgerEntryType.PRINCIPLE_VALUE } + if (proxyObligation.isCounterPartyFspProxy) { + counterPartyParticipantRecord1.externalParticipantId = await externalParticipantModel + .getIdByNameOrCreate(proxyObligation.counterPartyFspProxyOrParticipantId) + } let counterPartyParticipantRecord2 = null if (!proxyObligation.isCounterPartyFspProxy) { diff --git a/src/models/participant/externalParticipant.js b/src/models/participant/externalParticipant.js new file mode 100644 index 000000000..2215212de --- /dev/null +++ b/src/models/participant/externalParticipant.js @@ -0,0 +1,123 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * Eugen Klymniuk + -------------- + **********/ + +const ErrorHandler = require('@mojaloop/central-services-error-handling') +const Db = require('../../lib/db') +const { logger } = require('../../shared/logger') +const { TABLE_NAMES } = require('../../shared/constants') + +const TABLE = TABLE_NAMES.externalParticipant +const ID_FIELD = 'externalParticipantId' + +const log = logger.child(`DB#${TABLE}`) + +// todo: use caching lib +const CACHE = {} +const cache = { + get (key) { + return CACHE[key] + }, + set (key, value) { + CACHE[key] = value + } +} + +const create = async ({ name, proxyId }) => { + try { + const result = await Db.from(TABLE).insert({ name, proxyId }) + log.debug('create result:', { result }) + return result + } catch (err) { + log.error('error in create', err) + throw ErrorHandler.Factory.reformatFSPIOPError(err) + } +} + +const getOneBy = async (criteria, options) => { + try { + const result = await Db.from(TABLE).findOne(criteria, options) + log.debug('getOneBy result:', { criteria, result }) + return result + } catch (err) { + log.error('error in getOneBy:', err) + throw ErrorHandler.Factory.reformatFSPIOPError(err) + } +} +const getOneById = async (id, options) => getOneBy({ [ID_FIELD]: id }, options) +const getOneByName = async (name, options) => getOneBy({ name }, options) + +const getOneByNameCached = async (name, options = {}) => { + let data = cache.get(name) + if (data) { + log.debug('getOneByIdCached cache hit:', { name, data }) + } else { + data = await getOneByName(name, options) + cache.set(name, data) + log.debug('getOneByIdCached cache updated:', { name, data }) + } + return data +} + +const getIdByNameOrCreate = async ({ name, proxyId }) => { + try { + let dfsp = await getOneByNameCached(name) + if (!dfsp) { + await create({ name, proxyId }) + // todo: check if create returns id (to avoid getOneByNameCached call) + dfsp = await getOneByNameCached(name) + } + const id = dfsp?.[ID_FIELD] + log.verbose('getIdByNameOrCreate result:', { id, name }) + return id + } catch (err) { + log.child({ name, proxyId }).warn('error in getIdByNameOrCreate:', err) + return null + // todo: think, if we need to rethrow an error here? + } +} + +const destroyBy = async (criteria) => { + try { + const result = await Db.from(TABLE).destroy(criteria) + log.debug('destroyBy result:', { criteria, result }) + return result + } catch (err) { + log.error('error in destroyBy', err) + throw ErrorHandler.Factory.reformatFSPIOPError(err) + } +} +const destroyById = async (id) => destroyBy({ [ID_FIELD]: id }) +const destroyByName = async (name) => destroyBy({ name }) + +// todo: think, if we need update method +module.exports = { + create, + getIdByNameOrCreate, + getOneByNameCached, + getOneByName, + getOneById, + destroyById, + destroyByName +} diff --git a/src/models/transfer/facade.js b/src/models/transfer/facade.js index 191f90aa0..8b12e32ca 100644 --- a/src/models/transfer/facade.js +++ b/src/models/transfer/facade.js @@ -33,19 +33,22 @@ * @module src/models/transfer/facade/ */ -const Db = require('../../lib/db') +const ErrorHandler = require('@mojaloop/central-services-error-handling') +const Metrics = require('@mojaloop/central-services-metrics') +const MLNumber = require('@mojaloop/ml-number') const Enum = require('@mojaloop/central-services-shared').Enum -const TransferEventAction = Enum.Events.Event.Action -const TransferInternalState = Enum.Transfers.TransferInternalState -const TransferExtensionModel = require('./transferExtension') -const ParticipantFacade = require('../participant/facade') -const ParticipantCachedModel = require('../participant/participantCached') const Time = require('@mojaloop/central-services-shared').Util.Time -const MLNumber = require('@mojaloop/ml-number') + +const { logger } = require('../../shared/logger') +const Db = require('../../lib/db') const Config = require('../../lib/config') -const ErrorHandler = require('@mojaloop/central-services-error-handling') -const Logger = require('@mojaloop/central-services-logger') -const Metrics = require('@mojaloop/central-services-metrics') +const ParticipantFacade = require('../participant/facade') +const ParticipantCachedModel = require('../participant/participantCached') +const externalParticipantModel = require('../participant/externalParticipant') +const TransferExtensionModel = require('./transferExtension') + +const TransferEventAction = Enum.Events.Event.Action +const TransferInternalState = Enum.Transfers.TransferInternalState // Alphabetically ordered list of error texts used below const UnsupportedActionText = 'Unsupported action' @@ -356,12 +359,12 @@ const savePayeeTransferResponse = async (transferId, payload, action, fspiopErro .orderBy('changedDate', 'desc') }) transferFulfilmentRecord.settlementWindowId = res[0].settlementWindowId - Logger.isDebugEnabled && Logger.debug('savePayeeTransferResponse::settlementWindowId') + logger.debug('savePayeeTransferResponse::settlementWindowId') } if (isFulfilment) { await knex('transferFulfilment').transacting(trx).insert(transferFulfilmentRecord) result.transferFulfilmentRecord = transferFulfilmentRecord - Logger.isDebugEnabled && Logger.debug('savePayeeTransferResponse::transferFulfilment') + logger.debug('savePayeeTransferResponse::transferFulfilment') } if (transferExtensionRecordsList.length > 0) { // ###! CAN BE DONE THROUGH A BATCH @@ -370,11 +373,11 @@ const savePayeeTransferResponse = async (transferId, payload, action, fspiopErro } // ###! result.transferExtensionRecordsList = transferExtensionRecordsList - Logger.isDebugEnabled && Logger.debug('savePayeeTransferResponse::transferExtensionRecordsList') + logger.debug('savePayeeTransferResponse::transferExtensionRecordsList') } await knex('transferStateChange').transacting(trx).insert(transferStateChangeRecord) result.transferStateChangeRecord = transferStateChangeRecord - Logger.isDebugEnabled && Logger.debug('savePayeeTransferResponse::transferStateChange') + logger.debug('savePayeeTransferResponse::transferStateChange') if (fspiopError) { const insertedTransferStateChange = await knex('transferStateChange').transacting(trx) .where({ transferId }) @@ -383,14 +386,14 @@ const savePayeeTransferResponse = async (transferId, payload, action, fspiopErro transferErrorRecord.transferStateChangeId = insertedTransferStateChange.transferStateChangeId await knex('transferError').transacting(trx).insert(transferErrorRecord) result.transferErrorRecord = transferErrorRecord - Logger.isDebugEnabled && Logger.debug('savePayeeTransferResponse::transferError') + logger.debug('savePayeeTransferResponse::transferError') } histTPayeeResponseValidationPassedEnd({ success: true, queryName: 'facade_saveTransferPrepared_transaction' }) result.savePayeeTransferResponseExecuted = true - Logger.isDebugEnabled && Logger.debug('savePayeeTransferResponse::success') + logger.debug('savePayeeTransferResponse::success') } catch (err) { + logger.error('savePayeeTransferResponse::failure', err) histTPayeeResponseValidationPassedEnd({ success: false, queryName: 'facade_saveTransferPrepared_transaction' }) - Logger.isErrorEnabled && Logger.error('savePayeeTransferResponse::failure') throw err } }) @@ -402,6 +405,16 @@ const savePayeeTransferResponse = async (transferId, payload, action, fspiopErro } } +/** + * Saves prepare transfer details to DB. + * + * @param {Object} payload - Message payload. + * @param {string | null} stateReason - Validation failure reasons. + * @param {Boolean} hasPassedValidation - Is transfer prepare validation passed. + * @param {DeterminingTransferCheckResult} determiningTransferCheckResult - Determining transfer check result. + * @param {ProxyObligation} proxyObligation - The proxy obligation + * @returns {Promise} + */ const saveTransferPrepared = async (payload, stateReason = null, hasPassedValidation = true, determiningTransferCheckResult, proxyObligation) => { const histTimerSaveTransferPreparedEnd = Metrics.getHistogram( 'model_transfer', @@ -415,8 +428,7 @@ const saveTransferPrepared = async (payload, stateReason = null, hasPassedValida } // Iterate over the participants and get the details - const names = Object.keys(participants) - for (const name of names) { + for (const name of Object.keys(participants)) { const participant = await ParticipantCachedModel.getByName(name) if (participant) { participants[name].id = participant.participantId @@ -427,26 +439,26 @@ const saveTransferPrepared = async (payload, stateReason = null, hasPassedValida const participantCurrencyRecord = await ParticipantFacade.getByNameAndCurrency(participantCurrency.participantName, participantCurrency.currencyId, Enum.Accounts.LedgerAccountType.POSITION) participants[name].participantCurrencyId = participantCurrencyRecord?.participantCurrencyId } + } - if (proxyObligation?.isInitiatingFspProxy) { - const proxyId = proxyObligation.initiatingFspProxyOrParticipantId.proxyId - const proxyParticipant = await ParticipantCachedModel.getByName(proxyId) - participants[proxyId] = {} - participants[proxyId].id = proxyParticipant.participantId - const participantCurrencyRecord = await ParticipantFacade.getByNameAndCurrency( - proxyId, payload.amount.currency, Enum.Accounts.LedgerAccountType.POSITION - ) - // In a regional scheme, the stand-in initiating FSP proxy may not have a participantCurrencyId - // of the target currency of the transfer, so set to null if not found - participants[proxyId].participantCurrencyId = participantCurrencyRecord?.participantCurrencyId - } + if (proxyObligation?.isInitiatingFspProxy) { + const proxyId = proxyObligation.initiatingFspProxyOrParticipantId.proxyId + const proxyParticipant = await ParticipantCachedModel.getByName(proxyId) + participants[proxyId] = {} + participants[proxyId].id = proxyParticipant.participantId + const participantCurrencyRecord = await ParticipantFacade.getByNameAndCurrency( + proxyId, payload.amount.currency, Enum.Accounts.LedgerAccountType.POSITION + ) + // In a regional scheme, the stand-in initiating FSP proxy may not have a participantCurrencyId + // of the target currency of the transfer, so set to null if not found + participants[proxyId].participantCurrencyId = participantCurrencyRecord?.participantCurrencyId + } - if (proxyObligation?.isCounterPartyFspProxy) { - const proxyId = proxyObligation.counterPartyFspProxyOrParticipantId.proxyId - const proxyParticipant = await ParticipantCachedModel.getByName(proxyId) - participants[proxyId] = {} - participants[proxyId].id = proxyParticipant.participantId - } + if (proxyObligation?.isCounterPartyFspProxy) { + const proxyId = proxyObligation.counterPartyFspProxyOrParticipantId.proxyId + const proxyParticipant = await ParticipantCachedModel.getByName(proxyId) + participants[proxyId] = {} + participants[proxyId].id = proxyParticipant.participantId } const transferRecord = { @@ -462,24 +474,25 @@ const saveTransferPrepared = async (payload, stateReason = null, hasPassedValida value: payload.ilpPacket } - const state = ((hasPassedValidation) ? Enum.Transfers.TransferInternalState.RECEIVED_PREPARE : Enum.Transfers.TransferInternalState.INVALID) - const transferStateChangeRecord = { transferId: payload.transferId, - transferStateId: state, + transferStateId: hasPassedValidation ? TransferInternalState.RECEIVED_PREPARE : TransferInternalState.INVALID, reason: stateReason, createdDate: Time.getUTCString(new Date()) } let payerTransferParticipantRecord if (proxyObligation?.isInitiatingFspProxy) { + const externalParticipantId = await externalParticipantModel.getIdByNameOrCreate(proxyObligation.initiatingFspProxyOrParticipantId) + // todo: think, what if externalParticipantId is null? payerTransferParticipantRecord = { transferId: payload.transferId, participantId: participants[proxyObligation.initiatingFspProxyOrParticipantId.proxyId].id, participantCurrencyId: participants[proxyObligation.initiatingFspProxyOrParticipantId.proxyId].participantCurrencyId, transferParticipantRoleTypeId: Enum.Accounts.TransferParticipantRoleType.PAYER_DFSP, ledgerEntryTypeId: Enum.Accounts.LedgerEntryType.PRINCIPLE_VALUE, - amount: -payload.amount.amount + amount: -payload.amount.amount, + externalParticipantId } } else { payerTransferParticipantRecord = { @@ -492,16 +505,19 @@ const saveTransferPrepared = async (payload, stateReason = null, hasPassedValida } } - console.log(participants) + logger.debug('saveTransferPrepared participants:', { participants }) let payeeTransferParticipantRecord if (proxyObligation?.isCounterPartyFspProxy) { + const externalParticipantId = await externalParticipantModel.getIdByNameOrCreate(proxyObligation.counterPartyFspProxyOrParticipantId) + // todo: think, what if externalParticipantId is null? payeeTransferParticipantRecord = { transferId: payload.transferId, participantId: participants[proxyObligation.counterPartyFspProxyOrParticipantId.proxyId].id, participantCurrencyId: null, transferParticipantRoleTypeId: Enum.Accounts.TransferParticipantRoleType.PAYEE_DFSP, ledgerEntryTypeId: Enum.Accounts.LedgerEntryType.PRINCIPLE_VALUE, - amount: -payload.amount.amount + amount: -payload.amount.amount, + externalParticipantId } } else { payeeTransferParticipantRecord = { @@ -557,14 +573,14 @@ const saveTransferPrepared = async (payload, stateReason = null, hasPassedValida try { await knex('transferParticipant').insert(payerTransferParticipantRecord) } catch (err) { - Logger.isWarnEnabled && Logger.warn(`Payer transferParticipant insert error: ${err.message}`) + logger.warn('Payer transferParticipant insert error', err) histTimerSaveTranferNoValidationEnd({ success: false, queryName: 'facade_saveTransferPrepared_no_validation' }) } try { await knex('transferParticipant').insert(payeeTransferParticipantRecord) } catch (err) { + logger.warn('Payee transferParticipant insert error:', err) histTimerSaveTranferNoValidationEnd({ success: false, queryName: 'facade_saveTransferPrepared_no_validation' }) - Logger.isWarnEnabled && Logger.warn(`Payee transferParticipant insert error: ${err.message}`) } payerTransferParticipantRecord.name = payload.payerFsp payeeTransferParticipantRecord.name = payload.payeeFsp @@ -580,21 +596,21 @@ const saveTransferPrepared = async (payload, stateReason = null, hasPassedValida try { await knex.batchInsert('transferExtension', transferExtensionsRecordList) } catch (err) { - Logger.isWarnEnabled && Logger.warn(`batchInsert transferExtension error: ${err.message}`) + logger.warn('batchInsert transferExtension error:', err) histTimerSaveTranferNoValidationEnd({ success: false, queryName: 'facade_saveTransferPrepared_no_validation' }) } } try { await knex('ilpPacket').insert(ilpPacketRecord) } catch (err) { - Logger.isWarnEnabled && Logger.warn(`ilpPacket insert error: ${err.message}`) + logger.warn('ilpPacket insert error:', err) histTimerSaveTranferNoValidationEnd({ success: false, queryName: 'facade_saveTransferPrepared_no_validation' }) } try { await knex('transferStateChange').insert(transferStateChangeRecord) histTimerSaveTranferNoValidationEnd({ success: true, queryName: 'facade_saveTransferPrepared_no_validation' }) } catch (err) { - Logger.isWarnEnabled && Logger.warn(`transferStateChange insert error: ${err.message}`) + logger.warn('transferStateChange insert error:', err) histTimerSaveTranferNoValidationEnd({ success: false, queryName: 'facade_saveTransferPrepared_no_validation' }) } } @@ -1421,7 +1437,7 @@ const recordFundsIn = async (payload, transactionTimestamp, enums) => { await TransferFacade.reconciliationTransferReserve(payload, transactionTimestamp, enums, trx) await TransferFacade.reconciliationTransferCommit(payload, transactionTimestamp, enums, trx) } catch (err) { - Logger.isErrorEnabled && Logger.error(err) + logger.error('error in recordFundsIn:', err) throw ErrorHandler.Factory.reformatFSPIOPError(err) } }) diff --git a/src/shared/constants.js b/src/shared/constants.js index 5fdd7165e..91e90f501 100644 --- a/src/shared/constants.js +++ b/src/shared/constants.js @@ -1,6 +1,7 @@ const { Enum } = require('@mojaloop/central-services-shared') const TABLE_NAMES = Object.freeze({ + externalParticipant: 'externalParticipant', fxTransfer: 'fxTransfer', fxTransferDuplicateCheck: 'fxTransferDuplicateCheck', fxTransferErrorDuplicateCheck: 'fxTransferErrorDuplicateCheck', diff --git a/test/fixtures.js b/test/fixtures.js index 15974730d..a0e93007a 100644 --- a/test/fixtures.js +++ b/test/fixtures.js @@ -299,6 +299,18 @@ const watchListItemDto = ({ createdDate }) +const mockExternalParticipantDto = ({ + name = `extFsp-${Date.now()}`, + proxyId = `proxy-${Date.now()}`, + id = Date.now(), + createdDate = new Date() +} = {}) => ({ + name, + proxyId, + ...(id && { externalParticipantId: id }), + ...(createdDate && { createdDate }) +}) + module.exports = { ILP_PACKET, CONDITION, @@ -324,5 +336,6 @@ module.exports = { fxTransferDto, fxFulfilResponseDto, fxtGetAllDetailsByCommitRequestIdDto, - watchListItemDto + watchListItemDto, + mockExternalParticipantDto } diff --git a/test/unit/lib/proxyCache.test.js b/test/unit/lib/proxyCache.test.js index 4104b7570..ab8407760 100644 --- a/test/unit/lib/proxyCache.test.js +++ b/test/unit/lib/proxyCache.test.js @@ -86,17 +86,19 @@ Test('Proxy Cache test', async (proxyCacheTest) => { await proxyCacheTest.test('getFSPProxy', async (getFSPProxyTest) => { await getFSPProxyTest.test('resolve proxy id if participant not in scheme and proxyId is in cache', async (test) => { ParticipantService.getByName.returns(Promise.resolve(null)) - const result = await ProxyCache.getFSPProxy('existingDfspId1') + const dfspId = 'existingDfspId1' + const result = await ProxyCache.getFSPProxy(dfspId) - test.deepEqual(result, { inScheme: false, proxyId: 'proxyId' }) + test.deepEqual(result, { inScheme: false, proxyId: 'proxyId', name: dfspId }) test.end() }) await getFSPProxyTest.test('resolve proxy id if participant not in scheme and proxyId is not cache', async (test) => { ParticipantService.getByName.returns(Promise.resolve(null)) - const result = await ProxyCache.getFSPProxy('nonExistingDfspId1') + const dsfpId = 'nonExistingDfspId1' + const result = await ProxyCache.getFSPProxy(dsfpId) - test.deepEqual(result, { inScheme: false, proxyId: null }) + test.deepEqual(result, { inScheme: false, proxyId: null, name: dsfpId }) test.end() }) @@ -104,7 +106,7 @@ Test('Proxy Cache test', async (proxyCacheTest) => { ParticipantService.getByName.returns(Promise.resolve({ participantId: 1 })) const result = await ProxyCache.getFSPProxy('existingDfspId1') - test.deepEqual(result, { inScheme: true, proxyId: null }) + test.deepEqual(result, { inScheme: true, proxyId: null, name: 'existingDfspId1' }) test.end() }) diff --git a/test/unit/models/participant/externalParticipant.test.js b/test/unit/models/participant/externalParticipant.test.js new file mode 100644 index 000000000..8ba7dfb4b --- /dev/null +++ b/test/unit/models/participant/externalParticipant.test.js @@ -0,0 +1,135 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + + Contributors + -------------- + This is the official list of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + + * Eugen Klymniuk + -------------- + **********/ +process.env.LOG_LEVEL = 'debug' + +const Test = require('tapes')(require('tape')) +const Sinon = require('sinon') +const model = require('#src/models/participant/externalParticipant') +const Db = require('#src/lib/db') +const { TABLE_NAMES } = require('#src/shared/constants') + +const { tryCatchEndTest } = require('#test/util/helpers') +const { mockExternalParticipantDto } = require('#test/fixtures') + +const EP_TABLE = TABLE_NAMES.externalParticipant + +Test('externalParticipant Model Tests -->', (epmTest) => { + let sandbox + + epmTest.beforeEach(t => { + sandbox = Sinon.createSandbox() + + const dbStub = sandbox.stub(Db) + Db.from = table => dbStub[table] + Db[EP_TABLE] = { + insert: sandbox.stub(), + findOne: sandbox.stub(), + destroy: sandbox.stub() + } + t.end() + }) + + epmTest.afterEach(t => { + sandbox.restore() + t.end() + }) + + epmTest.test('should create externalParticipant in DB', tryCatchEndTest(async (t) => { + const data = mockExternalParticipantDto({ id: null, createdDate: null }) + Db[EP_TABLE].insert.withArgs(data).resolves(true) + const result = await model.create(data) + t.ok(result) + })) + + epmTest.test('should get externalParticipant by name from DB', tryCatchEndTest(async (t) => { + const data = mockExternalParticipantDto() + Db[EP_TABLE].findOne.withArgs({ name: data.name }).resolves(data) + const result = await model.getOneByName(data.name) + t.deepEqual(result, data) + })) + + epmTest.test('should get externalParticipant by name from cache', tryCatchEndTest(async (t) => { + const name = `extFsp-${Date.now()}` + const data = mockExternalParticipantDto({ name }) + Db[EP_TABLE].findOne.withArgs({ name }).resolves(data) + const result = await model.getOneByNameCached(name) + t.deepEqual(result, data) + + Db[EP_TABLE].findOne = sandbox.stub() + const cached = await model.getOneByNameCached(name) + t.deepEqual(cached, data, 'cached externalParticipant') + t.ok(Db[EP_TABLE].findOne.notCalled, 'db.findOne is called') + })) + + epmTest.test('should get externalParticipant ID from db (no data in cache)', tryCatchEndTest(async (t) => { + const name = `extFsp-${Date.now()}` + const data = mockExternalParticipantDto({ name }) + Db[EP_TABLE].findOne.withArgs({ name }).resolves(data) + + const id = await model.getIdByNameOrCreate({ name }) + t.equal(id, data.externalParticipantId) + })) + + epmTest.test('should create externalParticipant, and get its id from db (if no data in db)', tryCatchEndTest(async (t) => { + const data = mockExternalParticipantDto() + const { name, proxyId } = data + const fspList = [] + Db[EP_TABLE].findOne = async json => (json.name === name && fspList[0]) + Db[EP_TABLE].insert = async json => { if (json.name === name && json.proxyId === proxyId) fspList.push(data) } + + const id = await model.getIdByNameOrCreate({ name, proxyId }) + t.equal(id, data.externalParticipantId) + })) + + epmTest.test('should return null in case of error inside getIdByNameOrCreate method', tryCatchEndTest(async (t) => { + Db[EP_TABLE].findOne.rejects(new Error('DB error')) + const id = await model.getIdByNameOrCreate(mockExternalParticipantDto()) + t.equal(id, null) + })) + + epmTest.test('should get externalParticipant by id', tryCatchEndTest(async (t) => { + const id = 'id123' + const data = { name: 'extFsp', proxyId: '123' } + Db[EP_TABLE].findOne.withArgs({ externalParticipantId: id }).resolves(data) + const result = await model.getOneById(id) + t.deepEqual(result, data) + })) + + epmTest.test('should delete externalParticipant record by name', tryCatchEndTest(async (t) => { + const name = 'extFsp' + Db[EP_TABLE].destroy.withArgs({ name }).resolves(true) + const result = await model.destroyByName(name) + t.ok(result) + })) + + epmTest.test('should delete externalParticipant record by id', tryCatchEndTest(async (t) => { + const id = 123 + Db[EP_TABLE].destroy.withArgs({ externalParticipantId: id }).resolves(true) + const result = await model.destroyById(id) + t.ok(result) + })) + + epmTest.end() +}) diff --git a/test/util/helpers.js b/test/util/helpers.js index fec192a35..19ebcc99d 100644 --- a/test/util/helpers.js +++ b/test/util/helpers.js @@ -27,6 +27,7 @@ const { FSPIOPError } = require('@mojaloop/central-services-error-handling').Factory const Logger = require('@mojaloop/central-services-logger') const Config = require('#src/lib/config') +const { logger } = require('#src/shared/logger/index') /* Helper Functions */ @@ -178,6 +179,17 @@ const checkErrorPayload = test => (actualPayload, expectedFspiopError) => { test.equal(actualPayload.errorInformation?.errorDescription, errorDescription, 'errorDescription matches') } +// to use as a wrapper on Tape tests +const tryCatchEndTest = (testFn) => async (t) => { + try { + await testFn(t) + } catch (err) { + logger.error(`error in test: "${t.name}"`, err) + t.fail(t.name) + } + t.end() +} + module.exports = { checkErrorPayload, currentEventLoopEnd, @@ -186,5 +198,6 @@ module.exports = { unwrapResponse, waitFor, wrapWithRetries, - getMessagePayloadOrThrow + getMessagePayloadOrThrow, + tryCatchEndTest }