account-lookup-service 17.8.0-snapshot.9 → 17.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [17.8.0](https://github.com/mojaloop/account-lookup-service/compare/v17.7.1...v17.8.0) (2025-04-03)
6
+
7
+
8
+ ### Features
9
+
10
+ * **csi-1266:** reset party mappings in inter-scheme scenario ([#541](https://github.com/mojaloop/account-lookup-service/issues/541)) ([279fac6](https://github.com/mojaloop/account-lookup-service/commit/279fac6bff5e9a30c0583e1bcfb724787590516c))
11
+
12
+ ### [17.7.1](https://github.com/mojaloop/account-lookup-service/compare/v17.7.0...v17.7.1) (2025-03-27)
13
+
14
+
15
+ ### Chore
16
+
17
+ * **csi-1248:** update transform-lib, others ([#543](https://github.com/mojaloop/account-lookup-service/issues/543)) ([7a0a5c4](https://github.com/mojaloop/account-lookup-service/commit/7a0a5c402e8f8f28661540ea5982b1ac9a97a8dd))
18
+
5
19
  ## [17.7.0](https://github.com/mojaloop/account-lookup-service/compare/v17.6.0...v17.7.0) (2025-03-26)
6
20
 
7
21
 
@@ -38,24 +38,26 @@ app.get('/health', (req: Request, res: Response) => {
38
38
  app.get('/parties/:type/:id', (req: Request, res: Response) => {
39
39
  const { type, id } = req.params;
40
40
  const headers = hubCbHeaders(req.headers);
41
- console.log('parties request details:', { type, id, headers, CL_HOST, CL_PORT });
41
+ const statusCode = detectStatusCode(req);
42
+ console.log('parties request details:', { type, id, headers, CL_HOST, CL_PORT, statusCode });
42
43
 
43
44
  // todo: reply to CL with party info
44
45
 
45
46
  res
46
47
  .set(headers)
47
- .status(202)
48
+ .status(statusCode)
48
49
  .json({ success: true });
49
50
  });
50
51
 
51
52
  app.put('/parties/:type/:id/error', (req: Request, res: Response) => {
52
53
  const { type, id } = req.params;
53
54
  const headers = dfspCbHeaders(req.headers);
54
- console.log('parties put error request details:', { type, id, headers, CL_HOST, CL_PORT });
55
+ const statusCode = detectStatusCode(req);
56
+ console.log('parties put error request details:', { type, id, headers, CL_HOST, CL_PORT, statusCode });
55
57
 
56
58
  res
57
59
  .set(headers)
58
- .status(200)
60
+ .status(statusCode)
59
61
  .json({ success: true });
60
62
  });
61
63
 
@@ -92,3 +94,10 @@ const httpsServer = http.createServer(app);
92
94
  httpsServer.listen(PROXY_PORT, () => {
93
95
  console.log(`Mock proxyAdapter "${PROXY_NAME}" is running on port ${PROXY_PORT}...`);
94
96
  });
97
+
98
+ const X_RESPONSE_STATUS_HEADER = 'x-response-status';
99
+
100
+ const detectStatusCode = (req: Request) =>
101
+ typeof req.headers[X_RESPONSE_STATUS_HEADER] === 'string'
102
+ ? parseInt(req.headers[X_RESPONSE_STATUS_HEADER], 10)
103
+ : (req.method === 'GET' ? 202 : 200);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "account-lookup-service",
3
3
  "description": "Account Lookup Service is used to validate Party and Participant lookups.",
4
- "version": "17.8.0-snapshot.9",
4
+ "version": "17.8.0",
5
5
  "license": "Apache-2.0",
6
6
  "author": "ModusBox",
7
7
  "contributors": [
@@ -92,21 +92,21 @@
92
92
  "@hapi/vision": "7.0.3",
93
93
  "@mojaloop/central-services-error-handling": "13.0.7",
94
94
  "@mojaloop/central-services-health": "15.0.4",
95
- "@mojaloop/central-services-logger": "11.8.0",
95
+ "@mojaloop/central-services-logger": "11.8.1",
96
96
  "@mojaloop/central-services-metrics": "12.5.0",
97
- "@mojaloop/central-services-shared": "18.23.1",
97
+ "@mojaloop/central-services-shared": "18.23.2",
98
98
  "@mojaloop/central-services-stream": "11.5.2",
99
99
  "@mojaloop/database-lib": "11.1.4",
100
100
  "@mojaloop/event-sdk": "14.4.0",
101
101
  "@mojaloop/inter-scheme-proxy-cache-lib": "2.4.0",
102
- "@mojaloop/ml-schema-transformer-lib": "2.7.0",
102
+ "@mojaloop/ml-schema-transformer-lib": "2.7.1",
103
103
  "@mojaloop/sdk-standard-components": "19.11.3-snapshot.0",
104
104
  "@now-ims/hapi-now-auth": "2.1.0",
105
105
  "ajv": "8.17.1",
106
106
  "ajv-keywords": "5.1.0",
107
107
  "blipp": "4.0.2",
108
108
  "commander": "13.1.0",
109
- "cron": "4.1.1",
109
+ "cron": "4.1.3",
110
110
  "fast-safe-stringify": "^2.1.1",
111
111
  "hapi-auth-bearer-token": "8.0.0",
112
112
  "joi": "17.13.3",
package/src/constants.js CHANGED
@@ -29,12 +29,14 @@ const { API_TYPES } = require('@mojaloop/central-services-shared').Util.Hapi
29
29
 
30
30
  const ERROR_MESSAGES = Object.freeze({
31
31
  emptyFilteredPartyList: 'Empty oracle partyList, filtered based on callbackEndpointType',
32
+ externalPartyError: 'External party error', // todo: think better message
32
33
  failedToCacheSendToProxiesList: 'Failed to cache sendToProxiesList',
33
34
  noDiscoveryRequestsForwarded: 'No discovery requests forwarded to participants',
34
35
  sourceFspNotFound: 'Requester FSP not found',
35
36
  partyDestinationFspNotFound: 'Destination FSP not found',
36
37
  partyProxyNotFound: 'Proxy not found',
37
- proxyConnectionError: 'Proxy connection error - no successful requests sent to proxies'
38
+ proxyConnectionError: 'Proxy connection error - no successful requests sent to proxies',
39
+ noSuccessfulProxyDiscoveryResponses: 'No successful proxy discovery responses'
38
40
  })
39
41
 
40
42
  const HANDLER_TYPES = Object.freeze({
@@ -97,15 +97,7 @@ const putPartiesErrorByTypeAndID = async (headers, params, payload, dataUri, spa
97
97
  let fspiopError
98
98
 
99
99
  try {
100
- const needDiscovery = await service.handleRequest()
101
- if (needDiscovery) {
102
- const getPartiesService = new services.GetPartiesService(deps, inputs)
103
- await getPartiesService.triggerInterSchemeDiscoveryFlow(
104
- services.GetPartiesService.headersWithoutDestination(headers)
105
- )
106
- // think, if we need to start the whole processing with getPartiesService.handleRequest() ?
107
- }
108
-
100
+ await service.handleRequest()
109
101
  logger.info('putPartiesErrorByTypeAndID is done')
110
102
  histTimerEnd({ success: true })
111
103
  } catch (error) {
@@ -30,6 +30,7 @@ const { Enum } = require('@mojaloop/central-services-shared')
30
30
  const { decodePayload } = require('@mojaloop/central-services-shared').Util.StreamingProtocol
31
31
  const { initStepState } = require('../../../lib/util')
32
32
  const { createCallbackHeaders } = require('../../../lib/headers')
33
+ const { ERROR_MESSAGES } = require('../../../constants')
33
34
 
34
35
  const { FspEndpointTypes, FspEndpointTemplates } = Enum.EndPoints
35
36
  const { Headers, RestMethods } = Enum.Http
@@ -100,16 +101,17 @@ class BasePartiesService {
100
101
  get state () { return this.#state }
101
102
 
102
103
  async handleError (error) {
103
- const { headers, params } = this.inputs
104
+ const { params } = this.inputs
104
105
  const log = this.log.child({ method: 'handleError' })
105
106
  try {
106
107
  log.error('error in processing parties request: ', error)
107
108
  const fspiopError = ErrorHandler.Factory.reformatFSPIOPError(error)
108
- const errorInfo = await this.deps.partiesUtils.makePutPartiesErrorPayload(this.deps.config, fspiopError, headers, params)
109
+ const callbackHeaders = BasePartiesService.createErrorCallbackHeaders(this.inputs.headers, params)
110
+ const errorInfo = await this.deps.partiesUtils.makePutPartiesErrorPayload(this.deps.config, fspiopError, callbackHeaders, params)
109
111
 
110
112
  await this.sendErrorCallback({
111
113
  errorInfo,
112
- headers: BasePartiesService.createErrorCallbackHeaders(headers, params),
114
+ headers: callbackHeaders,
113
115
  params
114
116
  })
115
117
  log.info('handleError in done')
@@ -122,13 +124,39 @@ class BasePartiesService {
122
124
  }
123
125
 
124
126
  async validateParticipant (participantId) {
125
- this.stepInProgress('validateParticipant')
126
- return this.deps.participant.validateParticipant(participantId)
127
+ try {
128
+ this.stepInProgress('validateParticipant')
129
+ return this.deps.participant.validateParticipant(participantId)
130
+ } catch (err) {
131
+ this.log.warn(`error in validateParticipant ${participantId}: `, err)
132
+ return null
133
+ }
134
+ }
135
+
136
+ async identifyDestinationForCallback () {
137
+ this.stepInProgress('identifyDestinationForCallback')
138
+ const { destination } = this.state
139
+
140
+ const schemeParticipant = await this.validateParticipant(destination)
141
+ if (schemeParticipant) {
142
+ this.state.requester = destination
143
+ return
144
+ }
145
+
146
+ const proxyName = this.state.proxyEnabled && await this.deps.proxyCache.lookupProxyByDfspId(destination)
147
+ if (proxyName) {
148
+ this.state.requester = proxyName
149
+ return
150
+ }
151
+
152
+ const errMessage = ERROR_MESSAGES.partyDestinationFspNotFound
153
+ this.log.warn(`${errMessage} and no proxy`, { destination })
154
+ throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_FSP_ERROR, errMessage)
127
155
  }
128
156
 
129
157
  async sendErrorCallback ({ errorInfo, headers, params }) {
130
158
  this.stepInProgress('sendErrorCallback')
131
- const sendTo = this.state.requester || this.state.source
159
+ const sendTo = this.state.requester || headers[Headers.FSPIOP.DESTINATION] /* || this.state.source */
132
160
  const endpointType = this.deps.partiesUtils.errorPartyCbType(params.SubId)
133
161
 
134
162
  await this.deps.participant.sendErrorToParticipant(
@@ -143,6 +171,7 @@ class BasePartiesService {
143
171
  }
144
172
 
145
173
  async removeProxyGetPartiesTimeoutCache (alsReq) {
174
+ this.stepInProgress('removeProxyGetPartiesTimeoutCache')
146
175
  const isRemoved = await this.deps.proxyCache.removeProxyGetPartiesTimeout(alsReq, this.state.proxy)
147
176
  this.log.debug('removeProxyGetPartiesTimeoutCache is done', { isRemoved, alsReq })
148
177
  return isRemoved
@@ -153,6 +182,16 @@ class BasePartiesService {
153
182
  return ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
154
183
  }
155
184
 
185
+ createFspiopPartyNotFoundError (errMessage, log = this.log) {
186
+ log.warn(errMessage)
187
+ return ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.PARTY_NOT_FOUND, errMessage)
188
+ }
189
+
190
+ createFspiopServiceUnavailableError (errMessage, log = this.log) {
191
+ log.warn(errMessage)
192
+ return ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.SERVICE_CURRENTLY_UNAVAILABLE, errMessage)
193
+ }
194
+
156
195
  stepInProgress (stepName) {
157
196
  this.log.debug('step is in progress', { stepName })
158
197
  this.state.stepState?.inProgress(stepName)
@@ -192,8 +231,8 @@ class BasePartiesService {
192
231
  }
193
232
  }
194
233
 
195
- static createErrorCallbackHeaders (headers, params) {
196
- return createCallbackHeaders({
234
+ static createErrorCallbackHeaders (headers, params, overrideDestination = '') {
235
+ const cbHeaders = createCallbackHeaders({
197
236
  requestHeaders: headers,
198
237
  partyIdType: params.Type,
199
238
  partyIdentifier: params.ID,
@@ -201,6 +240,10 @@ class BasePartiesService {
201
240
  ? FspEndpointTemplates.PARTIES_SUB_ID_PUT_ERROR
202
241
  : FspEndpointTemplates.PARTIES_PUT_ERROR
203
242
  })
243
+ if (overrideDestination) {
244
+ cbHeaders[Headers.FSPIOP.DESTINATION] = overrideDestination
245
+ }
246
+ return cbHeaders
204
247
  }
205
248
 
206
249
  static createHubErrorCallbackHeaders (hubName, destination) {
@@ -148,10 +148,13 @@ class GetPartiesService extends BasePartiesService {
148
148
  }
149
149
 
150
150
  const alsReq = await this.#setProxyListToCache(proxyNames, source, params)
151
- const sentList = await this.#sendOutProxyRequests({ proxyNames, alsReq, headers, params })
151
+ const { sentList, wasLast } = await this.#sendOutProxyRequests({ proxyNames, alsReq, headers, params })
152
152
  if (sentList.length === 0) {
153
153
  throw super.createFspiopIdNotFoundError(ERROR_MESSAGES.proxyConnectionError, log)
154
154
  }
155
+ if (wasLast) {
156
+ throw super.createFspiopIdNotFoundError(ERROR_MESSAGES.noSuccessfulProxyDiscoveryResponses, log)
157
+ }
155
158
 
156
159
  log.info('triggerInterSchemeDiscoveryFlow is done:', { sentList, alsReq })
157
160
  return sentList
@@ -233,7 +236,7 @@ class GetPartiesService extends BasePartiesService {
233
236
  async #sendPartyNotFoundErrorCallback (headers) {
234
237
  const { params } = this.inputs
235
238
  const callbackHeaders = GetPartiesService.createErrorCallbackHeaders(headers, params)
236
- const fspiopError = super.createFspiopIdNotFoundError('No proxy found to start inter-scheme discovery flow')
239
+ const fspiopError = super.createFspiopPartyNotFoundError('No proxy found to start inter-scheme discovery flow')
237
240
  const errorInfo = await this.deps.partiesUtils.makePutPartiesErrorPayload(
238
241
  this.deps.config, fspiopError, callbackHeaders, params
239
242
  )
@@ -262,21 +265,23 @@ class GetPartiesService extends BasePartiesService {
262
265
  async #sendOutProxyRequests ({ proxyNames, alsReq, headers, params }) {
263
266
  this.stepInProgress('#sendOutProxyRequests')
264
267
  const sentList = []
268
+ let wasLast = false // if any failed proxy request was last
265
269
 
266
270
  const sendProxyRequest = (sendTo) => this.#forwardGetPartiesRequest({ sendTo, headers, params })
267
271
  .then(() => { sentList.push(sendTo) })
268
272
  .catch(err => {
269
273
  this.log.error(`error in sending request to proxy ${sendTo}: `, err)
270
- this.log.verbose(`remove proxy ${sendTo} from proxyCache...`)
274
+ this.log.verbose(`removing proxy ${sendTo} from proxyCache...`)
271
275
  return this.deps.proxyCache.receivedErrorResponse(alsReq, sendTo)
272
276
  })
277
+ .then((isLast) => { wasLast = isLast })
273
278
  .catch(err => {
274
279
  this.log.error(`failed to remove proxy ${sendTo} from proxyCache: `, err)
275
280
  })
276
281
  await Promise.all(proxyNames.map(sendProxyRequest))
277
282
 
278
- this.log.verbose('#sendOutProxyRequests is done:', { sentList, proxyNames })
279
- return sentList
283
+ this.log.verbose('#sendOutProxyRequests is done:', { sentList, wasLast, proxyNames })
284
+ return { sentList, wasLast }
280
285
  }
281
286
 
282
287
  async #getFilteredProxyList (proxy) {
@@ -25,35 +25,36 @@
25
25
  --------------
26
26
  ******/
27
27
 
28
- const ErrorHandler = require('@mojaloop/central-services-error-handling')
29
- const { ERROR_MESSAGES } = require('../../../constants')
30
28
  const BasePartiesService = require('./BasePartiesService')
29
+ const { ERROR_MESSAGES } = require('../../../constants')
31
30
 
32
31
  class PutPartiesErrorService extends BasePartiesService {
33
- /** @returns {Promise<true | undefined>} - If true, need to trigger inter-scheme discovery. */
34
32
  async handleRequest () {
35
33
  if (this.state.proxyEnabled && this.state.proxy) {
36
34
  const alsReq = this.deps.partiesUtils.alsRequestDto(this.state.destination, this.inputs.params) // or source?
37
- const isPending = await this.deps.proxyCache.isPendingCallback(alsReq)
38
-
39
- if (!isPending) {
40
- // not initial inter-scheme discovery case. Cleanup oracle and trigger inter-scheme discovery
41
- this.log.warn('Need to cleanup oracle and trigger new inter-scheme discovery flow')
42
- await this.cleanupOracle()
43
- await this.removeProxyGetPartiesTimeoutCache(alsReq)
44
- return true // need to trigger inter-scheme discovery
45
- }
46
-
47
- const isLast = await this.checkLastProxyCallback(alsReq)
48
- if (!isLast) {
49
- this.log.verbose('putPartiesErrorByTypeAndID proxy callback was processed')
50
- return
35
+ const isInterSchemeDiscoveryCase = await this.deps.proxyCache.isPendingCallback(alsReq)
36
+
37
+ if (isInterSchemeDiscoveryCase) {
38
+ const isLast = await this.checkLastProxyCallback(alsReq)
39
+ if (!isLast) {
40
+ this.log.verbose('proxy error callback was processed (not last)')
41
+ return
42
+ }
43
+ } else {
44
+ const schemeParticipant = await this.validateParticipant(this.state.destination)
45
+ if (schemeParticipant) {
46
+ this.log.info('Need to cleanup oracle and forward SERVICE_CURRENTLY_UNAVAILABLE error')
47
+ await this.cleanupOracle()
48
+ await this.removeProxyGetPartiesTimeoutCache(alsReq)
49
+ await this.forwardServiceUnavailableErrorCallback()
50
+ return
51
+ }
51
52
  }
52
53
  }
53
54
 
54
- await this.identifyDestinationForErrorCallback()
55
+ await super.identifyDestinationForCallback()
55
56
  await this.sendErrorCallbackToParticipant()
56
- this.log.info('putPartiesByTypeAndID is done')
57
+ this.log.info('handleRequest is done')
57
58
  }
58
59
 
59
60
  async cleanupOracle () {
@@ -72,32 +73,26 @@ class PutPartiesErrorService extends BasePartiesService {
72
73
  return isLast
73
74
  }
74
75
 
75
- async identifyDestinationForErrorCallback () {
76
- this.stepInProgress('identifyDestinationForErrorCallback')
77
- const { destination } = this.state
78
- const schemeParticipant = await super.validateParticipant(destination)
79
- if (schemeParticipant) {
80
- this.state.requester = destination
81
- return
82
- }
83
-
84
- this.stepInProgress('lookupProxyDestination-4')
85
- const proxyName = this.state.proxyEnabled && await this.deps.proxyCache.lookupProxyByDfspId(destination)
86
- if (proxyName) {
87
- this.state.requester = proxyName
88
- return
89
- }
90
-
91
- const errMessage = ERROR_MESSAGES.partyDestinationFspNotFound
92
- this.log.warn(errMessage, { destination })
93
- throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_FSP_ERROR, errMessage)
94
- }
95
-
96
76
  async sendErrorCallbackToParticipant () {
97
77
  const { headers, params, dataUri } = this.inputs
98
78
  const errorInfo = PutPartiesErrorService.decodeDataUriPayload(dataUri)
99
79
  return super.sendErrorCallback({ errorInfo, headers, params })
100
80
  }
81
+
82
+ async forwardServiceUnavailableErrorCallback () {
83
+ this.stepInProgress('forwardServiceUnavailableErrorCallback')
84
+ const { headers, params } = this.inputs
85
+ const error = super.createFspiopServiceUnavailableError(ERROR_MESSAGES.externalPartyError)
86
+ const callbackHeaders = BasePartiesService.createErrorCallbackHeaders(headers, params, this.state.destination)
87
+ const errorInfo = await this.deps.partiesUtils.makePutPartiesErrorPayload(this.deps.config, error, callbackHeaders, params)
88
+
89
+ await super.sendErrorCallback({
90
+ errorInfo,
91
+ headers: callbackHeaders,
92
+ params
93
+ })
94
+ this.log.verbose('#forwardServiceUnavailableErrorCallback is done', { callbackHeaders, errorInfo })
95
+ }
101
96
  }
102
97
 
103
98
  module.exports = PutPartiesErrorService
@@ -25,7 +25,6 @@
25
25
  --------------
26
26
  ******/
27
27
 
28
- const ErrorHandler = require('@mojaloop/central-services-error-handling')
29
28
  const { ERROR_MESSAGES } = require('../../../constants')
30
29
  const BasePartiesService = require('./BasePartiesService')
31
30
 
@@ -40,7 +39,7 @@ class PutPartiesService extends BasePartiesService {
40
39
  if (proxy) {
41
40
  await this.checkProxySuccessResponse()
42
41
  }
43
- await this.identifyDestinationForSuccessCallback()
42
+ await this.identifyDestinationForCallback()
44
43
  await this.sendSuccessCallback()
45
44
  }
46
45
 
@@ -85,25 +84,6 @@ class PutPartiesService extends BasePartiesService {
85
84
  }
86
85
  }
87
86
 
88
- async identifyDestinationForSuccessCallback () {
89
- const { destination } = this.state
90
- this.stepInProgress('identifyDestinationForSuccessCallback')
91
- const destinationParticipant = await super.validateParticipant(destination)
92
- if (destinationParticipant) {
93
- this.state.requester = destinationParticipant.name
94
- return
95
- }
96
-
97
- const proxyName = this.state.proxyEnabled && await this.deps.proxyCache.lookupProxyByDfspId(destination)
98
- if (!proxyName) {
99
- const errMessage = ERROR_MESSAGES.partyDestinationFspNotFound
100
- this.log.warn(`${errMessage} and no proxy`, { destination })
101
- throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_FSP_ERROR, errMessage)
102
- }
103
-
104
- this.state.requester = proxyName
105
- }
106
-
107
87
  async sendSuccessCallback () {
108
88
  const { headers, params, dataUri } = this.inputs
109
89
  const sendTo = this.state.requester
@@ -52,7 +52,7 @@ class TimeoutPartiesService extends PutPartiesErrorService {
52
52
  const { errorInfo, headers, params } = await this.prepareErrorInformation()
53
53
  this.#spanAuditStart(errorInfo)
54
54
 
55
- await this.identifyDestinationForErrorCallback()
55
+ await this.identifyDestinationForCallback()
56
56
  return super.sendErrorCallback({ errorInfo, headers, params })
57
57
  }
58
58
 
@@ -38,12 +38,12 @@ const { HANDLER_TYPES } = require('../constants')
38
38
  const Config = require('../lib/config')
39
39
  const log = require('../lib').logger.child('ALS-timeout-handler')
40
40
 
41
- process.on('uncaughtExceptionMonitor', (err) => {
42
- log.error(`uncaughtException: ${err?.message}`, err)
41
+ process.on('uncaughtException', (err, origin) => {
42
+ log.error(`uncaughtException event [origin: ${origin}]: `, err)
43
43
  process.exit(2)
44
44
  })
45
45
  process.on('unhandledRejection', (err) => {
46
- log.error(`unhandledRejection: ${err?.message}`, err)
46
+ log.error('unhandledRejection event: ', err)
47
47
  process.exit(3)
48
48
  })
49
49
 
package/src/index.js CHANGED
@@ -24,7 +24,8 @@
24
24
 
25
25
  'use strict'
26
26
 
27
- process.env.HTTP_DEFAULT_RETRY_DELAY = process.env.HTTP_DEFAULT_RETRY_DELAY || '0'
27
+ process.env.HTTP_DEFAULT_RETRIES = process.env.HTTP_DEFAULT_RETRIES || '0'
28
+ // todo: think better way to avoid reties
28
29
 
29
30
  const Server = require('./server')
30
31
  const PJson = require('../package.json')
package/src/server.js CHANGED
@@ -45,7 +45,18 @@ const OracleEndpointCache = require('./models/oracle/oracleEndpointCached')
45
45
  const Handlers = require('./handlers/register')
46
46
 
47
47
  const connectDatabase = async (dbConfig) => {
48
- return Db.connect(dbConfig)
48
+ await Db.connect(dbConfig)
49
+ logger.info('Database connected')
50
+ }
51
+
52
+ const initOpenApiBackend = async ({ isAdmin }) => {
53
+ const OpenAPISpecPath = Util.pathForInterface({ isAdmin, isMockInterface: false })
54
+ const apiHandlers = isAdmin
55
+ ? APIHandlers.AdminHandlers
56
+ : APIHandlers.ApiHandlers
57
+ const api = await OpenapiBackend.initialise(OpenAPISpecPath, apiHandlers)
58
+ logger.verbose('OpenAPI Backend initialized', { isAdmin })
59
+ return api
49
60
  }
50
61
 
51
62
  const migrate = async () => {
@@ -132,8 +143,7 @@ const initializeApi = async (appConfig) => {
132
143
  initializeInstrumentation(INSTRUMENTATION_METRICS_CONFIG)
133
144
  }
134
145
  await connectDatabase(DATABASE)
135
- const OpenAPISpecPath = Util.pathForInterface({ isAdmin: false, isMockInterface: false })
136
- const api = await OpenapiBackend.initialise(OpenAPISpecPath, APIHandlers.ApiHandlers)
146
+ const api = await initOpenApiBackend({ isAdmin: false })
137
147
 
138
148
  await Promise.all([
139
149
  Endpoints.initializeCache(CENTRAL_SHARED_ENDPOINT_CACHE_CONFIG, Util.hubNameConfig),
@@ -142,6 +152,7 @@ const initializeApi = async (appConfig) => {
142
152
  OracleEndpointCache.initialize(),
143
153
  Cache.initCache()
144
154
  ])
155
+ logger.verbose('all caches initialized')
145
156
 
146
157
  return createServer(API_PORT, api, Routes.APIRoutes(api), false, PROXY_CACHE_CONFIG)
147
158
  }
@@ -161,8 +172,8 @@ const initializeAdmin = async (appConfig) => {
161
172
  }
162
173
  await connectDatabase(DATABASE)
163
174
  RUN_MIGRATIONS && await migrate()
164
- const OpenAPISpecPath = Util.pathForInterface({ isAdmin: true, isMockInterface: false })
165
- const api = await OpenapiBackend.initialise(OpenAPISpecPath, APIHandlers.AdminHandlers)
175
+ const api = await initOpenApiBackend({ isAdmin: true })
176
+
166
177
  await Promise.all([
167
178
  OracleEndpointCache.initialize(),
168
179
  Cache.initCache()
@@ -26,10 +26,11 @@
26
26
  ******/
27
27
 
28
28
  const { randomUUID } = require('node:crypto')
29
- const { Enum } = require('@mojaloop/central-services-shared')
29
+ const { Enum, Util } = jest.requireActual('@mojaloop/central-services-shared')
30
30
  const isoFixtures = require('./iso')
31
31
 
32
32
  const { Headers } = Enum.Http
33
+ const { encodePayload } = Util.StreamingProtocol
33
34
 
34
35
  const headersDto = ({
35
36
  source = 'fromDfsp',
@@ -37,14 +38,16 @@ const headersDto = ({
37
38
  proxy = '',
38
39
  date = new Date().toUTCString(),
39
40
  accept,
40
- contentType
41
+ contentType,
42
+ addHeaders
41
43
  } = {}) => Object.freeze({
42
44
  [Headers.FSPIOP.SOURCE]: source,
43
45
  ...(destination && { [Headers.FSPIOP.DESTINATION]: destination }),
44
46
  ...(proxy && { [Headers.FSPIOP.PROXY]: proxy }),
45
47
  date,
46
48
  accept,
47
- 'content-type': contentType || accept
49
+ 'content-type': contentType || accept,
50
+ ...(addHeaders && { ...addHeaders })
48
51
  })
49
52
 
50
53
  const partiesParamsDto = ({
@@ -72,12 +75,14 @@ const partiesCallHeadersDto = ({
72
75
  source,
73
76
  destination,
74
77
  proxy,
75
- date
78
+ date,
79
+ addHeaders
76
80
  } = {}) => headersDto({
77
81
  source,
78
82
  destination,
79
83
  proxy,
80
84
  date,
85
+ addHeaders,
81
86
  accept: interopHeader('parties', '1'),
82
87
  contentType: interopHeader('parties', '1.1')
83
88
  })
@@ -141,6 +146,8 @@ const postParticipantsPayloadDto = ({
141
146
  ...(currency && { currency })
142
147
  })
143
148
 
149
+ const dataUriDto = (payload = {}) => encodePayload(JSON.stringify(payload), 'application/json')
150
+
144
151
  const errorCallbackResponseDto = ({
145
152
  errorCode = '1234',
146
153
  errorDescription = 'Error description',
@@ -191,6 +198,7 @@ module.exports = {
191
198
  oracleRequestResponseDto,
192
199
  putPartiesSuccessResponseDto,
193
200
  postParticipantsPayloadDto,
201
+ dataUriDto,
194
202
  errorCallbackResponseDto,
195
203
  expiredCacheKeyDto,
196
204
  mockAlsRequestDto,
@@ -62,6 +62,7 @@ describe('Parties Endpoints Tests -->', () => {
62
62
  partyIdType: PARTY_ID_TYPE,
63
63
  source: PAYER_DFSP,
64
64
  destination: ''
65
+ // addHeaders: { 'x-response-status': '503' }
65
66
  })
66
67
  expect(result.status).toBe(202)
67
68
 
@@ -587,20 +587,20 @@ describe('Parties Tests', () => {
587
587
  it('successfully sends the callback to the participant', async () => {
588
588
  expect.hasAssertions()
589
589
  // Arrange
590
- participant.validateParticipant = sandbox.stub().resolves({
591
- name: 'fsp1'
592
- })
590
+ participant.validateParticipant = sandbox.stub().resolves({})
593
591
  participant.sendRequest = sandbox.stub().resolves()
594
592
  const payload = JSON.stringify({ testPayload: true })
595
593
  const dataUri = encodePayload(payload, 'application/json')
594
+ const destination = 'destFsp'
595
+ const headers = fixtures.partiesCallHeadersDto({ destination })
596
596
 
597
597
  // Act
598
- await partiesDomain.putPartiesByTypeAndID(Helper.putByTypeIdRequest.headers, Helper.putByTypeIdRequest.params, 'put', payload, dataUri, null, proxyCache)
598
+ await partiesDomain.putPartiesByTypeAndID(headers, Helper.putByTypeIdRequest.params, 'put', payload, dataUri, null, proxyCache)
599
599
 
600
600
  // Assert
601
601
  expect(participant.sendRequest.callCount).toBe(1)
602
602
  const sendRequestCallArgs = participant.sendRequest.getCall(0).args
603
- expect(sendRequestCallArgs[1]).toStrictEqual('fsp1')
603
+ expect(sendRequestCallArgs[1]).toBe(destination)
604
604
  participant.sendRequest.reset()
605
605
  })
606
606
 
@@ -927,14 +927,15 @@ describe('Parties Tests', () => {
927
927
  expect(sendErrorCallArgs[1]).toBe(expectedCallbackEnpointType)
928
928
  })
929
929
 
930
- it('should handle notValidPayeeIdentifier case, and delete partyId from oracle', async () => {
930
+ it('should handle external party error callback, and delete partyId from oracle', async () => {
931
931
  Config.PROXY_CACHE_CONFIG.enabled = true
932
932
  const errorCode = MojaloopApiErrorCodes.PAYEE_IDENTIFIER_NOT_VALID.code
933
933
  const payload = fixtures.errorCallbackResponseDto({ errorCode })
934
- const source = `source-${Date.now()}`
934
+ const destination = `dest-${Date.now()}`
935
935
  const proxy = `proxy-${Date.now()}`
936
- const headers = fixtures.partiesCallHeadersDto({ source, proxy })
936
+ const headers = fixtures.partiesCallHeadersDto({ destination, proxy })
937
937
  const { params } = Helper.putByTypeIdRequest
938
+ participant.validateParticipant = sandbox.stub().resolves({})
938
939
  participant.sendRequest = sandbox.stub().resolves()
939
940
  participant.sendErrorToParticipant = sandbox.stub().resolves()
940
941
  oracleEndpointCached.getOracleEndpointByType = sandbox.stub().resolves([
@@ -944,13 +945,14 @@ describe('Parties Tests', () => {
944
945
 
945
946
  await partiesDomain.putPartiesErrorByTypeAndID(headers, params, payload, '', null, null, proxyCache)
946
947
 
947
- expect(participant.sendRequest.callCount).toBe(0)
948
948
  expect(oracle.oracleRequest.callCount).toBe(1)
949
- const [, method] = oracle.oracleRequest.getCall(0).args
950
- expect(method).toBe(RestMethods.DELETE)
951
- // todo: think, how to stub getPartiesByTypeAndID call
952
- // expect(partiesDomain.getPartiesByTypeAndID.callCount).toBe(1)
953
- // expect(participant.sendErrorToParticipant.callCount).toBe(0)
949
+ expect(oracle.oracleRequest.lastCall.args[1]).toBe(RestMethods.DELETE)
950
+ expect(participant.sendRequest.callCount).toBe(0)
951
+ expect(participant.sendErrorToParticipant.callCount).toBe(1)
952
+ // eslint-disable-next-line no-unused-vars
953
+ const [sentTo, _, data] = participant.sendErrorToParticipant.lastCall.args
954
+ expect(sentTo).toBe(destination)
955
+ expect(data.errorInformation.errorCode).toBe('2003')
954
956
  })
955
957
  })
956
958
  })
@@ -54,7 +54,8 @@ describe('BasePartiesService Tests -->', () => {
54
54
  expect(sentTo).toBe(source)
55
55
  expect(payload.Rpt.Rsn.Cd).toBe('2001')
56
56
  expect(payload.Rpt.OrgnlId).toBe(`${params.Type}/${params.ID}`)
57
- expect(payload.Assgnmt.Assgnr.Agt.FinInstnId.Othr.Id).toBe(source)
57
+ expect(payload.Assgnmt.Assgne.Agt.FinInstnId.Othr.Id).toBe(source)
58
+ expect(payload.Assgnmt.Assgnr.Agt.FinInstnId.Othr.Id).toBe(config.HUB_NAME)
58
59
  })
59
60
 
60
61
  test('should remove proxy getParties timeout cache key', async () => {
@@ -25,6 +25,7 @@
25
25
  --------------
26
26
  ******/
27
27
 
28
+ const { setTimeout: sleep } = require('node:timers/promises')
28
29
  const {
29
30
  createMockDeps,
30
31
  createProxyCacheMock,
@@ -197,6 +198,51 @@ describe('GetPartiesService Tests -->', () => {
197
198
  expect(deps.proxyCache.receivedErrorResponse).toHaveBeenCalledTimes(1)
198
199
  expect(participantMock.sendRequest.mock.lastCall[1]).toBe(proxyOk)
199
200
  })
201
+
202
+ const throwDelayedErrorOnNthCall = (N, delay = 1000, error = new Error('Nth call Delayed Error')) => {
203
+ let count = 0
204
+ return async () => {
205
+ count++
206
+ if (count !== N) return {}
207
+ await sleep(1000)
208
+ throw error
209
+ }
210
+ }
211
+
212
+ const prepareGetPartiesServiceForDelayedProxyError = () => {
213
+ participantMock.sendRequest = jest.fn(throwDelayedErrorOnNthCall(2)) // throw error on 2nd proxy call
214
+ const proxies = createProxiesUtilMock({
215
+ getAllProxiesNames: jest.fn().mockResolvedValue(['proxy1', 'proxy2'])
216
+ })
217
+ const proxyCache = createProxyCacheMock({
218
+ receivedErrorResponse: jest.fn().mockResolvedValue(true) // failed proxy request is last in inter-scheme discovery flow
219
+ })
220
+ const deps = createMockDeps({ proxies, proxyCache })
221
+ const headers = fixtures.partiesCallHeadersDto({ destination: '' })
222
+ const params = fixtures.partiesParamsDto()
223
+
224
+ return new GetPartiesService(deps, { headers, params })
225
+ }
226
+
227
+ test('should throw an error if proxyRequest failed after delay, and other proxies have already replied', async () => {
228
+ expect.assertions(1)
229
+ const service = prepareGetPartiesServiceForDelayedProxyError()
230
+ const { headers } = service.inputs
231
+
232
+ await expect(service.triggerInterSchemeDiscoveryFlow(headers))
233
+ .rejects.toThrow(ERROR_MESSAGES.noSuccessfulProxyDiscoveryResponses)
234
+ })
235
+
236
+ test('should send error callback in ISO format if proxyRequest failed after delay, and other proxies have already replied', async () => {
237
+ const service = prepareGetPartiesServiceForDelayedProxyError()
238
+ const { headers } = service.inputs
239
+ service.deps.config.API_TYPE = API_TYPES.iso20022
240
+
241
+ await service.triggerInterSchemeDiscoveryFlow(headers)
242
+ .catch(err => service.handleError(err))
243
+ expect(participantMock.sendErrorToParticipant).toHaveBeenCalledTimes(1)
244
+ expect(participantMock.sendErrorToParticipant.mock.lastCall[2].Rpt.Rsn.Cd).toBe('3200')
245
+ })
200
246
  })
201
247
 
202
248
  describe('setProxyGetPartiesTimeout Tests', () => {
@@ -25,7 +25,7 @@
25
25
  --------------
26
26
  ******/
27
27
 
28
- const { createMockDeps, oracleMock } = require('./deps')
28
+ const { createMockDeps, oracleMock, participantMock } = require('./deps')
29
29
  // ↑ should be first require to mock external deps ↑
30
30
  const { PutPartiesErrorService } = require('#src/domain/parties/services/index')
31
31
  const fixtures = require('#test/fixtures/index')
@@ -37,14 +37,41 @@ describe('PutPartiesErrorService Tests -->', () => {
37
37
  jest.clearAllMocks()
38
38
  })
39
39
 
40
- test('should cleanup oracle and trigger discovery flow for party from external dfsp', async () => {
41
- const headers = fixtures.partiesCallHeadersDto({ proxy: 'proxyA' })
40
+ test('should cleanup oracle and forward SERVICE_CURRENTLY_UNAVAILABLE error for party from external dfsp', async () => {
41
+ participantMock.validateParticipant = jest.fn().mockResolvedValue({})
42
+ const destination = 'destFsp'
43
+ const headers = fixtures.partiesCallHeadersDto({ destination, proxy: 'proxyA' })
42
44
  const params = fixtures.partiesParamsDto()
43
- const service = new PutPartiesErrorService(createMockDeps(), { headers, params })
45
+ const dataUri = fixtures.dataUriDto()
46
+ const service = new PutPartiesErrorService(createMockDeps(), { headers, params, dataUri })
44
47
 
45
- const needDiscovery = await service.handleRequest()
46
- expect(needDiscovery).toBe(true)
47
- expect(oracleMock.oracleRequest.mock.calls.length).toBe(1)
48
+ await service.handleRequest()
49
+ expect(oracleMock.oracleRequest).toHaveBeenCalledTimes(1)
48
50
  expect(oracleMock.oracleRequest.mock.lastCall[1]).toBe(RestMethods.DELETE)
51
+ expect(participantMock.sendErrorToParticipant).toHaveBeenCalledTimes(1)
52
+ // eslint-disable-next-line no-unused-vars
53
+ const [sentTo, _, payload] = participantMock.sendErrorToParticipant.mock.lastCall
54
+ expect(sentTo).toBe(destination)
55
+ expect(payload.errorInformation.errorCode).toBe('2003')
56
+ })
57
+
58
+ test('should NOT cleanup oracle if destination is external', async () => {
59
+ const destination = 'externalDfsp'
60
+ const proxyDest = 'proxyDest'
61
+ const deps = createMockDeps()
62
+ deps.participant.validateParticipant = jest.fn().mockResolvedValue(null)
63
+ deps.proxyCache.lookupProxyByDfspId = jest.fn().mockResolvedValue(proxyDest)
64
+
65
+ const headers = fixtures.partiesCallHeadersDto({
66
+ destination, proxy: 'proxyA'
67
+ })
68
+ const params = fixtures.partiesParamsDto()
69
+ const dataUri = fixtures.dataUriDto()
70
+ const service = new PutPartiesErrorService(deps, { headers, params, dataUri })
71
+
72
+ await service.handleRequest()
73
+ expect(oracleMock.oracleRequest).not.toHaveBeenCalled()
74
+ expect(participantMock.sendErrorToParticipant).toHaveBeenCalledTimes(1)
75
+ expect(participantMock.sendErrorToParticipant.mock.lastCall[0]).toBe(proxyDest)
49
76
  })
50
77
  })
@@ -8,9 +8,9 @@ class AlsProxyApiClient extends BasicApiClient {
8
8
  super({ ...deps, baseURL })
9
9
  }
10
10
 
11
- async getPartyByIdAndType ({ partyId, partyIdType, source, destination, proxy = '' }) {
11
+ async getPartyByIdAndType ({ partyId, partyIdType, source, destination, proxy = '', addHeaders = null }) {
12
12
  return this.sendPartyRequest({
13
- partyId, partyIdType, source, destination, proxy
13
+ partyId, partyIdType, source, destination, proxy, addHeaders
14
14
  })
15
15
  }
16
16
 
@@ -27,10 +27,12 @@ class AlsProxyApiClient extends BasicApiClient {
27
27
  })
28
28
  }
29
29
 
30
- async sendPartyRequest ({ partyId, partyIdType, source, destination, proxy = '', body = null, isError = false }) {
30
+ async sendPartyRequest ({
31
+ partyId, partyIdType, source, destination, proxy = '', body = null, isError = false, addHeaders = null
32
+ }) {
31
33
  const method = body ? 'PUT' : 'GET'
32
34
  const url = `/parties/${partyIdType}/${partyId}${isError ? '/error' : ''}`
33
- const headers = this.fixtures.partiesCallHeadersDto({ source, destination, proxy })
35
+ const headers = this.fixtures.partiesCallHeadersDto({ source, destination, proxy, addHeaders })
34
36
 
35
37
  return this.sendRequest({
36
38
  method,