From 97e969a63798d909d861325dfb6ef6fc367b3efd Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Tue, 2 Jul 2024 08:31:52 +0100 Subject: [PATCH 01/51] feat: add proxy cache lib and config --- config/default.json | 7 +++ package-lock.json | 113 +++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + src/lib/config.js | 1 + 4 files changed, 120 insertions(+), 2 deletions(-) diff --git a/config/default.json b/config/default.json index e250b4e6..4549551b 100644 --- a/config/default.json +++ b/config/default.json @@ -77,6 +77,13 @@ "ENUM_DATA_EXPIRES_IN_MS": 4170000, "PARTICIPANT_DATA_EXPIRES_IN_MS": 60000 }, + "PROXY_CACHE": { + "type": "redis", + "proxyConfig": { + "host": "localhost", + "port": 6379 + } + }, "KAFKA": { "CONSUMER": { "QUOTE": { diff --git a/package-lock.json b/package-lock.json index 816bafda..4613300f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@mojaloop/central-services-shared": "18.5.0-snapshot.2", "@mojaloop/central-services-stream": "11.3.1", "@mojaloop/event-sdk": "14.1.1", + "@mojaloop/inter-scheme-proxy-cache-lib": "^1.0.0", "@mojaloop/ml-number": "11.2.4", "@mojaloop/sdk-standard-components": "18.1.0", "ajv": "8.16.0", @@ -1401,6 +1402,11 @@ "node": ">=6.9.0" } }, + "node_modules/@ioredis/commands": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2099,6 +2105,21 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, + "node_modules/@mojaloop/inter-scheme-proxy-cache-lib": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@mojaloop/inter-scheme-proxy-cache-lib/-/inter-scheme-proxy-cache-lib-1.0.0.tgz", + "integrity": "sha512-mgSwOIMmE9QwQ12PwMa77purtcmZkw/VWBmyTwZDac4CXiABVNa+CYz9m++6ItnsHXMTq4JApOcLQ0joLoOw/Q==", + "dependencies": { + "@mojaloop/central-services-logger": "^11.3.1", + "ajv": "^8.16.0", + "convict": "^6.2.4", + "fast-safe-stringify": "^2.1.1", + "ioredis": "^5.4.1" + }, + "engines": { + "node": ">=18.x" + } + }, "node_modules/@mojaloop/ml-number": { "version": "11.2.4", "resolved": "https://registry.npmjs.org/@mojaloop/ml-number/-/ml-number-11.2.4.tgz", @@ -4311,6 +4332,14 @@ "node": ">=0.8" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -4948,6 +4977,18 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/convict": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/convict/-/convict-6.2.4.tgz", + "integrity": "sha512-qN60BAwdMVdofckX7AlohVJ2x9UvjTNoKVXCL2LxFk1l7757EJqf1nySdMkPQer0bt8kQ5lQiyZ9/2NvrFBuwQ==", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "yargs-parser": "^20.2.7" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/cookie": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", @@ -5148,7 +5189,6 @@ "version": "4.3.5", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -5349,6 +5389,14 @@ "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "dev": true }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -8572,6 +8620,29 @@ "node": ">=4" } }, + "node_modules/ioredis": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz", + "integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==", + "dependencies": { + "@ioredis/commands": "^1.1.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, "node_modules/ip-address": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", @@ -10477,6 +10548,16 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, "node_modules/lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", @@ -10489,6 +10570,11 @@ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "dev": true }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" + }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -14169,6 +14255,25 @@ "node": ">=8" } }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", @@ -15393,6 +15498,11 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + }, "node_modules/standard-engine": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-15.1.0.tgz", @@ -17725,7 +17835,6 @@ "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, "engines": { "node": ">=10" } diff --git a/package.json b/package.json index 61521c31..ea45d964 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "@mojaloop/central-services-shared": "18.5.0-snapshot.2", "@mojaloop/central-services-stream": "11.3.1", "@mojaloop/event-sdk": "14.1.1", + "@mojaloop/inter-scheme-proxy-cache-lib": "^1.0.0", "@mojaloop/ml-number": "11.2.4", "@mojaloop/sdk-standard-components": "18.1.0", "ajv": "8.16.0", diff --git a/src/lib/config.js b/src/lib/config.js index 76fac630..65818883 100644 --- a/src/lib/config.js +++ b/src/lib/config.js @@ -157,6 +157,7 @@ class Config { this.instrumentationMetricsConfig = RC.INSTRUMENTATION.METRICS.config this.enumDataCacheExpiresInMs = RC.CACHE.ENUM_DATA_EXPIRES_IN_MS || 4170000 this.participantDataCacheExpiresInMs = RC.CACHE.PARTICIPANT_DATA_EXPIRES_IN_MS || 60000 + this.proxyCache = RC.PROXY_CACHE } } From 232d143b5d38d6cae0cb3d3394e86160583794fd Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Tue, 2 Jul 2024 09:01:22 +0100 Subject: [PATCH 02/51] feat: update proxy cache config --- config/default.json | 1 + 1 file changed, 1 insertion(+) diff --git a/config/default.json b/config/default.json index 4549551b..3722177b 100644 --- a/config/default.json +++ b/config/default.json @@ -78,6 +78,7 @@ "PARTICIPANT_DATA_EXPIRES_IN_MS": 60000 }, "PROXY_CACHE": { + "enabled": false, "type": "redis", "proxyConfig": { "host": "localhost", From 691c82a1aa54f976f7e05f13cd57c361a6b74284 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Tue, 2 Jul 2024 17:13:16 +0100 Subject: [PATCH 03/51] feat: refactor models to use private methods for participants endpoint query --- package-lock.json | 8 ++++---- package.json | 2 +- src/lib/util.js | 22 +++++++++++++++++++++- src/model/bulkQuotes.js | 14 +++++++++----- src/model/fxQuotes.js | 15 ++++++++++----- src/model/quotes.js | 40 +++++++++++++++++++++++++++------------- 6 files changed, 72 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4613300f..9b300abd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@mojaloop/central-services-shared": "18.5.0-snapshot.2", "@mojaloop/central-services-stream": "11.3.1", "@mojaloop/event-sdk": "14.1.1", - "@mojaloop/inter-scheme-proxy-cache-lib": "^1.0.0", + "@mojaloop/inter-scheme-proxy-cache-lib": "^1.1.0", "@mojaloop/ml-number": "11.2.4", "@mojaloop/sdk-standard-components": "18.1.0", "ajv": "8.16.0", @@ -2106,9 +2106,9 @@ "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, "node_modules/@mojaloop/inter-scheme-proxy-cache-lib": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@mojaloop/inter-scheme-proxy-cache-lib/-/inter-scheme-proxy-cache-lib-1.0.0.tgz", - "integrity": "sha512-mgSwOIMmE9QwQ12PwMa77purtcmZkw/VWBmyTwZDac4CXiABVNa+CYz9m++6ItnsHXMTq4JApOcLQ0joLoOw/Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mojaloop/inter-scheme-proxy-cache-lib/-/inter-scheme-proxy-cache-lib-1.1.0.tgz", + "integrity": "sha512-4uBms3wLiP3qCdojLr80depj34vKcPwwHV0HX4uqk09/riIaBz2OX9GYWAUk0cHfJzv1Yq6AxP1U8aJmmqtXaw==", "dependencies": { "@mojaloop/central-services-logger": "^11.3.1", "ajv": "^8.16.0", diff --git a/package.json b/package.json index ea45d964..5a325cf9 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "@mojaloop/central-services-shared": "18.5.0-snapshot.2", "@mojaloop/central-services-stream": "11.3.1", "@mojaloop/event-sdk": "14.1.1", - "@mojaloop/inter-scheme-proxy-cache-lib": "^1.0.0", + "@mojaloop/inter-scheme-proxy-cache-lib": "^1.1.0", "@mojaloop/ml-number": "11.2.4", "@mojaloop/sdk-standard-components": "18.1.0", "ajv": "8.16.0", diff --git a/src/lib/util.js b/src/lib/util.js index 7efdea87..181504cf 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -233,6 +233,25 @@ const fetchParticipantInfo = async (source, destination, cache) => { return { payer, payee } } +const getParticipantEndpoint = async ({ fspId, db, loggerFn, endpointType, proxyClient = null }) => { + let endpoint = await db.getParticipantEndpoint(fspId, endpointType) + + loggerFn(`Resolved participant '${fspId}' ${endpointType} to: '${endpoint}'`) + + // if endpoint is not found in db, check the proxy cache (this might be an inter-scheme request) + if (!endpoint && proxyClient) { + if (!proxyClient.isConnected) await proxyClient.connect() + const proxyId = await proxyClient.lookupProxyByDfspId(fspId) + if (proxyId) { + endpoint = await db.getParticipantEndpoint(proxyId, endpointType) + } + + loggerFn(`Proxy participant ${endpointType} endpoint resolved: fspid: ${fspId}, proxyId: ${proxyId}, proxy endpoint: ${endpoint}`) + } + + return endpoint +} + const auditSpan = async (request) => { const { span, headers, payload, method } = request span.setTags(getSpanTags(request, 'quote', method)) @@ -259,5 +278,6 @@ module.exports = { calculateRequestHash, removeEmptyKeys, rethrowFspiopError, - fetchParticipantInfo + fetchParticipantInfo, + getParticipantEndpoint } diff --git a/src/model/bulkQuotes.js b/src/model/bulkQuotes.js index 68154598..5bb0cc60 100644 --- a/src/model/bulkQuotes.js +++ b/src/model/bulkQuotes.js @@ -42,7 +42,7 @@ const JwsSigner = require('@mojaloop/sdk-standard-components').Jws.signer const Config = require('../lib/config') const { httpRequest } = require('../lib/http') -const { getStackOrInspect, generateRequestHeadersForJWS, generateRequestHeaders } = require('../lib/util') +const { getStackOrInspect, generateRequestHeadersForJWS, generateRequestHeaders, getParticipantEndpoint } = require('../lib/util') const LOCAL_ENUM = require('../lib/enum') delete axios.defaults.headers.common.Accept @@ -121,7 +121,7 @@ class BulkQuotesModel { // lookup payee dfsp callback endpoint // TODO: for MVP we assume initiator is always payer dfsp! this may not always be the // case if a xfer is requested by payee - endpoint = await this.db.getParticipantEndpoint(fspiopDest, ENUM.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_BULK_QUOTES) + endpoint = await this._getParticipantEndpoint(fspiopDest) this.writeLog(`Resolved FSPIOP_CALLBACK_URL_BULK_QUOTES endpoint for bulkQuote ${bulkQuoteId} to: ${util.inspect(endpoint)}`) @@ -203,7 +203,7 @@ class BulkQuotesModel { try { // lookup payer dfsp callback endpoint - endpoint = await this.db.getParticipantEndpoint(fspiopDest, 'FSPIOP_CALLBACK_URL_BULK_QUOTES') + endpoint = await this._getParticipantEndpoint(fspiopDest) this.writeLog(`Resolved PAYER party FSPIOP_CALLBACK_URL_BULK_QUOTES endpoint for bulk quote ${bulkQuoteId} to: ${util.inspect(endpoint)}`) if (!endpoint) { @@ -279,7 +279,7 @@ class BulkQuotesModel { // todo: for MVP we assume initiator is always payer dfsp! this may not always be the case if a xfer is requested by payee const fspiopSource = headers[ENUM.Http.Headers.FSPIOP.SOURCE] const fspiopDest = headers[ENUM.Http.Headers.FSPIOP.DESTINATION] - endpoint = await this.db.getParticipantEndpoint(fspiopDest, 'FSPIOP_CALLBACK_URL_BULK_QUOTES') + endpoint = await this._getParticipantEndpoint(fspiopDest) this.writeLog(`Resolved ${fspiopDest} FSPIOP_CALLBACK_URL_BULK_QUOTES endpoint for bulk quote GET ${bulkQuoteId} to: ${util.inspect(endpoint)}`) @@ -376,7 +376,7 @@ class BulkQuotesModel { const fspiopDest = headers[ENUM.Http.Headers.FSPIOP.DESTINATION] try { // look up the callback base url - const endpoint = await this.db.getParticipantEndpoint(fspiopSource, ENUM.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_BULK_QUOTES) + const endpoint = await this._getParticipantEndpoint(fspiopSource) this.writeLog(`Resolved participant '${fspiopSource}' '${ENUM.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_BULK_QUOTES}' to: '${endpoint}'`) @@ -485,6 +485,10 @@ class BulkQuotesModel { } } + async _getParticipantEndpoint (fspId, endpointType = ENUM.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_BULK_QUOTES) { + return getParticipantEndpoint({ fspId, db: this.db, loggerFn: this.writeLog.bind(this), endpointType, proxyClient: this.proxyClient }) + } + /** * Writes a formatted message to the console * diff --git a/src/model/fxQuotes.js b/src/model/fxQuotes.js index 675ca091..826cfeef 100644 --- a/src/model/fxQuotes.js +++ b/src/model/fxQuotes.js @@ -28,7 +28,7 @@ const JwsSigner = require('@mojaloop/sdk-standard-components').Jws.signer const Config = require('../lib/config') const { httpRequest } = require('../lib/http') -const { getStackOrInspect, generateRequestHeadersForJWS, generateRequestHeaders } = require('../lib/util') +const { getStackOrInspect, generateRequestHeadersForJWS, generateRequestHeaders, getParticipantEndpoint } = require('../lib/util') const LOCAL_ENUM = require('../lib/enum') delete axios.defaults.headers.common.Accept @@ -39,6 +39,7 @@ class FxQuotesModel { this.config = config this.db = config.db this.requestId = config.requestId + this.proxyClient = config.proxyClient } /** @@ -92,7 +93,7 @@ class FxQuotesModel { try { // lookup the fxp callback endpoint - endpoint = await this.db.getParticipantEndpoint(fspiopDest, ENUM.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_FX_QUOTES) + endpoint = await this._getParticipantEndpoint(fspiopDest) this.writeLog(`Resolved FSPIOP_CALLBACK_URL_FX_QUOTES endpoint for fxQuote ${conversionRequestId} to: ${util.inspect(endpoint)}`) @@ -164,7 +165,7 @@ class FxQuotesModel { const fspiopDest = headers[ENUM.Http.Headers.FSPIOP.DESTINATION] try { - endpoint = await this.db.getParticipantEndpoint(fspiopDest, 'FSPIOP_CALLBACK_URL_FX_QUOTES') + endpoint = await this._getParticipantEndpoint(fspiopDest) this.writeLog(`Resolved PAYER party FSPIOP_CALLBACK_URL_FX_QUOTES endpoint for fx quote ${conversionRequestId} to: ${util.inspect(endpoint)}`) if (!endpoint) { @@ -233,7 +234,7 @@ class FxQuotesModel { // lookup fxp callback endpoint const fspiopSource = headers[ENUM.Http.Headers.FSPIOP.SOURCE] const fspiopDest = headers[ENUM.Http.Headers.FSPIOP.DESTINATION] - endpoint = await this.db.getParticipantEndpoint(fspiopDest, 'FSPIOP_CALLBACK_URL_FX_QUOTES') + endpoint = await this._getParticipantEndpoint(fspiopDest) this.writeLog(`Resolved ${fspiopDest} FSPIOP_CALLBACK_URL_FX_QUOTES endpoint for fx quote GET ${conversionRequestId} to: ${util.inspect(endpoint)}`) @@ -319,7 +320,7 @@ class FxQuotesModel { const envConfig = new Config() const fspiopDest = headers[ENUM.Http.Headers.FSPIOP.DESTINATION] try { - const endpoint = await this.db.getParticipantEndpoint(fspiopSource, ENUM.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_FX_QUOTES) + const endpoint = await this._getParticipantEndpoint(fspiopSource) this.writeLog(`Resolved participant '${fspiopSource}' '${ENUM.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_FX_QUOTES}' to: '${endpoint}'`) @@ -421,6 +422,10 @@ class FxQuotesModel { } } + async _getParticipantEndpoint (fspId, endpointType = ENUM.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_FX_QUOTES) { + return getParticipantEndpoint({ fspId, db: this.db, loggerFn: this.writeLog.bind(this), endpointType, proxyClient: this.proxyClient }) + } + /** * Writes a formatted message to the console * diff --git a/src/model/quotes.js b/src/model/quotes.js index 8265e144..2e1ce2a6 100644 --- a/src/model/quotes.js +++ b/src/model/quotes.js @@ -48,7 +48,7 @@ const Metrics = require('@mojaloop/central-services-metrics') const Config = require('../lib/config') const { httpRequest } = require('../lib/http') -const { getStackOrInspect, generateRequestHeadersForJWS, generateRequestHeaders, calculateRequestHash, fetchParticipantInfo } = require('../lib/util') +const { getStackOrInspect, generateRequestHeadersForJWS, generateRequestHeaders, calculateRequestHash, fetchParticipantInfo, getParticipantEndpoint } = require('../lib/util') const LOCAL_ENUM = require('../lib/enum') const rules = require('../../config/rules.json') const RulesEngine = require('./rules.js') @@ -66,6 +66,7 @@ class QuotesModel { this.config = config this.db = config.db this.requestId = config.requestId + this.proxyClient = config.proxyClient } async executeRules (headers, quoteRequest, payer, payee) { @@ -176,7 +177,10 @@ class QuotesModel { )) } else { // If it is not passed in, then we validate payee against the `amount` currency. - await this.db.getParticipant(fspiopDestination, LOCAL_ENUM.PAYEE_DFSP, quoteRequest.amount.currency, ENUM.Accounts.LedgerAccountType.POSITION) + // if the payee dfsp has a proxy cache entry, we do not validate the dfsp here + if (!(await this.proxyClient?.lookupProxyByDfspId(fspiopDestination))) { + await this.db.getParticipant(fspiopDestination, LOCAL_ENUM.PAYEE_DFSP, quoteRequest.amount.currency, ENUM.Accounts.LedgerAccountType.POSITION) + } } histTimer({ success: true, queryName: 'quote_validateQuoteRequest' }) @@ -185,19 +189,17 @@ class QuotesModel { if (envConfig.simpleRoutingMode) { // Lets make sure the optional fspId exists in the payer's partyIdInfo before we validate it if ( - quoteRequest.payer && - quoteRequest.payer.partyIdInfo && - quoteRequest.payer.partyIdInfo.fspId && + quoteRequest.payer?.partyIdInfo?.fspId && quoteRequest.payer.partyIdInfo.fspId !== fspiopSource ) { await this.db.getParticipant(quoteRequest.payer.partyIdInfo.fspId, LOCAL_ENUM.PAYER_DFSP, quoteRequest.amount.currency, ENUM.Accounts.LedgerAccountType.POSITION) } // Lets make sure the optional fspId exists in the payee's partyIdInfo before we validate it if ( - quoteRequest.payee && - quoteRequest.payee.partyIdInfo && - quoteRequest.payee.partyIdInfo.fspId && - quoteRequest.payee.partyIdInfo.fspId !== fspiopDestination + quoteRequest.payee?.partyIdInfo?.fspId && + quoteRequest.payee.partyIdInfo.fspId !== fspiopDestination && + // if the payee dfsp has a proxy cache entry, we do not validate the dfsp here + !(await this.proxyClient?.lookupProxyByDfspId(quoteRequest.payee.partyIdInfo.fspId)) ) { await this.db.getParticipant(quoteRequest.payee.partyIdInfo.fspId, LOCAL_ENUM.PAYEE_DFSP, quoteRequest.amount.currency, ENUM.Accounts.LedgerAccountType.POSITION) } @@ -454,10 +456,11 @@ class QuotesModel { // lookup payee dfsp callback endpoint // TODO: for MVP we assume initiator is always payer dfsp! this may not always be the // case if a xfer is requested by payee - endpoint = await this.db.getParticipantEndpoint(fspiopDest, 'FSPIOP_CALLBACK_URL_QUOTES') + endpoint = await this._getParticipantEndpoint(fspiopDest) this.writeLog(`Resolved PAYEE party FSPIOP_CALLBACK_URL_QUOTES endpoint for quote ${quoteId} to: ${endpoint}, destination: ${fspiopDest}`) + // if the endpoint is also not found in the proxy cache, throw an error if (!endpoint) { // internal-error // we didnt get an endpoint for the payee dfsp! @@ -717,10 +720,17 @@ class QuotesModel { } // lookup payer dfsp callback endpoint - endpoint = await this.db.getParticipantEndpoint(fspiopDest, 'FSPIOP_CALLBACK_URL_QUOTES') + endpoint = await this._getParticipantEndpoint(fspiopDest) this.writeLog(`Resolved PAYER party FSPIOP_CALLBACK_URL_QUOTES endpoint for quote ${quoteId} to: ${util.inspect(endpoint)}`) + // if endpoint is not found, check the proxy cache (this might be an inter-scheme request) + if (!endpoint && this.proxyClient) { + const proxyResult = await this.getProxyEndpoint({ dfspId: fspiopDest, endpointType: ENUM.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_QUOTES }) + endpoint = proxyResult.endpoint + this.writeLog(`Proxy participant FSPIOP_CALLBACK_URL_QUOTES endpoint resolved for quote ${quoteId}: fspiop-destination: ${fspiopDest}, proxyId: ${proxyResult.proxyId}, proxy endpoint: ${proxyResult.endpoint}`) + } + if (!endpoint) { // we didnt get an endpoint for the payee dfsp! // make an error callback to the initiator @@ -919,7 +929,7 @@ class QuotesModel { // todo: for MVP we assume initiator is always payer dfsp! this may not always be the case if a xfer is requested by payee const fspiopSource = headers[ENUM.Http.Headers.FSPIOP.SOURCE] const fspiopDest = headers[ENUM.Http.Headers.FSPIOP.DESTINATION] - endpoint = await this.db.getParticipantEndpoint(fspiopDest, 'FSPIOP_CALLBACK_URL_QUOTES') + endpoint = await this._getParticipantEndpoint(fspiopDest) this.writeLog(`Resolved ${fspiopDest} FSPIOP_CALLBACK_URL_QUOTES endpoint for quote GET ${quoteId} to: ${util.inspect(endpoint)}`) @@ -1003,7 +1013,7 @@ class QuotesModel { const fspiopDest = headers[ENUM.Http.Headers.FSPIOP.DESTINATION] try { // look up the callback base url - const endpoint = await this.db.getParticipantEndpoint(fspiopSource, 'FSPIOP_CALLBACK_URL_QUOTES') + const endpoint = await this._getParticipantEndpoint(fspiopSource) this.writeLog(`Resolved participant '${fspiopSource}' FSPIOP_CALLBACK_URL_QUOTES to: '${endpoint}'`) @@ -1223,6 +1233,10 @@ class QuotesModel { } } + async _getParticipantEndpoint (fspId, endpointType = ENUM.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_QUOTES) { + return getParticipantEndpoint({ fspId, db: this.db, loggerFn: this.writeLog.bind(this), endpointType, proxyClient: this.proxyClient }) + } + /** * Writes a formatted message to the console * From 345f50a41effb094697f2d30ced0e1e9eedd817d Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Tue, 2 Jul 2024 17:39:55 +0100 Subject: [PATCH 04/51] feat: update model factory methods --- src/handlers/init.js | 11 ++++++++++- src/model/index.js | 8 ++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/handlers/init.js b/src/handlers/init.js index bb60799d..700f6ad1 100644 --- a/src/handlers/init.js +++ b/src/handlers/init.js @@ -1,6 +1,7 @@ const { Cache } = require('memory-cache') const { Tracer } = require('@mojaloop/event-sdk') const Logger = require('@mojaloop/central-services-logger') +const { createProxyCache } = require('@mojaloop/inter-scheme-proxy-cache-lib') const Config = require('../lib/config') const Database = require('../data/cachedDatabase') @@ -10,6 +11,7 @@ const createConsumers = require('./createConsumers') const { createMonitoringServer } = require('./monitoringServer') let db +let proxyClient let consumersMap let monitoringServer @@ -21,7 +23,14 @@ const startFn = async (handlerList) => { const isDbOk = await db.isConnected() if (!isDbOk) throw new Error('DB is not connected') - const { quotesModelFactory, bulkQuotesModelFactory, fxQuotesModelFactory } = modelFactory(db) + // initialize proxy client + if (config.proxyCache.enabled) { + proxyClient = createProxyCache(config.proxyCache) + const isProxyOk = await proxyClient.connect() + if (!isProxyOk) throw new Error('Proxy cache is not connected') + } + + const { quotesModelFactory, bulkQuotesModelFactory, fxQuotesModelFactory } = modelFactory(db, proxyClient) const handler = new QuotingHandler({ quotesModelFactory, diff --git a/src/model/index.js b/src/model/index.js index 1d36ff6f..458afd71 100644 --- a/src/model/index.js +++ b/src/model/index.js @@ -3,8 +3,8 @@ const BulkQuotesModel = require('./bulkQuotes') const FxQuotesModel = require('./fxQuotes') // todo: get models only through these factories -module.exports = db => ({ - quotesModelFactory: (requestId) => new QuotesModel({ db, requestId }), - bulkQuotesModelFactory: (requestId) => new BulkQuotesModel({ db, requestId }), - fxQuotesModelFactory: (requestId) => new FxQuotesModel({ db, requestId }) +module.exports = (db, proxyClient) => ({ + quotesModelFactory: (requestId) => new QuotesModel({ db, requestId, proxyClient }), + bulkQuotesModelFactory: (requestId) => new BulkQuotesModel({ db, requestId, proxyClient }), + fxQuotesModelFactory: (requestId) => new FxQuotesModel({ db, requestId, proxyClient }) }) From 7da665e93dd678997b65e287a5426d36707e382f Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Tue, 2 Jul 2024 19:08:40 +0100 Subject: [PATCH 05/51] feat: update proxy cache connection routine --- config/default.json | 4 +++- docker/quoting-service/default.json | 10 ++++++++++ src/handlers/init.js | 15 ++++++++++++--- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/config/default.json b/config/default.json index 3722177b..59c00e53 100644 --- a/config/default.json +++ b/config/default.json @@ -78,8 +78,10 @@ "PARTICIPANT_DATA_EXPIRES_IN_MS": 60000 }, "PROXY_CACHE": { - "enabled": false, + "enabled": true, "type": "redis", + "timeout": 5000, + "retryInterval": 200, "proxyConfig": { "host": "localhost", "port": 6379 diff --git a/docker/quoting-service/default.json b/docker/quoting-service/default.json index f83f17d6..296adeaa 100644 --- a/docker/quoting-service/default.json +++ b/docker/quoting-service/default.json @@ -77,6 +77,16 @@ "ENUM_DATA_EXPIRES_IN_MS": 4170000, "PARTICIPANT_DATA_EXPIRES_IN_MS": 60000 }, + "PROXY_CACHE": { + "enabled": false, + "type": "redis", + "timeout": 5000, + "retryInterval": 200, + "proxyConfig": { + "host": "redis", + "port": 6379 + } + }, "KAFKA": { "CONSUMER": { "QUOTE": { diff --git a/src/handlers/init.js b/src/handlers/init.js index 700f6ad1..861ed59f 100644 --- a/src/handlers/init.js +++ b/src/handlers/init.js @@ -25,9 +25,18 @@ const startFn = async (handlerList) => { // initialize proxy client if (config.proxyCache.enabled) { - proxyClient = createProxyCache(config.proxyCache) - const isProxyOk = await proxyClient.connect() - if (!isProxyOk) throw new Error('Proxy cache is not connected') + proxyClient = createProxyCache(config.proxyCache.type, config.proxyCache.proxyConfig) + + const retryInterval = Number(config.proxyCache.retryInterval) + + const timer = setTimeout(() => { + Logger.error('Unable to connect to proxy cache. Exiting...') + process.exit(1) + }, Number(config.proxyCache.timeout)) + + while (!proxyClient.isConnected) await new Promise(resolve => setTimeout(resolve, retryInterval)) + + clearTimeout(timer) } const { quotesModelFactory, bulkQuotesModelFactory, fxQuotesModelFactory } = modelFactory(db, proxyClient) From c3aa2c971183fed1f9cd4e859bf6231671e04362 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Tue, 2 Jul 2024 19:22:25 +0100 Subject: [PATCH 06/51] feat: refactor proxy client init --- src/handlers/init.js | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/handlers/init.js b/src/handlers/init.js index 861ed59f..5836f9f8 100644 --- a/src/handlers/init.js +++ b/src/handlers/init.js @@ -1,7 +1,6 @@ const { Cache } = require('memory-cache') const { Tracer } = require('@mojaloop/event-sdk') const Logger = require('@mojaloop/central-services-logger') -const { createProxyCache } = require('@mojaloop/inter-scheme-proxy-cache-lib') const Config = require('../lib/config') const Database = require('../data/cachedDatabase') @@ -9,6 +8,7 @@ const modelFactory = require('../model') const QuotingHandler = require('./QuotingHandler') const createConsumers = require('./createConsumers') const { createMonitoringServer } = require('./monitoringServer') +const { createProxyClient } = require('../lib/proxy') let db let proxyClient @@ -23,20 +23,8 @@ const startFn = async (handlerList) => { const isDbOk = await db.isConnected() if (!isDbOk) throw new Error('DB is not connected') - // initialize proxy client if (config.proxyCache.enabled) { - proxyClient = createProxyCache(config.proxyCache.type, config.proxyCache.proxyConfig) - - const retryInterval = Number(config.proxyCache.retryInterval) - - const timer = setTimeout(() => { - Logger.error('Unable to connect to proxy cache. Exiting...') - process.exit(1) - }, Number(config.proxyCache.timeout)) - - while (!proxyClient.isConnected) await new Promise(resolve => setTimeout(resolve, retryInterval)) - - clearTimeout(timer) + proxyClient = await createProxyClient(config.proxyCache) } const { quotesModelFactory, bulkQuotesModelFactory, fxQuotesModelFactory } = modelFactory(db, proxyClient) From cb087d8dbce4f31a707831dbaf44c8513fa9f4a0 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Tue, 2 Jul 2024 19:32:38 +0100 Subject: [PATCH 07/51] feat: update proxy init --- src/lib/proxy.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/lib/proxy.js diff --git a/src/lib/proxy.js b/src/lib/proxy.js new file mode 100644 index 00000000..46a073c1 --- /dev/null +++ b/src/lib/proxy.js @@ -0,0 +1,24 @@ +const process = require('node:process') +const Logger = require('@mojaloop/central-services-logger') + +const { createProxyCache } = require('@mojaloop/inter-scheme-proxy-cache-lib') + +const createProxyClient = async (proxyCacheConfig) => { + const retryInterval = Number(proxyCacheConfig.retryInterval) + const proxyClient = createProxyCache(proxyCacheConfig.type, proxyCacheConfig.proxyConfig) + + const timer = setTimeout(() => { + Logger.isErrorEnabled && Logger.error('Unable to connect to proxy cache. Exiting...', { proxyCacheConfig }) + process.exit(1) + }, Number(proxyCacheConfig.timeout)) + + while (!proxyClient.isConnected) { + await new Promise(resolve => setTimeout(resolve, retryInterval)) + } + + clearTimeout(timer) + + return proxyClient +} + +module.exports = { createProxyClient } From c20643ff3b75ac10b9b051f6ee7ce7113b746121 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Tue, 2 Jul 2024 20:40:23 +0100 Subject: [PATCH 08/51] test: update unit tests --- package-lock.json | 79 ++++++++++++------------------ package.json | 4 ++ src/handlers/init.js | 2 +- src/lib/proxy.js | 6 +-- test/unit/model/bulkQuotes.test.js | 40 +++++++-------- test/unit/model/quotes.test.js | 64 ++++++++++-------------- 6 files changed, 84 insertions(+), 111 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9b300abd..cf28eaa7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "ajv-keywords": "5.1.0", "axios": "1.7.2", "blipp": "4.0.2", + "cd": "^0.3.3", "commander": "12.1.0", "event-stream": "4.0.1", "fast-safe-stringify": "^2.1.1", @@ -4076,6 +4077,7 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, "engines": { "node": ">=6" } @@ -4117,6 +4119,14 @@ } ] }, + "node_modules/cd": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/cd/-/cd-0.3.3.tgz", + "integrity": "sha512-X2y0Ssu48ucdkrNgCdg6k3EZWjWVy/dsEywUUTeZEIW31f3bQfq65Svm+TzU1Hz+qqhdmyCdjGhUvRsSKHl/mw==", + "engines": { + "node": "*" + } + }, "node_modules/center-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", @@ -4989,6 +4999,14 @@ "node": ">=6" } }, + "node_modules/convict/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, "node_modules/cookie": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", @@ -10972,6 +10990,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/meow/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", @@ -12388,19 +12415,6 @@ "node": ">=8" } }, - "node_modules/nyc/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/oas-kit-common": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", @@ -14576,19 +14590,6 @@ "node": ">=8" } }, - "node_modules/replace/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -17476,15 +17477,6 @@ "yargs-parser": "^11.1.1" } }, - "node_modules/widdershins/node_modules/yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -17832,20 +17824,11 @@ } }, "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "engines": { - "node": ">=10" - } - }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "node": ">=12" } }, "node_modules/yocto-queue": { diff --git a/package.json b/package.json index 5a325cf9..c08b314f 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,9 @@ "yargs-parser": "13.1.2", "markdown-it": "12.3.2" }, + "yargs": { + "yargs-parser": "^21.1.1" + }, "jsonwebtoken": "9.0.0", "jsonpointer": "5.0.0" }, @@ -116,6 +119,7 @@ "ajv-keywords": "5.1.0", "axios": "1.7.2", "blipp": "4.0.2", + "cd": "^0.3.3", "commander": "12.1.0", "event-stream": "4.0.1", "fast-safe-stringify": "^2.1.1", diff --git a/src/handlers/init.js b/src/handlers/init.js index 5836f9f8..cd2f616e 100644 --- a/src/handlers/init.js +++ b/src/handlers/init.js @@ -24,7 +24,7 @@ const startFn = async (handlerList) => { if (!isDbOk) throw new Error('DB is not connected') if (config.proxyCache.enabled) { - proxyClient = await createProxyClient(config.proxyCache) + proxyClient = await createProxyClient({ proxyCacheConfig: config.proxyCache, logger: Logger }) } const { quotesModelFactory, bulkQuotesModelFactory, fxQuotesModelFactory } = modelFactory(db, proxyClient) diff --git a/src/lib/proxy.js b/src/lib/proxy.js index 46a073c1..a1623215 100644 --- a/src/lib/proxy.js +++ b/src/lib/proxy.js @@ -1,14 +1,12 @@ const process = require('node:process') -const Logger = require('@mojaloop/central-services-logger') - const { createProxyCache } = require('@mojaloop/inter-scheme-proxy-cache-lib') -const createProxyClient = async (proxyCacheConfig) => { +const createProxyClient = async ({ proxyCacheConfig, logger }) => { const retryInterval = Number(proxyCacheConfig.retryInterval) const proxyClient = createProxyCache(proxyCacheConfig.type, proxyCacheConfig.proxyConfig) const timer = setTimeout(() => { - Logger.isErrorEnabled && Logger.error('Unable to connect to proxy cache. Exiting...', { proxyCacheConfig }) + logger.isErrorEnabled && logger.error('Unable to connect to proxy cache. Exiting...', { proxyCacheConfig }) process.exit(1) }, Number(proxyCacheConfig.timeout)) diff --git a/test/unit/model/bulkQuotes.test.js b/test/unit/model/bulkQuotes.test.js index 548e78bd..723ef149 100644 --- a/test/unit/model/bulkQuotes.test.js +++ b/test/unit/model/bulkQuotes.test.js @@ -422,11 +422,11 @@ describe('BulkQuotesModel', () => { it('should get http status code 202 Accepted in simple routing mode', async () => { expect.assertions(1) mockConfig.simpleRoutingMode = true - bulkQuotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + bulkQuotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) await bulkQuotesModel.forwardBulkQuoteRequest(mockData.headers, mockData.bulkQuotePostRequest.bulkQuoteId, mockData.bulkQuotePostRequest, mockChildSpan) - expect(bulkQuotesModel.db.getParticipantEndpoint).toBeCalled() + expect(bulkQuotesModel._getParticipantEndpoint).toBeCalled() }) }) @@ -485,19 +485,19 @@ describe('BulkQuotesModel', () => { it('should get http status code 200 OK in simple routing mode', async () => { expect.assertions(2) - bulkQuotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + bulkQuotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) await expect(bulkQuotesModel.forwardBulkQuoteUpdate(mockData.headers, mockData.bulkQuoteId, mockData.bulkQuoteUpdate, mockChildSpan)) .resolves .toBe(undefined) - expect(bulkQuotesModel.db.getParticipantEndpoint).toBeCalled() + expect(bulkQuotesModel._getParticipantEndpoint).toBeCalled() }) it('should throw when participant endpoint is not found', async () => { expect.assertions(1) const endpoint = undefined - bulkQuotesModel.db.getParticipantEndpoint.mockReturnValueOnce(endpoint) + bulkQuotesModel._getParticipantEndpoint.mockReturnValueOnce(endpoint) bulkQuotesModel.sendErrorCallback = jest.fn((_, fspiopError) => { throw fspiopError }) await expect(bulkQuotesModel.forwardBulkQuoteUpdate(mockData.headers, mockData.bulkQuoteId, mockData.bulkQuoteUpdate, mockChildSpan)) @@ -507,7 +507,7 @@ describe('BulkQuotesModel', () => { it('should not use spans when undefined and should throw when participant endpoint is invalid', async () => { expect.assertions(3) - bulkQuotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.invalid) + bulkQuotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.invalid) Http.httpRequest.mockImplementationOnce(() => { throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_COMMUNICATION_ERROR) }) await expect(bulkQuotesModel.forwardBulkQuoteUpdate(mockData.headers, mockData.bulkQuoteId, mockData.bulkQuoteUpdate)) @@ -520,7 +520,7 @@ describe('BulkQuotesModel', () => { it('should throw when participant endpoint returns invalid response', async () => { expect.assertions(3) - bulkQuotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.invalidResponse) + bulkQuotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.invalidResponse) Http.httpRequest.mockImplementationOnce(() => { throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_COMMUNICATION_ERROR) }) await expect(bulkQuotesModel.forwardBulkQuoteUpdate(mockData.headers, mockData.bulkQuoteId, mockData.bulkQuoteUpdate)) @@ -535,7 +535,7 @@ describe('BulkQuotesModel', () => { const customErrorNoStack = new Error('Custom error') delete customErrorNoStack.stack - bulkQuotesModel.db.getParticipantEndpoint.mockRejectedValueOnce(customErrorNoStack) + bulkQuotesModel._getParticipantEndpoint.mockRejectedValueOnce(customErrorNoStack) await expect(bulkQuotesModel.forwardBulkQuoteUpdate(mockData.headers, mockData.bulkQuoteId, mockData.bulkQuoteUpdate)) .rejects @@ -600,7 +600,7 @@ describe('BulkQuotesModel', () => { it('fails to forward if the database has no endpoint for the dfsp', async () => { // Arrange expect.assertions(1) - bulkQuotesModel.db.getParticipantEndpoint.mockImplementation(() => null) + bulkQuotesModel._getParticipantEndpoint.mockImplementation(() => null) // Act const action = async () => bulkQuotesModel.forwardBulkQuoteGet(mockData.headers, mockData.bulkQuoteId, mockSpan) @@ -612,7 +612,7 @@ describe('BulkQuotesModel', () => { it('forwards the request to the payee dfsp without a span', async () => { // Arrange // expect.assertions(2) - bulkQuotesModel.db.getParticipantEndpoint.mockImplementation(() => 'http://localhost:3333') + bulkQuotesModel._getParticipantEndpoint.mockImplementation(() => 'http://localhost:3333') const expectedOptions = { headers: {}, method: 'GET', @@ -632,7 +632,7 @@ describe('BulkQuotesModel', () => { it('forwards the request to the payee dfsp', async () => { // Arrange expect.assertions(4) - bulkQuotesModel.db.getParticipantEndpoint.mockImplementation(() => 'http://localhost:3333') + bulkQuotesModel._getParticipantEndpoint.mockImplementation(() => 'http://localhost:3333') mockSpan.injectContextToHttpRequest = jest.fn().mockImplementation(() => ({ headers: { spanHeaders: '12345' @@ -658,7 +658,7 @@ describe('BulkQuotesModel', () => { it('handles a http error', async () => { // Arrange expect.assertions(1) - bulkQuotesModel.db.getParticipantEndpoint.mockImplementation(() => 'http://localhost:3333') + bulkQuotesModel._getParticipantEndpoint.mockImplementation(() => 'http://localhost:3333') Http.httpRequest.mockImplementationOnce(() => { throw new Error('Test HTTP Error') }) // Act @@ -776,7 +776,7 @@ describe('BulkQuotesModel', () => { it('sends the error callback without a span', async () => { // Arrange expect.assertions(1) - bulkQuotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + bulkQuotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) Util.generateRequestHeaders.mockReturnValueOnce({}) const error = new Error('Test Error') const fspiopError = ErrorHandler.ReformatFSPIOPError(error) @@ -797,7 +797,7 @@ describe('BulkQuotesModel', () => { it('sends the error callback and handles the span', async () => { // Arrange expect.assertions(3) - bulkQuotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + bulkQuotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) Util.generateRequestHeaders.mockReturnValueOnce({}) const error = new Error('Test Error') const fspiopError = ErrorHandler.ReformatFSPIOPError(error) @@ -832,7 +832,7 @@ describe('BulkQuotesModel', () => { // Arrange const jwsSignSpy = jest.spyOn(JwsSigner.prototype, 'getSignature') // expect.assertions(6) - bulkQuotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + bulkQuotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) Util.generateRequestHeaders.mockReturnValueOnce({}) const error = new Error('Test Error') const fspiopError = ErrorHandler.ReformatFSPIOPError(error) @@ -865,7 +865,7 @@ describe('BulkQuotesModel', () => { // Arrange const jwsSignSpy = jest.spyOn(JwsSigner.prototype, 'getSignature') expect.assertions(5) - bulkQuotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + bulkQuotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) Util.generateRequestHeaders.mockReturnValueOnce({}) const error = new Error('Test Error') const fspiopError = ErrorHandler.ReformatFSPIOPError(error) @@ -906,7 +906,7 @@ describe('BulkQuotesModel', () => { // Arrange const jwsSignSpy = jest.spyOn(JwsSigner.prototype, 'getSignature') expect.assertions(5) - bulkQuotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + bulkQuotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) Util.generateRequestHeaders.mockReturnValueOnce({}) const error = new Error('Test Error') const fspiopError = ErrorHandler.ReformatFSPIOPError(error) @@ -946,7 +946,7 @@ describe('BulkQuotesModel', () => { it('handles when the endpoint could not be found', async () => { // Arrange expect.assertions(2) - bulkQuotesModel.db.getParticipantEndpoint.mockReturnValueOnce(undefined) + bulkQuotesModel._getParticipantEndpoint.mockReturnValueOnce(undefined) Util.generateRequestHeaders.mockReturnValueOnce({}) const error = new Error('Test Error') const fspiopError = ErrorHandler.ReformatFSPIOPError(error) @@ -962,7 +962,7 @@ describe('BulkQuotesModel', () => { it('handles a http exception', async () => { // Arrange expect.assertions(2) - bulkQuotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + bulkQuotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) Util.generateRequestHeaders.mockReturnValueOnce({}) const error = new Error('Test Error') const fspiopError = ErrorHandler.ReformatFSPIOPError(error) @@ -979,7 +979,7 @@ describe('BulkQuotesModel', () => { it('handles a http bad status code', async () => { // Arrange expect.assertions(2) - bulkQuotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + bulkQuotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) Util.generateRequestHeaders.mockReturnValueOnce({}) const error = new Error('Test Error') const fspiopError = ErrorHandler.ReformatFSPIOPError(error) diff --git a/test/unit/model/quotes.test.js b/test/unit/model/quotes.test.js index 8d1ab3ed..5f2d05e9 100644 --- a/test/unit/model/quotes.test.js +++ b/test/unit/model/quotes.test.js @@ -1244,21 +1244,21 @@ describe('QuotesModel', () => { it('should get http status code 202 Accepted in simple routing mode', async () => { expect.assertions(1) mockConfig.simpleRoutingMode = true - quotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + quotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) await quotesModel.forwardQuoteRequest(mockData.headers, mockData.quoteRequest.quoteId, mockData.quoteRequest, mockChildSpan) - expect(quotesModel.db.getParticipantEndpoint).toBeCalled() + expect(quotesModel._getParticipantEndpoint).toBeCalled() }) it('should get http status code 202 Accepted in switch mode', async () => { expect.assertions(1) mockConfig.simpleRoutingMode = false - quotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + quotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) await quotesModel.forwardQuoteRequest(mockData.headers, mockData.quoteRequest.quoteId, mockData.quoteRequest, mockChildSpan) - expect(quotesModel.db.getParticipantEndpoint).toBeCalled() + expect(quotesModel._getParticipantEndpoint).toBeCalled() }) it('should throw when quoteRequest is undefined', async () => { expect.assertions(1) @@ -1270,7 +1270,7 @@ describe('QuotesModel', () => { it('should not use spans when undefined and should throw when participant endpoint is invalid', async () => { expect.assertions(3) mockConfig.simpleRoutingMode = false - quotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.invalid) + quotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.invalid) Http.httpRequest.mockImplementationOnce(() => { throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_COMMUNICATION_ERROR) }) await expect(quotesModel.forwardQuoteRequest(mockData.headers, mockData.quoteRequest.quoteId, mockData.quoteRequest)) @@ -1283,7 +1283,7 @@ describe('QuotesModel', () => { it('should throw when participant endpoint returns invalid response', async () => { expect.assertions(3) mockConfig.simpleRoutingMode = false - quotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.invalidResponse) + quotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.invalidResponse) Http.httpRequest.mockImplementationOnce(() => { throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_COMMUNICATION_ERROR) }) await expect(quotesModel.forwardQuoteRequest(mockData.headers, mockData.quoteRequest.quoteId, mockData.quoteRequest)) @@ -1299,7 +1299,7 @@ describe('QuotesModel', () => { mockConfig.simpleRoutingMode = false const customErrorNoStack = new Error('Custom error') delete customErrorNoStack.stack - quotesModel.db.getParticipantEndpoint.mockRejectedValueOnce(customErrorNoStack) + quotesModel._getParticipantEndpoint.mockRejectedValueOnce(customErrorNoStack) await expect(quotesModel.forwardQuoteRequest(mockData.headers, mockData.quoteRequest.quoteId, mockData.quoteRequest)) .rejects @@ -1618,25 +1618,25 @@ describe('QuotesModel', () => { it('should get http status code 200 OK in simple routing mode', async () => { expect.assertions(2) mockConfig.simpleRoutingMode = true - quotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + quotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) await expect(quotesModel.forwardQuoteUpdate(mockData.headers, mockData.quoteId, mockData.quoteUpdate, mockChildSpan)) .resolves .toBe(undefined) - expect(quotesModel.db.getParticipantEndpoint).toBeCalled() + expect(quotesModel._getParticipantEndpoint).toBeCalled() }) it('should get http status code 200 OK in switch mode', async () => { expect.assertions(2) mockConfig.simpleRoutingMode = false - quotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + quotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) await expect(quotesModel.forwardQuoteUpdate(mockData.headers, mockData.quoteId, mockData.quoteUpdate, mockChildSpan)) .resolves .toBe(undefined) - expect(quotesModel.db.getParticipantEndpoint).toBeCalled() + expect(quotesModel._getParticipantEndpoint).toBeCalled() }) it('should throw when quoteUpdate is undefined', async () => { expect.assertions(1) @@ -1649,7 +1649,7 @@ describe('QuotesModel', () => { expect.assertions(3) mockConfig.simpleRoutingMode = false - quotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.invalid) + quotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.invalid) Http.httpRequest.mockImplementationOnce(() => { throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_COMMUNICATION_ERROR) }) await expect(quotesModel.forwardQuoteUpdate(mockData.headers, mockData.quoteId, mockData.quoteUpdate)) @@ -1663,7 +1663,7 @@ describe('QuotesModel', () => { expect.assertions(3) mockConfig.simpleRoutingMode = false - quotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.invalidResponse) + quotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.invalidResponse) Http.httpRequest.mockImplementationOnce(() => { throw ErrorHandler.CreateFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_COMMUNICATION_ERROR) }) await expect(quotesModel.forwardQuoteUpdate(mockData.headers, mockData.quoteId, mockData.quoteUpdate)) @@ -1679,7 +1679,7 @@ describe('QuotesModel', () => { mockConfig.simpleRoutingMode = false const customErrorNoStack = new Error('Custom error') delete customErrorNoStack.stack - quotesModel.db.getParticipantEndpoint.mockRejectedValueOnce(customErrorNoStack) + quotesModel._getParticipantEndpoint.mockRejectedValueOnce(customErrorNoStack) await expect(quotesModel.forwardQuoteUpdate(mockData.headers, mockData.quoteId, mockData.quoteUpdate)) .rejects @@ -1902,22 +1902,10 @@ describe('QuotesModel', () => { quotesModel.forwardQuoteGet.mockRestore() }) - it('fails to forward if the database has no endpoint for the dfsp', async () => { - // Arrange - expect.assertions(1) - quotesModel.db.getParticipantEndpoint.mockImplementation(() => null) - - // Act - const action = async () => quotesModel.forwardQuoteGet(mockData.headers, mockData.quoteId, mockSpan) - - // Assert - await expect(action()).rejects.toThrowError('No FSPIOP_CALLBACK_URL_QUOTES found for quote GET test123') - }) - it('forwards the request to the payee dfsp without a span', async () => { // Arrange // expect.assertions(2) - quotesModel.db.getParticipantEndpoint.mockImplementation(() => 'http://localhost:3333') + quotesModel._getParticipantEndpoint.mockImplementation(() => 'http://localhost:3333') const expectedOptions = { headers: {}, method: 'GET', @@ -1937,7 +1925,7 @@ describe('QuotesModel', () => { it('forwards the request to the payee dfsp', async () => { // Arrange expect.assertions(4) - quotesModel.db.getParticipantEndpoint.mockImplementation(() => 'http://localhost:3333') + quotesModel._getParticipantEndpoint.mockImplementation(() => 'http://localhost:3333') mockSpan.injectContextToHttpRequest = jest.fn().mockImplementation(() => ({ headers: { spanHeaders: '12345' @@ -1963,7 +1951,7 @@ describe('QuotesModel', () => { it('handles a http error', async () => { // Arrange expect.assertions(1) - quotesModel.db.getParticipantEndpoint.mockImplementation(() => 'http://localhost:3333') + quotesModel._getParticipantEndpoint.mockImplementation(() => 'http://localhost:3333') Http.httpRequest.mockImplementationOnce(() => { throw new Error('Test HTTP Error') }) // Act @@ -2022,7 +2010,7 @@ describe('QuotesModel', () => { it('sends the error callback without a span', async () => { // Arrange expect.assertions(1) - quotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + quotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) Util.generateRequestHeaders.mockReturnValueOnce({}) const error = new Error('Test Error') const fspiopError = ErrorHandler.ReformatFSPIOPError(error) @@ -2043,7 +2031,7 @@ describe('QuotesModel', () => { it('sends the error callback and handles the span', async () => { // Arrange expect.assertions(3) - quotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + quotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) Util.generateRequestHeaders.mockReturnValueOnce({}) const error = new Error('Test Error') const fspiopError = ErrorHandler.ReformatFSPIOPError(error) @@ -2078,7 +2066,7 @@ describe('QuotesModel', () => { // Arrange const jwsSignSpy = jest.spyOn(JwsSigner.prototype, 'getSignature') // expect.assertions(6) - quotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + quotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) Util.generateRequestHeaders.mockReturnValueOnce({}) const error = new Error('Test Error') const fspiopError = ErrorHandler.ReformatFSPIOPError(error) @@ -2111,7 +2099,7 @@ describe('QuotesModel', () => { // Arrange const jwsSignSpy = jest.spyOn(JwsSigner.prototype, 'getSignature') // expect.assertions(6) - quotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + quotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) Util.generateRequestHeaders.mockReturnValueOnce({}) const error = new Error('Test Error') const fspiopError = ErrorHandler.ReformatFSPIOPError(error) @@ -2145,7 +2133,7 @@ describe('QuotesModel', () => { // Arrange const jwsSignSpy = jest.spyOn(JwsSigner.prototype, 'getSignature') expect.assertions(5) - quotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + quotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) Util.generateRequestHeaders.mockReturnValueOnce({}) const error = new Error('Test Error') const fspiopError = ErrorHandler.ReformatFSPIOPError(error) @@ -2186,7 +2174,7 @@ describe('QuotesModel', () => { // Arrange const jwsSignSpy = jest.spyOn(JwsSigner.prototype, 'getSignature') expect.assertions(5) - quotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + quotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) Util.generateRequestHeaders.mockReturnValueOnce({}) const error = new Error('Test Error') const fspiopError = ErrorHandler.ReformatFSPIOPError(error) @@ -2226,7 +2214,7 @@ describe('QuotesModel', () => { it('handles when the endpoint could not be found', async () => { // Arrange expect.assertions(2) - quotesModel.db.getParticipantEndpoint.mockReturnValueOnce(undefined) + quotesModel._getParticipantEndpoint.mockReturnValueOnce(undefined) Util.generateRequestHeaders.mockReturnValueOnce({}) const error = new Error('Test Error') const fspiopError = ErrorHandler.ReformatFSPIOPError(error) @@ -2242,7 +2230,7 @@ describe('QuotesModel', () => { it('handles a http exception', async () => { // Arrange expect.assertions(2) - quotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + quotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) Util.generateRequestHeaders.mockReturnValueOnce({}) const error = new Error('Test Error') const fspiopError = ErrorHandler.ReformatFSPIOPError(error) @@ -2259,7 +2247,7 @@ describe('QuotesModel', () => { it('handles a http bad status code', async () => { // Arrange expect.assertions(2) - quotesModel.db.getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) + quotesModel._getParticipantEndpoint.mockReturnValueOnce(mockData.endpoints.payeefsp) Util.generateRequestHeaders.mockReturnValueOnce({}) const error = new Error('Test Error') const fspiopError = ErrorHandler.ReformatFSPIOPError(error) From d6b2c249d50dc81aa4f34aa4f4aa8ffe339c9060 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Tue, 2 Jul 2024 21:28:54 +0100 Subject: [PATCH 09/51] test: add unit tests for util.getParticipantEndpoint --- test/unit/lib/util.test.js | 66 +++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/test/unit/lib/util.test.js b/test/unit/lib/util.test.js index 257f350e..10c4d580 100644 --- a/test/unit/lib/util.test.js +++ b/test/unit/lib/util.test.js @@ -34,7 +34,7 @@ const Logger = require('@mojaloop/central-services-logger') Logger.isDebugEnabled = jest.fn(() => true) Logger.isErrorEnabled = jest.fn(() => true) Logger.isInfoEnabled = jest.fn(() => true) -const { failActionHandler, getStackOrInspect, getSpanTags, generateRequestHeaders, generateRequestHeadersForJWS, removeEmptyKeys, fetchParticipantInfo } = require('../../../src/lib/util') +const { failActionHandler, getStackOrInspect, getSpanTags, generateRequestHeaders, generateRequestHeadersForJWS, removeEmptyKeys, fetchParticipantInfo, getParticipantEndpoint } = require('../../../src/lib/util') const Config = require('../../../src/lib/config.js') const { Cache } = require('memory-cache') @@ -215,6 +215,7 @@ describe('util', () => { subScenario: 'fakeSubScenario', transactionReference: 'fakeTxRef' } + describe('failActionHandler', () => { it('throws the reformatted error', async () => { // Arrange @@ -503,6 +504,7 @@ describe('util', () => { expect(result).toStrictEqual(expected) }) }) + describe('fetchParticipantInfo', () => { beforeEach(() => { // restore the current method in test to its original implementation @@ -579,4 +581,66 @@ describe('util', () => { expect(axios.request.mock.calls[1][0]).toEqual({ url: 'http://localhost:3001/participants/' + mockData.headers['fspiop-destination'] }) }) }) + + describe('getParticipantEndpoint', () => { + beforeEach(() => { + }) + + it('returns the participant endpoint by calling db.getParticipantEndpoint', async () => { + // Arrange + const expected = 'http://localhost:8444/payerfsp' + const params = { + fspId: 'fsp1', + db: { + getParticipantEndpoint: jest.fn().mockResolvedValue(expected) + }, + loggerFn: Logger.info, + endpointType: 'TEST_ENDPOINT_TYPE', + proxyClient: { + connect: jest.fn(), + lookupProxyByDfspId: jest.fn() + } + } + // Act + const result = await getParticipantEndpoint(params) + // Assert + expect(result).toEqual(expected) + expect(params.db.getParticipantEndpoint).toBeCalledTimes(1) + expect(params.db.getParticipantEndpoint).toBeCalledWith(params.fspId, params.endpointType) + expect(params.proxyClient.connect).toBeCalledTimes(0) + expect(params.proxyClient.lookupProxyByDfspId).toBeCalledTimes(0) + }) + + it('returns the participant endpoint using proxy client if participant not found in db', async () => { + // Arrange + const expected = 'http://localhost:8444/payerfsp' + const proxyId = 'proxy1' + const params = { + fspId: 'fsp1', + db: { + getParticipantEndpoint: jest.fn().mockImplementation((fspId, endpointType) => { + if (fspId === proxyId && endpointType === 'TEST_ENDPOINT_TYPE') { + return Promise.resolve(expected) + } + return Promise.resolve(null) + }) + }, + loggerFn: Logger.info, + endpointType: 'TEST_ENDPOINT_TYPE', + proxyClient: { + isConnecected: false, + connect: jest.fn().mockResolvedValue(true), + lookupProxyByDfspId: jest.fn().mockResolvedValue(proxyId) + } + } + // Act + const result = await getParticipantEndpoint(params) + // Assert + expect(result).toEqual(expected) + expect(params.db.getParticipantEndpoint).toBeCalledTimes(2) + expect(params.db.getParticipantEndpoint).toBeCalledWith(proxyId, params.endpointType) + expect(params.proxyClient.connect).toBeCalledTimes(1) + expect(params.proxyClient.lookupProxyByDfspId).toBeCalledTimes(1) + }) + }) }) From c2dc2e78ac31d361fd8cd11fd88b585ab7d97c48 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Tue, 2 Jul 2024 21:47:31 +0100 Subject: [PATCH 10/51] test: update tests --- src/model/quotes.js | 8 +------- test/unit/model/quotes.test.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/model/quotes.js b/src/model/quotes.js index 2e1ce2a6..bf94b224 100644 --- a/src/model/quotes.js +++ b/src/model/quotes.js @@ -724,13 +724,6 @@ class QuotesModel { this.writeLog(`Resolved PAYER party FSPIOP_CALLBACK_URL_QUOTES endpoint for quote ${quoteId} to: ${util.inspect(endpoint)}`) - // if endpoint is not found, check the proxy cache (this might be an inter-scheme request) - if (!endpoint && this.proxyClient) { - const proxyResult = await this.getProxyEndpoint({ dfspId: fspiopDest, endpointType: ENUM.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_QUOTES }) - endpoint = proxyResult.endpoint - this.writeLog(`Proxy participant FSPIOP_CALLBACK_URL_QUOTES endpoint resolved for quote ${quoteId}: fspiop-destination: ${fspiopDest}, proxyId: ${proxyResult.proxyId}, proxy endpoint: ${proxyResult.endpoint}`) - } - if (!endpoint) { // we didnt get an endpoint for the payee dfsp! // make an error callback to the initiator @@ -929,6 +922,7 @@ class QuotesModel { // todo: for MVP we assume initiator is always payer dfsp! this may not always be the case if a xfer is requested by payee const fspiopSource = headers[ENUM.Http.Headers.FSPIOP.SOURCE] const fspiopDest = headers[ENUM.Http.Headers.FSPIOP.DESTINATION] + endpoint = await this._getParticipantEndpoint(fspiopDest) this.writeLog(`Resolved ${fspiopDest} FSPIOP_CALLBACK_URL_QUOTES endpoint for quote GET ${quoteId} to: ${util.inspect(endpoint)}`) diff --git a/test/unit/model/quotes.test.js b/test/unit/model/quotes.test.js index 5f2d05e9..bb6a1755 100644 --- a/test/unit/model/quotes.test.js +++ b/test/unit/model/quotes.test.js @@ -356,6 +356,7 @@ describe('QuotesModel', () => { } })) }) + afterEach(() => { // Clears the mock.calls and mock.instances properties of all mocks. // Equivalent to calling .mockClear() on every mocked function. @@ -1293,6 +1294,15 @@ describe('QuotesModel', () => { expect(mockChildSpan.injectContextToHttpRequest).not.toHaveBeenCalled() expect(mockChildSpan.audit).not.toHaveBeenCalled() }) + it('should throw when participant endpoint is not found in db and proxy cache', async () => { + expect.assertions(1) + mockConfig.simpleRoutingMode = false + quotesModel._getParticipantEndpoint.mockReturnValueOnce(null) + + await expect(quotesModel.forwardQuoteRequest(mockData.headers, mockData.quoteRequest.quoteId, mockData.quoteRequest)) + .rejects + .toHaveProperty('apiErrorCode.code', ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_FSP_ERROR.code) + }) it('should inspect and throw custom error as FSPIOPerror', async () => { expect.assertions(3) From 7044f06522c051113137491a90fb6f7510037ee9 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Wed, 3 Jul 2024 10:27:49 +0100 Subject: [PATCH 11/51] test: update unit tests --- src/lib/util.js | 4 +++ src/model/bulkQuotes.js | 1 + src/model/fxQuotes.js | 1 + src/model/quotes.js | 1 + test/unit/handlers/init.test.js | 1 + test/unit/lib/util.test.js | 52 +++++++++++++++++++++++++++++++++ 6 files changed, 60 insertions(+) diff --git a/src/lib/util.js b/src/lib/util.js index 181504cf..24eaf762 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -234,6 +234,10 @@ const fetchParticipantInfo = async (source, destination, cache) => { } const getParticipantEndpoint = async ({ fspId, db, loggerFn, endpointType, proxyClient = null }) => { + if (!fspId || !db || !loggerFn || !endpointType) { + throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.INTERNAL_SERVER_ERROR, 'Missing required arguments for getParticipantEndpoint') + } + let endpoint = await db.getParticipantEndpoint(fspId, endpointType) loggerFn(`Resolved participant '${fspId}' ${endpointType} to: '${endpoint}'`) diff --git a/src/model/bulkQuotes.js b/src/model/bulkQuotes.js index 5bb0cc60..ed8c2cf1 100644 --- a/src/model/bulkQuotes.js +++ b/src/model/bulkQuotes.js @@ -485,6 +485,7 @@ class BulkQuotesModel { } } + // wrapping this dependency here to allow for easier use and testing async _getParticipantEndpoint (fspId, endpointType = ENUM.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_BULK_QUOTES) { return getParticipantEndpoint({ fspId, db: this.db, loggerFn: this.writeLog.bind(this), endpointType, proxyClient: this.proxyClient }) } diff --git a/src/model/fxQuotes.js b/src/model/fxQuotes.js index 826cfeef..5e72bdbb 100644 --- a/src/model/fxQuotes.js +++ b/src/model/fxQuotes.js @@ -422,6 +422,7 @@ class FxQuotesModel { } } + // wrapping this dependency here to allow for easier use and testing async _getParticipantEndpoint (fspId, endpointType = ENUM.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_FX_QUOTES) { return getParticipantEndpoint({ fspId, db: this.db, loggerFn: this.writeLog.bind(this), endpointType, proxyClient: this.proxyClient }) } diff --git a/src/model/quotes.js b/src/model/quotes.js index bf94b224..32af7ddd 100644 --- a/src/model/quotes.js +++ b/src/model/quotes.js @@ -1227,6 +1227,7 @@ class QuotesModel { } } + // wrapping this dependency here to allow for easier use and testing async _getParticipantEndpoint (fspId, endpointType = ENUM.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_QUOTES) { return getParticipantEndpoint({ fspId, db: this.db, loggerFn: this.writeLog.bind(this), endpointType, proxyClient: this.proxyClient }) } diff --git a/test/unit/handlers/init.test.js b/test/unit/handlers/init.test.js index 039a1c59..1a5ee53b 100644 --- a/test/unit/handlers/init.test.js +++ b/test/unit/handlers/init.test.js @@ -1,5 +1,6 @@ jest.mock('../../../src/handlers/createConsumers') jest.mock('../../../src/handlers/monitoringServer') +jest.mock('../../../src/lib/proxy') const init = require('../../../src/handlers/init') const Database = require('../../../src/data/cachedDatabase') diff --git a/test/unit/lib/util.test.js b/test/unit/lib/util.test.js index 10c4d580..9bd112eb 100644 --- a/test/unit/lib/util.test.js +++ b/test/unit/lib/util.test.js @@ -586,6 +586,58 @@ describe('util', () => { beforeEach(() => { }) + it('throws an error if required arguments are missing', async () => { + // Arrange + const fspId = 'fsp1' + const db = { + getParticipantEndpoint: jest.fn() + } + const endpointType = 'TEST_ENDPOINT_TYPE' + const proxyClient = { + connect: jest.fn(), + lookupProxyByDfspId: jest.fn() + } + const loggerFn = Logger.info + const params = { db, loggerFn, endpointType, proxyClient } + + // Act + const action = async () => getParticipantEndpoint(params) + // Assert + await expect(action()).rejects.toThrowError('Missing required arguments for getParticipantEndpoint') + + // Arrange + params.fspId = fspId + params.db = null + // Act + const action2 = async () => getParticipantEndpoint(params) + // Assert + await expect(action2()).rejects.toThrowError('Missing required arguments for getParticipantEndpoint') + + // Arrange + params.db = db + params.loggerFn = null + // Act + const action3 = async () => getParticipantEndpoint(params) + // Assert + await expect(action3()).rejects.toThrowError('Missing required arguments for getParticipantEndpoint') + + // Arrange + params.loggerFn = loggerFn + params.endpointType = null + // Act + const action4 = async () => getParticipantEndpoint(params) + // Assert + await expect(action4()).rejects.toThrowError('Missing required arguments for getParticipantEndpoint') + + // Arrange + params.endpointType = endpointType + params.proxyClient = null + // Act + const action5 = async () => getParticipantEndpoint(params) + // Assert + await expect(action5()).resolves.not.toThrow() + }) + it('returns the participant endpoint by calling db.getParticipantEndpoint', async () => { // Arrange const expected = 'http://localhost:8444/payerfsp' From 74c306f62ffc1bce3a60e50a121c52ffa5f0d8e6 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Wed, 3 Jul 2024 11:26:16 +0100 Subject: [PATCH 12/51] test: update unit tests --- src/lib/proxy.js | 24 ++++++++++----- test/unit/lib/proxy.test.js | 61 +++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 8 deletions(-) create mode 100644 test/unit/lib/proxy.test.js diff --git a/src/lib/proxy.js b/src/lib/proxy.js index a1623215..076f9faf 100644 --- a/src/lib/proxy.js +++ b/src/lib/proxy.js @@ -4,17 +4,25 @@ const { createProxyCache } = require('@mojaloop/inter-scheme-proxy-cache-lib') const createProxyClient = async ({ proxyCacheConfig, logger }) => { const retryInterval = Number(proxyCacheConfig.retryInterval) const proxyClient = createProxyCache(proxyCacheConfig.type, proxyCacheConfig.proxyConfig) + let timedOut = false - const timer = setTimeout(() => { - logger.isErrorEnabled && logger.error('Unable to connect to proxy cache. Exiting...', { proxyCacheConfig }) - process.exit(1) - }, Number(proxyCacheConfig.timeout)) + if (Object.prototype.hasOwnProperty.call(proxyCacheConfig.proxyConfig, 'lazyConnect') && !proxyCacheConfig.proxyConfig.lazyConnect) { + const timer = setTimeout(() => { + timedOut = true + logger.isErrorEnabled && logger.error('Unable to connect to proxy cache. Exiting...', { proxyCacheConfig }) + }, Number(proxyCacheConfig.timeout)) - while (!proxyClient.isConnected) { - await new Promise(resolve => setTimeout(resolve, retryInterval)) - } + while (!proxyClient.isConnected) { + if (timedOut) break + await new Promise(resolve => setTimeout(resolve, retryInterval)) + } + + clearTimeout(timer) - clearTimeout(timer) + if (timedOut) { + process.exit(1) + } + } return proxyClient } diff --git a/test/unit/lib/proxy.test.js b/test/unit/lib/proxy.test.js new file mode 100644 index 00000000..404b0188 --- /dev/null +++ b/test/unit/lib/proxy.test.js @@ -0,0 +1,61 @@ +jest.mock('@mojaloop/inter-scheme-proxy-cache-lib', () => ({ + createProxyCache: jest.fn() +})) +const { createProxyCache } = require('@mojaloop/inter-scheme-proxy-cache-lib') +const { createProxyClient } = require('../../../src/lib/proxy') + +describe('createProxyClient', () => { + let mockProxyClient + let proxyCacheConfig + let logger + + beforeEach(() => { + proxyCacheConfig = { + retryInterval: 200, + timeout: 5000, + type: 'redis', + proxyConfig: { + host: 'localhost', + port: 6379, + lazyConnect: true + } + } + + logger = { + isErrorEnabled: true, + error: jest.fn() + } + + mockProxyClient = { + isConnected: true + } + + createProxyCache.mockReturnValue(mockProxyClient) + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + it('should create a proxy client and return it', async () => { + const proxyClient = await createProxyClient({ proxyCacheConfig, logger }) + + expect(proxyClient).toBeDefined() + expect(proxyClient.isConnected).toBe(true) + }) + + it('should log an error and exit if unable to connect to proxy cache', async () => { + const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => { }) + mockProxyClient.isConnected = false + const modifiedConfig = { ...proxyCacheConfig, proxyConfig: { ...proxyCacheConfig.proxyConfig, lazyConnect: false } } + + await createProxyClient({ proxyCacheConfig: modifiedConfig, logger }) + + expect(logger.error).toHaveBeenCalledWith('Unable to connect to proxy cache. Exiting...', { + proxyCacheConfig: modifiedConfig + }) + expect(mockExit).toHaveBeenCalledWith(1) + + mockExit.mockRestore() + }, 10000) +}) From e7900077a1202efe8cea3006a35c4e10c3fdfb0f Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Wed, 3 Jul 2024 11:26:56 +0100 Subject: [PATCH 13/51] test: update unit tests --- src/lib/proxy.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/proxy.js b/src/lib/proxy.js index 076f9faf..45b0deb3 100644 --- a/src/lib/proxy.js +++ b/src/lib/proxy.js @@ -19,9 +19,7 @@ const createProxyClient = async ({ proxyCacheConfig, logger }) => { clearTimeout(timer) - if (timedOut) { - process.exit(1) - } + if (timedOut) process.exit(1) } return proxyClient From b5695138626fffd3ecb96c01c4c7dbcfbab37d2c Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Wed, 3 Jul 2024 11:30:26 +0100 Subject: [PATCH 14/51] test: update unit test --- test/unit/lib/proxy.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/lib/proxy.test.js b/test/unit/lib/proxy.test.js index 404b0188..03d0e561 100644 --- a/test/unit/lib/proxy.test.js +++ b/test/unit/lib/proxy.test.js @@ -57,5 +57,5 @@ describe('createProxyClient', () => { expect(mockExit).toHaveBeenCalledWith(1) mockExit.mockRestore() - }, 10000) + }, 10_000) }) From a97ec0541f021646deaa83e63c0ea8511fc56ffd Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Wed, 3 Jul 2024 11:31:41 +0100 Subject: [PATCH 15/51] test: update unit test --- src/lib/proxy.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/proxy.js b/src/lib/proxy.js index 45b0deb3..10245c79 100644 --- a/src/lib/proxy.js +++ b/src/lib/proxy.js @@ -2,6 +2,7 @@ const process = require('node:process') const { createProxyCache } = require('@mojaloop/inter-scheme-proxy-cache-lib') const createProxyClient = async ({ proxyCacheConfig, logger }) => { + const timeout = Number(proxyCacheConfig.timeout) const retryInterval = Number(proxyCacheConfig.retryInterval) const proxyClient = createProxyCache(proxyCacheConfig.type, proxyCacheConfig.proxyConfig) let timedOut = false @@ -10,7 +11,7 @@ const createProxyClient = async ({ proxyCacheConfig, logger }) => { const timer = setTimeout(() => { timedOut = true logger.isErrorEnabled && logger.error('Unable to connect to proxy cache. Exiting...', { proxyCacheConfig }) - }, Number(proxyCacheConfig.timeout)) + }, timeout) while (!proxyClient.isConnected) { if (timedOut) break From 8c432cfcc903813517bd2c7025b90bdebff90070 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Wed, 3 Jul 2024 11:32:55 +0100 Subject: [PATCH 16/51] test: update unit test --- src/lib/proxy.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/proxy.js b/src/lib/proxy.js index 10245c79..67c5bd1f 100644 --- a/src/lib/proxy.js +++ b/src/lib/proxy.js @@ -4,9 +4,10 @@ const { createProxyCache } = require('@mojaloop/inter-scheme-proxy-cache-lib') const createProxyClient = async ({ proxyCacheConfig, logger }) => { const timeout = Number(proxyCacheConfig.timeout) const retryInterval = Number(proxyCacheConfig.retryInterval) - const proxyClient = createProxyCache(proxyCacheConfig.type, proxyCacheConfig.proxyConfig) let timedOut = false + const proxyClient = createProxyCache(proxyCacheConfig.type, proxyCacheConfig.proxyConfig) + if (Object.prototype.hasOwnProperty.call(proxyCacheConfig.proxyConfig, 'lazyConnect') && !proxyCacheConfig.proxyConfig.lazyConnect) { const timer = setTimeout(() => { timedOut = true From ee765929d15717588a32e4ffb5ec6ca7b3b01f8d Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Wed, 3 Jul 2024 12:19:43 +0100 Subject: [PATCH 17/51] test: update tests --- docker-compose.yml | 6 ++++++ src/lib/proxy.js | 4 ++-- src/lib/util.js | 8 ++++---- test/integration/postRequest.test.js | 12 +++++++----- test/unit/lib/proxy.test.js | 5 ++++- test/unit/lib/util.test.js | 11 ++++------- 6 files changed, 27 insertions(+), 19 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index e90203c4..8700abae 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -97,6 +97,12 @@ services: healthcheck: <<: *healthcheckParams test: [ "CMD", "mysqladmin" ,"ping", "-h", "mysql" ] + + redis: + image: "redis:6.2.4-alpine" + container_name: redis + ports: + - "6379:6379" # mockserver: # image: jamesdbloom/mockserver diff --git a/src/lib/proxy.js b/src/lib/proxy.js index 67c5bd1f..ed5fd396 100644 --- a/src/lib/proxy.js +++ b/src/lib/proxy.js @@ -8,10 +8,11 @@ const createProxyClient = async ({ proxyCacheConfig, logger }) => { const proxyClient = createProxyCache(proxyCacheConfig.type, proxyCacheConfig.proxyConfig) + // If lazyConnect is false, wait for the connection to be established if (Object.prototype.hasOwnProperty.call(proxyCacheConfig.proxyConfig, 'lazyConnect') && !proxyCacheConfig.proxyConfig.lazyConnect) { const timer = setTimeout(() => { timedOut = true - logger.isErrorEnabled && logger.error('Unable to connect to proxy cache. Exiting...', { proxyCacheConfig }) + logger.error('Unable to connect to proxy cache. Exiting...', { proxyCacheConfig }) }, timeout) while (!proxyClient.isConnected) { @@ -20,7 +21,6 @@ const createProxyClient = async ({ proxyCacheConfig, logger }) => { } clearTimeout(timer) - if (timedOut) process.exit(1) } diff --git a/src/lib/util.js b/src/lib/util.js index 24eaf762..91a4a43d 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -235,14 +235,14 @@ const fetchParticipantInfo = async (source, destination, cache) => { const getParticipantEndpoint = async ({ fspId, db, loggerFn, endpointType, proxyClient = null }) => { if (!fspId || !db || !loggerFn || !endpointType) { - throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.INTERNAL_SERVER_ERROR, 'Missing required arguments for getParticipantEndpoint') + throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.INTERNAL_SERVER_ERROR, 'Missing required arguments for \'getParticipantEndpoint\'') } let endpoint = await db.getParticipantEndpoint(fspId, endpointType) - loggerFn(`Resolved participant '${fspId}' ${endpointType} to: '${endpoint}'`) + loggerFn(`DB lookup: resolved participant '${fspId}' ${endpointType} endpoint to: '${endpoint}'`) - // if endpoint is not found in db, check the proxy cache (this might be an inter-scheme request) + // if endpoint is not found in db, check the proxy cache for a proxy representative for the fsp (this might be an inter-scheme request) if (!endpoint && proxyClient) { if (!proxyClient.isConnected) await proxyClient.connect() const proxyId = await proxyClient.lookupProxyByDfspId(fspId) @@ -250,7 +250,7 @@ const getParticipantEndpoint = async ({ fspId, db, loggerFn, endpointType, proxy endpoint = await db.getParticipantEndpoint(proxyId, endpointType) } - loggerFn(`Proxy participant ${endpointType} endpoint resolved: fspid: ${fspId}, proxyId: ${proxyId}, proxy endpoint: ${endpoint}`) + loggerFn(`Proxy lookup: resolved participant '${fspId}' ${endpointType} endpoint to: '${endpoint}', proxyId: ${proxyId} `) } return endpoint diff --git a/test/integration/postRequest.test.js b/test/integration/postRequest.test.js index 9f27dc9e..27f0ba37 100644 --- a/test/integration/postRequest.test.js +++ b/test/integration/postRequest.test.js @@ -8,11 +8,13 @@ const uuid = require('crypto').randomUUID const hubClient = new MockServerClient() const base64Encode = (data) => Buffer.from(data).toString('base64') -const TEST_TIMEOUT = 20000 +const TEST_TIMEOUT = 20_000 const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)) describe('POST request tests --> ', () => { + jest.setTimeout(TEST_TIMEOUT) + const { kafkaConfig } = new Config() beforeEach(async () => { @@ -51,7 +53,7 @@ describe('POST request tests --> ', () => { const { url } = response.data.history[0] expect(url).toBe(`/${message.to}/quotes`) - }, TEST_TIMEOUT) + }) test('should fail validation for POST /quotes request if request amount currency is not registered (position account doesn not exist) for the payer pariticpant', async () => { let response = await hubClient.getHistory() @@ -83,7 +85,7 @@ describe('POST request tests --> ', () => { expect(url).toBe(`/${message.from}/quotes/${message.id}/error`) expect(body.errorInformation.errorCode).toBe('3201') expect(body.errorInformation.errorDescription).toBe(`Destination FSP Error - Unsupported participant '${message.to}'`) - }, TEST_TIMEOUT) + }) test('should pass validation for POST /quotes request if all request "supportedCurrencies" are registered (position account exists) for the payer pariticpant', async () => { let response = await hubClient.getHistory() @@ -113,7 +115,7 @@ describe('POST request tests --> ', () => { const { url } = response.data.history[0] expect(url).toBe(`/${message.to}/quotes`) - }, TEST_TIMEOUT) + }) test('should fail validation for POST /quotes request if any of request "supportedCurrencies" is not registered (no position account exists) for the payer pariticpant', async () => { let response = await hubClient.getHistory() @@ -145,5 +147,5 @@ describe('POST request tests --> ', () => { expect(url).toBe(`/${message.from}/quotes/${message.id}/error`) expect(body.errorInformation.errorCode).toBe('3202') expect(body.errorInformation.errorDescription).toBe(`Payer FSP ID not found - Unsupported participant '${message.from}'`) - }, TEST_TIMEOUT) + }) }) diff --git a/test/unit/lib/proxy.test.js b/test/unit/lib/proxy.test.js index 03d0e561..0b82a812 100644 --- a/test/unit/lib/proxy.test.js +++ b/test/unit/lib/proxy.test.js @@ -1,6 +1,7 @@ jest.mock('@mojaloop/inter-scheme-proxy-cache-lib', () => ({ createProxyCache: jest.fn() })) + const { createProxyCache } = require('@mojaloop/inter-scheme-proxy-cache-lib') const { createProxyClient } = require('../../../src/lib/proxy') @@ -46,8 +47,10 @@ describe('createProxyClient', () => { it('should log an error and exit if unable to connect to proxy cache', async () => { const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => { }) + mockProxyClient.isConnected = false - const modifiedConfig = { ...proxyCacheConfig, proxyConfig: { ...proxyCacheConfig.proxyConfig, lazyConnect: false } } + const modifiedConfig = { ...proxyCacheConfig } + modifiedConfig.proxyConfig.lazyConnect = false await createProxyClient({ proxyCacheConfig: modifiedConfig, logger }) diff --git a/test/unit/lib/util.test.js b/test/unit/lib/util.test.js index 9bd112eb..f66fd88e 100644 --- a/test/unit/lib/util.test.js +++ b/test/unit/lib/util.test.js @@ -583,9 +583,6 @@ describe('util', () => { }) describe('getParticipantEndpoint', () => { - beforeEach(() => { - }) - it('throws an error if required arguments are missing', async () => { // Arrange const fspId = 'fsp1' @@ -603,7 +600,7 @@ describe('util', () => { // Act const action = async () => getParticipantEndpoint(params) // Assert - await expect(action()).rejects.toThrowError('Missing required arguments for getParticipantEndpoint') + await expect(action()).rejects.toThrowError('Missing required arguments for \'getParticipantEndpoint\'') // Arrange params.fspId = fspId @@ -611,7 +608,7 @@ describe('util', () => { // Act const action2 = async () => getParticipantEndpoint(params) // Assert - await expect(action2()).rejects.toThrowError('Missing required arguments for getParticipantEndpoint') + await expect(action2()).rejects.toThrowError('Missing required arguments for \'getParticipantEndpoint\'') // Arrange params.db = db @@ -619,7 +616,7 @@ describe('util', () => { // Act const action3 = async () => getParticipantEndpoint(params) // Assert - await expect(action3()).rejects.toThrowError('Missing required arguments for getParticipantEndpoint') + await expect(action3()).rejects.toThrowError('Missing required arguments for \'getParticipantEndpoint\'') // Arrange params.loggerFn = loggerFn @@ -627,7 +624,7 @@ describe('util', () => { // Act const action4 = async () => getParticipantEndpoint(params) // Assert - await expect(action4()).rejects.toThrowError('Missing required arguments for getParticipantEndpoint') + await expect(action4()).rejects.toThrowError('Missing required arguments for \'getParticipantEndpoint\'') // Arrange params.endpointType = endpointType From bf2eab638a66cd01f09383f63e717428d235dc18 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Wed, 3 Jul 2024 12:33:35 +0100 Subject: [PATCH 18/51] chore: remove unused dep --- package-lock.json | 9 --------- package.json | 1 - 2 files changed, 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index cf28eaa7..bc7827cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,6 @@ "ajv-keywords": "5.1.0", "axios": "1.7.2", "blipp": "4.0.2", - "cd": "^0.3.3", "commander": "12.1.0", "event-stream": "4.0.1", "fast-safe-stringify": "^2.1.1", @@ -4119,14 +4118,6 @@ } ] }, - "node_modules/cd": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/cd/-/cd-0.3.3.tgz", - "integrity": "sha512-X2y0Ssu48ucdkrNgCdg6k3EZWjWVy/dsEywUUTeZEIW31f3bQfq65Svm+TzU1Hz+qqhdmyCdjGhUvRsSKHl/mw==", - "engines": { - "node": "*" - } - }, "node_modules/center-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", diff --git a/package.json b/package.json index c08b314f..52cb7429 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,6 @@ "ajv-keywords": "5.1.0", "axios": "1.7.2", "blipp": "4.0.2", - "cd": "^0.3.3", "commander": "12.1.0", "event-stream": "4.0.1", "fast-safe-stringify": "^2.1.1", From 78ccf52ae8c1bfc59d850b8ffdfe8191c6d760dc Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Wed, 3 Jul 2024 17:02:22 +0100 Subject: [PATCH 19/51] test: add integration test for PUT /quotes/{ID} --- Dockerfile | 2 +- config/default.json | 4 +- docker-compose.yml | 2 +- docker/quoting-service/default.json | 5 +- package-lock.json | 8 +- package.json | 2 +- src/lib/proxy.js | 33 ++++++- test/integration/postRequest.test.js | 88 +++++++++++++++++- test/integration/putCallback.test.js | 94 ++++++++++++++++++-- test/integration/scripts/env.sh | 2 +- test/integration/scripts/populateTestData.sh | 9 +- test/unit/lib/proxy.test.js | 29 ++++++ 12 files changed, 253 insertions(+), 25 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6a206a23..42647daf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ USER root WORKDIR /opt/app RUN apk --no-cache add git -RUN apk add --no-cache -t build-dependencies make gcc g++ python3 libtool openssl-dev autoconf automake bash \ +RUN apk add --no-cache -t build-dependencies make gcc g++ python3 py3-setuptools libtool openssl-dev autoconf automake bash \ && cd $(npm root -g)/npm COPY package.json package-lock.json* /opt/app/ diff --git a/config/default.json b/config/default.json index 59c00e53..ac154878 100644 --- a/config/default.json +++ b/config/default.json @@ -84,9 +84,11 @@ "retryInterval": 200, "proxyConfig": { "host": "localhost", - "port": 6379 + "port": 6379, + "lazyConnect": false } }, + "LOG_LEVEL": "info", "KAFKA": { "CONSUMER": { "QUOTE": { diff --git a/docker-compose.yml b/docker-compose.yml index 8700abae..eb8d1bb3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,7 +46,7 @@ services: quoting-service-handler: <<: *quotingServiceBase - command: npm run start:handlers + command: npm run start:handlers:debug ports: - "3003:3003" - "29229:9229" diff --git a/docker/quoting-service/default.json b/docker/quoting-service/default.json index 296adeaa..261c4492 100644 --- a/docker/quoting-service/default.json +++ b/docker/quoting-service/default.json @@ -78,13 +78,14 @@ "PARTICIPANT_DATA_EXPIRES_IN_MS": 60000 }, "PROXY_CACHE": { - "enabled": false, + "enabled": true, "type": "redis", "timeout": 5000, "retryInterval": 200, "proxyConfig": { "host": "redis", - "port": 6379 + "port": 6379, + "lazyConnect": false } }, "KAFKA": { diff --git a/package-lock.json b/package-lock.json index bc7827cb..88fab332 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@mojaloop/central-services-shared": "18.5.0-snapshot.2", "@mojaloop/central-services-stream": "11.3.1", "@mojaloop/event-sdk": "14.1.1", - "@mojaloop/inter-scheme-proxy-cache-lib": "^1.1.0", + "@mojaloop/inter-scheme-proxy-cache-lib": "^1.2.0", "@mojaloop/ml-number": "11.2.4", "@mojaloop/sdk-standard-components": "18.1.0", "ajv": "8.16.0", @@ -2106,9 +2106,9 @@ "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, "node_modules/@mojaloop/inter-scheme-proxy-cache-lib": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@mojaloop/inter-scheme-proxy-cache-lib/-/inter-scheme-proxy-cache-lib-1.1.0.tgz", - "integrity": "sha512-4uBms3wLiP3qCdojLr80depj34vKcPwwHV0HX4uqk09/riIaBz2OX9GYWAUk0cHfJzv1Yq6AxP1U8aJmmqtXaw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@mojaloop/inter-scheme-proxy-cache-lib/-/inter-scheme-proxy-cache-lib-1.2.0.tgz", + "integrity": "sha512-rxBYukTy9s/MpcSSKnVpvYGH+uLBf77fgQ/vuQQEAT5+/FtC5rBWvvTvaq95JupqysM+XRsNIctaZUUi1lZThQ==", "dependencies": { "@mojaloop/central-services-logger": "^11.3.1", "ajv": "^8.16.0", diff --git a/package.json b/package.json index 52cb7429..597918b0 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,7 @@ "@mojaloop/central-services-shared": "18.5.0-snapshot.2", "@mojaloop/central-services-stream": "11.3.1", "@mojaloop/event-sdk": "14.1.1", - "@mojaloop/inter-scheme-proxy-cache-lib": "^1.1.0", + "@mojaloop/inter-scheme-proxy-cache-lib": "^1.2.0", "@mojaloop/ml-number": "11.2.4", "@mojaloop/sdk-standard-components": "18.1.0", "ajv": "8.16.0", diff --git a/src/lib/proxy.js b/src/lib/proxy.js index ed5fd396..2e95b31b 100644 --- a/src/lib/proxy.js +++ b/src/lib/proxy.js @@ -1,3 +1,32 @@ +/***** + License + -------------- + Copyright © 2020 Mojaloop Foundation + + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 + (the "License") and you may not use these files except in compliance with the [License](http://www.apache.org/licenses/LICENSE-2.0). + + You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](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](http://www.apache.org/licenses/LICENSE-2.0). + + 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 + + * Steven Oderayi + -------------- + ******/ + const process = require('node:process') const { createProxyCache } = require('@mojaloop/inter-scheme-proxy-cache-lib') @@ -9,7 +38,7 @@ const createProxyClient = async ({ proxyCacheConfig, logger }) => { const proxyClient = createProxyCache(proxyCacheConfig.type, proxyCacheConfig.proxyConfig) // If lazyConnect is false, wait for the connection to be established - if (Object.prototype.hasOwnProperty.call(proxyCacheConfig.proxyConfig, 'lazyConnect') && !proxyCacheConfig.proxyConfig.lazyConnect) { + if (!proxyCacheConfig.proxyConfig.lazyConnect) { const timer = setTimeout(() => { timedOut = true logger.error('Unable to connect to proxy cache. Exiting...', { proxyCacheConfig }) @@ -22,6 +51,8 @@ const createProxyClient = async ({ proxyCacheConfig, logger }) => { clearTimeout(timer) if (timedOut) process.exit(1) + + logger.isInfoEnabled && logger.info('Connected to proxy cache') } return proxyClient diff --git a/test/integration/postRequest.test.js b/test/integration/postRequest.test.js index 27f0ba37..91e67cca 100644 --- a/test/integration/postRequest.test.js +++ b/test/integration/postRequest.test.js @@ -1,4 +1,34 @@ +/***** + License + -------------- + Copyright © 2020 Mojaloop Foundation + + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 + (the "License") and you may not use these files except in compliance with the [License](http://www.apache.org/licenses/LICENSE-2.0). + + You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](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](http://www.apache.org/licenses/LICENSE-2.0). + + 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 + + * Steven Oderayi + -------------- + ******/ + const { Producer } = require('@mojaloop/central-services-stream').Util +const { createProxyClient } = require('../../src/lib/proxy') const Config = require('../../src/lib/config') const dto = require('../../src/lib/dto') @@ -9,13 +39,14 @@ const uuid = require('crypto').randomUUID const hubClient = new MockServerClient() const base64Encode = (data) => Buffer.from(data).toString('base64') const TEST_TIMEOUT = 20_000 +const WAIT_TIMEOUT = 3_000 const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)) describe('POST request tests --> ', () => { jest.setTimeout(TEST_TIMEOUT) - const { kafkaConfig } = new Config() + const { kafkaConfig, proxyCache } = new Config() beforeEach(async () => { await hubClient.clearHistory() @@ -46,7 +77,7 @@ describe('POST request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - await wait(3000) + await wait(WAIT_TIMEOUT) response = await hubClient.getHistory() expect(response.data.history.length).toBe(1) @@ -76,7 +107,7 @@ describe('POST request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - await wait(3000) + await wait(WAIT_TIMEOUT) response = await hubClient.getHistory() expect(response.data.history.length).toBe(1) @@ -138,7 +169,7 @@ describe('POST request tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - await wait(3000) + await wait(WAIT_TIMEOUT) response = await hubClient.getHistory() expect(response.data.history.length).toBe(1) @@ -148,4 +179,53 @@ describe('POST request tests --> ', () => { expect(body.errorInformation.errorCode).toBe('3202') expect(body.errorInformation.errorDescription).toBe(`Payer FSP ID not found - Unsupported participant '${message.from}'`) }) + + test('should forward POST /quotes request to proxy if the payee dfsp is not registered in the hub', async () => { + let response = await hubClient.getHistory() + expect(response.data.history.length).toBe(0) + + const { topic, config } = kafkaConfig.PRODUCER.QUOTE.POST + const topicConfig = dto.topicConfigDto({ topicName: topic }) + const from = 'pinkbank' + // redbank not in the hub db + const to = 'redbank' + + // register proxy representative for redbank + const proxyId = 'redbankproxy' + const proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) + const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) + + // assert that the proxy representative is mapped in the cache + const key = `dfsp:${to}` + const representative = await proxyClient.redisClient.get(key) + + expect(isAdded).toBe(true) + expect(representative).toBe(proxyId) + + const payload = { + quoteId: uuid(), + transactionId: uuid(), + amountType: 'SEND', + amount: { amount: '100', currency: 'USD' }, + transactionType: { scenario: 'DEPOSIT', initiator: 'PAYER', initiatorType: 'CONSUMER' }, + payer: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from } }, + payee: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '123456789', fspId: to } } + } + const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) }) + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + + await wait(WAIT_TIMEOUT) + + response = await hubClient.getHistory() + expect(response.data.history.length).toBe(1) + + const request = response.data.history[0] + expect(request.url).toBe(`/${proxyId}/quotes`) + expect(request.body).toEqual(payload) + expect(request.headers['fspiop-source']).toBe(from) + expect(request.headers['fspiop-destination']).toBe(to) + + await proxyClient.disconnect() + }) }) diff --git a/test/integration/putCallback.test.js b/test/integration/putCallback.test.js index 2915f41a..7f916393 100644 --- a/test/integration/putCallback.test.js +++ b/test/integration/putCallback.test.js @@ -1,4 +1,35 @@ +/***** + License + -------------- + Copyright © 2020 Mojaloop Foundation + + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 + (the "License") and you may not use these files except in compliance with the [License](http://www.apache.org/licenses/LICENSE-2.0). + + You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](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](http://www.apache.org/licenses/LICENSE-2.0). + + 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 + * Steven Oderayi + -------------- + ******/ + const { Producer } = require('@mojaloop/central-services-stream').Util +const { createProxyClient } = require('../../src/lib/proxy') const Config = require('../../src/lib/config') const dto = require('../../src/lib/dto') @@ -8,7 +39,8 @@ const uuid = require('crypto').randomUUID const hubClient = new MockServerClient() const base64Encode = (data) => Buffer.from(data).toString('base64') -const TEST_TIMEOUT = 20000 +const TEST_TIMEOUT = 20_000 +const WAIT_TIMEOUT = 3_000 const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)) @@ -40,7 +72,7 @@ const createQuote = async ({ } describe('PUT callback Tests --> ', () => { - const { kafkaConfig } = new Config() + const { kafkaConfig, proxyCache } = new Config() beforeEach(async () => { await hubClient.clearHistory() @@ -53,7 +85,7 @@ describe('PUT callback Tests --> ', () => { test('should handle the JWS signing when a switch error event is produced to the PUT topic', async () => { // create test quote to prevent db (row reference) error on PUT request const quoteCreated = await createQuote() - await wait(3000) + await wait(WAIT_TIMEOUT) await hubClient.clearHistory() let response = await hubClient.getHistory() @@ -66,7 +98,7 @@ describe('PUT callback Tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - await wait(3000) + await wait(WAIT_TIMEOUT) response = await hubClient.getHistory() expect(response.data.history.length).toBe(1) @@ -81,7 +113,7 @@ describe('PUT callback Tests --> ', () => { test('should pass validation for PUT /quotes/{ID} request if request transferAmount/payeeReceiveAmount currency is registered (position account exists) for the payee pariticpant', async () => { // create test quote to prevent db (row reference) error on PUT request const quoteCreated = await createQuote() - await wait(3000) + await wait(WAIT_TIMEOUT) await hubClient.clearHistory() let response = await hubClient.getHistory() @@ -105,7 +137,7 @@ describe('PUT callback Tests --> ', () => { const isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - await wait(3000) + await wait(WAIT_TIMEOUT) response = await hubClient.getHistory() expect(response.data.history.length).toBe(1) @@ -118,7 +150,7 @@ describe('PUT callback Tests --> ', () => { // test the same scenario with only transferAmount set // create test quote to prevent db (row reference) error on PUT request const quoteCreated = await createQuote() - await wait(3000) + await wait(WAIT_TIMEOUT) await hubClient.clearHistory() let response = await hubClient.getHistory() @@ -169,7 +201,7 @@ describe('PUT callback Tests --> ', () => { isOk = await Producer.produceMessage(message, topicConfig, config) expect(isOk).toBe(true) - await wait(3000) + await wait(WAIT_TIMEOUT) response = await hubClient.getHistory() expect(response.data.history.length).toBe(1) @@ -179,4 +211,50 @@ describe('PUT callback Tests --> ', () => { expect(body2.errorInformation.errorCode).toBe('3201') expect(body2.errorInformation.errorDescription).toBe(`Destination FSP Error - Unsupported participant '${message.from}'`) }, TEST_TIMEOUT) + + test('should forward PUT /quotes/{ID} request to proxy if the payer dfsp is not registered in the hub', async () => { + let response = await hubClient.getHistory() + expect(response.data.history.length).toBe(0) + + const { topic, config } = kafkaConfig.PRODUCER.QUOTE.PUT + const topicConfig = dto.topicConfigDto({ topicName: topic }) + const from = 'greenbank' + // redbank not in the hub db + const to = 'redbank' + + // register proxy representative for redbank + const proxyId = 'redbankproxy' + const proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) + const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) + + // assert that the proxy representative is mapped in the cache + const key = `dfsp:${to}` + const representative = await proxyClient.redisClient.get(key) + + expect(isAdded).toBe(true) + expect(representative).toBe(proxyId) + + const payload = { + transferAmount: { amount: '100', currency: 'USD' }, + ilpPacket: 'test', + condition: 'test' + } + const message = mocks.kafkaMessagePayloadDto({ from, to, id: uuid(), payloadBase64: base64Encode(JSON.stringify(payload)) }) + delete message.content.headers.accept + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + + await wait(WAIT_TIMEOUT) + + response = await hubClient.getHistory() + expect(response.data.history.length).toBe(1) + + const request = response.data.history[0] + expect(request.url).toBe(`/${proxyId}/quotes/${message.id}`) + expect(request.body).toEqual(payload) + expect(request.headers['fspiop-source']).toBe(from) + expect(request.headers['fspiop-destination']).toBe(to) + + await proxyClient.disconnect() + }) }) diff --git a/test/integration/scripts/env.sh b/test/integration/scripts/env.sh index d5ac4c4e..e654df22 100755 --- a/test/integration/scripts/env.sh +++ b/test/integration/scripts/env.sh @@ -8,7 +8,7 @@ export HUB_NAME=$(cat "$DEFAULT_CONFIG_FILE" | jq -r '.HUB_PARTICIPANT.NAME') export MOCKSERVER_HOST=mock-hub export MOCKSERVER_PORT=7777 -export FSPList=("greenbank" "pinkbank") +export FSPList=("greenbank" "pinkbank" "redbankproxy") export DEFAULT_NET_DEBIT_CAP=1000 export CENTRAL_LEDGER_ADMIN_URI_PREFIX=http export CENTRAL_LEDGER_ADMIN_HOST=127.0.0.1 diff --git a/test/integration/scripts/populateTestData.sh b/test/integration/scripts/populateTestData.sh index 7d346018..aa03a7e0 100755 --- a/test/integration/scripts/populateTestData.sh +++ b/test/integration/scripts/populateTestData.sh @@ -12,6 +12,10 @@ then CWD="." fi +function isProxy() { + [[ "$1" =~ proxy$ ]] +} + echo "Loading env vars..." source $CWD/env.sh @@ -133,6 +137,8 @@ curl -i -X POST "${CENTRAL_LEDGER_ADMIN_URI_PREFIX}://${CENTRAL_LEDGER_ADMIN_HOS \"currency\":\"ZMW\" }" +if ! isProxy $FSP; then + echo echo "Setting limits and initial position for '$FSP'" echo "---------------------------------------------------------------------" @@ -162,7 +168,6 @@ curl -i -X POST "${CENTRAL_LEDGER_ADMIN_URI_PREFIX}://${CENTRAL_LEDGER_ADMIN_HOS \"initialPosition\": 0 }" - echo echo "Get accounts list for '$FSP' and filter by ledgerAccountType='SETTLEMENT'" echo "---------------------------------------------------------------------" @@ -221,6 +226,8 @@ curl -i -X POST "${CENTRAL_LEDGER_ADMIN_URI_PREFIX}://${CENTRAL_LEDGER_ADMIN_HOS echo "---------------------------------------------------------------------" curl -X GET "${CENTRAL_LEDGER_ADMIN_URI_PREFIX}://${CENTRAL_LEDGER_ADMIN_HOST}:${CENTRAL_LEDGER_ADMIN_PORT}${CENTRAL_LEDGER_ADMIN_BASE}participants/${FSP}/limits" -H 'Cache-Control: no-cache' +fi + echo echo "Set callback URIs for each FSP '$FSP'" echo "---------------------------------------------------------------------" diff --git a/test/unit/lib/proxy.test.js b/test/unit/lib/proxy.test.js index 0b82a812..cc10ed5b 100644 --- a/test/unit/lib/proxy.test.js +++ b/test/unit/lib/proxy.test.js @@ -1,3 +1,32 @@ +/***** + License + -------------- + Copyright © 2020 Mojaloop Foundation + + The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 + (the "License") and you may not use these files except in compliance with the [License](http://www.apache.org/licenses/LICENSE-2.0). + + You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](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](http://www.apache.org/licenses/LICENSE-2.0). + + 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 + + * Steven Oderayi + -------------- + ******/ + jest.mock('@mojaloop/inter-scheme-proxy-cache-lib', () => ({ createProxyCache: jest.fn() })) From 1685776730fda918d07fc4afce09b8a24d1c5c2e Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Wed, 3 Jul 2024 19:49:47 +0100 Subject: [PATCH 20/51] feat: minor refactor --- package-lock.json | 8 ++++---- package.json | 2 +- src/lib/proxy.js | 32 ++++++++++++++++++-------------- test/unit/lib/proxy.test.js | 4 +--- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index 88fab332..6d005910 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,7 @@ "rc": "1.2.8" }, "devDependencies": { - "audit-ci": "^7.0.1", + "audit-ci": "^7.1.0", "eslint": "8.16.0", "eslint-config-standard": "17.1.0", "eslint-plugin-jest": "28.6.0", @@ -3351,9 +3351,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/audit-ci": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/audit-ci/-/audit-ci-7.0.1.tgz", - "integrity": "sha512-NAZuQYyZHmtrNGpS4qfUp8nFvB+6UdfSOg7NUcsyvuDVfulXH3lpnN2PcXOUj7Jr3epAoQ6BCpXmjMODC8SBgQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/audit-ci/-/audit-ci-7.1.0.tgz", + "integrity": "sha512-PjjEejlST57S/aDbeWLic0glJ8CNl/ekY3kfGFPMrPkmuaYaDKcMH0F9x9yS9Vp6URhuefSCubl/G0Y2r6oP0g==", "dev": true, "dependencies": { "cross-spawn": "^7.0.3", diff --git a/package.json b/package.json index 597918b0..66ed20c6 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "rc": "1.2.8" }, "devDependencies": { - "audit-ci": "^7.0.1", + "audit-ci": "^7.1.0", "eslint": "8.16.0", "eslint-config-standard": "17.1.0", "eslint-plugin-jest": "28.6.0", diff --git a/src/lib/proxy.js b/src/lib/proxy.js index 2e95b31b..7d333bad 100644 --- a/src/lib/proxy.js +++ b/src/lib/proxy.js @@ -33,29 +33,33 @@ const { createProxyCache } = require('@mojaloop/inter-scheme-proxy-cache-lib') const createProxyClient = async ({ proxyCacheConfig, logger }) => { const timeout = Number(proxyCacheConfig.timeout) const retryInterval = Number(proxyCacheConfig.retryInterval) - let timedOut = false const proxyClient = createProxyCache(proxyCacheConfig.type, proxyCacheConfig.proxyConfig) - // If lazyConnect is false, wait for the connection to be established if (!proxyCacheConfig.proxyConfig.lazyConnect) { - const timer = setTimeout(() => { - timedOut = true - logger.error('Unable to connect to proxy cache. Exiting...', { proxyCacheConfig }) - }, timeout) + await waitForConnection({ proxyClient, timeout, retryInterval, logger }) + } + + return proxyClient +} - while (!proxyClient.isConnected) { - if (timedOut) break - await new Promise(resolve => setTimeout(resolve, retryInterval)) - } +const waitForConnection = async ({ proxyClient, timeout, retryInterval, logger }) => { + let timedOut = false - clearTimeout(timer) - if (timedOut) process.exit(1) + const timer = setTimeout(() => { + timedOut = true + logger.error('Unable to connect to proxy cache. Exiting...') + }, timeout) - logger.isInfoEnabled && logger.info('Connected to proxy cache') + while (!proxyClient.isConnected) { + if (timedOut) break + await new Promise(resolve => setTimeout(resolve, retryInterval)) } - return proxyClient + clearTimeout(timer) + if (timedOut) process.exit(1) + + logger.isInfoEnabled && logger.info('Connected to proxy cache') } module.exports = { createProxyClient } diff --git a/test/unit/lib/proxy.test.js b/test/unit/lib/proxy.test.js index cc10ed5b..85cb9e88 100644 --- a/test/unit/lib/proxy.test.js +++ b/test/unit/lib/proxy.test.js @@ -83,9 +83,7 @@ describe('createProxyClient', () => { await createProxyClient({ proxyCacheConfig: modifiedConfig, logger }) - expect(logger.error).toHaveBeenCalledWith('Unable to connect to proxy cache. Exiting...', { - proxyCacheConfig: modifiedConfig - }) + expect(logger.error).toHaveBeenCalledWith('Unable to connect to proxy cache. Exiting...') expect(mockExit).toHaveBeenCalledWith(1) mockExit.mockRestore() From edc859031c49fd650a91dde8996f3c3139d94601 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Wed, 3 Jul 2024 20:07:06 +0100 Subject: [PATCH 21/51] feat: minor refactor --- docker-compose.yml | 6 +++--- src/handlers/init.js | 3 +++ src/model/quotes.js | 2 ++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index eb8d1bb3..0c0fef34 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,7 +46,7 @@ services: quoting-service-handler: <<: *quotingServiceBase - command: npm run start:handlers:debug + command: npm run start:handlers ports: - "3003:3003" - "29229:9229" @@ -101,8 +101,8 @@ services: redis: image: "redis:6.2.4-alpine" container_name: redis - ports: - - "6379:6379" + # ports: + # - "6379:6379" # mockserver: # image: jamesdbloom/mockserver diff --git a/src/handlers/init.js b/src/handlers/init.js index cd2f616e..a7fa3313 100644 --- a/src/handlers/init.js +++ b/src/handlers/init.js @@ -46,6 +46,9 @@ const startFn = async (handlerList) => { const stopFn = async () => { await monitoringServer?.stop() + + proxyClient?.isConnected && await proxyClient.disconnect() + /* istanbul ignore next */ if (consumersMap) { await Promise.all(Object.values(consumersMap).map(consumer => consumer.disconnect())) diff --git a/src/model/quotes.js b/src/model/quotes.js index 32af7ddd..f5dbf6e8 100644 --- a/src/model/quotes.js +++ b/src/model/quotes.js @@ -169,6 +169,8 @@ class QuotesModel { throw ErrorHandler.CreateInternalServerFSPIOPError('Missing quoteRequest', null, fspiopSource) } + if (this.proxyClient && !this.proxyClient.isConnected) await this.proxyClient.connect() + // In fspiop api spec 2.0, to support FX, `supportedCurrencies` can be optionally passed in via the payer property. // If `supportedCurrencies` is present, then payer FSP must have position accounts for all those currencies. if (quoteRequest.payer.supportedCurrencies && quoteRequest.payer.supportedCurrencies.length > 0) { From 4ea92608561577aa6e8543a74a7903a13531f993 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 4 Jul 2024 05:02:43 +0100 Subject: [PATCH 22/51] feat: minor refactor --- src/model/quotes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/model/quotes.js b/src/model/quotes.js index f5dbf6e8..cbeb5c47 100644 --- a/src/model/quotes.js +++ b/src/model/quotes.js @@ -169,7 +169,8 @@ class QuotesModel { throw ErrorHandler.CreateInternalServerFSPIOPError('Missing quoteRequest', null, fspiopSource) } - if (this.proxyClient && !this.proxyClient.isConnected) await this.proxyClient.connect() + // Ensure the proxy client is connected if we need to use it down the road + if (this.proxyClient?.isConnected === false) await this.proxyClient.connect() // In fspiop api spec 2.0, to support FX, `supportedCurrencies` can be optionally passed in via the payer property. // If `supportedCurrencies` is present, then payer FSP must have position accounts for all those currencies. From 70a42f5cebc341adcebc258d136f4e833660eb0b Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 4 Jul 2024 08:40:12 +0100 Subject: [PATCH 23/51] ci: update docker-compose config --- docker-compose.yml | 4 ++-- src/lib/proxy.js | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 0c0fef34..8700abae 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -101,8 +101,8 @@ services: redis: image: "redis:6.2.4-alpine" container_name: redis - # ports: - # - "6379:6379" + ports: + - "6379:6379" # mockserver: # image: jamesdbloom/mockserver diff --git a/src/lib/proxy.js b/src/lib/proxy.js index 7d333bad..d6d93a7c 100644 --- a/src/lib/proxy.js +++ b/src/lib/proxy.js @@ -26,8 +26,6 @@ * Steven Oderayi -------------- ******/ - -const process = require('node:process') const { createProxyCache } = require('@mojaloop/inter-scheme-proxy-cache-lib') const createProxyClient = async ({ proxyCacheConfig, logger }) => { @@ -57,6 +55,7 @@ const waitForConnection = async ({ proxyClient, timeout, retryInterval, logger } } clearTimeout(timer) + if (timedOut) process.exit(1) logger.isInfoEnabled && logger.info('Connected to proxy cache') From 7680bb0cbf4cf723ceb49658d4674f389a90134a Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 4 Jul 2024 08:58:26 +0100 Subject: [PATCH 24/51] ci: add healthcheck to redis --- docker-compose.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 8700abae..6a713169 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -103,6 +103,11 @@ services: container_name: redis ports: - "6379:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 30s + timeout: 10s + retries: 3 # mockserver: # image: jamesdbloom/mockserver From 8eecdc90e96e8b5baa086204548e1a03a04ab10d Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 4 Jul 2024 09:09:19 +0100 Subject: [PATCH 25/51] test: update integration test --- test/integration/postRequest.test.js | 72 +++++++++++++++------------- test/integration/putCallback.test.js | 66 +++++++++++++------------ 2 files changed, 73 insertions(+), 65 deletions(-) diff --git a/test/integration/postRequest.test.js b/test/integration/postRequest.test.js index 91e67cca..c1489ac6 100644 --- a/test/integration/postRequest.test.js +++ b/test/integration/postRequest.test.js @@ -192,40 +192,44 @@ describe('POST request tests --> ', () => { // register proxy representative for redbank const proxyId = 'redbankproxy' - const proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) - const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) - - // assert that the proxy representative is mapped in the cache - const key = `dfsp:${to}` - const representative = await proxyClient.redisClient.get(key) - - expect(isAdded).toBe(true) - expect(representative).toBe(proxyId) - - const payload = { - quoteId: uuid(), - transactionId: uuid(), - amountType: 'SEND', - amount: { amount: '100', currency: 'USD' }, - transactionType: { scenario: 'DEPOSIT', initiator: 'PAYER', initiatorType: 'CONSUMER' }, - payer: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from } }, - payee: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '123456789', fspId: to } } + let proxyClient + + try { + proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) + const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) + + // assert that the proxy representative is mapped in the cache + const key = `dfsp:${to}` + const representative = await proxyClient.redisClient.get(key) + + expect(isAdded).toBe(true) + expect(representative).toBe(proxyId) + + const payload = { + quoteId: uuid(), + transactionId: uuid(), + amountType: 'SEND', + amount: { amount: '100', currency: 'USD' }, + transactionType: { scenario: 'DEPOSIT', initiator: 'PAYER', initiatorType: 'CONSUMER' }, + payer: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from } }, + payee: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '123456789', fspId: to } } + } + const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) }) + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + + await wait(WAIT_TIMEOUT) + + response = await hubClient.getHistory() + expect(response.data.history.length).toBe(1) + + const request = response.data.history[0] + expect(request.url).toBe(`/${proxyId}/quotes`) + expect(request.body).toEqual(payload) + expect(request.headers['fspiop-source']).toBe(from) + expect(request.headers['fspiop-destination']).toBe(to) + } finally { + await proxyClient.disconnect() } - const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) }) - const isOk = await Producer.produceMessage(message, topicConfig, config) - expect(isOk).toBe(true) - - await wait(WAIT_TIMEOUT) - - response = await hubClient.getHistory() - expect(response.data.history.length).toBe(1) - - const request = response.data.history[0] - expect(request.url).toBe(`/${proxyId}/quotes`) - expect(request.body).toEqual(payload) - expect(request.headers['fspiop-source']).toBe(from) - expect(request.headers['fspiop-destination']).toBe(to) - - await proxyClient.disconnect() }) }) diff --git a/test/integration/putCallback.test.js b/test/integration/putCallback.test.js index 7f916393..ac10a765 100644 --- a/test/integration/putCallback.test.js +++ b/test/integration/putCallback.test.js @@ -224,37 +224,41 @@ describe('PUT callback Tests --> ', () => { // register proxy representative for redbank const proxyId = 'redbankproxy' - const proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) - const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) - - // assert that the proxy representative is mapped in the cache - const key = `dfsp:${to}` - const representative = await proxyClient.redisClient.get(key) - - expect(isAdded).toBe(true) - expect(representative).toBe(proxyId) - - const payload = { - transferAmount: { amount: '100', currency: 'USD' }, - ilpPacket: 'test', - condition: 'test' + let proxyClient + + try { + proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) + const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) + + // assert that the proxy representative is mapped in the cache + const key = `dfsp:${to}` + const representative = await proxyClient.redisClient.get(key) + + expect(isAdded).toBe(true) + expect(representative).toBe(proxyId) + + const payload = { + transferAmount: { amount: '100', currency: 'USD' }, + ilpPacket: 'test', + condition: 'test' + } + const message = mocks.kafkaMessagePayloadDto({ from, to, id: uuid(), payloadBase64: base64Encode(JSON.stringify(payload)) }) + delete message.content.headers.accept + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + + await wait(WAIT_TIMEOUT) + + response = await hubClient.getHistory() + expect(response.data.history.length).toBe(1) + + const request = response.data.history[0] + expect(request.url).toBe(`/${proxyId}/quotes/${message.id}`) + expect(request.body).toEqual(payload) + expect(request.headers['fspiop-source']).toBe(from) + expect(request.headers['fspiop-destination']).toBe(to) + } finally { + await proxyClient.disconnect() } - const message = mocks.kafkaMessagePayloadDto({ from, to, id: uuid(), payloadBase64: base64Encode(JSON.stringify(payload)) }) - delete message.content.headers.accept - const isOk = await Producer.produceMessage(message, topicConfig, config) - expect(isOk).toBe(true) - - await wait(WAIT_TIMEOUT) - - response = await hubClient.getHistory() - expect(response.data.history.length).toBe(1) - - const request = response.data.history[0] - expect(request.url).toBe(`/${proxyId}/quotes/${message.id}`) - expect(request.body).toEqual(payload) - expect(request.headers['fspiop-source']).toBe(from) - expect(request.headers['fspiop-destination']).toBe(to) - - await proxyClient.disconnect() }) }) From 484e07f798a84b16ef562f4abc29af2e709503c9 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 4 Jul 2024 09:21:40 +0100 Subject: [PATCH 26/51] test: update integration test --- test/integration/postRequest.test.js | 1 + test/integration/putCallback.test.js | 1 + 2 files changed, 2 insertions(+) diff --git a/test/integration/postRequest.test.js b/test/integration/postRequest.test.js index c1489ac6..f3baf967 100644 --- a/test/integration/postRequest.test.js +++ b/test/integration/postRequest.test.js @@ -229,6 +229,7 @@ describe('POST request tests --> ', () => { expect(request.headers['fspiop-source']).toBe(from) expect(request.headers['fspiop-destination']).toBe(to) } finally { + console.log(JSON.stringify(response.data.history, null, 2)) await proxyClient.disconnect() } }) diff --git a/test/integration/putCallback.test.js b/test/integration/putCallback.test.js index ac10a765..0956b8ce 100644 --- a/test/integration/putCallback.test.js +++ b/test/integration/putCallback.test.js @@ -258,6 +258,7 @@ describe('PUT callback Tests --> ', () => { expect(request.headers['fspiop-source']).toBe(from) expect(request.headers['fspiop-destination']).toBe(to) } finally { + console.log(JSON.stringify(response.data.history, null, 2)) await proxyClient.disconnect() } }) From cec18dff4951b9cb4a4da157a8e6afa35f707cdf Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 4 Jul 2024 10:26:10 +0100 Subject: [PATCH 27/51] test: update integration test --- test/integration/putCallback.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/integration/putCallback.test.js b/test/integration/putCallback.test.js index 0956b8ce..e017d49b 100644 --- a/test/integration/putCallback.test.js +++ b/test/integration/putCallback.test.js @@ -250,8 +250,7 @@ describe('PUT callback Tests --> ', () => { await wait(WAIT_TIMEOUT) response = await hubClient.getHistory() - expect(response.data.history.length).toBe(1) - + const request = response.data.history[0] expect(request.url).toBe(`/${proxyId}/quotes/${message.id}`) expect(request.body).toEqual(payload) From 07a0b52c5e327a168f42c15b64debb5d10e60bd4 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 4 Jul 2024 10:26:20 +0100 Subject: [PATCH 28/51] test: update integration test --- test/integration/postRequest.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/integration/postRequest.test.js b/test/integration/postRequest.test.js index f3baf967..c26fa916 100644 --- a/test/integration/postRequest.test.js +++ b/test/integration/postRequest.test.js @@ -221,8 +221,7 @@ describe('POST request tests --> ', () => { await wait(WAIT_TIMEOUT) response = await hubClient.getHistory() - expect(response.data.history.length).toBe(1) - + const request = response.data.history[0] expect(request.url).toBe(`/${proxyId}/quotes`) expect(request.body).toEqual(payload) From 2b83d1809ab93168f9afc4d30d05c3beec8c1d59 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 4 Jul 2024 10:32:52 +0100 Subject: [PATCH 29/51] chore: linting --- test/integration/postRequest.test.js | 3 ++- test/integration/putCallback.test.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/integration/postRequest.test.js b/test/integration/postRequest.test.js index c26fa916..c8f88879 100644 --- a/test/integration/postRequest.test.js +++ b/test/integration/postRequest.test.js @@ -221,7 +221,8 @@ describe('POST request tests --> ', () => { await wait(WAIT_TIMEOUT) response = await hubClient.getHistory() - + expect(response.data.history.length).toBeLessThanOrEqual(2) + const request = response.data.history[0] expect(request.url).toBe(`/${proxyId}/quotes`) expect(request.body).toEqual(payload) diff --git a/test/integration/putCallback.test.js b/test/integration/putCallback.test.js index e017d49b..724c65ea 100644 --- a/test/integration/putCallback.test.js +++ b/test/integration/putCallback.test.js @@ -250,7 +250,8 @@ describe('PUT callback Tests --> ', () => { await wait(WAIT_TIMEOUT) response = await hubClient.getHistory() - + expect(response.data.history.length).toBeLessThanOrEqual(2) + const request = response.data.history[0] expect(request.url).toBe(`/${proxyId}/quotes/${message.id}`) expect(request.body).toEqual(payload) From d9658f174efafe62283225c220c2731b823aab6e Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 4 Jul 2024 10:37:42 +0100 Subject: [PATCH 30/51] test: update integration test --- test/integration/postRequest.test.js | 2 +- test/integration/putCallback.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/postRequest.test.js b/test/integration/postRequest.test.js index c8f88879..fc76f986 100644 --- a/test/integration/postRequest.test.js +++ b/test/integration/postRequest.test.js @@ -221,7 +221,7 @@ describe('POST request tests --> ', () => { await wait(WAIT_TIMEOUT) response = await hubClient.getHistory() - expect(response.data.history.length).toBeLessThanOrEqual(2) + expect([1, 2]).toContain(response.data.history.length) const request = response.data.history[0] expect(request.url).toBe(`/${proxyId}/quotes`) diff --git a/test/integration/putCallback.test.js b/test/integration/putCallback.test.js index 724c65ea..58291587 100644 --- a/test/integration/putCallback.test.js +++ b/test/integration/putCallback.test.js @@ -250,7 +250,7 @@ describe('PUT callback Tests --> ', () => { await wait(WAIT_TIMEOUT) response = await hubClient.getHistory() - expect(response.data.history.length).toBeLessThanOrEqual(2) + expect([1, 2]).toContain(response.data.history.length) const request = response.data.history[0] expect(request.url).toBe(`/${proxyId}/quotes/${message.id}`) From 5892c3946336bcc04bb507c0e203231d36ed0e19 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 4 Jul 2024 12:07:42 +0100 Subject: [PATCH 31/51] test: add integration tests for fx quotes --- docker-compose.yml | 4 +- src/model/bulkQuotes.js | 9 ++- src/model/fxQuotes.js | 12 +++- test/integration/postRequest.test.js | 61 +++++++++++++++++++- test/integration/putCallback.test.js | 58 ++++++++++++++++++- test/integration/scripts/populateTestData.sh | 9 +++ 6 files changed, 145 insertions(+), 8 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 6a713169..ec1bfc72 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,7 +46,7 @@ services: quoting-service-handler: <<: *quotingServiceBase - command: npm run start:handlers + command: npm run start:handlers:debug ports: - "3003:3003" - "29229:9229" @@ -54,7 +54,7 @@ services: central-ledger: - image: mojaloop/central-ledger + image: mojaloop/central-ledger:v17.7.0-snapshot.20 container_name: qs_central-ledger ports: - "3001:3001" diff --git a/src/model/bulkQuotes.js b/src/model/bulkQuotes.js index ed8c2cf1..a27dd3dd 100644 --- a/src/model/bulkQuotes.js +++ b/src/model/bulkQuotes.js @@ -59,6 +59,7 @@ class BulkQuotesModel { this.config = config this.db = config.db this.requestId = config.requestId + this.proxyClient = config.proxyClient } /** @@ -68,7 +69,13 @@ class BulkQuotesModel { */ async validateBulkQuoteRequest (fspiopSource, fspiopDestination, bulkQuoteRequest) { await this.db.getParticipant(fspiopSource, LOCAL_ENUM.PAYER_DFSP, bulkQuoteRequest.individualQuotes[0].amount.currency, ENUM.Accounts.LedgerAccountType.POSITION) - await this.db.getParticipant(fspiopDestination, LOCAL_ENUM.PAYEE_DFSP, bulkQuoteRequest.individualQuotes[0].amount.currency, ENUM.Accounts.LedgerAccountType.POSITION) + + // Ensure the proxy client is connected + if (this.proxyClient?.isConnected === false) await this.proxyClient.connect() + // if the payee dfsp has a proxy cache entry, we do not validate the dfsp here + if (!(await this.proxyClient?.lookupProxyByDfspId(fspiopDestination))) { + await this.db.getParticipant(fspiopDestination, LOCAL_ENUM.PAYEE_DFSP, bulkQuoteRequest.individualQuotes[0].amount.currency, ENUM.Accounts.LedgerAccountType.POSITION) + } } /** diff --git a/src/model/fxQuotes.js b/src/model/fxQuotes.js index 5e72bdbb..1efba8d1 100644 --- a/src/model/fxQuotes.js +++ b/src/model/fxQuotes.js @@ -49,9 +49,15 @@ class FxQuotesModel { */ async validateFxQuoteRequest (fspiopDestination, fxQuoteRequest) { const currencies = [fxQuoteRequest.conversionTerms.sourceAmount.currency, fxQuoteRequest.conversionTerms.targetAmount.currency] - await Promise.all(currencies.map(async (currency) => { - await this.db.getParticipant(fspiopDestination, LOCAL_ENUM.COUNTERPARTY_FSP, currency, ENUM.Accounts.LedgerAccountType.POSITION) - })) + + // Ensure the proxy client is connected + if (this.proxyClient?.isConnected === false) await this.proxyClient.connect() + // if the payee dfsp has a proxy cache entry, we do not validate the dfsp here + if (!(await this.proxyClient?.lookupProxyByDfspId(fspiopDestination))) { + await Promise.all(currencies.map(async (currency) => { + await this.db.getParticipant(fspiopDestination, LOCAL_ENUM.COUNTERPARTY_FSP, currency, ENUM.Accounts.LedgerAccountType.POSITION) + })) + } } /** diff --git a/test/integration/postRequest.test.js b/test/integration/postRequest.test.js index fc76f986..0d89564d 100644 --- a/test/integration/postRequest.test.js +++ b/test/integration/postRequest.test.js @@ -229,7 +229,66 @@ describe('POST request tests --> ', () => { expect(request.headers['fspiop-source']).toBe(from) expect(request.headers['fspiop-destination']).toBe(to) } finally { - console.log(JSON.stringify(response.data.history, null, 2)) + await proxyClient.disconnect() + } + }) + + test('should forward POST /fxQuotes request to proxy if the payee dfsp is not registered in the hub', async () => { + let response = await hubClient.getHistory() + expect(response.data.history.length).toBe(0) + + const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.POST + const topicConfig = dto.topicConfigDto({ topicName: topic }) + const from = 'pinkbank' + // redbank not in the hub db + const to = 'redbank' + + // register proxy representative for redbank + const proxyId = 'redbankproxy' + let proxyClient + + try { + proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) + const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) + + // assert that the proxy representative is mapped in the cache + const key = `dfsp:${to}` + const representative = await proxyClient.redisClient.get(key) + + expect(isAdded).toBe(true) + expect(representative).toBe(proxyId) + + const payload = { + conversionRequestId: uuid(), + conversionTerms: { + conversionId: uuid(), + initiatingFsp: from, + counterPartyFsp: to, + amountType: 'SEND', + sourceAmount: { + currency: 'USD', + amount: 300 + }, + targetAmount: { + currency: 'TZS' + } + } + } + const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.conversionRequestId, payloadBase64: base64Encode(JSON.stringify(payload)) }) + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + + await wait(WAIT_TIMEOUT) + + response = await hubClient.getHistory() + expect(response.data.history.length).toBe(1) + + const request = response.data.history[0] + expect(request.url).toBe(`/${proxyId}/fxQuotes`) + expect(request.body).toEqual(payload) + expect(request.headers['fspiop-source']).toBe(from) + expect(request.headers['fspiop-destination']).toBe(to) + } finally { await proxyClient.disconnect() } }) diff --git a/test/integration/putCallback.test.js b/test/integration/putCallback.test.js index 58291587..730c1579 100644 --- a/test/integration/putCallback.test.js +++ b/test/integration/putCallback.test.js @@ -258,7 +258,63 @@ describe('PUT callback Tests --> ', () => { expect(request.headers['fspiop-source']).toBe(from) expect(request.headers['fspiop-destination']).toBe(to) } finally { - console.log(JSON.stringify(response.data.history, null, 2)) + await proxyClient.disconnect() + } + }) + + test('should forward PUT /fxQuotes/{ID} request to proxy if the payer dfsp is not registered in the hub', async () => { + let response = await hubClient.getHistory() + expect(response.data.history.length).toBe(0) + + const { topic, config } = kafkaConfig.PRODUCER.FX_QUOTE.PUT + const topicConfig = dto.topicConfigDto({ topicName: topic }) + const from = 'greenbank' + // redbank not in the hub db + const to = 'redbank' + + // register proxy representative for redbank + const proxyId = 'redbankproxy' + let proxyClient + + try { + proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) + const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) + + // assert that the proxy representative is mapped in the cache + const key = `dfsp:${to}` + const representative = await proxyClient.redisClient.get(key) + + expect(isAdded).toBe(true) + expect(representative).toBe(proxyId) + + const payload = { + condition: 'test', + conversionTerms: { + conversionId: uuid(), + initiatingFsp: from, + counterPartyFsp: to, + amountType: 'SEND', + sourceAmount: { amount: '100', currency: 'USD' }, + targetAmount: { amount: '100', currency: 'ZWS' }, + expiration: new Date(Date.now() + 5 * 60 * 1000).toISOString() + } + } + const message = mocks.kafkaMessagePayloadDto({ from, to, id: uuid(), payloadBase64: base64Encode(JSON.stringify(payload)) }) + delete message.content.headers.accept + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + + await wait(WAIT_TIMEOUT) + + response = await hubClient.getHistory() + expect(response.data.history.length).toBe(1) + + const request = response.data.history[0] + expect(request.url).toBe(`/${proxyId}/fxQuotes/${message.id}`) + expect(request.body).toEqual(payload) + expect(request.headers['fspiop-source']).toBe(from) + expect(request.headers['fspiop-destination']).toBe(to) + } finally { await proxyClient.disconnect() } }) diff --git a/test/integration/scripts/populateTestData.sh b/test/integration/scripts/populateTestData.sh index aa03a7e0..559bcaec 100755 --- a/test/integration/scripts/populateTestData.sh +++ b/test/integration/scripts/populateTestData.sh @@ -311,6 +311,15 @@ fi \"type\": \"FSPIOP_CALLBACK_URL_QUOTES\", \"value\": \"http://${MOCKSERVER_HOST}:${MOCKSERVER_PORT}/${FSP}\" }" + + curl -i -X POST "${CENTRAL_LEDGER_ADMIN_URI_PREFIX}://${CENTRAL_LEDGER_ADMIN_HOST}:${CENTRAL_LEDGER_ADMIN_PORT}${CENTRAL_LEDGER_ADMIN_BASE}participants/${FSP}/endpoints" \ + --header 'Cache-Control: no-cache' \ + --header 'Content-Type: application/json' \ + --header 'FSPIOP-Source: populateTestData.sh' \ + --data-raw "{ + \"type\": \"FSPIOP_CALLBACK_URL_FX_QUOTES\", + \"value\": \"http://${MOCKSERVER_HOST}:${MOCKSERVER_PORT}/${FSP}\" + }" curl -i -X POST "${CENTRAL_LEDGER_ADMIN_URI_PREFIX}://${CENTRAL_LEDGER_ADMIN_HOST}:${CENTRAL_LEDGER_ADMIN_PORT}${CENTRAL_LEDGER_ADMIN_BASE}participants/${FSP}/endpoints" \ --header 'Cache-Control: no-cache' \ From 20c4d4e0431b33beab80c4662845fa021972e06e Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 4 Jul 2024 12:36:02 +0100 Subject: [PATCH 32/51] test: add integration test for bulk quotes and fx quotes --- test/integration/postRequest.test.js | 58 ++++++++++++++++++++ test/integration/scripts/populateTestData.sh | 2 +- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/test/integration/postRequest.test.js b/test/integration/postRequest.test.js index 0d89564d..4e185724 100644 --- a/test/integration/postRequest.test.js +++ b/test/integration/postRequest.test.js @@ -292,4 +292,62 @@ describe('POST request tests --> ', () => { await proxyClient.disconnect() } }) + + test('should forward POST /bulkQuotes request to proxy if the payee dfsp is not registered in the hub', async () => { + let response = await hubClient.getHistory() + expect(response.data.history.length).toBe(0) + + const { topic, config } = kafkaConfig.PRODUCER.BULK_QUOTE.POST + const topicConfig = dto.topicConfigDto({ topicName: topic }) + const from = 'pinkbank' + // redbank not in the hub db + const to = 'redbank' + + // register proxy representative for redbank + const proxyId = 'redbankproxy' + let proxyClient + + try { + proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) + const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) + + // assert that the proxy representative is mapped in the cache + const key = `dfsp:${to}` + const representative = await proxyClient.redisClient.get(key) + + expect(isAdded).toBe(true) + expect(representative).toBe(proxyId) + + const payload = { + bulkQuoteId: uuid(), + payer: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '987654321', fspId: from } }, + individualQuotes: [ + { + quoteId: uuid(), + transactionId: uuid(), + payee: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '123456789', fspId: to } }, + amountType: 'SEND', + amount: { amount: '100', currency: 'USD' }, + transactionType: { scenario: 'DEPOSIT', initiator: 'PAYER', initiatorType: 'CONSUMER' } + } + ] + } + const message = mocks.kafkaMessagePayloadPostDto({ from, to, id: payload.quoteId, payloadBase64: base64Encode(JSON.stringify(payload)) }) + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + + await wait(WAIT_TIMEOUT) + + response = await hubClient.getHistory() + expect([1, 2]).toContain(response.data.history.length) + + const request = response.data.history[0] + expect(request.url).toBe(`/${proxyId}/bulkQuotes`) + expect(request.body).toEqual(payload) + expect(request.headers['fspiop-source']).toBe(from) + expect(request.headers['fspiop-destination']).toBe(to) + } finally { + await proxyClient.disconnect() + } + }) }) diff --git a/test/integration/scripts/populateTestData.sh b/test/integration/scripts/populateTestData.sh index 559bcaec..3a862de1 100755 --- a/test/integration/scripts/populateTestData.sh +++ b/test/integration/scripts/populateTestData.sh @@ -345,7 +345,7 @@ fi --header 'FSPIOP-Source: populateTestData.sh' \ --data-raw "{ \"type\": \"FSPIOP_CALLBACK_URL_BULK_QUOTES\", - \"value\": \"http://${MOCKSERVER_HOST}:${MOCKSERVER_PORT}\" + \"value\": \"http://${MOCKSERVER_HOST}:${MOCKSERVER_PORT}/${FSP}\" }" echo From 528ca65b4809e681131526be65ba287635d9c7ed Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 4 Jul 2024 12:56:57 +0100 Subject: [PATCH 33/51] test: add integration tests for bulk quotes --- test/integration/postRequest.test.js | 2 +- test/integration/putCallback.test.js | 60 ++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/test/integration/postRequest.test.js b/test/integration/postRequest.test.js index 4e185724..34bd68d4 100644 --- a/test/integration/postRequest.test.js +++ b/test/integration/postRequest.test.js @@ -339,7 +339,7 @@ describe('POST request tests --> ', () => { await wait(WAIT_TIMEOUT) response = await hubClient.getHistory() - expect([1, 2]).toContain(response.data.history.length) + expect(response.data.history.length).toBe(1) const request = response.data.history[0] expect(request.url).toBe(`/${proxyId}/bulkQuotes`) diff --git a/test/integration/putCallback.test.js b/test/integration/putCallback.test.js index 730c1579..396f2055 100644 --- a/test/integration/putCallback.test.js +++ b/test/integration/putCallback.test.js @@ -318,4 +318,64 @@ describe('PUT callback Tests --> ', () => { await proxyClient.disconnect() } }) + + test('should forward PUT /bulkQuotes/{ID} request to proxy if the payer dfsp is not registered in the hub', async () => { + let response = await hubClient.getHistory() + expect(response.data.history.length).toBe(0) + + const { topic, config } = kafkaConfig.PRODUCER.BULK_QUOTE.PUT + const topicConfig = dto.topicConfigDto({ topicName: topic }) + const from = 'greenbank' + // redbank not in the hub db + const to = 'redbank' + + // register proxy representative for redbank + const proxyId = 'redbankproxy' + let proxyClient + + try { + proxyClient = await createProxyClient({ proxyCacheConfig: proxyCache, logger: console }) + const isAdded = await proxyClient.addDfspIdToProxyMapping(to, proxyId) + + // assert that the proxy representative is mapped in the cache + const key = `dfsp:${to}` + const representative = await proxyClient.redisClient.get(key) + + expect(isAdded).toBe(true) + expect(representative).toBe(proxyId) + + const payload = { + individualQuoteResults: [ + { + quoteId: uuid(), + payee: { partyIdInfo: { partyIdType: 'MSISDN', partyIdentifier: '123456789', fspId: to } }, + transferAmount: { amount: '100', currency: 'USD' }, + payeeReceiveAmount: { amount: '100', currency: 'USD' }, + payeeFspFee: { amount: '0', currency: 'USD' }, + payeeFspCommission: { amount: '0', currency: 'USD' }, + ilpPacket: 'test', + condition: 'test' + } + ], + expiration: new Date(Date.now() + 5 * 60 * 1000).toISOString() + } + const message = mocks.kafkaMessagePayloadDto({ from, to, id: uuid(), payloadBase64: base64Encode(JSON.stringify(payload)) }) + delete message.content.headers.accept + const isOk = await Producer.produceMessage(message, topicConfig, config) + expect(isOk).toBe(true) + + await wait(WAIT_TIMEOUT) + + response = await hubClient.getHistory() + expect(response.data.history.length).toBe(1) + + const request = response.data.history[0] + expect(request.url).toBe(`/${proxyId}/bulkQuotes/${message.id}`) + expect(request.body).toEqual(payload) + expect(request.headers['fspiop-source']).toBe(from) + expect(request.headers['fspiop-destination']).toBe(to) + } finally { + await proxyClient.disconnect() + } + }) }) From deb905cb3d048fbe55214251be4ea60cfa9daace Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 4 Jul 2024 13:24:47 +0100 Subject: [PATCH 34/51] chore(snapshot): 15.8.0-snapshot.25 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6d005910..29d7d792 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "quoting-service", - "version": "15.8.0-snapshot.24", + "version": "15.8.0-snapshot.25", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "quoting-service", - "version": "15.8.0-snapshot.24", + "version": "15.8.0-snapshot.25", "license": "Apache-2.0", "dependencies": { "@hapi/good": "9.0.1", diff --git a/package.json b/package.json index 66ed20c6..d48da0f8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "quoting-service", "description": "Quoting Service hosted by a scheme", "license": "Apache-2.0", - "version": "15.8.0-snapshot.24", + "version": "15.8.0-snapshot.25", "author": "ModusBox", "contributors": [ "Georgi Georgiev ", From 0c3766d29255fa7eac99a85d45ed8b38fd4f3ac6 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 4 Jul 2024 13:36:02 +0100 Subject: [PATCH 35/51] feat: remove retryInterval config --- config/default.json | 1 - docker/quoting-service/default.json | 1 - src/lib/proxy.js | 2 +- test/unit/lib/proxy.test.js | 1 - 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/config/default.json b/config/default.json index ac154878..0c597b56 100644 --- a/config/default.json +++ b/config/default.json @@ -81,7 +81,6 @@ "enabled": true, "type": "redis", "timeout": 5000, - "retryInterval": 200, "proxyConfig": { "host": "localhost", "port": 6379, diff --git a/docker/quoting-service/default.json b/docker/quoting-service/default.json index 261c4492..ab3d7647 100644 --- a/docker/quoting-service/default.json +++ b/docker/quoting-service/default.json @@ -81,7 +81,6 @@ "enabled": true, "type": "redis", "timeout": 5000, - "retryInterval": 200, "proxyConfig": { "host": "redis", "port": 6379, diff --git a/src/lib/proxy.js b/src/lib/proxy.js index d6d93a7c..08a4982f 100644 --- a/src/lib/proxy.js +++ b/src/lib/proxy.js @@ -30,7 +30,7 @@ const { createProxyCache } = require('@mojaloop/inter-scheme-proxy-cache-lib') const createProxyClient = async ({ proxyCacheConfig, logger }) => { const timeout = Number(proxyCacheConfig.timeout) - const retryInterval = Number(proxyCacheConfig.retryInterval) + const retryInterval = 200 const proxyClient = createProxyCache(proxyCacheConfig.type, proxyCacheConfig.proxyConfig) diff --git a/test/unit/lib/proxy.test.js b/test/unit/lib/proxy.test.js index 85cb9e88..ea2f10bd 100644 --- a/test/unit/lib/proxy.test.js +++ b/test/unit/lib/proxy.test.js @@ -41,7 +41,6 @@ describe('createProxyClient', () => { beforeEach(() => { proxyCacheConfig = { - retryInterval: 200, timeout: 5000, type: 'redis', proxyConfig: { From 3c2939b33db36f1dd5b848ebf11145828255d0e2 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 4 Jul 2024 13:36:20 +0100 Subject: [PATCH 36/51] chore(snapshot): 15.8.0-snapshot.26 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 29d7d792..9847795c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "quoting-service", - "version": "15.8.0-snapshot.25", + "version": "15.8.0-snapshot.26", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "quoting-service", - "version": "15.8.0-snapshot.25", + "version": "15.8.0-snapshot.26", "license": "Apache-2.0", "dependencies": { "@hapi/good": "9.0.1", diff --git a/package.json b/package.json index d48da0f8..bc25efc3 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "quoting-service", "description": "Quoting Service hosted by a scheme", "license": "Apache-2.0", - "version": "15.8.0-snapshot.25", + "version": "15.8.0-snapshot.26", "author": "ModusBox", "contributors": [ "Georgi Georgiev ", From 6115f022cb1ea75a32a98cf6b4e6c7e3253fc075 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 4 Jul 2024 13:49:32 +0100 Subject: [PATCH 37/51] ci: remove debug start --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index ec1bfc72..a3fdbc8d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,7 +46,7 @@ services: quoting-service-handler: <<: *quotingServiceBase - command: npm run start:handlers:debug + command: npm run start:handlers ports: - "3003:3003" - "29229:9229" From 76876092f6d03f4f1991dbb8b335f2c1eb5883b0 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 4 Jul 2024 13:52:57 +0100 Subject: [PATCH 38/51] feat: disable proxy cache by default --- config/default.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/default.json b/config/default.json index 0c597b56..0c812367 100644 --- a/config/default.json +++ b/config/default.json @@ -78,7 +78,7 @@ "PARTICIPANT_DATA_EXPIRES_IN_MS": 60000 }, "PROXY_CACHE": { - "enabled": true, + "enabled": false, "type": "redis", "timeout": 5000, "proxyConfig": { From 2c59938d105ed2c31a3a64825cfb08207eccc0a3 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 4 Jul 2024 16:43:15 +0100 Subject: [PATCH 39/51] chore(snapshot): 15.8.0-snapshot.27 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9847795c..81f1a230 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "quoting-service", - "version": "15.8.0-snapshot.26", + "version": "15.8.0-snapshot.27", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "quoting-service", - "version": "15.8.0-snapshot.26", + "version": "15.8.0-snapshot.27", "license": "Apache-2.0", "dependencies": { "@hapi/good": "9.0.1", diff --git a/package.json b/package.json index bc25efc3..95a4b9be 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "quoting-service", "description": "Quoting Service hosted by a scheme", "license": "Apache-2.0", - "version": "15.8.0-snapshot.26", + "version": "15.8.0-snapshot.27", "author": "ModusBox", "contributors": [ "Georgi Georgiev ", From 155ec729349d3051cf79a54ab553c3be130b423d Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Thu, 4 Jul 2024 17:07:24 +0100 Subject: [PATCH 40/51] feat: add db number for redis --- config/default.json | 3 ++- docker/quoting-service/default.json | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/config/default.json b/config/default.json index 0c812367..e75ced6d 100644 --- a/config/default.json +++ b/config/default.json @@ -78,12 +78,13 @@ "PARTICIPANT_DATA_EXPIRES_IN_MS": 60000 }, "PROXY_CACHE": { - "enabled": false, + "enabled": true, "type": "redis", "timeout": 5000, "proxyConfig": { "host": "localhost", "port": 6379, + "db": 0, "lazyConnect": false } }, diff --git a/docker/quoting-service/default.json b/docker/quoting-service/default.json index ab3d7647..79ffcfe2 100644 --- a/docker/quoting-service/default.json +++ b/docker/quoting-service/default.json @@ -84,6 +84,7 @@ "proxyConfig": { "host": "redis", "port": 6379, + "db": 0, "lazyConnect": false } }, From f454919f0cd523eb6ed7b3289183e08df1f800b6 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Fri, 5 Jul 2024 07:20:17 +0100 Subject: [PATCH 41/51] feat: minor refactor --- src/lib/proxy.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/proxy.js b/src/lib/proxy.js index 08a4982f..9f18816b 100644 --- a/src/lib/proxy.js +++ b/src/lib/proxy.js @@ -46,7 +46,6 @@ const waitForConnection = async ({ proxyClient, timeout, retryInterval, logger } const timer = setTimeout(() => { timedOut = true - logger.error('Unable to connect to proxy cache. Exiting...') }, timeout) while (!proxyClient.isConnected) { @@ -56,7 +55,10 @@ const waitForConnection = async ({ proxyClient, timeout, retryInterval, logger } clearTimeout(timer) - if (timedOut) process.exit(1) + if (timedOut) { + logger.error('Unable to connect to proxy cache. Exiting...') + process.exit(1) + } logger.isInfoEnabled && logger.info('Connected to proxy cache') } From 720e4c5d58ffb8883622ea2ab6cc3c953cf24c4a Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Fri, 5 Jul 2024 07:25:32 +0100 Subject: [PATCH 42/51] feat: minor refactor --- src/lib/proxy.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/proxy.js b/src/lib/proxy.js index 9f18816b..7cd67c90 100644 --- a/src/lib/proxy.js +++ b/src/lib/proxy.js @@ -44,9 +44,7 @@ const createProxyClient = async ({ proxyCacheConfig, logger }) => { const waitForConnection = async ({ proxyClient, timeout, retryInterval, logger }) => { let timedOut = false - const timer = setTimeout(() => { - timedOut = true - }, timeout) + const timer = setTimeout(() => (timedOut = true), timeout) while (!proxyClient.isConnected) { if (timedOut) break From dbd65a72249b6a28e607738718d0c31141466434 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Fri, 5 Jul 2024 10:00:13 +0100 Subject: [PATCH 43/51] feat: remove support for falsy lazyConnect --- config/default.json | 4 +--- docker/quoting-service/default.json | 4 +--- src/lib/proxy.js | 32 +++++------------------------ test/unit/lib/proxy.test.js | 29 ++++---------------------- 4 files changed, 11 insertions(+), 58 deletions(-) diff --git a/config/default.json b/config/default.json index e75ced6d..0c719921 100644 --- a/config/default.json +++ b/config/default.json @@ -80,12 +80,10 @@ "PROXY_CACHE": { "enabled": true, "type": "redis", - "timeout": 5000, "proxyConfig": { "host": "localhost", "port": 6379, - "db": 0, - "lazyConnect": false + "db": 0 } }, "LOG_LEVEL": "info", diff --git a/docker/quoting-service/default.json b/docker/quoting-service/default.json index 79ffcfe2..4327e2fe 100644 --- a/docker/quoting-service/default.json +++ b/docker/quoting-service/default.json @@ -80,12 +80,10 @@ "PROXY_CACHE": { "enabled": true, "type": "redis", - "timeout": 5000, "proxyConfig": { "host": "redis", "port": 6379, - "db": 0, - "lazyConnect": false + "db": 0 } }, "KAFKA": { diff --git a/src/lib/proxy.js b/src/lib/proxy.js index 7cd67c90..56be7e91 100644 --- a/src/lib/proxy.js +++ b/src/lib/proxy.js @@ -28,37 +28,15 @@ ******/ const { createProxyCache } = require('@mojaloop/inter-scheme-proxy-cache-lib') -const createProxyClient = async ({ proxyCacheConfig, logger }) => { - const timeout = Number(proxyCacheConfig.timeout) - const retryInterval = 200 - - const proxyClient = createProxyCache(proxyCacheConfig.type, proxyCacheConfig.proxyConfig) - +const createProxyClient = async ({ proxyCacheConfig }) => { + // Ensure lazyConnect is set to true if (!proxyCacheConfig.proxyConfig.lazyConnect) { - await waitForConnection({ proxyClient, timeout, retryInterval, logger }) + proxyCacheConfig.proxyConfig.lazyConnect = true } + const proxyClient = createProxyCache(proxyCacheConfig.type, proxyCacheConfig.proxyConfig) + await proxyClient.connect() return proxyClient } -const waitForConnection = async ({ proxyClient, timeout, retryInterval, logger }) => { - let timedOut = false - - const timer = setTimeout(() => (timedOut = true), timeout) - - while (!proxyClient.isConnected) { - if (timedOut) break - await new Promise(resolve => setTimeout(resolve, retryInterval)) - } - - clearTimeout(timer) - - if (timedOut) { - logger.error('Unable to connect to proxy cache. Exiting...') - process.exit(1) - } - - logger.isInfoEnabled && logger.info('Connected to proxy cache') -} - module.exports = { createProxyClient } diff --git a/test/unit/lib/proxy.test.js b/test/unit/lib/proxy.test.js index ea2f10bd..d6dd912a 100644 --- a/test/unit/lib/proxy.test.js +++ b/test/unit/lib/proxy.test.js @@ -37,7 +37,6 @@ const { createProxyClient } = require('../../../src/lib/proxy') describe('createProxyClient', () => { let mockProxyClient let proxyCacheConfig - let logger beforeEach(() => { proxyCacheConfig = { @@ -45,18 +44,13 @@ describe('createProxyClient', () => { type: 'redis', proxyConfig: { host: 'localhost', - port: 6379, - lazyConnect: true + port: 6379 } } - logger = { - isErrorEnabled: true, - error: jest.fn() - } - mockProxyClient = { - isConnected: true + isConnected: true, + connect: jest.fn() } createProxyCache.mockReturnValue(mockProxyClient) @@ -67,24 +61,9 @@ describe('createProxyClient', () => { }) it('should create a proxy client and return it', async () => { - const proxyClient = await createProxyClient({ proxyCacheConfig, logger }) + const proxyClient = await createProxyClient({ proxyCacheConfig }) expect(proxyClient).toBeDefined() expect(proxyClient.isConnected).toBe(true) }) - - it('should log an error and exit if unable to connect to proxy cache', async () => { - const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => { }) - - mockProxyClient.isConnected = false - const modifiedConfig = { ...proxyCacheConfig } - modifiedConfig.proxyConfig.lazyConnect = false - - await createProxyClient({ proxyCacheConfig: modifiedConfig, logger }) - - expect(logger.error).toHaveBeenCalledWith('Unable to connect to proxy cache. Exiting...') - expect(mockExit).toHaveBeenCalledWith(1) - - mockExit.mockRestore() - }, 10_000) }) From dd1c5bfd52592499ee4217309904a9e0fe99071f Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Fri, 5 Jul 2024 10:01:29 +0100 Subject: [PATCH 44/51] chore(snapshot): 15.8.0-snapshot.28 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 81f1a230..ff453482 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "quoting-service", - "version": "15.8.0-snapshot.27", + "version": "15.8.0-snapshot.28", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "quoting-service", - "version": "15.8.0-snapshot.27", + "version": "15.8.0-snapshot.28", "license": "Apache-2.0", "dependencies": { "@hapi/good": "9.0.1", diff --git a/package.json b/package.json index 95a4b9be..bb8c0df8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "quoting-service", "description": "Quoting Service hosted by a scheme", "license": "Apache-2.0", - "version": "15.8.0-snapshot.27", + "version": "15.8.0-snapshot.28", "author": "ModusBox", "contributors": [ "Georgi Georgiev ", From 3f59c931d6ac0bbe4a8316719e29b3aa8cd21e5c Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Fri, 5 Jul 2024 10:19:04 +0100 Subject: [PATCH 45/51] feat: refactor init, proxy and update test --- src/handlers/init.js | 3 ++- src/lib/proxy.js | 7 ++----- test/unit/handlers/init.test.js | 7 ++++++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/handlers/init.js b/src/handlers/init.js index a7fa3313..9fc28f89 100644 --- a/src/handlers/init.js +++ b/src/handlers/init.js @@ -24,7 +24,8 @@ const startFn = async (handlerList) => { if (!isDbOk) throw new Error('DB is not connected') if (config.proxyCache.enabled) { - proxyClient = await createProxyClient({ proxyCacheConfig: config.proxyCache, logger: Logger }) + proxyClient = createProxyClient({ proxyCacheConfig: config.proxyCache, logger: Logger }) + await proxyClient.connect() } const { quotesModelFactory, bulkQuotesModelFactory, fxQuotesModelFactory } = modelFactory(db, proxyClient) diff --git a/src/lib/proxy.js b/src/lib/proxy.js index 56be7e91..c53b960d 100644 --- a/src/lib/proxy.js +++ b/src/lib/proxy.js @@ -28,15 +28,12 @@ ******/ const { createProxyCache } = require('@mojaloop/inter-scheme-proxy-cache-lib') -const createProxyClient = async ({ proxyCacheConfig }) => { +const createProxyClient = ({ proxyCacheConfig }) => { // Ensure lazyConnect is set to true if (!proxyCacheConfig.proxyConfig.lazyConnect) { proxyCacheConfig.proxyConfig.lazyConnect = true } - const proxyClient = createProxyCache(proxyCacheConfig.type, proxyCacheConfig.proxyConfig) - await proxyClient.connect() - - return proxyClient + return createProxyCache(proxyCacheConfig.type, proxyCacheConfig.proxyConfig) } module.exports = { createProxyClient } diff --git a/test/unit/handlers/init.test.js b/test/unit/handlers/init.test.js index 1a5ee53b..1e02e0fe 100644 --- a/test/unit/handlers/init.test.js +++ b/test/unit/handlers/init.test.js @@ -1,6 +1,11 @@ jest.mock('../../../src/handlers/createConsumers') jest.mock('../../../src/handlers/monitoringServer') -jest.mock('../../../src/lib/proxy') +jest.mock('../../../src/lib/proxy', () => ({ + createProxyClient: () => ({ + connect: jest.fn(), + isConnected: false + }) +})) const init = require('../../../src/handlers/init') const Database = require('../../../src/data/cachedDatabase') From 3eebad3eb7b4174cb9dbac69aa67de58e112e4bc Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Fri, 5 Jul 2024 10:19:49 +0100 Subject: [PATCH 46/51] chore(snapshot): 15.8.0-snapshot.29 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ff453482..838d93dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "quoting-service", - "version": "15.8.0-snapshot.28", + "version": "15.8.0-snapshot.29", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "quoting-service", - "version": "15.8.0-snapshot.28", + "version": "15.8.0-snapshot.29", "license": "Apache-2.0", "dependencies": { "@hapi/good": "9.0.1", diff --git a/package.json b/package.json index bb8c0df8..791f0ea0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "quoting-service", "description": "Quoting Service hosted by a scheme", "license": "Apache-2.0", - "version": "15.8.0-snapshot.28", + "version": "15.8.0-snapshot.29", "author": "ModusBox", "contributors": [ "Georgi Georgiev ", From 06fa95caa1fc5dcac36e261162488824d264390a Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Fri, 5 Jul 2024 10:49:38 +0100 Subject: [PATCH 47/51] feat: throw on failed proxy connect --- src/handlers/init.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/handlers/init.js b/src/handlers/init.js index 9fc28f89..fc8651a8 100644 --- a/src/handlers/init.js +++ b/src/handlers/init.js @@ -24,8 +24,9 @@ const startFn = async (handlerList) => { if (!isDbOk) throw new Error('DB is not connected') if (config.proxyCache.enabled) { - proxyClient = createProxyClient({ proxyCacheConfig: config.proxyCache, logger: Logger }) + const isProxyOk = proxyClient = createProxyClient({ proxyCacheConfig: config.proxyCache, logger: Logger }) await proxyClient.connect() + if (!isProxyOk) throw new Error('Proxy is not connected') } const { quotesModelFactory, bulkQuotesModelFactory, fxQuotesModelFactory } = modelFactory(db, proxyClient) From 2c606be5b0ebc3fb97341cd0ea45e3ea19087f56 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Fri, 5 Jul 2024 10:49:56 +0100 Subject: [PATCH 48/51] chore(snapshot): 15.8.0-snapshot.30 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 838d93dd..3364b33e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "quoting-service", - "version": "15.8.0-snapshot.29", + "version": "15.8.0-snapshot.30", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "quoting-service", - "version": "15.8.0-snapshot.29", + "version": "15.8.0-snapshot.30", "license": "Apache-2.0", "dependencies": { "@hapi/good": "9.0.1", diff --git a/package.json b/package.json index 791f0ea0..567b79ed 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "quoting-service", "description": "Quoting Service hosted by a scheme", "license": "Apache-2.0", - "version": "15.8.0-snapshot.29", + "version": "15.8.0-snapshot.30", "author": "ModusBox", "contributors": [ "Georgi Georgiev ", From 0b4dd4b0ccd00cc3a7e6ee6204eb55848fbd6e7c Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Fri, 5 Jul 2024 12:06:26 +0100 Subject: [PATCH 49/51] feat: update logging --- src/handlers/init.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/handlers/init.js b/src/handlers/init.js index fc8651a8..0ab5b7f1 100644 --- a/src/handlers/init.js +++ b/src/handlers/init.js @@ -24,9 +24,10 @@ const startFn = async (handlerList) => { if (!isDbOk) throw new Error('DB is not connected') if (config.proxyCache.enabled) { - const isProxyOk = proxyClient = createProxyClient({ proxyCacheConfig: config.proxyCache, logger: Logger }) + const isProxyOk = proxyClient = createProxyClient({ proxyCacheConfig: config.proxyCache }) await proxyClient.connect() if (!isProxyOk) throw new Error('Proxy is not connected') + Logger.isInfoEnabled && Logger.info('Proxy cache is connected') } const { quotesModelFactory, bulkQuotesModelFactory, fxQuotesModelFactory } = modelFactory(db, proxyClient) From f5ea17499883398182f05d594f9bfdc7d52d2203 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Fri, 5 Jul 2024 12:06:48 +0100 Subject: [PATCH 50/51] chore(snapshot): 15.8.0-snapshot.31 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3364b33e..54de87f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "quoting-service", - "version": "15.8.0-snapshot.30", + "version": "15.8.0-snapshot.31", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "quoting-service", - "version": "15.8.0-snapshot.30", + "version": "15.8.0-snapshot.31", "license": "Apache-2.0", "dependencies": { "@hapi/good": "9.0.1", diff --git a/package.json b/package.json index 567b79ed..f855d553 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "quoting-service", "description": "Quoting Service hosted by a scheme", "license": "Apache-2.0", - "version": "15.8.0-snapshot.30", + "version": "15.8.0-snapshot.31", "author": "ModusBox", "contributors": [ "Georgi Georgiev ", From 9686ada03903edfd48c06aac99a5fd50c7114a99 Mon Sep 17 00:00:00 2001 From: Steven Oderayi Date: Fri, 5 Jul 2024 12:34:42 +0100 Subject: [PATCH 51/51] feat: disbale proxy by default --- config/default.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/default.json b/config/default.json index 0c719921..0c4d48a1 100644 --- a/config/default.json +++ b/config/default.json @@ -78,7 +78,7 @@ "PARTICIPANT_DATA_EXPIRES_IN_MS": 60000 }, "PROXY_CACHE": { - "enabled": true, + "enabled": false, "type": "redis", "proxyConfig": { "host": "localhost",