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.
Files changed (34) hide show
  1. package/CHANGELOG.md +0 -7
  2. package/package.json +8 -5
  3. package/src/constants.js +34 -2
  4. package/src/domain/parties/deps.js +11 -4
  5. package/src/domain/parties/getPartiesByTypeAndID.js +9 -13
  6. package/src/domain/parties/partiesUtils.js +4 -34
  7. package/src/domain/parties/putParties.js +26 -71
  8. package/src/domain/parties/services/BasePartiesService.js +143 -15
  9. package/src/domain/parties/services/GetPartiesService.js +194 -164
  10. package/src/domain/parties/services/PutPartiesErrorService.js +51 -27
  11. package/src/domain/parties/services/PutPartiesService.js +51 -33
  12. package/src/domain/parties/services/TimeoutPartiesService.js +84 -0
  13. package/src/domain/parties/services/index.js +3 -1
  14. package/src/domain/timeout/createSpan.js +55 -0
  15. package/src/domain/timeout/index.js +27 -36
  16. package/src/handlers/TimeoutHandler.js +2 -2
  17. package/src/lib/util.js +11 -3
  18. package/test/fixtures/index.js +46 -0
  19. package/test/integration/domain/timeout/index.test.js +83 -28
  20. package/test/unit/domain/parties/parties.test.js +26 -18
  21. package/test/unit/domain/parties/partiesUtils.test.js +51 -0
  22. package/test/unit/domain/parties/services/BasePartiesService.test.js +71 -0
  23. package/test/unit/domain/parties/services/GetPartiesService.test.js +245 -0
  24. package/test/unit/domain/parties/services/PutPartiesErrorService.test.js +50 -0
  25. package/test/unit/domain/parties/services/TimeoutPartiesService.test.js +72 -0
  26. package/test/unit/domain/parties/services/deps.js +51 -0
  27. package/test/unit/domain/timeout/index.test.js +17 -12
  28. package/test/util/apiClients/BasicApiClient.js +33 -6
  29. package/test/util/apiClients/ProxyApiClient.js +46 -1
  30. package/test/util/index.js +5 -6
  31. package/test/util/mockDeps.js +72 -0
  32. package/src/domain/timeout/dto.js +0 -54
  33. package/test/unit/domain/parties/utils.test.js +0 -60
  34. 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
- // async handleRequest () {
36
- // // todo: add impl.
37
- // }
38
-
39
- async validateSourceParticipant ({ source, proxy }) {
40
- this.deps.stepState.inProgress('validateSourceParticipant-1')
41
- const requesterParticipant = await super.validateParticipant(source)
42
-
43
- if (!requesterParticipant) {
44
- if (!this.proxyEnabled || !proxy) {
45
- const errMessage = ERROR_MESSAGES.sourceFspNotFound
46
- this.log.warn(`${errMessage} and no proxy`, { source })
47
- throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, errMessage)
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
- const errMessage = 'failed to addDfspIdToProxyMapping'
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
- this.log.info('addDfspIdToProxyMapping is done', { source, proxy })
63
+ log.info('addDfspIdToProxyMapping is done', { source, proxy })
57
64
  }
58
65
  }
59
66
 
60
- async checkProxySuccessResponse ({ destination, source, headers, params }) {
61
- if (this.proxyEnabled) {
62
- this.deps.stepState.inProgress('checkProxySuccessResponse-2')
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
- await this.#updateOracleWithParticipantMapping({ source, headers, params })
75
+ if (!isExists) {
76
+ this.log.verbose('NOT inter-scheme receivedSuccessResponse case')
77
+ await this.removeProxyGetPartiesTimeoutCache(alsReq)
68
78
  return
69
79
  }
70
- this.log.warn('destination is NOT in scheme, and no cached sendToProxiesList', { destination, alsReq })
71
- // todo: think, if we need to throw an error here
80
+
81
+ const schemeParticipant = await super.validateParticipant(destination)
82
+ if (schemeParticipant) {
83
+ await this.#updateOracleWithParticipantMapping({ source, headers, params })
84
+ }
72
85
  }
73
86
  }
74
87
 
75
- async identifyDestinationForSuccessCallback (destination) {
76
- this.deps.stepState.inProgress('validateDestinationParticipant-4')
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
- return destinationParticipant.name
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
- return proxyName
103
+
104
+ this.state.requester = proxyName
89
105
  }
90
106
 
91
- async sendSuccessCallback ({ sendTo, headers, params, dataUri }) {
92
- this.deps.stepState.inProgress('#sendSuccessCallback-6')
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 done', { sendTo, payload })
118
+ this.log.verbose('sendSuccessCallback is sent', { sendTo, payload })
101
119
  }
102
120
 
103
121
  async #updateOracleWithParticipantMapping ({ source, headers, params }) {
104
- this.deps.stepState.inProgress('#updateOracleWithParticipantMapping-3')
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
- Factory: { createFSPIOPError, reformatFSPIOPError },
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 util = require('../../lib/util')
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
- return proxyCache.processExpiredAlsKeys(sendTimeoutCallback, batchSize)
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 sendTimeoutCallback = async (cacheKey) => {
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
- let step
62
- const [, destination, partyType, partyId] = cacheKey.split(':')
63
- const { errorInformation, params, headers, endpointType, span } = await timeoutCallbackDto({ destination, partyId, partyType })
64
- logger.debug('sendTimeoutCallback details:', { destination, partyType, partyId, cacheKey })
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
- step = 'validateParticipant-1'
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
- logger.warn('error in sendTimeoutCallback: ', err)
74
+ log.warn('error in sendTimeoutCallback: ', err)
75
75
  histTimerEnd({ success: false })
76
76
  const fspiopError = reformatFSPIOPError(err)
77
- util.countFspiopError(fspiopError, { operation: 'sendTimeoutCallback', step })
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, Hapi } = require('@mojaloop/central-services-shared').Util
32
+ const { HeaderValidation } = require('@mojaloop/central-services-shared').Util
33
33
  const rethrow = require('@mojaloop/central-services-shared').Util.rethrow.with('ALS')
34
34
 
35
35
  const Config = require('../lib/config')
36
+ const { API_TYPES } = require('../constants')
36
37
  const { logger } = require('./index')
37
38
 
38
39
  const getSpanTags = ({ headers }, transactionType, transactionAction) => {
@@ -69,7 +70,7 @@ const pathForInterface = ({ isAdmin, isMockInterface }) => {
69
70
  if (isMockInterface) {
70
71
  apiFile = 'api_swagger.json'
71
72
  } else {
72
- apiFile = Config.API_TYPE === Hapi.API_TYPES.iso20022
73
+ apiFile = Config.API_TYPE === API_TYPES.iso20022
73
74
  ? 'api-swagger-iso20022-parties.yaml'
74
75
  : 'api-swagger.yaml'
75
76
  }
@@ -100,7 +101,14 @@ const countFspiopError = (error, options) => {
100
101
  rethrow.countFspiopError(error, options)
101
102
  }
102
103
 
103
- // todo: think better name
104
+ /**
105
+ * An immutable object representing the step state
106
+ * @typedef {Object} StepState
107
+ * @property {string} step - The current step value (read-only getter property)
108
+ * @property {(string) => void} inProgress - Method to update the current step
109
+ */
110
+
111
+ /** @returns {StepState} */
104
112
  const initStepState = (initStep = 'start') => {
105
113
  let step = initStep
106
114
  return Object.freeze({
@@ -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 { PAYER_DFSP, PARTY_ID_TYPE, PROXY_NAME } = require('../../constants')
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 config = require('../../../../src/lib/config')
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('test', async () => {
33
- let history = await proxyClient.deleteHistory()
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.includes(false)).toBe(false)
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
- const exists = await Promise.all(keys.map(key => proxyCache.redisClient.exists(key)))
56
- expect(exists.includes(1)).toBe(false)
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
- }, 60_000)
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
  })