account-lookup-service 17.7.1 → 17.8.0-snapshot.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +0 -7
- package/package.json +8 -5
- package/src/constants.js +34 -2
- package/src/domain/parties/deps.js +11 -4
- package/src/domain/parties/getPartiesByTypeAndID.js +9 -13
- package/src/domain/parties/partiesUtils.js +4 -34
- package/src/domain/parties/putParties.js +26 -71
- package/src/domain/parties/services/BasePartiesService.js +143 -15
- package/src/domain/parties/services/GetPartiesService.js +194 -164
- package/src/domain/parties/services/PutPartiesErrorService.js +51 -27
- package/src/domain/parties/services/PutPartiesService.js +51 -33
- package/src/domain/parties/services/TimeoutPartiesService.js +84 -0
- package/src/domain/parties/services/index.js +3 -1
- package/src/domain/timeout/createSpan.js +55 -0
- package/src/domain/timeout/index.js +27 -36
- package/src/handlers/TimeoutHandler.js +2 -2
- package/src/lib/util.js +11 -3
- package/test/fixtures/index.js +46 -0
- package/test/integration/domain/timeout/index.test.js +83 -28
- package/test/unit/domain/parties/parties.test.js +26 -18
- package/test/unit/domain/parties/partiesUtils.test.js +51 -0
- package/test/unit/domain/parties/services/BasePartiesService.test.js +71 -0
- package/test/unit/domain/parties/services/GetPartiesService.test.js +245 -0
- package/test/unit/domain/parties/services/PutPartiesErrorService.test.js +50 -0
- package/test/unit/domain/parties/services/TimeoutPartiesService.test.js +72 -0
- package/test/unit/domain/parties/services/deps.js +51 -0
- package/test/unit/domain/timeout/index.test.js +17 -12
- package/test/util/apiClients/BasicApiClient.js +33 -6
- package/test/util/apiClients/ProxyApiClient.js +46 -1
- package/test/util/index.js +5 -6
- package/test/util/mockDeps.js +72 -0
- package/src/domain/timeout/dto.js +0 -54
- package/test/unit/domain/parties/utils.test.js +0 -60
- package/test/unit/domain/timeout/dto.test.js +0 -24
@@ -32,64 +32,82 @@ const BasePartiesService = require('./BasePartiesService')
|
|
32
32
|
const { RestMethods } = BasePartiesService.enums()
|
33
33
|
|
34
34
|
class PutPartiesService extends BasePartiesService {
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
35
|
+
async handleRequest () {
|
36
|
+
const { destination, source, proxy } = this.state
|
37
|
+
this.log.info('handleRequest start', { destination, source, proxy })
|
38
|
+
|
39
|
+
await this.validateSourceParticipant()
|
40
|
+
if (proxy) {
|
41
|
+
await this.checkProxySuccessResponse()
|
42
|
+
}
|
43
|
+
await this.identifyDestinationForSuccessCallback()
|
44
|
+
await this.sendSuccessCallback()
|
45
|
+
}
|
46
|
+
|
47
|
+
async validateSourceParticipant () {
|
48
|
+
const { source, proxy, proxyEnabled } = this.state
|
49
|
+
const log = this.log.child({ source, proxy, method: 'validateSourceParticipant' })
|
50
|
+
this.stepInProgress('validateSourceParticipant-1')
|
51
|
+
|
52
|
+
const schemeParticipant = await super.validateParticipant(source)
|
53
|
+
if (!schemeParticipant) {
|
54
|
+
if (!proxyEnabled || !proxy) {
|
55
|
+
throw super.createFspiopIdNotFoundError(ERROR_MESSAGES.sourceFspNotFound, log)
|
48
56
|
}
|
57
|
+
|
49
58
|
const isCached = await this.deps.proxyCache.addDfspIdToProxyMapping(source, proxy)
|
50
59
|
if (!isCached) {
|
51
|
-
|
52
|
-
this.log.warn(errMessage, { source, proxy })
|
53
|
-
throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
|
60
|
+
throw super.createFspiopIdNotFoundError('failed to addDfspIdToProxyMapping', log)
|
54
61
|
}
|
55
62
|
|
56
|
-
|
63
|
+
log.info('addDfspIdToProxyMapping is done', { source, proxy })
|
57
64
|
}
|
58
65
|
}
|
59
66
|
|
60
|
-
async checkProxySuccessResponse (
|
61
|
-
if (this.proxyEnabled) {
|
62
|
-
this.
|
67
|
+
async checkProxySuccessResponse () {
|
68
|
+
if (this.state.proxyEnabled) {
|
69
|
+
this.stepInProgress('checkProxySuccessResponse')
|
70
|
+
const { headers, params } = this.inputs
|
71
|
+
const { destination, source } = this.state
|
63
72
|
const alsReq = this.deps.partiesUtils.alsRequestDto(destination, params)
|
64
73
|
|
65
74
|
const isExists = await this.deps.proxyCache.receivedSuccessResponse(alsReq)
|
66
|
-
if (isExists) {
|
67
|
-
|
75
|
+
if (!isExists) {
|
76
|
+
this.log.verbose('NOT inter-scheme receivedSuccessResponse case')
|
77
|
+
await this.removeProxyGetPartiesTimeoutCache(alsReq)
|
68
78
|
return
|
69
79
|
}
|
70
|
-
|
71
|
-
|
80
|
+
|
81
|
+
const schemeParticipant = await super.validateParticipant(destination)
|
82
|
+
if (schemeParticipant) {
|
83
|
+
await this.#updateOracleWithParticipantMapping({ source, headers, params })
|
84
|
+
}
|
72
85
|
}
|
73
86
|
}
|
74
87
|
|
75
|
-
async identifyDestinationForSuccessCallback (
|
76
|
-
this.
|
88
|
+
async identifyDestinationForSuccessCallback () {
|
89
|
+
const { destination } = this.state
|
90
|
+
this.stepInProgress('identifyDestinationForSuccessCallback')
|
77
91
|
const destinationParticipant = await super.validateParticipant(destination)
|
78
92
|
if (destinationParticipant) {
|
79
|
-
|
93
|
+
this.state.requester = destinationParticipant.name
|
94
|
+
return
|
80
95
|
}
|
81
96
|
|
82
|
-
const proxyName = this.proxyEnabled && await this.deps.proxyCache.lookupProxyByDfspId(destination)
|
97
|
+
const proxyName = this.state.proxyEnabled && await this.deps.proxyCache.lookupProxyByDfspId(destination)
|
83
98
|
if (!proxyName) {
|
84
99
|
const errMessage = ERROR_MESSAGES.partyDestinationFspNotFound
|
85
100
|
this.log.warn(`${errMessage} and no proxy`, { destination })
|
86
101
|
throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.DESTINATION_FSP_ERROR, errMessage)
|
87
102
|
}
|
88
|
-
|
103
|
+
|
104
|
+
this.state.requester = proxyName
|
89
105
|
}
|
90
106
|
|
91
|
-
async sendSuccessCallback (
|
92
|
-
this.
|
107
|
+
async sendSuccessCallback () {
|
108
|
+
const { headers, params, dataUri } = this.inputs
|
109
|
+
const sendTo = this.state.requester
|
110
|
+
this.stepInProgress('sendSuccessCallback')
|
93
111
|
const payload = PutPartiesService.decodeDataUriPayload(dataUri)
|
94
112
|
const callbackEndpointType = this.deps.partiesUtils.putPartyCbType(params.SubId)
|
95
113
|
const options = this.deps.partiesUtils.partiesRequestOptionsDto(params)
|
@@ -97,11 +115,11 @@ class PutPartiesService extends BasePartiesService {
|
|
97
115
|
await this.deps.participant.sendRequest(
|
98
116
|
headers, sendTo, callbackEndpointType, RestMethods.PUT, payload, options
|
99
117
|
)
|
100
|
-
this.log.verbose('sendSuccessCallback is
|
118
|
+
this.log.verbose('sendSuccessCallback is sent', { sendTo, payload })
|
101
119
|
}
|
102
120
|
|
103
121
|
async #updateOracleWithParticipantMapping ({ source, headers, params }) {
|
104
|
-
this.
|
122
|
+
this.stepInProgress('#updateOracleWithParticipantMapping-3')
|
105
123
|
const mappingPayload = {
|
106
124
|
fspId: source
|
107
125
|
}
|
@@ -0,0 +1,84 @@
|
|
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
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
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.
|
10
|
+
|
11
|
+
Contributors
|
12
|
+
--------------
|
13
|
+
This is the official list of the Mojaloop project contributors for this file.
|
14
|
+
Names of the original copyright holders (individuals or organizations)
|
15
|
+
should be listed with a '*' in the first column. People who have
|
16
|
+
contributed from an organization can be listed under the organization
|
17
|
+
that actually holds the copyright for their contributions (see the
|
18
|
+
Mojaloop Foundation for an example). Those individuals should have
|
19
|
+
their names indented and be marked with a '-'. Email address can be added
|
20
|
+
optionally within square brackets <email>.
|
21
|
+
|
22
|
+
* Mojaloop Foundation
|
23
|
+
* Eugen Klymniuk <eugen.klymniuk@infitx.com>
|
24
|
+
|
25
|
+
--------------
|
26
|
+
******/
|
27
|
+
|
28
|
+
const ErrorHandler = require('@mojaloop/central-services-error-handling')
|
29
|
+
const { AuditEventAction } = require('@mojaloop/event-sdk')
|
30
|
+
const createSpan = require('../../timeout/createSpan') // todo: think, how to avoid this external deps
|
31
|
+
const PutPartiesErrorService = require('./PutPartiesErrorService')
|
32
|
+
|
33
|
+
class TimeoutPartiesService extends PutPartiesErrorService {
|
34
|
+
/**
|
35
|
+
* Should be used to get TimeoutPartiesService instance
|
36
|
+
*
|
37
|
+
* @param deps {PartiesDeps}
|
38
|
+
* @param cacheKey {string}
|
39
|
+
* @param spanName {string}
|
40
|
+
* @returns {TimeoutPartiesService}
|
41
|
+
*/
|
42
|
+
static createInstance (deps, cacheKey, spanName) {
|
43
|
+
const { destination, partyType, partyId } = TimeoutPartiesService.parseExpiredKey(cacheKey)
|
44
|
+
const headers = TimeoutPartiesService.createHubErrorCallbackHeaders(deps.config.HUB_NAME, destination)
|
45
|
+
const params = { Type: partyType, ID: partyId } // todo: think, if we need to handle party SubId
|
46
|
+
const childSpan = createSpan(spanName, headers, params)
|
47
|
+
|
48
|
+
return new TimeoutPartiesService({ ...deps, childSpan }, { headers, params })
|
49
|
+
}
|
50
|
+
|
51
|
+
async handleExpiredKey () {
|
52
|
+
const { errorInfo, headers, params } = await this.prepareErrorInformation()
|
53
|
+
this.#spanAuditStart(errorInfo)
|
54
|
+
|
55
|
+
await this.identifyDestinationForErrorCallback()
|
56
|
+
return super.sendErrorCallback({ errorInfo, headers, params })
|
57
|
+
}
|
58
|
+
|
59
|
+
async prepareErrorInformation () {
|
60
|
+
const { headers, params } = this.inputs
|
61
|
+
const error = TimeoutPartiesService.createFSPIOPExpiredError()
|
62
|
+
const errorInfo = await this.deps.partiesUtils.makePutPartiesErrorPayload(
|
63
|
+
this.deps.config, error, headers, params
|
64
|
+
)
|
65
|
+
this.log.verbose('prepareErrorInformation is done', { errorInfo, headers, params })
|
66
|
+
return { errorInfo, headers, params }
|
67
|
+
}
|
68
|
+
|
69
|
+
#spanAuditStart (errorInformation) {
|
70
|
+
const { headers } = this.inputs
|
71
|
+
this.deps.childSpan?.audit({ errorInformation, headers }, AuditEventAction.start)
|
72
|
+
}
|
73
|
+
|
74
|
+
static parseExpiredKey (cacheKey) {
|
75
|
+
const [destination, partyType, partyId] = cacheKey.split(':').slice(-3)
|
76
|
+
return { destination, partyType, partyId }
|
77
|
+
}
|
78
|
+
|
79
|
+
static createFSPIOPExpiredError () {
|
80
|
+
return ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.EXPIRED_ERROR)
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
module.exports = TimeoutPartiesService
|
@@ -28,9 +28,11 @@
|
|
28
28
|
const GetPartiesService = require('./GetPartiesService')
|
29
29
|
const PutPartiesService = require('./PutPartiesService')
|
30
30
|
const PutPartiesErrorService = require('./PutPartiesErrorService')
|
31
|
+
const TimeoutPartiesService = require('./TimeoutPartiesService')
|
31
32
|
|
32
33
|
module.exports = {
|
33
34
|
GetPartiesService,
|
34
35
|
PutPartiesService,
|
35
|
-
PutPartiesErrorService
|
36
|
+
PutPartiesErrorService,
|
37
|
+
TimeoutPartiesService
|
36
38
|
}
|
@@ -0,0 +1,55 @@
|
|
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
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
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.
|
10
|
+
|
11
|
+
Contributors
|
12
|
+
--------------
|
13
|
+
This is the official list of the Mojaloop project contributors for this file.
|
14
|
+
Names of the original copyright holders (individuals or organizations)
|
15
|
+
should be listed with a '*' in the first column. People who have
|
16
|
+
contributed from an organization can be listed under the organization
|
17
|
+
that actually holds the copyright for their contributions (see the
|
18
|
+
Mojaloop Foundation for an example). Those individuals should have
|
19
|
+
their names indented and be marked with a '-'. Email address can be added
|
20
|
+
optionally within square brackets <email>.
|
21
|
+
|
22
|
+
* Mojaloop Foundation
|
23
|
+
* Eugen Klymniuk <eugen.klymniuk@infitx.com>
|
24
|
+
|
25
|
+
--------------
|
26
|
+
******/
|
27
|
+
|
28
|
+
const {
|
29
|
+
Events: { Event },
|
30
|
+
Tags: { QueryTags }
|
31
|
+
} = require('@mojaloop/central-services-shared').Enum
|
32
|
+
const { Tracer } = require('@mojaloop/event-sdk')
|
33
|
+
const EventFrameworkUtil = require('@mojaloop/central-services-shared').Util.EventFramework
|
34
|
+
|
35
|
+
const { getSpanTags } = require('../../lib/util')
|
36
|
+
|
37
|
+
const createSpan = (spanName, headers, params) => {
|
38
|
+
const span = Tracer.createSpan(spanName, { headers })
|
39
|
+
const spanTags = getSpanTags({ headers }, Event.Type.PARTY, Event.Action.PUT)
|
40
|
+
span.setTags(spanTags)
|
41
|
+
const queryTags = EventFrameworkUtil.Tags.getQueryTags(
|
42
|
+
QueryTags.serviceName.accountLookupService,
|
43
|
+
QueryTags.auditType.transactionFlow,
|
44
|
+
QueryTags.contentType.httpRequest,
|
45
|
+
QueryTags.operation.timeoutInterschemePartiesLookups,
|
46
|
+
{
|
47
|
+
partyIdType: params.Type,
|
48
|
+
partyIdentifier: params.ID
|
49
|
+
}
|
50
|
+
)
|
51
|
+
span.setTags(queryTags)
|
52
|
+
return span
|
53
|
+
}
|
54
|
+
|
55
|
+
module.exports = createSpan
|
@@ -31,66 +31,56 @@
|
|
31
31
|
******/
|
32
32
|
'use strict'
|
33
33
|
|
34
|
-
const {
|
35
|
-
|
36
|
-
Enums: { FSPIOPErrorCodes }
|
37
|
-
} = require('@mojaloop/central-services-error-handling')
|
38
|
-
const {
|
39
|
-
EventStateMetadata,
|
40
|
-
EventStatusType,
|
41
|
-
AuditEventAction
|
42
|
-
} = require('@mojaloop/event-sdk')
|
34
|
+
const { Factory: { reformatFSPIOPError } } = require('@mojaloop/central-services-error-handling')
|
35
|
+
const { EventStateMetadata, EventStatusType } = require('@mojaloop/event-sdk')
|
43
36
|
const Metrics = require('@mojaloop/central-services-metrics')
|
44
37
|
|
45
|
-
const Participant = require('../../models/participantEndpoint/facade')
|
46
|
-
const { ERROR_MESSAGES } = require('../../constants')
|
47
|
-
const { timeoutCallbackDto } = require('./dto')
|
48
38
|
const { logger } = require('../../lib')
|
49
|
-
const
|
39
|
+
const { countFspiopError } = require('../../lib/util')
|
40
|
+
const { createDeps } = require('../parties/deps')
|
41
|
+
const { TimeoutPartiesService } = require('../parties/services')
|
50
42
|
|
51
43
|
const timeoutInterschemePartiesLookups = async ({ proxyCache, batchSize }) => {
|
52
|
-
|
44
|
+
const operation = timeoutInterschemePartiesLookups.name
|
45
|
+
logger.info(`${operation} start...`, { batchSize })
|
46
|
+
return proxyCache.processExpiredAlsKeys(
|
47
|
+
(key) => sendTimeoutCallback(key, proxyCache, operation), batchSize
|
48
|
+
)
|
53
49
|
}
|
54
50
|
|
55
|
-
const
|
51
|
+
const timeoutProxyGetPartiesLookups = async ({ proxyCache, batchSize }) => {
|
52
|
+
const operation = timeoutProxyGetPartiesLookups.name
|
53
|
+
logger.info(`${operation} start...`, { batchSize })
|
54
|
+
return proxyCache.processExpiredProxyGetPartiesKeys(
|
55
|
+
(key) => sendTimeoutCallback(key, proxyCache, operation), batchSize
|
56
|
+
)
|
57
|
+
}
|
58
|
+
|
59
|
+
const sendTimeoutCallback = async (cacheKey, proxyCache, operation) => {
|
56
60
|
const histTimerEnd = Metrics.getHistogram(
|
57
61
|
'eg_timeoutInterschemePartiesLookups',
|
58
62
|
'Egress - Interscheme parties lookup timeout callback',
|
59
63
|
['success']
|
60
64
|
).startTimer()
|
61
|
-
|
62
|
-
const
|
63
|
-
const
|
64
|
-
|
65
|
+
const log = logger.child({ cacheKey, operation })
|
66
|
+
const deps = createDeps({ proxyCache, log })
|
67
|
+
const service = TimeoutPartiesService.createInstance(deps, cacheKey, operation)
|
68
|
+
const span = service.deps.childSpan
|
65
69
|
|
66
70
|
try {
|
67
|
-
|
68
|
-
await validateParticipant(destination)
|
69
|
-
await span.audit({ headers, errorInformation }, AuditEventAction.start)
|
70
|
-
step = 'sendErrorToParticipant-2'
|
71
|
-
await Participant.sendErrorToParticipant(destination, endpointType, errorInformation, headers, params, undefined, span)
|
71
|
+
await service.handleExpiredKey()
|
72
72
|
histTimerEnd({ success: true })
|
73
73
|
} catch (err) {
|
74
|
-
|
74
|
+
log.warn('error in sendTimeoutCallback: ', err)
|
75
75
|
histTimerEnd({ success: false })
|
76
76
|
const fspiopError = reformatFSPIOPError(err)
|
77
|
-
|
77
|
+
countFspiopError(fspiopError, { operation, step: service?.currenStep })
|
78
78
|
|
79
79
|
await finishSpan(span, fspiopError)
|
80
80
|
throw fspiopError
|
81
81
|
}
|
82
82
|
}
|
83
83
|
|
84
|
-
const validateParticipant = async (fspId) => {
|
85
|
-
const participant = await Participant.validateParticipant(fspId)
|
86
|
-
if (!participant) {
|
87
|
-
const errMessage = ERROR_MESSAGES.partyDestinationFspNotFound
|
88
|
-
logger.error(`error in validateParticipant: ${errMessage}`)
|
89
|
-
throw createFSPIOPError(FSPIOPErrorCodes.DESTINATION_FSP_ERROR, errMessage)
|
90
|
-
}
|
91
|
-
return participant
|
92
|
-
}
|
93
|
-
|
94
84
|
const finishSpan = async (span, err) => {
|
95
85
|
if (!span.isFinished) {
|
96
86
|
const state = new EventStateMetadata(
|
@@ -105,5 +95,6 @@ const finishSpan = async (span, err) => {
|
|
105
95
|
|
106
96
|
module.exports = {
|
107
97
|
timeoutInterschemePartiesLookups,
|
98
|
+
timeoutProxyGetPartiesLookups,
|
108
99
|
sendTimeoutCallback // Exposed for testing
|
109
100
|
}
|
@@ -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({
|
package/test/fixtures/index.js
CHANGED
@@ -1,3 +1,30 @@
|
|
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
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
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.
|
10
|
+
|
11
|
+
Contributors
|
12
|
+
--------------
|
13
|
+
This is the official list of the Mojaloop project contributors for this file.
|
14
|
+
Names of the original copyright holders (individuals or organizations)
|
15
|
+
should be listed with a '*' in the first column. People who have
|
16
|
+
contributed from an organization can be listed under the organization
|
17
|
+
that actually holds the copyright for their contributions (see the
|
18
|
+
Mojaloop Foundation for an example). Those individuals should have
|
19
|
+
their names indented and be marked with a '-'. Email address can be added
|
20
|
+
optionally within square brackets <email>.
|
21
|
+
|
22
|
+
* Mojaloop Foundation
|
23
|
+
* Eugen Klymniuk <eugen.klymniuk@infitx.com>
|
24
|
+
|
25
|
+
--------------
|
26
|
+
******/
|
27
|
+
|
1
28
|
const { randomUUID } = require('node:crypto')
|
2
29
|
const { Enum } = require('@mojaloop/central-services-shared')
|
3
30
|
const isoFixtures = require('./iso')
|
@@ -20,6 +47,16 @@ const headersDto = ({
|
|
20
47
|
'content-type': contentType || accept
|
21
48
|
})
|
22
49
|
|
50
|
+
const partiesParamsDto = ({
|
51
|
+
Type = 'MSISDN',
|
52
|
+
ID = String(Date.now()),
|
53
|
+
SubId
|
54
|
+
} = {}) => ({
|
55
|
+
Type,
|
56
|
+
ID,
|
57
|
+
...(SubId && { SubId })
|
58
|
+
})
|
59
|
+
|
23
60
|
const protocolVersionsDto = () => ({
|
24
61
|
CONTENT: {
|
25
62
|
DEFAULT: '2.1',
|
@@ -126,6 +163,13 @@ const mockAlsRequestDto = (sourceId, type, partyId) => ({
|
|
126
163
|
partyId
|
127
164
|
})
|
128
165
|
|
166
|
+
const expiredCacheKeyDto = ({
|
167
|
+
sourceId = 'sourceId',
|
168
|
+
type = 'MSISDN',
|
169
|
+
partyId = 'partyId-123',
|
170
|
+
prefix = 'prefix'
|
171
|
+
} = {}) => `${prefix}:${sourceId}:${type}:${partyId}`
|
172
|
+
|
129
173
|
const mockHapiRequestDto = ({ // https://hapi.dev/api/?v=21.3.3#request-properties
|
130
174
|
method = 'GET',
|
131
175
|
traceid = randomUUID(),
|
@@ -142,11 +186,13 @@ const mockHapiRequestDto = ({ // https://hapi.dev/api/?v=21.3.3#request-properti
|
|
142
186
|
module.exports = {
|
143
187
|
...isoFixtures,
|
144
188
|
partiesCallHeadersDto,
|
189
|
+
partiesParamsDto,
|
145
190
|
participantsCallHeadersDto,
|
146
191
|
oracleRequestResponseDto,
|
147
192
|
putPartiesSuccessResponseDto,
|
148
193
|
postParticipantsPayloadDto,
|
149
194
|
errorCallbackResponseDto,
|
195
|
+
expiredCacheKeyDto,
|
150
196
|
mockAlsRequestDto,
|
151
197
|
protocolVersionsDto,
|
152
198
|
mockHapiRequestDto,
|
@@ -1,18 +1,50 @@
|
|
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
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
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.
|
10
|
+
|
11
|
+
Contributors
|
12
|
+
--------------
|
13
|
+
This is the official list of the Mojaloop project contributors for this file.
|
14
|
+
Names of the original copyright holders (individuals or organizations)
|
15
|
+
should be listed with a '*' in the first column. People who have
|
16
|
+
contributed from an organization can be listed under the organization
|
17
|
+
that actually holds the copyright for their contributions (see the
|
18
|
+
Mojaloop Foundation for an example). Those individuals should have
|
19
|
+
their names indented and be marked with a '-'. Email address can be added
|
20
|
+
optionally within square brackets <email>.
|
21
|
+
|
22
|
+
* Mojaloop Foundation
|
23
|
+
* Eugen Klymniuk <eugen.klymniuk@infitx.com>
|
24
|
+
|
25
|
+
--------------
|
26
|
+
******/
|
27
|
+
|
1
28
|
const { createProxyCache } = require('@mojaloop/inter-scheme-proxy-cache-lib')
|
2
|
-
const {
|
29
|
+
const { RedisProxyCache } = require('@mojaloop/inter-scheme-proxy-cache-lib/dist/lib/storages/RedisProxyCache')
|
30
|
+
const config = require('#src/lib/config')
|
3
31
|
const fixtures = require('../../../fixtures')
|
4
32
|
const { ProxyApiClient } = require('../../../util')
|
5
|
-
const
|
33
|
+
const { PAYER_DFSP, PARTY_ID_TYPE, PROXY_NAME } = require('../../constants')
|
6
34
|
|
7
35
|
const wait = (sec) => new Promise(resolve => setTimeout(resolve, sec * 1000))
|
8
36
|
|
9
37
|
const CRON_TIMEOUT_SEC = 15 // see TIMEXP
|
10
38
|
|
39
|
+
jest.setTimeout(60_000)
|
40
|
+
|
11
41
|
describe('Timeout Handler', () => {
|
12
42
|
const { type, proxyConfig } = config.PROXY_CACHE_CONFIG
|
13
43
|
const proxyCache = createProxyCache(type, proxyConfig)
|
14
44
|
const proxyClient = new ProxyApiClient()
|
15
45
|
|
46
|
+
const checkKeysExistence = async (keys) => Promise.all(keys.map(key => proxyCache.redisClient.exists(key)))
|
47
|
+
|
16
48
|
beforeAll(async () => {
|
17
49
|
await proxyCache.connect()
|
18
50
|
const redisClient = proxyCache.redisClient
|
@@ -22,6 +54,11 @@ describe('Timeout Handler', () => {
|
|
22
54
|
}))
|
23
55
|
})
|
24
56
|
|
57
|
+
beforeEach(async () => {
|
58
|
+
const history = await proxyClient.deleteHistory()
|
59
|
+
expect(history).toEqual([])
|
60
|
+
})
|
61
|
+
|
25
62
|
afterAll(async () => {
|
26
63
|
return Promise.all([
|
27
64
|
proxyClient.deleteHistory(),
|
@@ -29,47 +66,65 @@ describe('Timeout Handler', () => {
|
|
29
66
|
])
|
30
67
|
})
|
31
68
|
|
32
|
-
it('
|
33
|
-
|
34
|
-
expect(history).toEqual([])
|
35
|
-
|
36
|
-
// send a couple of keys to redis
|
37
|
-
const partyIds = ['1234567', '7654321']
|
38
|
-
const keys = [
|
39
|
-
`'als:${PAYER_DFSP}:${PARTY_ID_TYPE}:${partyIds[0]}:expiresAt'`,
|
40
|
-
`'als:${PAYER_DFSP}:${PARTY_ID_TYPE}:${partyIds[1]}:expiresAt'`
|
41
|
-
]
|
69
|
+
it('should pass timeoutInterschemePartiesLookups flow', async () => {
|
70
|
+
const partyIds = [`isp1-${Date.now()}`, `isp2-${Date.now()}`]
|
42
71
|
const proxies = [PROXY_NAME]
|
43
72
|
const alsReq1 = fixtures.mockAlsRequestDto(PAYER_DFSP, PARTY_ID_TYPE, partyIds[0])
|
44
73
|
const alsReq2 = fixtures.mockAlsRequestDto(PAYER_DFSP, PARTY_ID_TYPE, partyIds[1])
|
74
|
+
const keys = [
|
75
|
+
RedisProxyCache.formatAlsCacheExpiryKey(alsReq1),
|
76
|
+
RedisProxyCache.formatAlsCacheExpiryKey(alsReq2)
|
77
|
+
]
|
78
|
+
// send a couple of keys to redis
|
45
79
|
const results = await Promise.all([
|
46
80
|
proxyCache.setSendToProxiesList(alsReq1, proxies, CRON_TIMEOUT_SEC),
|
47
81
|
proxyCache.setSendToProxiesList(alsReq2, proxies, CRON_TIMEOUT_SEC)
|
48
82
|
])
|
49
|
-
expect(results
|
83
|
+
expect(results).toEqual([true, true])
|
84
|
+
expect(await checkKeysExistence(keys)).toEqual([1, 1])
|
50
85
|
|
51
86
|
// wait for the timeout handler to process the keys
|
52
87
|
await wait(CRON_TIMEOUT_SEC * 1.5)
|
88
|
+
const history = await proxyClient.waitForNHistoryCalls(2)
|
53
89
|
|
54
90
|
// check that the keys are no longer in redis
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
// check that the callbacks are sent and received at the FSP
|
59
|
-
// for test resilience, we will retry the history check a few times
|
60
|
-
const retryMaxCount = 20
|
61
|
-
const retryIntervalSec = 2
|
62
|
-
let retryCount = 0
|
63
|
-
|
64
|
-
while (history.length < 2 && retryCount < retryMaxCount) {
|
65
|
-
await wait(retryIntervalSec)
|
66
|
-
history = await proxyClient.getHistory()
|
67
|
-
retryCount++
|
68
|
-
}
|
91
|
+
expect(await checkKeysExistence(keys)).toEqual([0, 0])
|
92
|
+
|
69
93
|
expect(history.length).toBe(2)
|
70
94
|
const path0 = history.find(h => h.path.includes(partyIds[0])).path
|
71
95
|
const path1 = history.find(h => h.path.includes(partyIds[1])).path
|
72
96
|
expect(path0).toBe(`/parties/${PARTY_ID_TYPE}/${partyIds[0]}/error`)
|
73
97
|
expect(path1).toBe(`/parties/${PARTY_ID_TYPE}/${partyIds[1]}/error`)
|
74
|
-
}
|
98
|
+
})
|
99
|
+
|
100
|
+
it('should pass timeoutProxyGetPartiesLookups flow', async () => {
|
101
|
+
const partyId1 = `pgp1-${Date.now()}`
|
102
|
+
const partyId2 = `pgp2-${Date.now()}`
|
103
|
+
const alsReq1 = fixtures.mockAlsRequestDto(PAYER_DFSP, PARTY_ID_TYPE, partyId1)
|
104
|
+
const alsReq2 = fixtures.mockAlsRequestDto(PAYER_DFSP, PARTY_ID_TYPE, partyId2)
|
105
|
+
const keys = [
|
106
|
+
RedisProxyCache.formatProxyGetPartiesExpiryKey(alsReq1, PROXY_NAME),
|
107
|
+
RedisProxyCache.formatProxyGetPartiesExpiryKey(alsReq2, PROXY_NAME)
|
108
|
+
]
|
109
|
+
// send a couple of keys to redis
|
110
|
+
const results = await Promise.all([
|
111
|
+
proxyCache.setProxyGetPartiesTimeout(alsReq1, PROXY_NAME, CRON_TIMEOUT_SEC),
|
112
|
+
proxyCache.setProxyGetPartiesTimeout(alsReq2, PROXY_NAME, CRON_TIMEOUT_SEC)
|
113
|
+
])
|
114
|
+
expect(results).toEqual([true, true])
|
115
|
+
expect(await checkKeysExistence(keys)).toEqual([1, 1])
|
116
|
+
|
117
|
+
// wait for the timeout handler to process the keys
|
118
|
+
await wait(CRON_TIMEOUT_SEC * 1.5)
|
119
|
+
const history = await proxyClient.waitForNHistoryCalls(2)
|
120
|
+
|
121
|
+
// check that the keys are no longer in redis
|
122
|
+
expect(await checkKeysExistence(keys)).toEqual([0, 0])
|
123
|
+
|
124
|
+
expect(history.length).toBe(2)
|
125
|
+
const path1 = history.find(h => h.path.includes(partyId1)).path
|
126
|
+
const path2 = history.find(h => h.path.includes(partyId2)).path
|
127
|
+
expect(path1).toBe(`/parties/${PARTY_ID_TYPE}/${partyId1}/error`)
|
128
|
+
expect(path2).toBe(`/parties/${PARTY_ID_TYPE}/${partyId2}/error`)
|
129
|
+
})
|
75
130
|
})
|