account-lookup-service 17.7.0-snapshot.0 → 17.7.0-snapshot.2

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/package.json CHANGED
@@ -1,10 +1,11 @@
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.7.0-snapshot.0",
4
+ "version": "17.7.0-snapshot.2",
5
5
  "license": "Apache-2.0",
6
6
  "author": "ModusBox",
7
7
  "contributors": [
8
+ "Eugen Klymniuk <eugen.klymniuk@infitx.com>",
8
9
  "Rajiv Mothilal <rajiv.mothilal@modusbox.com>",
9
10
  "Matt Kingston <matt.kingston@modusbox.com>",
10
11
  "Lewis Daly <lewisd@crosslaketech.com>",
@@ -93,13 +94,13 @@
93
94
  "@mojaloop/central-services-health": "15.0.4",
94
95
  "@mojaloop/central-services-logger": "11.7.0",
95
96
  "@mojaloop/central-services-metrics": "12.5.0",
96
- "@mojaloop/central-services-shared": "18.23.1-snapshot.0",
97
- "@mojaloop/central-services-stream": "11.5.1",
97
+ "@mojaloop/central-services-shared": "18.23.1",
98
+ "@mojaloop/central-services-stream": "11.5.2",
98
99
  "@mojaloop/database-lib": "11.1.4",
99
- "@mojaloop/event-sdk": "14.3.2",
100
- "@mojaloop/inter-scheme-proxy-cache-lib": "2.4.0-snapshot.0",
101
- "@mojaloop/ml-schema-transformer-lib": "2.5.8",
102
- "@mojaloop/sdk-standard-components": "19.11.0",
100
+ "@mojaloop/event-sdk": "14.4.0",
101
+ "@mojaloop/inter-scheme-proxy-cache-lib": "2.4.0",
102
+ "@mojaloop/ml-schema-transformer-lib": "2.6.0",
103
+ "@mojaloop/sdk-standard-components": "19.11.1",
103
104
  "@now-ims/hapi-now-auth": "2.1.0",
104
105
  "ajv": "8.17.1",
105
106
  "ajv-keywords": "5.1.0",
@@ -171,12 +172,12 @@
171
172
  "jest-junit": "16.0.0",
172
173
  "jsdoc": "4.0.4",
173
174
  "nodemon": "3.1.9",
174
- "npm-check-updates": "17.1.15",
175
+ "npm-check-updates": "17.1.16",
175
176
  "nyc": "17.1.0",
176
177
  "pre-commit": "1.2.2",
177
178
  "proxyquire": "2.1.3",
178
179
  "replace": "^1.2.2",
179
- "sinon": "19.0.4",
180
+ "sinon": "20.0.0",
180
181
  "standard": "17.1.2",
181
182
  "standard-version": "^9.5.0",
182
183
  "swagmock": "1.0.0"
package/src/constants.js CHANGED
@@ -25,6 +25,8 @@
25
25
  --------------
26
26
  ******/
27
27
 
28
+ const { API_TYPES } = require('@mojaloop/central-services-shared').Util.Hapi
29
+
28
30
  const ERROR_MESSAGES = Object.freeze({
29
31
  emptyFilteredPartyList: 'Empty oracle partyList, filtered based on callbackEndpointType',
30
32
  failedToCacheSendToProxiesList: 'Failed to cache sendToProxiesList',
@@ -40,6 +42,7 @@ const HANDLER_TYPES = Object.freeze({
40
42
  })
41
43
 
42
44
  module.exports = {
45
+ API_TYPES,
43
46
  ERROR_MESSAGES,
44
47
  HANDLER_TYPES
45
48
  }
@@ -32,6 +32,7 @@ const oracle = require('../../models/oracle/facade')
32
32
  const participant = require('../../models/participantEndpoint/facade')
33
33
  const partiesUtils = require('./partiesUtils')
34
34
 
35
+ /** @returns {PartiesDeps} */
35
36
  const createDeps = ({ cache, proxyCache, childSpan, log = logger }) => Object.freeze({
36
37
  cache,
37
38
  proxyCache,
@@ -25,13 +25,9 @@
25
25
  --------------
26
26
  ******/
27
27
 
28
- const { Enum, Util: { Hapi } } = require('@mojaloop/central-services-shared')
29
- const { MojaloopApiErrorCodes } = require('@mojaloop/sdk-standard-components').Errors
30
- // todo: check why do we need sdk-standard-components deps here !!!
31
- const ErrorHandler = require('@mojaloop/central-services-error-handling')
32
-
33
- const participant = require('../../models/participantEndpoint/facade')
28
+ const { Enum } = require('@mojaloop/central-services-shared')
34
29
  const { TransformFacades } = require('../../lib')
30
+ const { API_TYPES } = require('../../constants')
35
31
 
36
32
  const { FspEndpointTypes } = Enum.EndPoints
37
33
  const { Headers } = Enum.Http
@@ -50,7 +46,7 @@ const errorPartyCbType = (partySubId) => partySubId
50
46
 
51
47
  const makePutPartiesErrorPayload = async (config, fspiopError, headers, params) => {
52
48
  const body = fspiopError.toApiErrorObject(config.ERROR_HANDLING)
53
- return config.API_TYPE === Hapi.API_TYPES.iso20022
49
+ return config.API_TYPE === API_TYPES.iso20022
54
50
  ? (await TransformFacades.FSPIOP.parties.putError({ body, headers, params })).body
55
51
  : body
56
52
  }
@@ -81,38 +77,12 @@ const swapSourceDestinationHeaders = (headers) => {
81
77
  }
82
78
  }
83
79
 
84
- // todo: check if we need this function
85
- const createErrorHandlerOnSendingCallback = (config, logger) => async (err, headers, params, requester) => {
86
- try {
87
- logger.error('error in sending parties callback: ', err)
88
- const sendTo = requester || headers[Headers.FSPIOP.SOURCE]
89
- const errorCallbackEndpointType = errorPartyCbType(params.SubId)
90
- const fspiopError = ErrorHandler.Factory.reformatFSPIOPError(err)
91
- const errInfo = await makePutPartiesErrorPayload(config, fspiopError, headers, params)
92
-
93
- await participant.sendErrorToParticipant(sendTo, errorCallbackEndpointType, errInfo, headers, params)
94
-
95
- logger.info('handleErrorOnSendingCallback in done', { sendTo, params, errInfo })
96
- return fspiopError
97
- } catch (exc) {
98
- // We can't do anything else here- we _must_ handle all errors _within_ this function because
99
- // we've already sent a sync response- we cannot throw.
100
- logger.error('failed to handleErrorOnSendingCallback. No further processing! ', exc)
101
- }
102
- }
103
-
104
- function isNotValidPayeeCase (payload) {
105
- return payload?.errorInformation?.errorCode === MojaloopApiErrorCodes.PAYEE_IDENTIFIER_NOT_VALID.code
106
- }
107
-
108
80
  module.exports = {
109
81
  getPartyCbType,
110
82
  putPartyCbType,
111
83
  errorPartyCbType,
112
84
  makePutPartiesErrorPayload,
113
- createErrorHandlerOnSendingCallback,
114
85
  alsRequestDto,
115
86
  partiesRequestOptionsDto,
116
- swapSourceDestinationHeaders,
117
- isNotValidPayeeCase
87
+ swapSourceDestinationHeaders
118
88
  }
@@ -35,6 +35,21 @@ const { FspEndpointTypes, FspEndpointTemplates } = Enum.EndPoints
35
35
  const { Headers, RestMethods } = Enum.Http
36
36
 
37
37
  /**
38
+ * @typedef {Object} PartiesDeps
39
+ * @property {Object} cache
40
+ * @property {Object} proxyCache
41
+ * @property {Object} log
42
+ * @property {Object} config
43
+ * @property {Object} oracle
44
+ * @property {Object} participant
45
+ * @property {Proxies} proxies
46
+ * @property {Object} partiesUtils
47
+ * @property {Object} [childSpan]
48
+ */
49
+
50
+ /**
51
+ * Input parameters from party lookup request
52
+ *
38
53
  * @typedef {Object} PartiesInputs
39
54
  * @property {Object} headers - incoming http request headers.
40
55
  * @property {Object} params - uri parameters of the http request.
@@ -43,13 +58,25 @@ const { Headers, RestMethods } = Enum.Http
43
58
  * @property {string} [dataUri] - encoded payload of the request being sent out.
44
59
  */
45
60
 
61
+ /**
62
+ * Any calculated values we get during request processing
63
+ *
64
+ * @typedef {Object} PartiesModelState
65
+ * @property {string} destination - The destination DFSP ID from headers
66
+ * @property {string} source - The source DFSP ID from headers
67
+ * @property {string} [proxy] - The proxy DFSP ID from headers, if present
68
+ * @property {string} requester - The entity initiating the request (either a DFSP in scheme or a proxy)
69
+ * @property {boolean} proxyEnabled - Indicates whether proxy functionality is enabled in the current configuration
70
+ * @property {StepState} stepState - Processing steps state
71
+ */
72
+
46
73
  class BasePartiesService {
47
- #deps
74
+ #deps // see PartiesDeps
48
75
  #inputs // see PartiesInputs
49
- #state // any calculated values we get during request processing
76
+ #state // see PartiesModelState
50
77
 
51
78
  /**
52
- * @param {Object} deps - The dependencies required by the class instance.
79
+ * @param {PartiesDeps} deps - The dependencies required by the class instance.
53
80
  * @param {PartiesInputs} inputs - The input parameters from incoming http request.
54
81
  * @return {void}
55
82
  */
@@ -63,8 +90,13 @@ class BasePartiesService {
63
90
  })
64
91
  }
65
92
 
93
+ /** @returns {PartiesDeps} */
66
94
  get deps () { return this.#deps }
95
+
96
+ /** @returns {PartiesInputs} */
67
97
  get inputs () { return this.#inputs }
98
+
99
+ /** @returns {PartiesModelState} */
68
100
  get state () { return this.#state }
69
101
 
70
102
  async handleError (error) {
@@ -110,6 +142,12 @@ class BasePartiesService {
110
142
  return this.deps.oracle.oracleRequest(headers, RestMethods.DELETE, params, null, null, this.deps.cache)
111
143
  }
112
144
 
145
+ async removeProxyGetPartiesTimeout (alsReq) {
146
+ const isRemoved = await this.deps.proxyCache.removeProxyGetPartiesTimeout(alsReq)
147
+ this.log.debug('removeProxyGetPartiesTimeout is done', { isRemoved, alsReq })
148
+ return isRemoved
149
+ }
150
+
113
151
  createFspiopIdNotFoundError (errMessage, log = this.log) {
114
152
  log.warn(errMessage)
115
153
  return ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
@@ -124,12 +162,14 @@ class BasePartiesService {
124
162
  return this.state.stepState?.step
125
163
  }
126
164
 
165
+ /** @returns {PartiesModelState} */
127
166
  #initiateState () {
128
167
  const { headers } = this.inputs
129
168
  return {
130
169
  destination: headers[Headers.FSPIOP.DESTINATION],
131
170
  source: headers[Headers.FSPIOP.SOURCE],
132
171
  proxy: headers[Headers.FSPIOP.PROXY],
172
+ requester: '', // dfsp in scheme OR proxy
133
173
  proxyEnabled: !!(this.deps.config.PROXY_CACHE_CONFIG?.enabled && this.deps.proxyCache),
134
174
  stepState: initStepState()
135
175
  }
@@ -39,7 +39,6 @@ class GetPartiesService extends BasePartiesService {
39
39
  // without consulting any oracles.
40
40
  this.log.info('handling getParties request...', { source, destination, proxy })
41
41
  this.state.requester = await this.validateRequester()
42
- // dfsp in scheme OR proxy
43
42
 
44
43
  if (destination) {
45
44
  await this.forwardRequestToDestination()
@@ -48,12 +47,11 @@ class GetPartiesService extends BasePartiesService {
48
47
 
49
48
  const response = await this.sendOracleDiscoveryRequest()
50
49
  if (Array.isArray(response?.data?.partyList) && response.data.partyList.length > 0) {
51
- const partyList = this.filterOraclePartyList(response)
52
- await this.processOraclePartyList(partyList)
53
- return
50
+ const isDone = await this.processOraclePartyListResponse(response)
51
+ if (isDone) return
54
52
  }
55
53
 
56
- this.log.info('empty partyList form oracle, checking inter-scheme discovery flow...')
54
+ this.log.info('no forwarded requests for oracle partyList')
57
55
  const fspiopError = await this.triggerInterSchemeDiscoveryFlow(this.inputs.headers)
58
56
  if (fspiopError) {
59
57
  this.state.fspiopError = fspiopError // todo: think, if we need this
@@ -85,7 +83,7 @@ class GetPartiesService extends BasePartiesService {
85
83
  throw super.createFspiopIdNotFoundError('failed to addDfspIdToProxyMapping', log)
86
84
  }
87
85
 
88
- log.info('source is added to proxyMapping cache:', { proxy, isCached })
86
+ log.info('source is added to proxyMapping cache')
89
87
  return proxy
90
88
  }
91
89
 
@@ -113,89 +111,26 @@ class GetPartiesService extends BasePartiesService {
113
111
  log.info('discovery getPartiesByTypeAndID request was sent', { sendTo })
114
112
  }
115
113
 
116
- filterOraclePartyList (response) {
117
- // Oracle's API is a standard rest-style end-point Thus a GET /party on the oracle will return all participant-party records.
118
- // We must filter the results based on the callbackEndpointType to make sure we remove records containing partySubIdOrType when we are in FSPIOP_CALLBACK_URL_PARTIES_GET mode:
119
- this.stepInProgress('filterOraclePartyList-5')
120
- const { params } = this.inputs
121
- const callbackEndpointType = this.deps.partiesUtils.getPartyCbType(params.SubId)
122
- let filteredPartyList
123
-
124
- switch (callbackEndpointType) {
125
- case FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_GET:
126
- filteredPartyList = response.data.partyList.filter(party => party.partySubIdOrType == null) // Filter records that DON'T contain a partySubIdOrType
127
- break
128
- case FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_GET:
129
- filteredPartyList = response.data.partyList.filter(party => party.partySubIdOrType === params.SubId) // Filter records that match partySubIdOrType
130
- break
131
- default:
132
- filteredPartyList = response // Fallback to providing the standard list
133
- }
134
-
135
- if (!Array.isArray(filteredPartyList) || !filteredPartyList.length) {
136
- throw super.createFspiopIdNotFoundError(ERROR_MESSAGES.emptyFilteredPartyList)
137
- }
138
-
139
- return filteredPartyList
114
+ async sendOracleDiscoveryRequest () {
115
+ this.stepInProgress('#sendOracleDiscoveryRequest')
116
+ const { headers, params, query } = this.inputs
117
+ return this.deps.oracle.oracleRequest(headers, RestMethods.GET, params, query, undefined, this.deps.cache)
140
118
  }
141
119
 
142
- async processOraclePartyList (partyList) {
120
+ async processOraclePartyListResponse (response) {
143
121
  this.stepInProgress('processOraclePartyList')
144
- const { headers, params } = this.inputs
145
-
146
- let sentCount = 0 // if sentCount === 0 after sending, we send idNotFound error
147
- const sending = partyList.map(async party => {
148
- const { fspId } = party
149
-
150
- const schemeParticipant = await this.validateParticipant(fspId)
151
- if (schemeParticipant) {
152
- sentCount++
153
- this.log.info('participant is in scheme, so forwarding to it...', { fspId })
154
- return this.#forwardGetPartiesRequest({
155
- sendTo: fspId,
156
- headers: GetPartiesService.overrideDestinationHeader(headers, fspId),
157
- params
158
- })
159
- }
160
-
161
- if (this.state.proxyEnabled) {
162
- const proxyName = await this.deps.proxyCache.lookupProxyByDfspId(fspId)
163
- if (!proxyName) {
164
- this.log.warn('no proxyMapping for external DFSP! Deleting reference in oracle...', { fspId })
165
- return super.sendDeleteOracleRequest(headers, params)
166
- // todo: check if it won't delete all parties
167
- }
168
-
169
- // Coz there's no destination header, it means we're inside initial inter-scheme discovery phase.
170
- // So we should proceed only if source is in scheme (local participant)
171
- const schemeSource = await this.validateParticipant(this.state.source)
172
- if (schemeSource) {
173
- sentCount++
174
- this.log.info('participant is NOT in scheme, but source is. So forwarding to proxy...', { fspId, proxyName })
175
- return this.#forwardGetPartiesRequest({
176
- sendTo: proxyName,
177
- headers: GetPartiesService.overrideDestinationHeader(headers, fspId),
178
- params
179
- })
180
- }
181
- }
182
- })
183
- await Promise.all(sending)
184
- this.log.verbose('processOraclePartyList is done', { sentCount })
185
-
186
- if (sentCount === 0) throw super.createFspiopIdNotFoundError(ERROR_MESSAGES.noDiscoveryRequestsForwarded)
187
- }
188
-
189
- async getFilteredProxyList (proxy) {
190
- this.stepInProgress('getFilteredProxyList')
191
- if (!this.state.proxyEnabled) {
192
- this.log.warn('proxyCache is not enabled')
193
- return []
194
- }
195
-
196
- const proxyNames = await this.deps.proxies.getAllProxiesNames(this.deps.config.SWITCH_ENDPOINT)
197
- this.log.debug('getAllProxiesNames is done', { proxyNames })
198
- return proxyNames.filter(name => name !== proxy)
122
+ const partyList = this.#filterOraclePartyList(response)
123
+
124
+ let sentCount = 0
125
+ await Promise.all(partyList.map(async party => {
126
+ const isSent = await this.#processSingleOracleParty(party)
127
+ if (isSent) sentCount++
128
+ }))
129
+
130
+ const isDone = sentCount > 0
131
+ this.log.verbose('processOraclePartyList is done', { isDone, sentCount })
132
+ // if NOT isDone, need to trigger interScheme discovery flow
133
+ return isDone
199
134
  }
200
135
 
201
136
  async triggerInterSchemeDiscoveryFlow (headers) {
@@ -204,9 +139,9 @@ class GetPartiesService extends BasePartiesService {
204
139
  const log = this.log.child({ method: 'triggerInterSchemeDiscoveryFlow' })
205
140
  log.verbose('triggerInterSchemeDiscoveryFlow start...', { proxy, source })
206
141
 
207
- const proxyNames = await this.getFilteredProxyList(proxy)
142
+ const proxyNames = await this.#getFilteredProxyList(proxy)
208
143
  if (!proxyNames.length) {
209
- return this.sendPartyNotFoundErrorCallback(headers)
144
+ return this.#sendPartyNotFoundErrorCallback(headers)
210
145
  }
211
146
 
212
147
  this.stepInProgress('setSendToProxiesList-10')
@@ -218,7 +153,7 @@ class GetPartiesService extends BasePartiesService {
218
153
  throw super.createFspiopIdNotFoundError(ERROR_MESSAGES.failedToCacheSendToProxiesList, log)
219
154
  }
220
155
 
221
- this.stepInProgress('sendingProxyRequests-11')
156
+ this.stepInProgress('sendingProxyRequests')
222
157
  const sending = proxyNames.map(
223
158
  sendTo => this.#forwardGetPartiesRequest({ sendTo, headers, params })
224
159
  .then(({ status, data } = {}) => ({ status, data }))
@@ -235,7 +170,80 @@ class GetPartiesService extends BasePartiesService {
235
170
  }
236
171
  }
237
172
 
238
- async sendPartyNotFoundErrorCallback (headers) {
173
+ isLocalSource () {
174
+ const isLocal = this.state.source === this.state.requester
175
+ this.log.debug(`isLocalSource: ${isLocal}`)
176
+ return isLocal
177
+ }
178
+
179
+ #filterOraclePartyList (response) {
180
+ // Oracle's API is a standard rest-style end-point Thus a GET /party on the oracle will return all participant-party records.
181
+ // We must filter the results based on the callbackEndpointType to make sure we remove records containing partySubIdOrType when we are in FSPIOP_CALLBACK_URL_PARTIES_GET mode:
182
+ this.stepInProgress('filterOraclePartyList')
183
+ const { params } = this.inputs
184
+ const callbackEndpointType = this.deps.partiesUtils.getPartyCbType(params.SubId)
185
+ let filteredPartyList
186
+
187
+ switch (callbackEndpointType) {
188
+ case FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_GET:
189
+ filteredPartyList = response.data.partyList.filter(party => party.partySubIdOrType == null) // Filter records that DON'T contain a partySubIdOrType
190
+ break
191
+ case FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_GET:
192
+ filteredPartyList = response.data.partyList.filter(party => party.partySubIdOrType === params.SubId) // Filter records that match partySubIdOrType
193
+ break
194
+ default:
195
+ filteredPartyList = response // Fallback to providing the standard list
196
+ }
197
+
198
+ if (!Array.isArray(filteredPartyList) || !filteredPartyList.length) {
199
+ throw super.createFspiopIdNotFoundError(ERROR_MESSAGES.emptyFilteredPartyList)
200
+ }
201
+
202
+ return filteredPartyList
203
+ }
204
+
205
+ /** @returns {Promise<boolean>} - is request forwarded to participant */
206
+ async #processSingleOracleParty (party) {
207
+ const { headers, params } = this.inputs
208
+ const { fspId } = party
209
+
210
+ const schemeParticipant = await this.validateParticipant(fspId)
211
+ if (schemeParticipant) {
212
+ this.log.info('participant is in scheme, so forwarding to it...', { fspId })
213
+ await this.#forwardGetPartiesRequest({
214
+ sendTo: fspId,
215
+ headers: GetPartiesService.overrideDestinationHeader(headers, fspId),
216
+ params
217
+ })
218
+ return true
219
+ }
220
+
221
+ if (this.state.proxyEnabled) {
222
+ const proxyName = await this.deps.proxyCache.lookupProxyByDfspId(fspId)
223
+ if (!proxyName) {
224
+ this.log.warn('no proxyMapping for external DFSP! Deleting reference in oracle...', { fspId })
225
+ await super.sendDeleteOracleRequest(headers, params)
226
+ // todo: check if it won't delete all parties
227
+ return false
228
+ }
229
+
230
+ // Coz there's no destination header, it means we're inside initial inter-scheme discovery phase.
231
+ // So we should proceed only if source is in scheme (local participant)
232
+ const isLocalSource = this.isLocalSource()
233
+ if (isLocalSource) {
234
+ this.log.info('participant is NOT in scheme, but source is. So forwarding to proxy...', { fspId, proxyName })
235
+ await this.#forwardGetPartiesRequest({ // todo: add timeout if sendTo is proxy
236
+ sendTo: proxyName,
237
+ headers: GetPartiesService.overrideDestinationHeader(headers, fspId),
238
+ params
239
+ })
240
+ return true
241
+ }
242
+ }
243
+ return false
244
+ }
245
+
246
+ async #sendPartyNotFoundErrorCallback (headers) {
239
247
  const { params } = this.inputs
240
248
  const fspiopError = super.createFspiopIdNotFoundError('No proxy found to start inter-scheme discovery flow')
241
249
 
@@ -247,19 +255,53 @@ class GetPartiesService extends BasePartiesService {
247
255
  return fspiopError
248
256
  }
249
257
 
250
- async sendOracleDiscoveryRequest () {
251
- this.stepInProgress('sendOracleDiscoveryRequest')
252
- const { headers, params, query } = this.inputs
253
- return this.deps.oracle.oracleRequest(headers, RestMethods.GET, params, query, undefined, this.deps.cache)
254
- }
255
-
256
258
  async #forwardGetPartiesRequest ({ sendTo, headers, params }) {
257
259
  this.stepInProgress('#forwardGetPartiesRequest')
258
260
  const callbackEndpointType = this.deps.partiesUtils.getPartyCbType(params.SubId)
259
261
  const options = this.deps.partiesUtils.partiesRequestOptionsDto(params)
260
262
 
261
- return this.deps.participant.sendRequest(headers, sendTo, callbackEndpointType, RestMethods.GET, undefined, options, this.deps.childSpan)
263
+ const sentResult = await this.deps.participant.sendRequest(
264
+ headers, sendTo, callbackEndpointType, RestMethods.GET, undefined, options, this.deps.childSpan
265
+ )
266
+ await this.#setProxyGetPartiesTimeout(sendTo)
267
+ return sentResult
268
+ }
269
+
270
+ async #getFilteredProxyList (proxy) {
271
+ this.stepInProgress('#getFilteredProxyList')
272
+ if (!this.state.proxyEnabled) {
273
+ this.log.warn('proxyCache is not enabled')
274
+ return []
275
+ }
276
+
277
+ const proxyNames = await this.deps.proxies.getAllProxiesNames(this.deps.config.SWITCH_ENDPOINT)
278
+ this.log.debug('getAllProxiesNames is done', { proxyNames })
279
+ return proxyNames.filter(name => name !== proxy)
280
+ }
281
+
282
+ async #setProxyGetPartiesTimeout (sendTo) {
283
+ const isLocalSource = this.isLocalSource()
284
+ const isSentToProxy = this.state.destination !== sendTo
285
+ this.log.verbose('isLocalSource and isSentToProxy: ', { isLocalSource, isSentToProxy })
286
+
287
+ if (isSentToProxy && isLocalSource) {
288
+ this.stepInProgress('#setProxyGetPartiesTimeout')
289
+ const { source, proxy } = this.state
290
+ const alsReq = this.deps.partiesUtils.alsRequestDto(source, this.inputs.params)
291
+ const isSet = await this.deps.proxyCache.setProxyGetPartiesTimeout(alsReq, proxy)
292
+ this.log.info('#setProxyGetPartiesTimeout is done', { isSet })
293
+ return isSet
294
+ }
262
295
  }
263
296
  }
264
297
 
265
298
  module.exports = GetPartiesService
299
+
300
+ // As Payee DFSP Scheme Oracle identifies direct participant.
301
+ // As Payer DFSP Scheme oracle identifies interscheme participant.
302
+
303
+ // zm-dfsp --> ZM
304
+ // region-dfsp --> Region
305
+ // mw-dfsp --> MW (mw-party-123)
306
+
307
+ // 1. region-dfsp gets info about mw-party-123
@@ -39,6 +39,7 @@ class PutPartiesErrorService extends BasePartiesService {
39
39
  // not initial inter-scheme discovery case. Cleanup oracle and trigger inter-scheme discovery
40
40
  this.log.warn('Need to cleanup oracle and trigger new inter-scheme discovery flow')
41
41
  await this.cleanupOracle()
42
+ await this.removeProxyGetPartiesTimeout(alsReq)
42
43
  return true // need to trigger inter-scheme discovery
43
44
  }
44
45
 
@@ -49,8 +49,8 @@ class PutPartiesService extends BasePartiesService {
49
49
  const log = this.log.child({ source, proxy, method: 'validateSourceParticipant' })
50
50
  this.stepInProgress('validateSourceParticipant-1')
51
51
 
52
- const requesterParticipant = await super.validateParticipant(source)
53
- if (!requesterParticipant) {
52
+ const schemeParticipant = await super.validateParticipant(source)
53
+ if (!schemeParticipant) {
54
54
  if (!proxyEnabled || !proxy) {
55
55
  throw super.createFspiopIdNotFoundError(ERROR_MESSAGES.sourceFspNotFound, log)
56
56
  }
@@ -72,12 +72,16 @@ class PutPartiesService extends BasePartiesService {
72
72
  const alsReq = this.deps.partiesUtils.alsRequestDto(destination, params)
73
73
 
74
74
  const isExists = await this.deps.proxyCache.receivedSuccessResponse(alsReq)
75
- if (isExists) {
76
- await this.#updateOracleWithParticipantMapping({ source, headers, params })
75
+ if (!isExists) {
76
+ this.log.verbose('NOT inter-scheme receivedSuccessResponse case')
77
+ await this.removeProxyGetPartiesTimeout(alsReq)
77
78
  return
78
79
  }
79
- this.log.warn('destination is NOT in scheme, and no cached sendToProxiesList', { destination, alsReq })
80
- // todo: think, if we need to throw an error here
80
+
81
+ const schemeParticipant = await super.validateParticipant(destination)
82
+ if (schemeParticipant) {
83
+ await this.#updateOracleWithParticipantMapping({ source, headers, params })
84
+ }
81
85
  }
82
86
  }
83
87
 
@@ -52,6 +52,10 @@ const timeoutInterschemePartiesLookups = async ({ proxyCache, batchSize }) => {
52
52
  return proxyCache.processExpiredAlsKeys(sendTimeoutCallback, batchSize)
53
53
  }
54
54
 
55
+ const timeoutProxyGetPartiesLookups = async ({ proxyCache, batchSize }) => {
56
+ return proxyCache.processExpiredProxyGetPartiesKeys(sendTimeoutCallback, batchSize)
57
+ }
58
+
55
59
  const sendTimeoutCallback = async (cacheKey) => {
56
60
  const histTimerEnd = Metrics.getHistogram(
57
61
  'eg_timeoutInterschemePartiesLookups',
@@ -59,7 +63,7 @@ const sendTimeoutCallback = async (cacheKey) => {
59
63
  ['success']
60
64
  ).startTimer()
61
65
  let step
62
- const [, destination, partyType, partyId] = cacheKey.split(':')
66
+ const [destination, partyType, partyId] = parseCacheKey(cacheKey)
63
67
  const { errorInformation, params, headers, endpointType, span } = await timeoutCallbackDto({ destination, partyId, partyType })
64
68
  logger.debug('sendTimeoutCallback details:', { destination, partyType, partyId, cacheKey })
65
69
 
@@ -103,7 +107,13 @@ const finishSpan = async (span, err) => {
103
107
  }
104
108
  }
105
109
 
110
+ const parseCacheKey = (cacheKey) => {
111
+ const [destination, partyType, partyId] = cacheKey.split(':').slice(-3)
112
+ return [destination, partyType, partyId]
113
+ }
114
+
106
115
  module.exports = {
107
116
  timeoutInterschemePartiesLookups,
117
+ timeoutProxyGetPartiesLookups,
108
118
  sendTimeoutCallback // Exposed for testing
109
119
  }
@@ -41,13 +41,13 @@ let isRunning
41
41
 
42
42
  const timeout = async (options) => {
43
43
  if (isRunning) return
44
-
45
44
  const { logger } = options
46
45
 
47
46
  try {
48
47
  isRunning = true
49
- logger.debug('Timeout handler triggered')
50
48
  await TimeoutService.timeoutInterschemePartiesLookups(options)
49
+ await TimeoutService.timeoutProxyGetPartiesLookups(options)
50
+ logger.verbose('ALS timeout handler is done')
51
51
  } catch (err) {
52
52
  logger.error('error in timeout: ', err)
53
53
  } finally {
package/src/lib/util.js CHANGED
@@ -29,10 +29,11 @@ const util = require('node:util')
29
29
  const Path = require('node:path')
30
30
  const EventSdk = require('@mojaloop/event-sdk')
31
31
  const Enum = require('@mojaloop/central-services-shared').Enum
32
- const { HeaderValidation, Hapi } = require('@mojaloop/central-services-shared').Util
32
+ const { HeaderValidation } = require('@mojaloop/central-services-shared').Util
33
33
  const rethrow = require('@mojaloop/central-services-shared').Util.rethrow.with('ALS')
34
34
 
35
35
  const Config = require('../lib/config')
36
+ const { API_TYPES } = require('../constants')
36
37
  const { logger } = require('./index')
37
38
 
38
39
  const getSpanTags = ({ headers }, transactionType, transactionAction) => {
@@ -69,7 +70,7 @@ const pathForInterface = ({ isAdmin, isMockInterface }) => {
69
70
  if (isMockInterface) {
70
71
  apiFile = 'api_swagger.json'
71
72
  } else {
72
- apiFile = Config.API_TYPE === Hapi.API_TYPES.iso20022
73
+ apiFile = Config.API_TYPE === API_TYPES.iso20022
73
74
  ? 'api-swagger-iso20022-parties.yaml'
74
75
  : 'api-swagger.yaml'
75
76
  }
@@ -100,7 +101,14 @@ const countFspiopError = (error, options) => {
100
101
  rethrow.countFspiopError(error, options)
101
102
  }
102
103
 
103
- // todo: think better name
104
+ /**
105
+ * An immutable object representing the step state
106
+ * @typedef {Object} StepState
107
+ * @property {string} step - The current step value (read-only getter property)
108
+ * @property {(string) => void} inProgress - Method to update the current step
109
+ */
110
+
111
+ /** @returns {StepState} */
104
112
  const initStepState = (initStep = 'start') => {
105
113
  let step = initStep
106
114
  return Object.freeze({
@@ -1369,7 +1369,7 @@ describe('participant Tests', () => {
1369
1369
 
1370
1370
  // Assert
1371
1371
  expect(logStub.getCall(0).firstArg).toBe(ERROR_MESSAGES.sourceFspNotFound)
1372
- expect(logStub.getCall(2).lastArg).toEqual(cbError)
1372
+ expect(logStub.getCall(3).lastArg).toEqual(cbError)
1373
1373
  })
1374
1374
 
1375
1375
  it('handles error when `oracleBatchRequest` returns no result', async () => {
@@ -248,7 +248,7 @@ describe('Parties Tests', () => {
248
248
  await partiesDomain.getPartiesByTypeAndID(Helper.getByTypeIdRequest.headers, Helper.getByTypeIdRequest.params, Helper.getByTypeIdRequest.method, Helper.getByTypeIdRequest.query)
249
249
 
250
250
  // Assert
251
- expect(loggerStub.callCount).toBe(1)
251
+ expect(loggerStub.callCount).toBe(2)
252
252
  expect(participant.sendErrorToParticipant.callCount).toBe(1)
253
253
 
254
254
  const { errorInformation } = participant.sendErrorToParticipant.getCall(0).args[2]
@@ -481,7 +481,7 @@ describe('Parties Tests', () => {
481
481
  })
482
482
  sandbox.stub(oracle, 'oracleRequest').resolves(oracleResponse)
483
483
  participant.validateParticipant = sandbox.stub()
484
- .onFirstCall().resolves({}) // source
484
+ .onFirstCall().resolves(null) // source
485
485
  .onSecondCall().resolves(null) // oracle dfsp
486
486
  participant.sendRequest = sandbox.stub().resolves()
487
487
  participant.sendErrorToParticipant = sandbox.stub().resolves()
@@ -490,7 +490,7 @@ describe('Parties Tests', () => {
490
490
  expect(isAdded).toBe(true)
491
491
 
492
492
  const source = `fromDfsp-${Date.now()}`
493
- const headers = fixtures.partiesCallHeadersDto({ destination: '', source })
493
+ const headers = fixtures.partiesCallHeadersDto({ destination: '', source, proxy: 'proxy' })
494
494
  const { params, method, query } = Helper.getByTypeIdRequest
495
495
 
496
496
  await partiesDomain.getPartiesByTypeAndID(headers, params, method, query, null, null, proxyCache)
@@ -901,7 +901,7 @@ describe('Parties Tests', () => {
901
901
 
902
902
  // Assert
903
903
  expect(participant.sendErrorToParticipant.callCount).toBe(1)
904
- expect(loggerStub.callCount).toBe(1)
904
+ expect(loggerStub.callCount).toBe(2)
905
905
  const sendErrorCallArgs = participant.sendErrorToParticipant.getCall(0).args
906
906
  expect(sendErrorCallArgs[1]).toBe(expectedCallbackEnpointType)
907
907
  })
@@ -922,7 +922,7 @@ describe('Parties Tests', () => {
922
922
 
923
923
  // Assert
924
924
  expect(participant.sendErrorToParticipant.callCount).toBe(1)
925
- expect(loggerStub.callCount).toBe(1)
925
+ expect(loggerStub.callCount).toBe(2)
926
926
  const sendErrorCallArgs = participant.sendErrorToParticipant.getCall(0).args
927
927
  expect(sendErrorCallArgs[1]).toBe(expectedCallbackEnpointType)
928
928
  })
@@ -0,0 +1,51 @@
1
+ /*****
2
+ License
3
+ --------------
4
+ Copyright © 2020-2025 Mojaloop Foundation
5
+ 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. You may obtain a copy of the License at
6
+ http://www.apache.org/licenses/LICENSE-2.0
7
+ 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.
8
+
9
+ Contributors
10
+ --------------
11
+ This is the official list of the Mojaloop project contributors for this file.
12
+ Names of the original copyright holders (individuals or organizations)
13
+ should be listed with a '*' in the first column. People who have
14
+ contributed from an organization can be listed under the organization
15
+ that actually holds the copyright for their contributions (see the
16
+ Mojaloop Foundation organization for an example). Those individuals should have
17
+ their names indented and be marked with a '-'. Email address can be added
18
+ optionally within square brackets <email>.
19
+ * Mojaloop Foundation
20
+ - Name Surname <name.surname@mojaloop.io>
21
+
22
+ * Eugen Klymniuk <eugen.klymniuk@infitx.com>
23
+ --------------
24
+ **********/
25
+
26
+ const ErrorHandler = require('@mojaloop/central-services-error-handling')
27
+ const partiesUtils = require('#src/domain/parties/partiesUtils')
28
+ const config = require('#src/lib/config')
29
+ const { API_TYPES } = require('#src/constants')
30
+ const fixtures = require('#test/fixtures/index')
31
+
32
+ describe('partiesUtils Tests -->', () => {
33
+ describe('makePutPartiesErrorPayload Tests', () => {
34
+ const error = ErrorHandler.Factory.reformatFSPIOPError(new Error('Test Error'))
35
+ const ERR_CODE = '2001'
36
+ const headers = fixtures.partiesCallHeadersDto()
37
+ const params = fixtures.partiesParamsDto()
38
+
39
+ test('should make putParties error payload in FSPIOP format', async () => {
40
+ const fspiopConfig = { ...config, API_TYPE: API_TYPES.fspiop }
41
+ const payload = await partiesUtils.makePutPartiesErrorPayload(fspiopConfig, error, headers, params)
42
+ expect(payload.errorInformation.errorCode).toBe(ERR_CODE)
43
+ })
44
+
45
+ test('should make putParties error payload in ISO20022 format', async () => {
46
+ const fspiopConfig = { ...config, API_TYPE: API_TYPES.iso20022 }
47
+ const payload = await partiesUtils.makePutPartiesErrorPayload(fspiopConfig, error, headers, params)
48
+ expect(payload.Rpt.Rsn.Cd).toBe(ERR_CODE)
49
+ })
50
+ })
51
+ })
@@ -3,7 +3,9 @@
3
3
  --------------
4
4
  Copyright © 2020-2025 Mojaloop Foundation
5
5
  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. You may obtain a copy of the License at
6
+
6
7
  http://www.apache.org/licenses/LICENSE-2.0
8
+
7
9
  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.
8
10
 
9
11
  Contributors
@@ -13,46 +15,43 @@
13
15
  should be listed with a '*' in the first column. People who have
14
16
  contributed from an organization can be listed under the organization
15
17
  that actually holds the copyright for their contributions (see the
16
- Mojaloop Foundation organization for an example). Those individuals should have
18
+ Mojaloop Foundation for an example). Those individuals should have
17
19
  their names indented and be marked with a '-'. Email address can be added
18
20
  optionally within square brackets <email>.
19
- * Mojaloop Foundation
20
- - Name Surname <name.surname@mojaloop.io>
21
21
 
22
+ * Mojaloop Foundation
22
23
  * Eugen Klymniuk <eugen.klymniuk@infitx.com>
23
- --------------
24
- **********/
25
-
26
- const mockSendRequest = jest.fn()
27
-
28
- jest.mock('@mojaloop/central-services-shared', () => ({
29
- ...jest.requireActual('@mojaloop/central-services-shared'),
30
- Util: {
31
- ...jest.requireActual('@mojaloop/central-services-shared').Util,
32
- Endpoints: { getEndpoint: jest.fn() },
33
- Request: { sendRequest: mockSendRequest }
34
- }
35
- }))
36
-
37
- const { API_TYPES } = require('@mojaloop/central-services-shared').Util.Hapi
38
- const { logger } = require('../../../../src/lib')
39
- const partiesUtils = require('../../../../src/domain/parties/partiesUtils')
40
- const config = require('../../../../src/lib/config')
41
- const fixtures = require('../../../fixtures')
42
-
43
- describe('parties utils Tests -->', () => {
44
- test('should send error party callback in ISO format', async () => {
45
- const isoConfig = { ...config, API_TYPE: API_TYPES.iso20022 }
46
- const err = new Error('test error')
47
- const source = 'dfsp1'
48
- const headers = fixtures.partiesCallHeadersDto({ source })
49
- const params = { ID: '1234', Type: 'MSISDN' }
50
24
 
51
- const handleError = partiesUtils.createErrorHandlerOnSendingCallback(isoConfig, logger)
52
- await handleError(err, headers, params, source)
25
+ --------------
26
+ ******/
27
+
28
+ const { createMockDeps, participantMock } = require('./deps')
29
+ // should be first require to mock external deps
30
+ const BasePartiesService = require('#src/domain/parties/services/BasePartiesService')
31
+ const config = require('#src/lib/config')
32
+ const { API_TYPES } = require('#src/constants')
33
+ const fixtures = require('#test/fixtures/index')
34
+
35
+ describe('BasePartiesService Tests -->', () => {
36
+ beforeEach(() => {
37
+ jest.clearAllMocks()
38
+ })
53
39
 
54
- expect(mockSendRequest).toHaveBeenCalledTimes(1)
55
- const { payload } = mockSendRequest.mock.calls[0][0]
40
+ test('should send error party callback in ISO20022 format', async () => {
41
+ const deps = {
42
+ ...createMockDeps(),
43
+ config: { ...config, API_TYPE: API_TYPES.iso20022 }
44
+ }
45
+ const source = 'sourceFsp'
46
+ const headers = fixtures.partiesCallHeadersDto({ source })
47
+ const params = fixtures.partiesParamsDto()
48
+ const service = new BasePartiesService(deps, { headers, params })
49
+
50
+ await service.handleError(new Error('test error'))
51
+ expect(participantMock.sendErrorToParticipant.mock.calls.length).toBe(1)
52
+ // eslint-disable-next-line no-unused-vars
53
+ const [sentTo, _, payload] = participantMock.sendErrorToParticipant.mock.lastCall
54
+ expect(sentTo).toBe(source)
56
55
  expect(payload.Rpt.Rsn.Cd).toBe('2001')
57
56
  expect(payload.Rpt.OrgnlId).toBe(`${params.Type}/${params.ID}`)
58
57
  expect(payload.Assgnmt.Assgnr.Agt.FinInstnId.Othr.Id).toBe(source)
@@ -25,15 +25,10 @@
25
25
  --------------
26
26
  ******/
27
27
 
28
- jest.mock('#src/models/oracle/facade')
29
- jest.mock('#src/models/participantEndpoint/facade')
30
-
28
+ const { createMockDeps, createProxyCacheMock, oracleMock, participantMock } = require('./deps')
29
+ // should be first require to mock external deps
31
30
  const { GetPartiesService } = require('#src/domain/parties/services/index')
32
- const { ERROR_MESSAGES } = require('#src/constants')
33
- const oracle = require('#src/models/oracle/facade')
34
- const participant = require('#src/models/participantEndpoint/facade')
35
31
  const fixtures = require('#test/fixtures/index')
36
- const { createMockDeps, createProxyCacheMock } = require('./deps')
37
32
 
38
33
  const { RestMethods, Headers } = GetPartiesService.enums()
39
34
 
@@ -44,7 +39,7 @@ describe('GetPartiesService Tests -->', () => {
44
39
 
45
40
  describe('forwardRequestToDestination method', () => {
46
41
  test('should delete party info from oracle, if no destination DFSP in proxy mapping', async () => {
47
- participant.validateParticipant = jest.fn().mockResolvedValueOnce(false)
42
+ participantMock.validateParticipant = jest.fn().mockResolvedValueOnce(null)
48
43
  const proxyCache = createProxyCacheMock({
49
44
  lookupProxyByDfspId: jest.fn().mockResolvedValueOnce(null)
50
45
  })
@@ -58,8 +53,8 @@ describe('GetPartiesService Tests -->', () => {
58
53
 
59
54
  await service.forwardRequestToDestination()
60
55
 
61
- expect(oracle.oracleRequest.mock.calls.length).toBe(1)
62
- const [sentHeaders, method, sentParams] = oracle.oracleRequest.mock.lastCall
56
+ expect(oracleMock.oracleRequest.mock.calls.length).toBe(1)
57
+ const [sentHeaders, method, sentParams] = oracleMock.oracleRequest.mock.lastCall
63
58
  expect(method).toBe(RestMethods.DELETE)
64
59
  expect(sentHeaders).toEqual(headers)
65
60
  expect(sentParams).toEqual(params)
@@ -79,10 +74,10 @@ describe('GetPartiesService Tests -->', () => {
79
74
  let proxyCache
80
75
 
81
76
  beforeEach(async () => {
82
- oracle.oracleRequest = jest.fn().mockResolvedValueOnce(
77
+ oracleMock.oracleRequest = jest.fn().mockResolvedValueOnce(
83
78
  fixtures.oracleRequestResponseDto({ partyList: [{ fspId: EXTERNAL_DFSP_ID }] })
84
79
  )
85
- participant.validateParticipant = jest.fn()
80
+ participantMock.validateParticipant = jest.fn()
86
81
  .mockResolvedValueOnce(null) // source
87
82
  .mockResolvedValueOnce({}) // proxy
88
83
 
@@ -93,50 +88,135 @@ describe('GetPartiesService Tests -->', () => {
93
88
  deps = createMockDeps({ proxyCache })
94
89
  })
95
90
 
96
- test('should throw ID_NOT_FOUND error and cleanup oracle, if no proxyMapping for external dfsp', async () => {
97
- expect.hasAssertions()
91
+ test('should cleanup oracle and trigger interScheme discovery, if no proxyMapping for external dfsp', async () => {
98
92
  proxyCache.lookupProxyByDfspId = jest.fn().mockResolvedValueOnce(null)
99
93
  const headers = fixtures.partiesCallHeadersDto({
100
94
  destination: '', proxy: 'proxyA'
101
95
  })
102
96
  const params = fixtures.partiesParamsDto()
103
97
  const service = new GetPartiesService(deps, { headers, params })
98
+ service.triggerInterSchemeDiscoveryFlow = jest.fn()
104
99
 
105
100
  await service.handleRequest()
106
- .catch((err) => {
107
- expect(err).toEqual(service.createFspiopIdNotFoundError(ERROR_MESSAGES.noDiscoveryRequestsForwarded))
108
- })
109
- expect(oracle.oracleRequest.mock.calls.length).toBe(2) // GET + DELETE
110
- expect(oracle.oracleRequest.mock.lastCall[1]).toBe(RestMethods.DELETE)
101
+ expect(oracleMock.oracleRequest).toHaveBeenCalledTimes(2) // GET + DELETE
102
+ expect(oracleMock.oracleRequest.mock.lastCall[1]).toBe(RestMethods.DELETE)
103
+ expect(service.triggerInterSchemeDiscoveryFlow).toHaveBeenCalledWith(headers)
111
104
  })
112
105
 
113
- test('should throw ID_NOT_FOUND error, if source is external', async () => {
114
- expect.hasAssertions()
106
+ test('should trigger interScheme discovery flow, if source is external', async () => {
115
107
  const headers = fixtures.partiesCallHeadersDto({
116
108
  destination: '', proxy: 'proxyA'
117
109
  })
118
110
  const params = fixtures.partiesParamsDto()
119
111
  const service = new GetPartiesService(deps, { headers, params })
112
+ service.triggerInterSchemeDiscoveryFlow = jest.fn()
120
113
 
121
114
  await service.handleRequest()
122
- .catch((err) => {
123
- expect(err).toEqual(service.createFspiopIdNotFoundError(ERROR_MESSAGES.noDiscoveryRequestsForwarded))
124
- })
115
+ expect(service.triggerInterSchemeDiscoveryFlow).toHaveBeenCalledWith(headers)
125
116
  })
126
117
 
127
- test('should forward request, if source is in scheme', async () => {
128
- participant.validateParticipant = jest.fn(async (fsp) => (fsp === EXTERNAL_DFSP_ID ? null : {}))
118
+ test('should forward request, if source is in scheme (no proxy header)', async () => {
119
+ participantMock.validateParticipant = jest.fn(async (fsp) => (fsp === EXTERNAL_DFSP_ID ? null : {}))
129
120
  const headers = fixtures.partiesCallHeadersDto({
130
- destination: '', proxy: 'proxyA'
121
+ destination: '', proxy: ''
131
122
  })
132
123
  const params = fixtures.partiesParamsDto()
133
124
  const service = new GetPartiesService(deps, { headers, params })
134
125
 
135
126
  await service.handleRequest()
136
- expect(participant.sendRequest.mock.calls.length).toBe(1)
137
- const [sentHeaders, sendTo] = participant.sendRequest.mock.lastCall
127
+ expect(participantMock.sendRequest.mock.calls.length).toBe(1)
128
+ const [sentHeaders, sendTo] = participantMock.sendRequest.mock.lastCall
138
129
  expect(sendTo).toEqual(PROXY_ID)
139
130
  expect(sentHeaders[Headers.FSPIOP.DESTINATION]).toBe(EXTERNAL_DFSP_ID)
140
131
  })
132
+
133
+ test('should trigger inter-scheme discovery flow, if source is NOT in scheme', async () => {
134
+ const source = 'test-zm-dfsp'
135
+ const proxyZm = 'proxy-zm'
136
+ participantMock.validateParticipant = jest.fn(
137
+ async (fsp) => ([EXTERNAL_DFSP_ID, source].includes(fsp) ? null : {})
138
+ )
139
+ deps.proxies.getAllProxiesNames = jest.fn().mockResolvedValueOnce([PROXY_ID, proxyZm])
140
+ const headers = fixtures.partiesCallHeadersDto({
141
+ source, destination: '', proxy: proxyZm
142
+ })
143
+ const params = fixtures.partiesParamsDto()
144
+ const service = new GetPartiesService(deps, { headers, params })
145
+
146
+ await service.handleRequest()
147
+ expect(participantMock.sendRequest).toHaveBeenCalledTimes(1)
148
+ const [sentHeaders, sendTo] = participantMock.sendRequest.mock.lastCall
149
+ expect(sendTo).toEqual(PROXY_ID)
150
+ expect(sentHeaders[Headers.FSPIOP.DESTINATION]).toBeUndefined()
151
+ })
152
+ })
153
+
154
+ describe('setProxyGetPartiesTimeout Tests', () => {
155
+ test('should set getParties timeout for local source and external destination', async () => {
156
+ participantMock.validateParticipant = jest.fn()
157
+ .mockResolvedValueOnce({}) // source
158
+ .mockResolvedValueOnce(null) // destination
159
+ const proxyCache = createProxyCacheMock({
160
+ lookupProxyByDfspId: jest.fn().mockResolvedValueOnce('proxy-dest')
161
+ })
162
+ const deps = createMockDeps({ proxyCache })
163
+ const headers = fixtures.partiesCallHeadersDto()
164
+ const params = fixtures.partiesParamsDto()
165
+ const service = new GetPartiesService(deps, { headers, params })
166
+
167
+ await service.handleRequest()
168
+ expect(proxyCache.setProxyGetPartiesTimeout).toHaveBeenCalledTimes(1)
169
+ expect(participantMock.sendRequest).toHaveBeenCalledTimes(1)
170
+ })
171
+
172
+ test('should NOT set getParties timeout if source is external', async () => {
173
+ participantMock.validateParticipant = jest.fn()
174
+ .mockResolvedValueOnce(null) // source
175
+ .mockResolvedValueOnce({}) // proxy-src
176
+ const proxyCache = createProxyCacheMock({
177
+ lookupProxyByDfspId: jest.fn().mockResolvedValue('proxy-desc')
178
+ })
179
+ const deps = createMockDeps({ proxyCache })
180
+ const headers = fixtures.partiesCallHeadersDto({ proxy: 'proxy-src' })
181
+ const params = fixtures.partiesParamsDto()
182
+ const service = new GetPartiesService(deps, { headers, params })
183
+
184
+ await service.handleRequest()
185
+ expect(proxyCache.setProxyGetPartiesTimeout).not.toHaveBeenCalled()
186
+ expect(participantMock.sendRequest).toHaveBeenCalledTimes(1)
187
+ })
188
+
189
+ test('should NOT set getParties timeout if destination is local', async () => {
190
+ participantMock.validateParticipant = jest.fn().mockResolvedValue({})
191
+ const proxyCache = createProxyCacheMock()
192
+ const deps = createMockDeps({ proxyCache })
193
+ const headers = fixtures.partiesCallHeadersDto()
194
+ const params = fixtures.partiesParamsDto()
195
+ const service = new GetPartiesService(deps, { headers, params })
196
+
197
+ await service.handleRequest()
198
+ expect(proxyCache.setProxyGetPartiesTimeout).not.toHaveBeenCalled()
199
+ expect(participantMock.sendRequest).toHaveBeenCalledTimes(1)
200
+ })
201
+
202
+ test('should set getParties timeout if oracle returns external participant', async () => {
203
+ participantMock.validateParticipant = jest.fn()
204
+ .mockResolvedValueOnce({}) // source
205
+ .mockResolvedValueOnce(null) // externalDfsp
206
+ oracleMock.oracleRequest = jest.fn(async () => fixtures.oracleRequestResponseDto({
207
+ partyList: [{ fspId: 'externalDfsp' }]
208
+ }))
209
+ const proxyCache = createProxyCacheMock({
210
+ lookupProxyByDfspId: jest.fn().mockResolvedValue('proxyExternal')
211
+ })
212
+ const deps = createMockDeps({ proxyCache })
213
+ const headers = fixtures.partiesCallHeadersDto({ destination: '' })
214
+ const params = fixtures.partiesParamsDto()
215
+ const service = new GetPartiesService(deps, { headers, params })
216
+
217
+ await service.handleRequest()
218
+ expect(proxyCache.setProxyGetPartiesTimeout).toHaveBeenCalledTimes(1)
219
+ expect(participantMock.sendRequest).toHaveBeenCalledTimes(1)
220
+ })
141
221
  })
142
222
  })
@@ -25,13 +25,9 @@
25
25
  --------------
26
26
  ******/
27
27
 
28
- jest.mock('#src/models/oracle/facade')
29
- jest.mock('#src/models/participantEndpoint/facade')
30
-
28
+ const { createMockDeps, oracleMock } = require('./deps')
31
29
  const { PutPartiesErrorService } = require('#src/domain/parties/services/index')
32
- const oracle = require('#src/models/oracle/facade')
33
30
  const fixtures = require('#test/fixtures/index')
34
- const { createMockDeps } = require('./deps')
35
31
 
36
32
  const { RestMethods } = PutPartiesErrorService.enums()
37
33
 
@@ -47,7 +43,7 @@ describe('PutPartiesErrorService Tests -->', () => {
47
43
 
48
44
  const needDiscovery = await service.handleRequest()
49
45
  expect(needDiscovery).toBe(true)
50
- expect(oracle.oracleRequest.mock.calls.length).toBe(1)
51
- expect(oracle.oracleRequest.mock.lastCall[1]).toBe(RestMethods.DELETE)
46
+ expect(oracleMock.oracleRequest.mock.calls.length).toBe(1)
47
+ expect(oracleMock.oracleRequest.mock.lastCall[1]).toBe(RestMethods.DELETE)
52
48
  })
53
49
  })
@@ -25,16 +25,25 @@
25
25
  --------------
26
26
  ******/
27
27
 
28
+ jest.mock('#src/models/oracle/facade')
29
+ jest.mock('#src/models/participantEndpoint/facade')
30
+
31
+ const oracleMock = require('#src/models/oracle/facade')
32
+ const participantMock = require('#src/models/participantEndpoint/facade')
28
33
  const { createDeps } = require('#src/domain/parties/deps')
29
34
  const { logger } = require('#src/lib/index')
30
35
  const { createProxyCacheMock } = require('#test/util/mockDeps')
31
36
 
37
+ /** @returns {PartiesDeps} */
32
38
  const createMockDeps = ({
33
39
  proxyCache = createProxyCacheMock(),
34
- log = logger.child({ test: true })
35
- } = {}) => createDeps({ proxyCache, log })
40
+ log = logger.child({ test: true }),
41
+ cache
42
+ } = {}) => createDeps({ cache, proxyCache, log })
36
43
 
37
44
  module.exports = {
38
45
  createMockDeps,
39
- createProxyCacheMock
46
+ createProxyCacheMock,
47
+ oracleMock,
48
+ participantMock
40
49
  }
@@ -64,16 +64,16 @@ describe('Timeout Domain', () => {
64
64
 
65
65
  describe('sendTimeoutCallback', () => {
66
66
  it('should send error to participant', async () => {
67
- jest.spyOn(Participant, 'validateParticipant').mockResolvedValue({ fspId: 'sourceId' })
67
+ const SOURCE_ID = 'sourceId'
68
+ jest.spyOn(Participant, 'validateParticipant').mockResolvedValue({ fspId: SOURCE_ID })
68
69
 
69
- const cacheKey = 'als:sourceId:2:3:expiresAt'
70
- const [, destination] = cacheKey.split(':')
70
+ const cacheKey = `als:${SOURCE_ID}:2:3` // ':expiresAt' part is removed inside proxyCache.processExpiryKey()
71
71
 
72
72
  await TimeoutDomain.sendTimeoutCallback(cacheKey)
73
73
 
74
- expect(Participant.validateParticipant).toHaveBeenCalledWith('sourceId')
74
+ expect(Participant.validateParticipant).toHaveBeenCalledWith(SOURCE_ID)
75
75
  expect(Participant.sendErrorToParticipant).toHaveBeenCalledWith(
76
- destination,
76
+ SOURCE_ID,
77
77
  'FSPIOP_CALLBACK_URL_PARTIES_PUT_ERROR',
78
78
  { errorInformation: { errorCode: '3300', errorDescription: 'Generic expired error' } },
79
79
  expect.any(Object), expect.any(Object), undefined, expect.any(Object)
@@ -32,6 +32,8 @@ const createProxyCacheMock = ({
32
32
  receivedErrorResponse = jest.fn(async () => false),
33
33
  receivedSuccessResponse = jest.fn(async () => true),
34
34
  removeDfspIdFromProxyMapping = jest.fn(async () => true),
35
+ removeProxyGetPartiesTimeout = jest.fn(async () => true),
36
+ setProxyGetPartiesTimeout = jest.fn(async () => true),
35
37
  setSendToProxiesList = jest.fn(async () => true)
36
38
  } = {}) => ({
37
39
  addDfspIdToProxyMapping,
@@ -40,6 +42,8 @@ const createProxyCacheMock = ({
40
42
  receivedErrorResponse,
41
43
  receivedSuccessResponse,
42
44
  removeDfspIdFromProxyMapping,
45
+ removeProxyGetPartiesTimeout,
46
+ setProxyGetPartiesTimeout,
43
47
  setSendToProxiesList
44
48
  })
45
49