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 +10 -9
- package/src/constants.js +3 -0
- package/src/domain/parties/deps.js +1 -0
- package/src/domain/parties/partiesUtils.js +4 -34
- package/src/domain/parties/services/BasePartiesService.js +43 -3
- package/src/domain/parties/services/GetPartiesService.js +139 -97
- package/src/domain/parties/services/PutPartiesErrorService.js +1 -0
- package/src/domain/parties/services/PutPartiesService.js +10 -6
- package/src/domain/timeout/index.js +11 -1
- package/src/handlers/TimeoutHandler.js +2 -2
- package/src/lib/util.js +11 -3
- package/test/unit/domain/participants/participants.test.js +1 -1
- package/test/unit/domain/parties/parties.test.js +5 -5
- package/test/unit/domain/parties/partiesUtils.test.js +51 -0
- package/test/unit/domain/parties/{utils.test.js → services/BasePartiesService.test.js} +33 -34
- package/test/unit/domain/parties/services/GetPartiesService.test.js +109 -29
- package/test/unit/domain/parties/services/PutPartiesErrorService.test.js +3 -7
- package/test/unit/domain/parties/services/deps.js +12 -3
- package/test/unit/domain/timeout/index.test.js +5 -5
- package/test/util/mockDeps.js +4 -0
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.
|
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
|
97
|
-
"@mojaloop/central-services-stream": "11.5.
|
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.
|
100
|
-
"@mojaloop/inter-scheme-proxy-cache-lib": "2.4.0
|
101
|
-
"@mojaloop/ml-schema-transformer-lib": "2.
|
102
|
-
"@mojaloop/sdk-standard-components": "19.11.
|
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.
|
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": "
|
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
|
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 ===
|
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 //
|
76
|
+
#state // see PartiesModelState
|
50
77
|
|
51
78
|
/**
|
52
|
-
* @param {
|
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
|
52
|
-
|
53
|
-
return
|
50
|
+
const isDone = await this.processOraclePartyListResponse(response)
|
51
|
+
if (isDone) return
|
54
52
|
}
|
55
53
|
|
56
|
-
this.log.info('
|
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
|
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
|
-
|
117
|
-
|
118
|
-
|
119
|
-
this.
|
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
|
120
|
+
async processOraclePartyListResponse (response) {
|
143
121
|
this.stepInProgress('processOraclePartyList')
|
144
|
-
const
|
145
|
-
|
146
|
-
let sentCount = 0
|
147
|
-
|
148
|
-
const
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
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
|
142
|
+
const proxyNames = await this.#getFilteredProxyList(proxy)
|
208
143
|
if (!proxyNames.length) {
|
209
|
-
return this
|
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
|
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
|
-
|
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
|
-
|
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
|
53
|
-
if (!
|
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
|
-
|
75
|
+
if (!isExists) {
|
76
|
+
this.log.verbose('NOT inter-scheme receivedSuccessResponse case')
|
77
|
+
await this.removeProxyGetPartiesTimeout(alsReq)
|
77
78
|
return
|
78
79
|
}
|
79
|
-
|
80
|
-
|
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 [
|
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
|
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 ===
|
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
|
-
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|
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
|
-
|
52
|
-
|
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
|
-
|
55
|
-
const
|
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
|
-
|
29
|
-
|
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
|
-
|
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(
|
62
|
-
const [sentHeaders, method, sentParams] =
|
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
|
-
|
77
|
+
oracleMock.oracleRequest = jest.fn().mockResolvedValueOnce(
|
83
78
|
fixtures.oracleRequestResponseDto({ partyList: [{ fspId: EXTERNAL_DFSP_ID }] })
|
84
79
|
)
|
85
|
-
|
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
|
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
|
-
|
107
|
-
|
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
|
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
|
-
|
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
|
-
|
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: '
|
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(
|
137
|
-
const [sentHeaders, sendTo] =
|
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
|
-
|
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(
|
51
|
-
expect(
|
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
|
-
|
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
|
-
|
67
|
+
const SOURCE_ID = 'sourceId'
|
68
|
+
jest.spyOn(Participant, 'validateParticipant').mockResolvedValue({ fspId: SOURCE_ID })
|
68
69
|
|
69
|
-
const cacheKey =
|
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(
|
74
|
+
expect(Participant.validateParticipant).toHaveBeenCalledWith(SOURCE_ID)
|
75
75
|
expect(Participant.sendErrorToParticipant).toHaveBeenCalledWith(
|
76
|
-
|
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)
|
package/test/util/mockDeps.js
CHANGED
@@ -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
|
|