account-lookup-service 17.12.10 → 17.13.0-snapshot.10
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/.grype.yaml +0 -1
- package/audit-ci.jsonc +1 -0
- package/package.json +9 -8
- package/{sbom-v17.12.9.csv → sbom-v17.12.10.csv} +136 -133
- package/src/constants.js +1 -1
- package/src/domain/parties/deps.js +6 -4
- package/src/domain/parties/putParties.js +1 -1
- package/src/domain/parties/services/BasePartiesService.js +43 -7
- package/src/domain/parties/services/GetPartiesService.js +45 -32
- package/src/domain/parties/services/PutPartiesErrorService.js +17 -21
- package/src/domain/parties/services/PutPartiesService.js +1 -1
- package/src/lib/headers.js +1 -0
- package/test/unit/domain/parties/parties.test.js +7 -4
- package/test/unit/domain/parties/services/GetPartiesService.test.js +181 -3
- package/test/unit/domain/parties/services/PutPartiesErrorService.test.js +28 -19
- package/test/unit/domain/parties/services/deps.js +20 -4
- package/test/util/mockDeps.js +20 -0
package/src/constants.js
CHANGED
@@ -29,7 +29,7 @@ const { API_TYPES } = require('@mojaloop/central-services-shared').Util.Hapi
|
|
29
29
|
|
30
30
|
const ERROR_MESSAGES = Object.freeze({
|
31
31
|
emptyFilteredPartyList: 'Empty oracle partyList, filtered based on callbackEndpointType',
|
32
|
-
externalPartyError: 'External party error',
|
32
|
+
externalPartyError: 'External party resolution error',
|
33
33
|
failedToCacheSendToProxiesList: 'Failed to cache sendToProxiesList',
|
34
34
|
noDiscoveryRequestsForwarded: 'No discovery requests forwarded to participants',
|
35
35
|
sourceFspNotFound: 'Requester FSP not found',
|
@@ -28,8 +28,8 @@
|
|
28
28
|
const { Util } = require('@mojaloop/central-services-shared')
|
29
29
|
const { logger } = require('../../lib')
|
30
30
|
const config = require('../../lib/config')
|
31
|
-
const
|
32
|
-
const
|
31
|
+
const oracleFacade = require('../../models/oracle/facade')
|
32
|
+
const participantFacade = require('../../models/participantEndpoint/facade')
|
33
33
|
const partiesUtils = require('./partiesUtils')
|
34
34
|
|
35
35
|
/** @returns {PartiesDeps} */
|
@@ -37,6 +37,8 @@ const createDeps = ({
|
|
37
37
|
cache,
|
38
38
|
proxyCache,
|
39
39
|
proxies = Util.proxies,
|
40
|
+
participant = participantFacade,
|
41
|
+
oracle = oracleFacade,
|
40
42
|
childSpan = null,
|
41
43
|
log = logger
|
42
44
|
}) => Object.freeze({
|
@@ -44,10 +46,10 @@ const createDeps = ({
|
|
44
46
|
proxyCache,
|
45
47
|
childSpan,
|
46
48
|
log,
|
47
|
-
config,
|
48
49
|
oracle,
|
49
|
-
participant,
|
50
50
|
proxies,
|
51
|
+
participant,
|
52
|
+
config,
|
51
53
|
partiesUtils
|
52
54
|
})
|
53
55
|
|
@@ -47,7 +47,7 @@ const services = require('./services')
|
|
47
47
|
* @param {IProxyCache} [proxyCache] - IProxyCache instance
|
48
48
|
*/
|
49
49
|
const putPartiesByTypeAndID = async (headers, params, method, payload, dataUri, cache, proxyCache = undefined) => {
|
50
|
-
//
|
50
|
+
// think, if we need to pass span here
|
51
51
|
const component = putPartiesByTypeAndID.name
|
52
52
|
const histTimerEnd = Metrics.getHistogram(
|
53
53
|
component,
|
@@ -26,12 +26,10 @@
|
|
26
26
|
******/
|
27
27
|
|
28
28
|
const ErrorHandler = require('@mojaloop/central-services-error-handling')
|
29
|
-
const { Enum } = require('@mojaloop/central-services-shared')
|
30
|
-
const { decodePayload } = require('@mojaloop/central-services-shared').Util.StreamingProtocol
|
29
|
+
const { Enum, Util } = require('@mojaloop/central-services-shared')
|
31
30
|
const { initStepState } = require('../../../lib/util')
|
32
31
|
const { createCallbackHeaders } = require('../../../lib/headers')
|
33
32
|
const { ERROR_MESSAGES } = require('../../../constants')
|
34
|
-
const { makeAcceptContentTypeHeader } = require('@mojaloop/central-services-shared').Util.Headers
|
35
33
|
|
36
34
|
const { FspEndpointTypes, FspEndpointTemplates } = Enum.EndPoints
|
37
35
|
const { Headers, RestMethods, HeaderResources } = Enum.Http
|
@@ -170,6 +168,26 @@ class BasePartiesService {
|
|
170
168
|
this.log.info('sendErrorCallback is done', { sendTo, errorInfo })
|
171
169
|
}
|
172
170
|
|
171
|
+
/**
|
172
|
+
* @returns {Promise<{ fspId: string, partySubIdOrType?: string }[]>} List of parties from oracle response
|
173
|
+
*/
|
174
|
+
async sendOracleDiscoveryRequest () {
|
175
|
+
this.stepInProgress('#sendOracleDiscoveryRequest')
|
176
|
+
const { headers, params, query } = this.inputs
|
177
|
+
|
178
|
+
const response = await this.deps.oracle.oracleRequest(headers, RestMethods.GET, params, query, undefined, this.deps.cache)
|
179
|
+
this.log.verbose('oracle discovery raw response:', { response })
|
180
|
+
|
181
|
+
let { partyList } = response?.data || {}
|
182
|
+
if (!Array.isArray(partyList)) {
|
183
|
+
this.log.warn('invalid oracle discovery response:', { response })
|
184
|
+
// todo: maybe, it's better to throw an error
|
185
|
+
partyList = []
|
186
|
+
}
|
187
|
+
|
188
|
+
return partyList
|
189
|
+
}
|
190
|
+
|
173
191
|
async sendDeleteOracleRequest (headers, params) {
|
174
192
|
this.stepInProgress('sendDeleteOracleRequest')
|
175
193
|
const result = await this.deps.oracle.oracleRequest(headers, RestMethods.DELETE, params, null, null, this.deps.cache)
|
@@ -184,6 +202,24 @@ class BasePartiesService {
|
|
184
202
|
return isRemoved
|
185
203
|
}
|
186
204
|
|
205
|
+
async sendPartyResolutionErrorCallback () {
|
206
|
+
this.stepInProgress('sendPartyResolutionErrorCallback')
|
207
|
+
const { headers, params } = this.inputs
|
208
|
+
|
209
|
+
const error = this.createFspiopPartyResolutionError(ERROR_MESSAGES.externalPartyError)
|
210
|
+
const callbackHeaders = BasePartiesService.createErrorCallbackHeaders(headers, params)
|
211
|
+
const errorInfo = await this.deps.partiesUtils.makePutPartiesErrorPayload(this.deps.config, error, callbackHeaders, params)
|
212
|
+
this.state.destination = callbackHeaders[Headers.FSPIOP.DESTINATION]
|
213
|
+
|
214
|
+
await this.identifyDestinationForCallback()
|
215
|
+
await this.sendErrorCallback({
|
216
|
+
errorInfo,
|
217
|
+
headers: callbackHeaders,
|
218
|
+
params
|
219
|
+
})
|
220
|
+
this.log.verbose('sendPartyResolutionErrorCallback is done', { callbackHeaders, errorInfo })
|
221
|
+
}
|
222
|
+
|
187
223
|
createFspiopIdNotFoundError (errMessage, log = this.log) {
|
188
224
|
log.warn(errMessage)
|
189
225
|
return ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
|
@@ -194,9 +230,9 @@ class BasePartiesService {
|
|
194
230
|
return ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.PARTY_NOT_FOUND, errMessage)
|
195
231
|
}
|
196
232
|
|
197
|
-
|
233
|
+
createFspiopPartyResolutionError (errMessage, log = this.log) {
|
198
234
|
log.warn(errMessage)
|
199
|
-
return ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.
|
235
|
+
return ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.PARTY_RESOLUTION_FAILURE, errMessage)
|
200
236
|
}
|
201
237
|
|
202
238
|
stepInProgress (stepName) {
|
@@ -222,7 +258,7 @@ class BasePartiesService {
|
|
222
258
|
}
|
223
259
|
|
224
260
|
static decodeDataUriPayload (dataUri) {
|
225
|
-
const decoded = decodePayload(dataUri, { asParsed: false })
|
261
|
+
const decoded = Util.StreamingProtocol.decodePayload(dataUri, { asParsed: false })
|
226
262
|
return decoded.body.toString()
|
227
263
|
}
|
228
264
|
|
@@ -257,7 +293,7 @@ class BasePartiesService {
|
|
257
293
|
return {
|
258
294
|
[Headers.FSPIOP.SOURCE]: hubName,
|
259
295
|
[Headers.FSPIOP.DESTINATION]: destination,
|
260
|
-
[Headers.GENERAL.CONTENT_TYPE.value]: makeAcceptContentTypeHeader(
|
296
|
+
[Headers.GENERAL.CONTENT_TYPE.value]: Util.Headers.makeAcceptContentTypeHeader(
|
261
297
|
HeaderResources.PARTIES,
|
262
298
|
config.PROTOCOL_VERSIONS.CONTENT.DEFAULT.toString(),
|
263
299
|
config.API_TYPE
|
@@ -28,7 +28,7 @@
|
|
28
28
|
const { ERROR_MESSAGES } = require('../../../constants')
|
29
29
|
const BasePartiesService = require('./BasePartiesService')
|
30
30
|
|
31
|
-
const {
|
31
|
+
const { RestMethods } = BasePartiesService.enums()
|
32
32
|
const proxyCacheTtlSec = 40 // todo: make configurable
|
33
33
|
|
34
34
|
class GetPartiesService extends BasePartiesService {
|
@@ -45,8 +45,8 @@ class GetPartiesService extends BasePartiesService {
|
|
45
45
|
return
|
46
46
|
}
|
47
47
|
|
48
|
-
const
|
49
|
-
const isSent = await this.processOraclePartyListResponse(
|
48
|
+
const partyList = await this.sendOracleDiscoveryRequest()
|
49
|
+
const isSent = await this.processOraclePartyListResponse(partyList)
|
50
50
|
this.log.info(`getParties request is ${isSent ? '' : 'NOT '}forwarded to oracle lookup DFSP`)
|
51
51
|
if (isSent) return
|
52
52
|
|
@@ -63,7 +63,7 @@ class GetPartiesService extends BasePartiesService {
|
|
63
63
|
|
64
64
|
const schemeSource = await this.validateParticipant(source)
|
65
65
|
if (schemeSource) {
|
66
|
-
log.
|
66
|
+
log.verbose('source participant is in scheme')
|
67
67
|
return source
|
68
68
|
}
|
69
69
|
|
@@ -91,9 +91,9 @@ class GetPartiesService extends BasePartiesService {
|
|
91
91
|
const log = this.log.child({ method: 'forwardRequestToDestination' })
|
92
92
|
let sendTo = destination
|
93
93
|
|
94
|
-
const
|
95
|
-
if (!
|
96
|
-
this.stepInProgress('lookupProxyDestination
|
94
|
+
const localParticipant = await this.validateParticipant(destination)
|
95
|
+
if (!localParticipant) {
|
96
|
+
this.stepInProgress('lookupProxyDestination')
|
97
97
|
const proxyId = this.state.proxyEnabled && await this.deps.proxyCache.lookupProxyByDfspId(destination)
|
98
98
|
|
99
99
|
if (!proxyId) {
|
@@ -103,26 +103,28 @@ class GetPartiesService extends BasePartiesService {
|
|
103
103
|
return
|
104
104
|
}
|
105
105
|
sendTo = proxyId
|
106
|
+
} else {
|
107
|
+
// OSS-4203: Oracle validation for external source + local destination
|
108
|
+
const isValid = await this.#validateLocalDestinationForExternalSource()
|
109
|
+
if (!isValid) {
|
110
|
+
log.warn('incorrect destination from external source', { destination })
|
111
|
+
await this.sendPartyResolutionErrorCallback()
|
112
|
+
return
|
113
|
+
}
|
106
114
|
}
|
107
115
|
|
108
116
|
await this.#forwardGetPartiesRequest({ sendTo, headers, params })
|
109
117
|
log.info('discovery getPartiesByTypeAndID request was sent', { sendTo })
|
110
118
|
}
|
111
119
|
|
112
|
-
async
|
113
|
-
|
114
|
-
const { headers, params, query } = this.inputs
|
115
|
-
return this.deps.oracle.oracleRequest(headers, RestMethods.GET, params, query, undefined, this.deps.cache)
|
116
|
-
}
|
117
|
-
|
118
|
-
async processOraclePartyListResponse (response) {
|
119
|
-
if (!Array.isArray(response?.data?.partyList) || response.data.partyList.length === 0) {
|
120
|
+
async processOraclePartyListResponse (rawPartyList) {
|
121
|
+
if (rawPartyList.length === 0) {
|
120
122
|
this.log.verbose('oracle partyList is empty')
|
121
123
|
return false
|
122
124
|
}
|
123
125
|
|
124
126
|
this.stepInProgress('processOraclePartyList')
|
125
|
-
const partyList = this.#filterOraclePartyList(
|
127
|
+
const partyList = this.#filterOraclePartyList(rawPartyList)
|
126
128
|
|
127
129
|
let sentCount = 0
|
128
130
|
await Promise.all(partyList.map(async party => {
|
@@ -166,28 +168,20 @@ class GetPartiesService extends BasePartiesService {
|
|
166
168
|
return isLocal
|
167
169
|
}
|
168
170
|
|
169
|
-
#filterOraclePartyList (
|
171
|
+
#filterOraclePartyList (partyList) {
|
170
172
|
// Oracle's API is a standard rest-style end-point Thus a GET /party on the oracle will return all participant-party records.
|
171
173
|
// 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:
|
172
|
-
this.stepInProgress('filterOraclePartyList')
|
174
|
+
this.stepInProgress('#filterOraclePartyList')
|
173
175
|
const { params } = this.inputs
|
174
|
-
const callbackEndpointType = this.deps.partiesUtils.getPartyCbType(params.SubId)
|
175
|
-
let filteredPartyList
|
176
|
-
|
177
|
-
switch (callbackEndpointType) {
|
178
|
-
case FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_GET:
|
179
|
-
filteredPartyList = response.data.partyList.filter(party => party.partySubIdOrType == null) // Filter records that DON'T contain a partySubIdOrType
|
180
|
-
break
|
181
|
-
case FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_GET:
|
182
|
-
filteredPartyList = response.data.partyList.filter(party => party.partySubIdOrType === params.SubId) // Filter records that match partySubIdOrType
|
183
|
-
break
|
184
|
-
default:
|
185
|
-
filteredPartyList = response // Fallback to providing the standard list
|
186
|
-
}
|
187
176
|
|
188
|
-
|
177
|
+
const filteredPartyList = !params?.SubId
|
178
|
+
? partyList.filter(party => party.partySubIdOrType == null) // Filter records that DON'T contain a partySubIdOrType
|
179
|
+
: partyList.filter(party => party.partySubIdOrType === params.SubId) // Filter records that match partySubIdOrType
|
180
|
+
|
181
|
+
if (!filteredPartyList.length) {
|
189
182
|
throw super.createFspiopIdNotFoundError(ERROR_MESSAGES.emptyFilteredPartyList)
|
190
183
|
}
|
184
|
+
this.log.verbose('#filterOraclePartyList is done:', { filteredPartyList })
|
191
185
|
|
192
186
|
return filteredPartyList
|
193
187
|
}
|
@@ -321,6 +315,25 @@ class GetPartiesService extends BasePartiesService {
|
|
321
315
|
return isSet
|
322
316
|
}
|
323
317
|
}
|
318
|
+
|
319
|
+
async #validateLocalDestinationForExternalSource () {
|
320
|
+
// this method is called ONLY for local destination
|
321
|
+
const { state, log } = this
|
322
|
+
|
323
|
+
const needValidation = state.requester !== state.source
|
324
|
+
log.verbose('needOracleValidation: ', { needValidation })
|
325
|
+
if (!needValidation) return true
|
326
|
+
|
327
|
+
const partyList = await this.sendOracleDiscoveryRequest()
|
328
|
+
if (partyList.length === 0) {
|
329
|
+
log.warn('Oracle returned empty party list')
|
330
|
+
return false
|
331
|
+
}
|
332
|
+
|
333
|
+
const isValid = partyList.some(party => party.fspId === state.destination)
|
334
|
+
log.verbose('#validateLocalDestinationForExternalSource is done', { isValid })
|
335
|
+
return isValid
|
336
|
+
}
|
324
337
|
}
|
325
338
|
|
326
339
|
module.exports = GetPartiesService
|
@@ -26,13 +26,13 @@
|
|
26
26
|
******/
|
27
27
|
|
28
28
|
const BasePartiesService = require('./BasePartiesService')
|
29
|
-
const { ERROR_MESSAGES } = require('../../../constants')
|
30
29
|
|
31
30
|
class PutPartiesErrorService extends BasePartiesService {
|
32
31
|
async handleRequest () {
|
33
32
|
if (this.state.proxyEnabled && this.state.proxy) {
|
34
|
-
const alsReq = this.deps.partiesUtils.alsRequestDto(this.state.destination, this.inputs.params)
|
33
|
+
const alsReq = this.deps.partiesUtils.alsRequestDto(this.state.destination, this.inputs.params)
|
35
34
|
const isInterSchemeDiscoveryCase = await this.deps.proxyCache.isPendingCallback(alsReq)
|
35
|
+
this.log.verbose(`isInterSchemeDiscoveryCase: ${isInterSchemeDiscoveryCase}`, this.state)
|
36
36
|
|
37
37
|
if (isInterSchemeDiscoveryCase) {
|
38
38
|
const isLast = await this.checkLastProxyCallback(alsReq)
|
@@ -41,13 +41,11 @@ class PutPartiesErrorService extends BasePartiesService {
|
|
41
41
|
return
|
42
42
|
}
|
43
43
|
} else {
|
44
|
-
const
|
45
|
-
if (
|
46
|
-
this.log.info('
|
44
|
+
const isExternal = await this.#isPartyFromExternalDfsp()
|
45
|
+
if (isExternal) {
|
46
|
+
this.log.info('need to cleanup oracle coz party is from external DFSP')
|
47
47
|
await this.cleanupOracle()
|
48
|
-
await this.removeProxyGetPartiesTimeoutCache(alsReq)
|
49
|
-
await this.forwardServiceUnavailableErrorCallback()
|
50
|
-
return
|
48
|
+
await this.removeProxyGetPartiesTimeoutCache(alsReq) // think if we need this
|
51
49
|
}
|
52
50
|
}
|
53
51
|
}
|
@@ -79,20 +77,18 @@ class PutPartiesErrorService extends BasePartiesService {
|
|
79
77
|
return super.sendErrorCallback({ errorInfo, headers, params })
|
80
78
|
}
|
81
79
|
|
82
|
-
async
|
83
|
-
this.stepInProgress('
|
84
|
-
const
|
85
|
-
|
86
|
-
|
87
|
-
|
80
|
+
async #isPartyFromExternalDfsp () {
|
81
|
+
this.stepInProgress('#isPartyFromExternalDfsp')
|
82
|
+
const partyList = await super.sendOracleDiscoveryRequest()
|
83
|
+
if (!partyList.length) {
|
84
|
+
this.log.verbose('oracle returns empty partyList')
|
85
|
+
return false
|
86
|
+
}
|
87
|
+
// think, if we have several parties from oracle
|
88
|
+
const isExternal = !(await this.validateParticipant(partyList[0].fspId))
|
89
|
+
this.log.verbose('#isPartyFromExternalDfsp is done:', { isExternal, partyList })
|
88
90
|
|
89
|
-
|
90
|
-
await super.sendErrorCallback({
|
91
|
-
errorInfo,
|
92
|
-
headers: callbackHeaders,
|
93
|
-
params
|
94
|
-
})
|
95
|
-
this.log.verbose('#forwardServiceUnavailableErrorCallback is done', { callbackHeaders, errorInfo })
|
91
|
+
return isExternal
|
96
92
|
}
|
97
93
|
}
|
98
94
|
|
@@ -104,7 +104,7 @@ class PutPartiesService extends BasePartiesService {
|
|
104
104
|
fspId: source
|
105
105
|
}
|
106
106
|
await this.deps.oracle.oracleRequest(headers, RestMethods.POST, params, null, mappingPayload, this.deps.cache)
|
107
|
-
this.log.info('oracle was updated with mappingPayload',
|
107
|
+
this.log.info('oracle was updated with mappingPayload: ', mappingPayload)
|
108
108
|
}
|
109
109
|
}
|
110
110
|
|
package/src/lib/headers.js
CHANGED
@@ -41,6 +41,7 @@ const Config = require('../lib/config')
|
|
41
41
|
*/
|
42
42
|
exports.createCallbackHeaders = (params) => {
|
43
43
|
const callbackHeaders = { ...params.requestHeaders }
|
44
|
+
delete callbackHeaders[Enums.Http.Headers.FSPIOP.PROXY]
|
44
45
|
|
45
46
|
callbackHeaders[Enums.Http.Headers.FSPIOP.SOURCE] = Config.HUB_NAME
|
46
47
|
callbackHeaders[Enums.Http.Headers.FSPIOP.DESTINATION] = params.requestHeaders[Enums.Http.Headers.FSPIOP.SOURCE]
|
@@ -947,6 +947,7 @@ describe('Parties Tests', () => {
|
|
947
947
|
Config.PROXY_CACHE_CONFIG.enabled = true
|
948
948
|
const errorCode = MojaloopApiErrorCodes.PAYEE_IDENTIFIER_NOT_VALID.code
|
949
949
|
const payload = fixtures.errorCallbackResponseDto({ errorCode })
|
950
|
+
const dataUri = encodePayload(JSON.stringify(payload), 'application/json')
|
950
951
|
const destination = `dest-${Date.now()}`
|
951
952
|
const proxy = `proxy-${Date.now()}`
|
952
953
|
const headers = fixtures.partiesCallHeadersDto({ destination, proxy })
|
@@ -958,11 +959,13 @@ describe('Parties Tests', () => {
|
|
958
959
|
oracleEndpointCached.getOracleEndpointByType = sandbox.stub().resolves([
|
959
960
|
{ value: 'http://oracle.endpoint' }
|
960
961
|
])
|
961
|
-
oracle.oracleRequest = sandbox.stub().resolves(
|
962
|
+
oracle.oracleRequest = sandbox.stub().resolves({
|
963
|
+
data: { partyList: [{ fspId: 'fspId' }] }
|
964
|
+
})
|
962
965
|
|
963
|
-
await partiesDomain.putPartiesErrorByTypeAndID(headers, params, payload,
|
966
|
+
await partiesDomain.putPartiesErrorByTypeAndID(headers, params, payload, dataUri, null, null, proxyCache)
|
964
967
|
|
965
|
-
expect(oracle.oracleRequest.callCount).toBe(
|
968
|
+
expect(oracle.oracleRequest.callCount).toBe(2)
|
966
969
|
expect(oracle.oracleRequest.lastCall.args[1]).toBe(RestMethods.DELETE)
|
967
970
|
expect(participant.sendRequest.callCount).toBe(0)
|
968
971
|
expect(participant.sendErrorToParticipant.callCount).toBe(1)
|
@@ -970,7 +973,7 @@ describe('Parties Tests', () => {
|
|
970
973
|
const [sentTo, _, data, cbHeaders] = participant.sendErrorToParticipant.lastCall.args
|
971
974
|
expect(sentTo).toBe(proxy)
|
972
975
|
expect(cbHeaders[Headers.FSPIOP.DESTINATION]).toBe(destination)
|
973
|
-
expect(data.errorInformation.errorCode).toBe(
|
976
|
+
expect(JSON.parse(data).errorInformation.errorCode).toBe(errorCode)
|
974
977
|
})
|
975
978
|
})
|
976
979
|
})
|
@@ -28,10 +28,12 @@
|
|
28
28
|
const { setTimeout: sleep } = require('node:timers/promises')
|
29
29
|
const {
|
30
30
|
createMockDeps,
|
31
|
+
createOracleFacadeMock,
|
32
|
+
createParticipantFacadeMock,
|
31
33
|
createProxyCacheMock,
|
32
34
|
createProxiesUtilMock,
|
33
|
-
oracleMock,
|
34
|
-
participantMock
|
35
|
+
oracleMock, // deprecated! use createOracleFacadeMock instead
|
36
|
+
participantMock // deprecated! use createParticipantFacadeMock instead
|
35
37
|
} = require('./deps')
|
36
38
|
// ↑ should be first require to mock external deps ↑
|
37
39
|
const { GetPartiesService } = require('#src/domain/parties/services/index')
|
@@ -42,11 +44,16 @@ const { RestMethods, Headers } = GetPartiesService.enums()
|
|
42
44
|
|
43
45
|
describe('GetPartiesService Tests -->', () => {
|
44
46
|
const { config } = createMockDeps()
|
47
|
+
const { API_TYPE } = config
|
45
48
|
|
46
49
|
beforeEach(() => {
|
47
50
|
jest.clearAllMocks()
|
48
51
|
})
|
49
52
|
|
53
|
+
afterEach(() => {
|
54
|
+
config.API_TYPE = API_TYPE // to avoid side effects
|
55
|
+
})
|
56
|
+
|
50
57
|
describe('forwardRequestToDestination method', () => {
|
51
58
|
test('should delete party info from oracle, if no destination DFSP in proxy mapping', async () => {
|
52
59
|
participantMock.validateParticipant = jest.fn().mockResolvedValueOnce(null)
|
@@ -234,12 +241,14 @@ describe('GetPartiesService Tests -->', () => {
|
|
234
241
|
})
|
235
242
|
|
236
243
|
test('should send error callback in ISO format if proxyRequest failed after delay, and other proxies have already replied', async () => {
|
244
|
+
participantMock.validateParticipant = jest.fn().mockResolvedValue({})
|
237
245
|
const service = prepareGetPartiesServiceForDelayedProxyError()
|
238
246
|
const { headers } = service.inputs
|
239
247
|
service.deps.config.API_TYPE = API_TYPES.iso20022
|
240
248
|
|
241
249
|
await service.triggerInterSchemeDiscoveryFlow(headers)
|
242
250
|
.catch(err => service.handleError(err))
|
251
|
+
|
243
252
|
expect(participantMock.sendErrorToParticipant).toHaveBeenCalledTimes(1)
|
244
253
|
expect(participantMock.sendErrorToParticipant.mock.lastCall[2].Rpt.Rsn.Cd).toBe('3200')
|
245
254
|
})
|
@@ -325,12 +334,181 @@ describe('GetPartiesService Tests -->', () => {
|
|
325
334
|
}
|
326
335
|
const headers = fixtures.partiesCallHeadersDto({ destination: '' })
|
327
336
|
const params = fixtures.partiesParamsDto()
|
328
|
-
const service = new GetPartiesService(deps, { headers, params })
|
329
337
|
|
338
|
+
const service = new GetPartiesService(deps, { headers, params })
|
330
339
|
await service.handleRequest()
|
340
|
+
|
331
341
|
expect(participantMock.sendErrorToParticipant).toHaveBeenCalledTimes(1)
|
332
342
|
const isoPayload = participantMock.sendErrorToParticipant.mock.lastCall[2]
|
333
343
|
expect(isoPayload.Assgnmt).toBeDefined()
|
334
344
|
expect(isoPayload.Rpt).toBeDefined()
|
335
345
|
})
|
346
|
+
|
347
|
+
describe('OSS-4203: oracle validation for external source + local destination', () => {
|
348
|
+
const EXTERNAL_SOURCE_DFSP = 'externalSourceDfsp'
|
349
|
+
const LOCAL_DESTINATION_DFSP = 'localDestinationDfsp'
|
350
|
+
const ORACLE_DFSP_DIFFERENT = 'differentDfsp'
|
351
|
+
const PROXY_ID = 'proxyForExternal'
|
352
|
+
|
353
|
+
let deps
|
354
|
+
let oracle // facade
|
355
|
+
let participant // facade
|
356
|
+
let proxyCache
|
357
|
+
let headers
|
358
|
+
let params
|
359
|
+
|
360
|
+
beforeEach(() => {
|
361
|
+
oracle = createOracleFacadeMock()
|
362
|
+
participant = createParticipantFacadeMock()
|
363
|
+
proxyCache = createProxyCacheMock({
|
364
|
+
addDfspIdToProxyMapping: jest.fn().mockResolvedValueOnce(true)
|
365
|
+
})
|
366
|
+
deps = createMockDeps({ oracle, participant, proxyCache })
|
367
|
+
headers = fixtures.partiesCallHeadersDto({
|
368
|
+
source: EXTERNAL_SOURCE_DFSP,
|
369
|
+
destination: LOCAL_DESTINATION_DFSP,
|
370
|
+
proxy: PROXY_ID
|
371
|
+
})
|
372
|
+
params = fixtures.partiesParamsDto()
|
373
|
+
})
|
374
|
+
|
375
|
+
test('should forward request when oracle DFSP matches destination DFSP', async () => {
|
376
|
+
participant.validateParticipant = jest.fn()
|
377
|
+
.mockResolvedValueOnce(null) // external source (validateRequester)
|
378
|
+
.mockResolvedValueOnce({}) // proxy exists (validateRequester)
|
379
|
+
.mockResolvedValueOnce({}) // local destination (forwardRequestToDestination)
|
380
|
+
.mockResolvedValueOnce(null) // external source (shouldValidateViaOracle)
|
381
|
+
.mockResolvedValueOnce({}) // local destination (shouldValidateViaOracle)
|
382
|
+
oracle.oracleRequest = jest.fn().mockResolvedValueOnce(
|
383
|
+
fixtures.oracleRequestResponseDto({
|
384
|
+
partyList: [{ fspId: LOCAL_DESTINATION_DFSP }]
|
385
|
+
})
|
386
|
+
)
|
387
|
+
|
388
|
+
const service = new GetPartiesService(deps, { headers, params })
|
389
|
+
await service.handleRequest()
|
390
|
+
|
391
|
+
expect(oracle.oracleRequest).toHaveBeenCalledWith(
|
392
|
+
headers,
|
393
|
+
RestMethods.GET,
|
394
|
+
params,
|
395
|
+
undefined,
|
396
|
+
undefined,
|
397
|
+
deps.cache
|
398
|
+
)
|
399
|
+
expect(participant.sendRequest).toHaveBeenCalledWith(
|
400
|
+
expect.objectContaining({
|
401
|
+
[Headers.FSPIOP.DESTINATION]: LOCAL_DESTINATION_DFSP
|
402
|
+
}),
|
403
|
+
LOCAL_DESTINATION_DFSP,
|
404
|
+
expect.any(String),
|
405
|
+
RestMethods.GET,
|
406
|
+
undefined,
|
407
|
+
expect.any(Object),
|
408
|
+
null
|
409
|
+
)
|
410
|
+
expect(participant.sendErrorToParticipant).not.toHaveBeenCalled()
|
411
|
+
})
|
412
|
+
|
413
|
+
test('should send error callback when oracle DFSP differs from destination DFSP', async () => {
|
414
|
+
participant.validateParticipant = jest.fn()
|
415
|
+
.mockResolvedValueOnce(null) // external source (validateRequester)
|
416
|
+
.mockResolvedValueOnce({}) // proxy exists (validateRequester)
|
417
|
+
.mockResolvedValueOnce({}) // local destination (forwardRequestToDestination)
|
418
|
+
.mockResolvedValueOnce(null) // external source (shouldValidateViaOracle)
|
419
|
+
.mockResolvedValueOnce({})
|
420
|
+
oracle.oracleRequest = jest.fn().mockResolvedValueOnce(
|
421
|
+
fixtures.oracleRequestResponseDto({
|
422
|
+
partyList: [{ fspId: ORACLE_DFSP_DIFFERENT }]
|
423
|
+
})
|
424
|
+
)
|
425
|
+
proxyCache.lookupProxyByDfspId = jest.fn().mockResolvedValueOnce(PROXY_ID)
|
426
|
+
|
427
|
+
const service = new GetPartiesService(deps, { headers, params })
|
428
|
+
await service.handleRequest()
|
429
|
+
|
430
|
+
expect(oracle.oracleRequest).toHaveBeenCalledTimes(1)
|
431
|
+
expect(participant.sendErrorToParticipant).toHaveBeenCalledTimes(1)
|
432
|
+
expect(participant.sendRequest).not.toHaveBeenCalled()
|
433
|
+
|
434
|
+
const [sentTo, , errorPayload] = participant.sendErrorToParticipant.mock.lastCall
|
435
|
+
expect(sentTo).toBe(PROXY_ID) // Error is sent to the proxy (requester)
|
436
|
+
expect(errorPayload.errorInformation.errorCode).toBeDefined()
|
437
|
+
})
|
438
|
+
|
439
|
+
test('should send error callback when oracle returns empty result', async () => {
|
440
|
+
participant.validateParticipant = jest.fn()
|
441
|
+
.mockResolvedValueOnce(null) // external source (validateRequester)
|
442
|
+
.mockResolvedValueOnce({}) // proxy exists (validateRequester)
|
443
|
+
.mockResolvedValueOnce(null) // external source (shouldValidateViaOracle)
|
444
|
+
.mockResolvedValueOnce({}) // local destination (shouldValidateViaOracle)
|
445
|
+
.mockResolvedValueOnce({}) // local destination (forwardRequestToDestination)
|
446
|
+
|
447
|
+
oracle.oracleRequest = jest.fn().mockResolvedValueOnce(
|
448
|
+
fixtures.oracleRequestResponseDto({ partyList: [] })
|
449
|
+
)
|
450
|
+
|
451
|
+
const service = new GetPartiesService(deps, { headers, params })
|
452
|
+
await service.handleRequest()
|
453
|
+
|
454
|
+
expect(oracle.oracleRequest).toHaveBeenCalledTimes(1)
|
455
|
+
expect(participant.sendErrorToParticipant).toHaveBeenCalledTimes(1)
|
456
|
+
expect(participant.sendRequest).not.toHaveBeenCalled()
|
457
|
+
})
|
458
|
+
|
459
|
+
test('should skip oracle validation when source is local (not external)', async () => {
|
460
|
+
const headers = fixtures.partiesCallHeadersDto()
|
461
|
+
participant.validateParticipant = jest.fn().mockResolvedValue({})
|
462
|
+
|
463
|
+
const service = new GetPartiesService(deps, { headers, params })
|
464
|
+
await service.handleRequest()
|
465
|
+
|
466
|
+
expect(oracle.oracleRequest).not.toHaveBeenCalled()
|
467
|
+
expect(participant.sendRequest).toHaveBeenCalledTimes(1)
|
468
|
+
})
|
469
|
+
|
470
|
+
test('should skip oracle validation when destination is external (not local)', async () => {
|
471
|
+
const externalDestHeaders = fixtures.partiesCallHeadersDto({
|
472
|
+
source: EXTERNAL_SOURCE_DFSP,
|
473
|
+
destination: 'externalDestinationDfsp',
|
474
|
+
proxy: PROXY_ID
|
475
|
+
})
|
476
|
+
participant.validateParticipant = jest.fn()
|
477
|
+
.mockResolvedValueOnce(null) // external source
|
478
|
+
.mockResolvedValueOnce({}) // proxy exists
|
479
|
+
.mockResolvedValueOnce(null) // external destination
|
480
|
+
proxyCache.lookupProxyByDfspId = jest.fn().mockResolvedValueOnce(PROXY_ID)
|
481
|
+
|
482
|
+
const service = new GetPartiesService(deps, { headers: externalDestHeaders, params })
|
483
|
+
await service.handleRequest()
|
484
|
+
|
485
|
+
expect(oracle.oracleRequest).not.toHaveBeenCalled()
|
486
|
+
expect(participant.sendRequest).toHaveBeenCalledTimes(1)
|
487
|
+
})
|
488
|
+
|
489
|
+
test('should send error callback in ISO20022 format when API_TYPE is iso20022', async () => {
|
490
|
+
const isoConfig = { ...deps.config, API_TYPE: API_TYPES.iso20022 }
|
491
|
+
const isoDeps = { ...deps, config: isoConfig }
|
492
|
+
|
493
|
+
participant.validateParticipant = jest.fn()
|
494
|
+
.mockResolvedValueOnce(null) // external source (validateRequester)
|
495
|
+
.mockResolvedValueOnce({}) // proxy exists (validateRequester)
|
496
|
+
.mockResolvedValueOnce(null) // external source (shouldValidateViaOracle)
|
497
|
+
.mockResolvedValueOnce({}) // local destination (shouldValidateViaOracle)
|
498
|
+
.mockResolvedValueOnce({}) // local destination (forwardRequestToDestination)
|
499
|
+
oracle.oracleRequest = jest.fn().mockResolvedValueOnce(
|
500
|
+
fixtures.oracleRequestResponseDto({
|
501
|
+
partyList: [{ fspId: ORACLE_DFSP_DIFFERENT }]
|
502
|
+
})
|
503
|
+
)
|
504
|
+
|
505
|
+
const service = new GetPartiesService(isoDeps, { headers, params })
|
506
|
+
await service.handleRequest()
|
507
|
+
|
508
|
+
expect(participant.sendErrorToParticipant).toHaveBeenCalledTimes(1)
|
509
|
+
const isoPayload = participant.sendErrorToParticipant.mock.lastCall[2]
|
510
|
+
expect(isoPayload.Assgnmt).toBeDefined()
|
511
|
+
expect(isoPayload.Rpt).toBeDefined()
|
512
|
+
})
|
513
|
+
})
|
336
514
|
})
|