account-lookup-service 15.5.0-iso.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (209) hide show
  1. package/.circleci/config.yml +11 -0
  2. package/.ncurc.yaml +6 -0
  3. package/.nvmrc +1 -0
  4. package/.nycrc.yml +20 -0
  5. package/.versionrc +15 -0
  6. package/CHANGELOG.md +330 -0
  7. package/CODEOWNERS +38 -0
  8. package/Dockerfile +45 -0
  9. package/LICENSE.md +10 -0
  10. package/README.md +252 -0
  11. package/audit-ci.jsonc +32 -0
  12. package/audit-resolve.json +161 -0
  13. package/config/default.json +109 -0
  14. package/config/knexfile.js +21 -0
  15. package/docker/account-lookup-service/default.json +106 -0
  16. package/docker/account-lookup-service/make-default-json.sh +5 -0
  17. package/docker/account-lookup-service/override.json +15 -0
  18. package/docker/central-ledger/default.json +458 -0
  19. package/docker/config-modifier/account-lookup-service.js +31 -0
  20. package/docker/kafka/consumer.properties +26 -0
  21. package/docker/kafka/producer.properties +45 -0
  22. package/docker/kafka/server.properties +143 -0
  23. package/docker/kafka/tools-log4j.properties +21 -0
  24. package/docker/mock-proxy/Dockerfile +15 -0
  25. package/docker/mock-proxy/package-lock.json +4986 -0
  26. package/docker/mock-proxy/package.json +24 -0
  27. package/docker/mock-proxy/src/config.ts +14 -0
  28. package/docker/mock-proxy/src/server.ts +94 -0
  29. package/docker/mock-proxy/src/utils.ts +29 -0
  30. package/docker/mock-proxy/tsconfig.json +24 -0
  31. package/docker/sql-init/01_permissions.sql +2 -0
  32. package/docker/sql-init-central-ledger/01_permissions.sql +2 -0
  33. package/docker/wait-for/wait-for-account-lookup-service.sh +10 -0
  34. package/docker/wait-for/wait-for-central-ledger.sh +11 -0
  35. package/docker/wait-for/wait-for-kafka.sh +7 -0
  36. package/docker/wait-for/wait-for-ml-api-adapter.sh +9 -0
  37. package/docker/wait-for/wait-for-mockserver.sh +20 -0
  38. package/docker/wait-for/wait-for-mysql-als.sh +14 -0
  39. package/docker/wait-for/wait-for-mysql-central-ledger.sh +11 -0
  40. package/docker/wait-for/wait-for-mysql.sh +11 -0
  41. package/docker/wait-for/wait-for-objstore.sh +12 -0
  42. package/docker/wait-for/wait-for.env +18 -0
  43. package/docker/wait-for/wait-for.sh +81 -0
  44. package/docker-compose.integration.yml +29 -0
  45. package/docker-compose.yml +243 -0
  46. package/jest-int.config.js +8 -0
  47. package/jest.config.js +16 -0
  48. package/jsdoc.json +38 -0
  49. package/migrations/01_currency.js +42 -0
  50. package/migrations/02_endpointType.js +43 -0
  51. package/migrations/03_endpointType-indexes.js +37 -0
  52. package/migrations/04_partyIdType.js +43 -0
  53. package/migrations/05_partyIdType-indexes.js +38 -0
  54. package/migrations/08_oracleEndpoint.js +51 -0
  55. package/migrations/09_oracleEndpoint-indexes.js +41 -0
  56. package/migrations/10_oracleEndpoint-remove-constraints.js +38 -0
  57. package/package.json +180 -0
  58. package/scripts/_wait4_all.js +143 -0
  59. package/scripts/test-functional.sh +76 -0
  60. package/secrets/jwsSigningKey.key +27 -0
  61. package/seeds/currency.js +765 -0
  62. package/seeds/endpointType.js +65 -0
  63. package/seeds/partyIdType.js +79 -0
  64. package/src/api/endpointcache.js +67 -0
  65. package/src/api/health.js +66 -0
  66. package/src/api/index.js +85 -0
  67. package/src/api/oracles/{ID}.js +100 -0
  68. package/src/api/oracles.js +96 -0
  69. package/src/api/participants/{ID}/error.js +44 -0
  70. package/src/api/participants/{ID}.js +44 -0
  71. package/src/api/participants/{Type}/{ID}/error.js +74 -0
  72. package/src/api/participants/{Type}/{ID}/{SubId}/error.js +68 -0
  73. package/src/api/participants/{Type}/{ID}/{SubId}.js +113 -0
  74. package/src/api/participants/{Type}/{ID}.js +133 -0
  75. package/src/api/participants.js +63 -0
  76. package/src/api/parties/{Type}/{ID}/error.js +66 -0
  77. package/src/api/parties/{Type}/{ID}/{SubId}/error.js +56 -0
  78. package/src/api/parties/{Type}/{ID}/{SubId}.js +77 -0
  79. package/src/api/parties/{Type}/{ID}.js +98 -0
  80. package/src/api/routes.js +294 -0
  81. package/src/constants.js +16 -0
  82. package/src/domain/oracle/index.js +33 -0
  83. package/src/domain/oracle/oracle.js +234 -0
  84. package/src/domain/participants/index.js +35 -0
  85. package/src/domain/participants/participants.js +560 -0
  86. package/src/domain/parties/getPartiesByTypeAndID.js +239 -0
  87. package/src/domain/parties/index.js +32 -0
  88. package/src/domain/parties/parties.js +215 -0
  89. package/src/domain/parties/utils.js +84 -0
  90. package/src/domain/timeout/dto.js +48 -0
  91. package/src/domain/timeout/index.js +104 -0
  92. package/src/handlers/TimeoutHandler.js +94 -0
  93. package/src/handlers/index.js +70 -0
  94. package/src/handlers/monitoring/index.js +51 -0
  95. package/src/handlers/monitoring/plugins/health.js +61 -0
  96. package/src/handlers/monitoring/plugins/metrics.js +48 -0
  97. package/src/handlers/register.js +102 -0
  98. package/src/index.js +66 -0
  99. package/src/interface/admin-swagger.yaml +804 -0
  100. package/src/interface/admin_swagger.json +959 -0
  101. package/src/interface/api-swagger-iso20022-parties.yaml +1734 -0
  102. package/src/interface/api-swagger.yaml +1733 -0
  103. package/src/interface/api_swagger.json +3046 -0
  104. package/src/interface/fspiop-rest-v2.0-ISO20022_parties.yaml +2256 -0
  105. package/src/interface/thirdparty/admin-swagger.yaml +808 -0
  106. package/src/interface/thirdparty/admin_swagger.json +961 -0
  107. package/src/interface/thirdparty/api-swagger.yaml +1739 -0
  108. package/src/interface/thirdparty/api_swagger.json +3142 -0
  109. package/src/lib/argv.js +39 -0
  110. package/src/lib/cache.js +126 -0
  111. package/src/lib/config.js +183 -0
  112. package/src/lib/db.js +26 -0
  113. package/src/lib/headers.js +53 -0
  114. package/src/lib/healthCheck/subServiceHealth.js +84 -0
  115. package/src/lib/index.js +11 -0
  116. package/src/lib/migrator.js +17 -0
  117. package/src/lib/requestLogger.js +54 -0
  118. package/src/lib/util.js +66 -0
  119. package/src/metrics/handler.js +33 -0
  120. package/src/metrics/plugin.js +52 -0
  121. package/src/metrics/routes.js +43 -0
  122. package/src/models/currency/currency.js +48 -0
  123. package/src/models/currency/index.js +32 -0
  124. package/src/models/endpointType/endpointType.js +48 -0
  125. package/src/models/endpointType/index.js +32 -0
  126. package/src/models/misc/migrationLock.js +49 -0
  127. package/src/models/oracle/facade.js +341 -0
  128. package/src/models/oracle/index.js +41 -0
  129. package/src/models/oracle/oracleEndpoint.js +192 -0
  130. package/src/models/oracle/oracleEndpointCached.js +108 -0
  131. package/src/models/participantEndpoint/facade.js +238 -0
  132. package/src/models/partyIdType/index.js +32 -0
  133. package/src/models/partyIdType/partyIdType.js +41 -0
  134. package/src/plugins.js +139 -0
  135. package/src/server.js +199 -0
  136. package/test/fixtures/index.js +131 -0
  137. package/test/fixtures/iso.js +110 -0
  138. package/test/integration/.env +8 -0
  139. package/test/integration/api/parties.test.js +137 -0
  140. package/test/integration/constants.js +20 -0
  141. package/test/integration/domain/oracle/index.test.js +324 -0
  142. package/test/integration/domain/timeout/index.test.js +75 -0
  143. package/test/integration/env.sh +15 -0
  144. package/test/integration/example.test.js +12 -0
  145. package/test/integration/models/currency/currency.test.js +68 -0
  146. package/test/integration/plugins.test.js +62 -0
  147. package/test/integration/prepareTestParticipants.js +30 -0
  148. package/test/integration/setup.js +5 -0
  149. package/test/integration-config.json +81 -0
  150. package/test/integration-runner.sh +108 -0
  151. package/test/unit/api/health.test.js +142 -0
  152. package/test/unit/api/oracles/{ID}.test.js +264 -0
  153. package/test/unit/api/oracles.test.js +173 -0
  154. package/test/unit/api/participants/participants.test.js +117 -0
  155. package/test/unit/api/participants/{Type}/{ID}/error.test.js +155 -0
  156. package/test/unit/api/participants/{Type}/{ID}/{SubId}/error.test.js +131 -0
  157. package/test/unit/api/participants/{Type}/{ID}/{SubId}.test.js +377 -0
  158. package/test/unit/api/participants/{Type}/{ID}.test.js +383 -0
  159. package/test/unit/api/participants.test.js +108 -0
  160. package/test/unit/api/parties/endpointcache.test.js +83 -0
  161. package/test/unit/api/parties/parties.test.js +102 -0
  162. package/test/unit/api/parties/{Type}/{ID}/error.test.js +145 -0
  163. package/test/unit/api/parties/{Type}/{ID}/{SubId}/error.test.js +141 -0
  164. package/test/unit/api/parties/{Type}/{ID}/{SubId}.test.js +241 -0
  165. package/test/unit/api/parties/{Type}/{ID}.test.js +240 -0
  166. package/test/unit/domain/oracle/oracle.test.js +505 -0
  167. package/test/unit/domain/participants/participants.test.js +1724 -0
  168. package/test/unit/domain/parties/parties.test.js +940 -0
  169. package/test/unit/domain/timeout/dto.test.js +28 -0
  170. package/test/unit/domain/timeout/index.test.js +81 -0
  171. package/test/unit/handlers/TimeoutHandler.test.js +125 -0
  172. package/test/unit/handlers/index.test.js +56 -0
  173. package/test/unit/handlers/register.test.js +90 -0
  174. package/test/unit/index.test.js +139 -0
  175. package/test/unit/iso20022/partiesValidation.test.js +129 -0
  176. package/test/unit/lib/TransformFacades.test.js +18 -0
  177. package/test/unit/lib/argv.test.js +40 -0
  178. package/test/unit/lib/cache.test.js +172 -0
  179. package/test/unit/lib/config.test.js +108 -0
  180. package/test/unit/lib/healthCheck/subServiceHealth.test.js +89 -0
  181. package/test/unit/lib/migrator.test.js +52 -0
  182. package/test/unit/lib/requestLogger.test.js +115 -0
  183. package/test/unit/lib/util.test.js +68 -0
  184. package/test/unit/mocks.js +66 -0
  185. package/test/unit/models/currency/currency.test.js +91 -0
  186. package/test/unit/models/endpointType/endpointType.test.js +69 -0
  187. package/test/unit/models/misc/migrationLock.test.js +96 -0
  188. package/test/unit/models/oracle/facade.test.js +546 -0
  189. package/test/unit/models/oracle/oracleEndpoint.test.js +409 -0
  190. package/test/unit/models/oracle/oracleEndpointCached.test.js +153 -0
  191. package/test/unit/models/participantEndpoint/facade.test.js +295 -0
  192. package/test/unit/models/partyIdType/partyIdType.test.js +88 -0
  193. package/test/unit/plugins.test.js +89 -0
  194. package/test/unit/setup.js +7 -0
  195. package/test/util/apiClients/AlsApiClient.js +44 -0
  196. package/test/util/apiClients/BasicApiClient.js +34 -0
  197. package/test/util/apiClients/ProxyApiClient.js +25 -0
  198. package/test/util/apiClients/index.js +7 -0
  199. package/test/util/helper.js +332 -0
  200. package/test/util/index.js +11 -0
  201. package/test/util/mockgen.js +43 -0
  202. package/test/util/onboarding.js +132 -0
  203. package/test/util/scripts/addAlsDb.sh +33 -0
  204. package/test/util/scripts/configureMockServer.sh +35 -0
  205. package/test/util/scripts/env.sh +19 -0
  206. package/test/util/scripts/populateTestData.sh +62 -0
  207. package/test/util/scripts/startMockCentralServer.sh +45 -0
  208. package/test/util/scripts/startMockOracleServer.sh +45 -0
  209. package/test/util/testConfig.js +44 -0
@@ -0,0 +1,940 @@
1
+ /*****
2
+ License
3
+ --------------
4
+ Copyright © 2017 Bill & Melinda Gates Foundation
5
+ The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at
6
+ http://www.apache.org/licenses/LICENSE-2.0
7
+ Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
8
+ Contributors
9
+ --------------
10
+ This is the official list of the Mojaloop project contributors for this file.
11
+ Names of the original copyright holders (individuals or organizations)
12
+ should be listed with a '*' in the first column. People who have
13
+ contributed from an organization can be listed under the organization
14
+ that actually holds the copyright for their contributions (see the
15
+ Gates Foundation organization for an example). Those individuals should have
16
+ their names indented and be marked with a '-'. Email address can be added
17
+ optionally within square brackets <email>.
18
+ * Gates Foundation
19
+ - Name Surname <name.surname@gatesfoundation.com>
20
+
21
+ * ModusBox
22
+ - Rajiv Mothilal <rajiv.mothilal@modusbox.com>
23
+ - Steven Oderayi <steven.oderayi@modusbox.com>
24
+
25
+ * Crosslake
26
+ - Lewis Daly <lewisd@crosslaketech.com>
27
+
28
+ --------------
29
+ ******/
30
+
31
+ 'use strict'
32
+
33
+ const { randomUUID } = require('node:crypto')
34
+ const { setTimeout: sleep } = require('node:timers/promises')
35
+ const Sinon = require('sinon')
36
+ const { createProxyCache } = require('@mojaloop/inter-scheme-proxy-cache-lib')
37
+ const { Enum, Util } = require('@mojaloop/central-services-shared')
38
+ const { MojaloopApiErrorCodes } = require('@mojaloop/sdk-standard-components').Errors
39
+ const Logger = require('@mojaloop/central-services-logger')
40
+
41
+ const Config = require('../../../../src/lib/config')
42
+ const Db = require('../../../../src/lib/db')
43
+ const partiesDomain = require('../../../../src/domain/parties/parties')
44
+ const partiesUtils = require('../../../../src/domain/parties/utils')
45
+ const participant = require('../../../../src/models/participantEndpoint/facade')
46
+ const oracle = require('../../../../src/models/oracle/facade')
47
+ const oracleEndpointCached = require('../../../../src/models/oracle/oracleEndpointCached')
48
+ const libUtil = require('../../../../src/lib/util')
49
+ const { logger } = require('../../../../src/lib')
50
+ const { ERROR_MESSAGES } = require('../../../../src/constants')
51
+
52
+ const Helper = require('../../../util/helper')
53
+ const fixtures = require('../../../fixtures')
54
+
55
+ const { type: proxyCacheType, proxyConfig: proxyCacheConfig } = Config.PROXY_CACHE_CONFIG
56
+
57
+ const { encodePayload } = Util.StreamingProtocol
58
+ const { RestMethods } = Enum.Http
59
+
60
+ Logger.isDebugEnabled = jest.fn(() => true)
61
+ Logger.isErrorEnabled = jest.fn(() => true)
62
+ Logger.isInfoEnabled = jest.fn(() => true)
63
+ let sandbox
64
+
65
+ describe('Parties Tests', () => {
66
+ let proxyCache
67
+
68
+ beforeEach(async () => {
69
+ await Util.Endpoints.initializeCache(Config.CENTRAL_SHARED_ENDPOINT_CACHE_CONFIG, libUtil.hubNameConfig)
70
+ sandbox = Sinon.createSandbox()
71
+ sandbox.stub(Util.Request)
72
+ sandbox.stub(Util.Http, 'SwitchDefaultHeaders').returns(Helper.defaultSwitchHeaders)
73
+ sandbox.stub(Util.proxies)
74
+
75
+ Db.oracleEndpoint = {
76
+ query: sandbox.stub()
77
+ }
78
+ Db.from = (table) => {
79
+ return Db[table]
80
+ }
81
+ proxyCache = createProxyCache(proxyCacheType, proxyCacheConfig)
82
+ await proxyCache.connect()
83
+ })
84
+
85
+ afterEach(async () => {
86
+ await proxyCache.disconnect()
87
+ sandbox.restore()
88
+ })
89
+
90
+ describe('getPartiesByTypeAndID', () => {
91
+ beforeEach(() => {
92
+ sandbox.stub(participant)
93
+ Config.PROXY_CACHE_CONFIG.enabled = false
94
+ })
95
+
96
+ afterEach(() => {
97
+ sandbox.restore()
98
+ })
99
+
100
+ it('handles error case where destination header is missing', async () => {
101
+ expect.hasAssertions()
102
+ // Arrange
103
+ participant.validateParticipant = sandbox.stub().returns({})
104
+ sandbox.stub(oracle, 'oracleRequest').returns({
105
+ data: {
106
+ partyList: [
107
+ { fspId: 'fsp1' }
108
+ ]
109
+ }
110
+ })
111
+ participant.sendRequest = sandbox.stub().resolves()
112
+ const headers = {
113
+ accept: 'application/vnd.interoperability.participants+json;version=1',
114
+ 'content-type': 'application/vnd.interoperability.participants+json;version=1.1',
115
+ date: '2019-05-24 08:52:19',
116
+ 'fspiop-source': 'payerfsp'
117
+ }
118
+ const expectedHeaders = {
119
+ ...headers,
120
+ 'fspiop-source': 'payerfsp',
121
+ 'fspiop-destination': 'fsp1'
122
+ }
123
+
124
+ // Act
125
+ await partiesDomain.getPartiesByTypeAndID(headers, Helper.getByTypeIdRequest.params, Helper.getByTypeIdRequest.method, Helper.getByTypeIdRequest.query, Helper.mockSpan())
126
+
127
+ // Assert
128
+ const lastCallHeaderArgs = participant.sendRequest.getCall(0).args
129
+ expect(participant.sendRequest.callCount).toBe(1)
130
+ expect(lastCallHeaderArgs[0]).toStrictEqual(expectedHeaders)
131
+ expect(lastCallHeaderArgs[1]).toBe('fsp1')
132
+ })
133
+
134
+ it('forwards request to destination if specified in headers without consulting any oracles', async () => {
135
+ expect.hasAssertions()
136
+ // Arrange
137
+ participant.validateParticipant = sandbox.stub().returns({})
138
+ sandbox.stub(oracle, 'oracleRequest')
139
+ participant.sendRequest = sandbox.stub().resolves()
140
+
141
+ const headers = {
142
+ accept: 'application/vnd.interoperability.participants+json;version=1',
143
+ 'content-type': 'application/vnd.interoperability.participants+json;version=1.1',
144
+ date: '2019-05-24 08:52:19',
145
+ 'fspiop-source': 'payerfsp',
146
+ 'fspiop-destination': 'destfsp'
147
+ }
148
+
149
+ const expectedHeaders = {
150
+ ...headers,
151
+ 'fspiop-source': 'payerfsp',
152
+ 'fspiop-destination': 'destfsp'
153
+ }
154
+
155
+ // Act
156
+ await partiesDomain.getPartiesByTypeAndID(headers, Helper.getByTypeIdRequest.params, Helper.getByTypeIdRequest.method, Helper.getByTypeIdRequest.query, Helper.mockSpan())
157
+
158
+ // Assert
159
+ const lastCallHeaderArgs = participant.sendRequest.getCall(0).args
160
+ expect(participant.sendRequest.callCount).toBe(1)
161
+ expect(oracle.oracleRequest.callCount).toBe(0)
162
+ expect(lastCallHeaderArgs[0]).toStrictEqual(expectedHeaders)
163
+ expect(lastCallHeaderArgs[1]).toBe('destfsp')
164
+ })
165
+
166
+ it('should set source proxyMapping if source is not in scheme, and there is proxy-header', async () => {
167
+ Config.PROXY_CACHE_CONFIG.enabled = true
168
+ participant.validateParticipant = sandbox.stub()
169
+ .onFirstCall().resolves(null) // source
170
+ .onSecondCall().resolves({}) // proxy
171
+ const source = `source-${Date.now()}`
172
+ const proxy = `proxy-${Date.now()}`
173
+
174
+ let cached = await proxyCache.lookupProxyByDfspId(source)
175
+ expect(cached).toBe(null)
176
+
177
+ const headers = fixtures.partiesCallHeadersDto({ source, proxy })
178
+ const { params, method, query } = Helper.getByTypeIdRequest
179
+
180
+ await partiesDomain.getPartiesByTypeAndID(headers, params, method, query, Helper.mockSpan(), null, proxyCache)
181
+ await sleep(1000)
182
+
183
+ cached = await proxyCache.lookupProxyByDfspId(source)
184
+ expect(cached).toBe(proxy)
185
+ })
186
+
187
+ it('should send error callback if destination is not in the scheme, and not in proxyCache', async () => {
188
+ participant.validateParticipant = sandbox.stub()
189
+ .onFirstCall().resolves({}) // source
190
+ .onSecondCall().resolves(null) // destination
191
+ participant.sendRequest = sandbox.stub().resolves()
192
+ participant.sendErrorToParticipant = sandbox.stub().resolves()
193
+ sandbox.stub(oracle, 'oracleRequest')
194
+ const headers = fixtures.partiesCallHeadersDto()
195
+
196
+ await partiesDomain.getPartiesByTypeAndID(headers, Helper.getByTypeIdRequest.params, Helper.getByTypeIdRequest.method, Helper.getByTypeIdRequest.query, Helper.mockSpan(), null, proxyCache)
197
+
198
+ expect(participant.sendRequest.callCount).toBe(0)
199
+ expect(oracle.oracleRequest.callCount).toBe(0)
200
+ expect(participant.sendErrorToParticipant.callCount).toBe(1)
201
+
202
+ const { errorInformation } = participant.sendErrorToParticipant.getCall(0).args[2]
203
+ expect(errorInformation.errorCode).toBe('3200')
204
+ expect(errorInformation.errorDescription).toContain(ERROR_MESSAGES.partyDestinationFspNotFound)
205
+ })
206
+
207
+ it('should send request to proxy, if destination is not in the scheme, but has proxyMapping', async () => {
208
+ Config.PROXY_CACHE_CONFIG.enabled = true
209
+ participant.validateParticipant = sandbox.stub()
210
+ .onFirstCall().resolves({}) // source
211
+ .onSecondCall().resolves(null) // destination
212
+ participant.sendRequest = sandbox.stub().resolves()
213
+ participant.sendErrorToParticipant = sandbox.stub().resolves()
214
+
215
+ const destination = `destination-${Date.now()}`
216
+ const proxyId = `proxy-${Date.now()}`
217
+ await proxyCache.addDfspIdToProxyMapping(destination, proxyId)
218
+ const headers = fixtures.partiesCallHeadersDto({ destination })
219
+
220
+ await partiesDomain.getPartiesByTypeAndID(headers, Helper.getByTypeIdRequest.params, Helper.getByTypeIdRequest.method, Helper.getByTypeIdRequest.query, Helper.mockSpan(), null, proxyCache)
221
+
222
+ expect(participant.sendErrorToParticipant.callCount).toBe(0)
223
+ expect(participant.sendRequest.callCount).toBe(1)
224
+
225
+ const [proxyHeaders, proxyDestination] = participant.sendRequest.getCall(0).args
226
+ expect(proxyHeaders).toEqual(headers)
227
+ expect(proxyDestination).toEqual(proxyId)
228
+ })
229
+
230
+ it('handles error when sourceDfsp cannot be found (no fspiop-proxy in headers)', async () => {
231
+ expect.hasAssertions()
232
+ // Arrange
233
+ participant.validateParticipant = sandbox.stub().resolves(null)
234
+ participant.sendErrorToParticipant = sandbox.stub().resolves(null)
235
+ const loggerStub = sandbox.stub(logger.mlLogger, 'error')
236
+
237
+ // Act
238
+ await partiesDomain.getPartiesByTypeAndID(Helper.getByTypeIdRequest.headers, Helper.getByTypeIdRequest.params, Helper.getByTypeIdRequest.method, Helper.getByTypeIdRequest.query)
239
+
240
+ // Assert
241
+ expect(loggerStub.callCount).toBe(1)
242
+ expect(participant.sendErrorToParticipant.callCount).toBe(1)
243
+
244
+ const { errorInformation } = participant.sendErrorToParticipant.getCall(0).args[2]
245
+ expect(errorInformation.errorCode).toBe('3200')
246
+ expect(errorInformation.errorDescription).toContain(ERROR_MESSAGES.partySourceFspNotFound)
247
+ })
248
+
249
+ it('should send error callback, if proxy-header is present, but no proxy in the scheme', async () => {
250
+ Config.PROXY_CACHE_CONFIG.enabled = true
251
+ participant.validateParticipant = sandbox.stub().resolves(null)
252
+ participant.sendErrorToParticipant = sandbox.stub().resolves()
253
+ participant.sendRequest = sandbox.stub().resolves()
254
+ const proxy = `proxy-${Date.now()}`
255
+ const headers = fixtures.partiesCallHeadersDto({ proxy })
256
+
257
+ await partiesDomain.getPartiesByTypeAndID(headers, Helper.getByTypeIdRequest.params, Helper.getByTypeIdRequest.method, Helper.getByTypeIdRequest.query, null, null, proxyCache)
258
+
259
+ expect(participant.sendRequest.callCount).toBe(0)
260
+ expect(participant.sendErrorToParticipant.callCount).toBe(1)
261
+
262
+ const { errorInformation } = participant.sendErrorToParticipant.getCall(0).args[2]
263
+ expect(errorInformation.errorCode).toBe('3200')
264
+ expect(errorInformation.errorDescription).toContain(ERROR_MESSAGES.partyProxyNotFound)
265
+ })
266
+
267
+ it('handles error when `participant.validateParticipant()`cannot be found and `sendErrorToParticipant()` fails', async () => {
268
+ expect.hasAssertions()
269
+ // Arrange
270
+ participant.validateParticipant = sandbox.stub().returns({})
271
+ sandbox.stub(oracle, 'oracleRequest').returns({
272
+ data: {
273
+ partyList: [
274
+ { fspId: 'fsp1' }
275
+ ]
276
+ }
277
+ })
278
+ participant.sendRequest = sandbox.stub().throws(new Error('Error sending request'))
279
+ participant.sendErrorToParticipant = sandbox.stub().throws(new Error('Error sending Error'))
280
+ const loggerStub = sandbox.stub(logger.mlLogger, 'error')
281
+
282
+ const headers = {
283
+ accept: 'application/vnd.interoperability.participants+json;version=1',
284
+ 'content-type': 'application/vnd.interoperability.participants+json;version=1.1',
285
+ date: '2019-05-24 08:52:19',
286
+ 'fspiop-source': 'payerfsp',
287
+ // Also test the empty DESTINATION header case
288
+ 'fspiop-destination': null
289
+ }
290
+
291
+ // Act
292
+ await partiesDomain.getPartiesByTypeAndID(headers, Helper.getByTypeIdRequest.params, Helper.getByTypeIdRequest.method, Helper.getByTypeIdRequest.query)
293
+
294
+ // Assert
295
+ expect(participant.sendRequest.callCount).toBe(1)
296
+ expect(participant.sendErrorToParticipant.callCount).toBe(1)
297
+ expect(loggerStub.callCount).toBe(2)
298
+ })
299
+
300
+ it('handles error when SubId is supplied but `participant.validateParticipant()` cannot be found and `sendErrorToParticipant()` fails', async () => {
301
+ expect.hasAssertions()
302
+ // Arrange
303
+ participant.validateParticipant = sandbox.stub().returns({})
304
+ sandbox.stub(oracle, 'oracleRequest').returns({
305
+ data: {
306
+ partyList: [
307
+ { fspId: 'fsp1' }
308
+ ]
309
+ }
310
+ })
311
+ participant.sendRequest = sandbox.stub().throws(new Error('Error sending request'))
312
+ participant.sendErrorToParticipant = sandbox.stub().throws(new Error('Error sending Error'))
313
+ sandbox.stub(Logger)
314
+
315
+ const headers = {
316
+ accept: 'application/vnd.interoperability.participants+json;version=1',
317
+ 'content-type': 'application/vnd.interoperability.participants+json;version=1.1',
318
+ date: '2019-05-24 08:52:19',
319
+ 'fspiop-source': 'payerfsp'
320
+ }
321
+ const params = { ...Helper.getByTypeIdRequest.params, SubId: 'subId' }
322
+ const expectedErrorCallbackEnpointType = Enum.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_PUT_ERROR
323
+
324
+ // Act
325
+ await partiesDomain.getPartiesByTypeAndID(headers, params, Helper.getByTypeIdRequest.method, Helper.getByTypeIdRequest.query, Helper.mockSpan())
326
+
327
+ // Assert
328
+ const firstCallArgs = participant.sendErrorToParticipant.getCall(0).args
329
+ expect(firstCallArgs[1]).toBe(expectedErrorCallbackEnpointType)
330
+ })
331
+
332
+ it('ensures sendRequest is called with the right endpoint type when no SubId is supplied', async () => {
333
+ expect.hasAssertions()
334
+ // Arrange
335
+ participant.validateParticipant = sandbox.stub().returns({})
336
+ sandbox.stub(oracle, 'oracleRequest').returns({
337
+ data: {
338
+ partyList: [
339
+ { fspId: 'fsp1' }
340
+ ]
341
+ }
342
+ })
343
+ participant.sendRequest = sandbox.stub().resolves()
344
+ const headers = {
345
+ accept: 'application/vnd.interoperability.participants+json;version=1',
346
+ 'content-type': 'application/vnd.interoperability.participants+json;version=1.1',
347
+ date: '2019-05-24 08:52:19',
348
+ 'fspiop-source': 'payerfsp'
349
+ }
350
+ const params = { ...Helper.getByTypeIdRequest.params }
351
+ const expectedCallbackEnpointType = Enum.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_GET
352
+
353
+ // Act
354
+ await partiesDomain.getPartiesByTypeAndID(headers, params, Helper.getByTypeIdRequest.method, Helper.getByTypeIdRequest.query)
355
+
356
+ // Assert
357
+ const firstCallArgs = participant.sendRequest.getCall(0).args
358
+ expect(participant.sendRequest.callCount).toBe(1)
359
+ expect(firstCallArgs[2]).toBe(expectedCallbackEnpointType)
360
+ })
361
+
362
+ it('ensures sendRequest is called with the right endpoint type when SubId is supplied', async () => {
363
+ expect.hasAssertions()
364
+ // Arrange
365
+ participant.validateParticipant = sandbox.stub().returns({})
366
+ sandbox.stub(oracle, 'oracleRequest').returns({
367
+ data: {
368
+ partyList: [
369
+ {
370
+ fspId: 'fsp1',
371
+ partySubIdOrType: 'subId'
372
+ }
373
+ ]
374
+ }
375
+ })
376
+ participant.sendRequest = sandbox.stub().resolves()
377
+ const headers = {
378
+ accept: 'application/vnd.interoperability.participants+json;version=1',
379
+ 'content-type': 'application/vnd.interoperability.participants+json;version=1.1',
380
+ date: '2019-05-24 08:52:19',
381
+ 'fspiop-source': 'payerfsp'
382
+ }
383
+ const params = { ...Helper.getByTypeIdRequest.params, SubId: 'subId' }
384
+ const expectedCallbackEnpointType = Enum.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_GET
385
+
386
+ // Act
387
+ await partiesDomain.getPartiesByTypeAndID(headers, params, Helper.getByTypeIdRequest.method, Helper.getByTypeIdRequest.query)
388
+
389
+ // Assert
390
+ const firstCallArgs = participant.sendRequest.getCall(0).args
391
+ expect(participant.sendRequest.callCount).toBe(1)
392
+ expect(firstCallArgs[2]).toBe(expectedCallbackEnpointType)
393
+ })
394
+
395
+ it('ensures sendErrorToParticipant is called with the right endpoint type when SubId is supplied is not matched', async () => {
396
+ expect.hasAssertions()
397
+ // Arrange
398
+ participant.validateParticipant = sandbox.stub().returns({})
399
+ sandbox.stub(oracle, 'oracleRequest').returns({
400
+ data: {
401
+ partyList: [
402
+ {
403
+ fspId: 'fsp1',
404
+ partySubIdOrType: 'subId'
405
+ }
406
+ ]
407
+ }
408
+ })
409
+ participant.sendRequest = sandbox.stub().resolves()
410
+ participant.sendErrorToParticipant = sandbox.stub().throws(new Error('Error sending Error'))
411
+
412
+ const headers = {
413
+ accept: 'application/vnd.interoperability.participants+json;version=1',
414
+ 'content-type': 'application/vnd.interoperability.participants+json;version=1.1',
415
+ date: '2019-05-24 08:52:19',
416
+ 'fspiop-source': 'payerfsp'
417
+ }
418
+ const params = { ...Helper.getByTypeIdRequest.params, SubId: 'subIdNOTFOUND' }
419
+ const expectedErrorCallbackEnpointType = Enum.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_PUT_ERROR
420
+
421
+ // Act
422
+ await partiesDomain.getPartiesByTypeAndID(headers, params, Helper.getByTypeIdRequest.method, Helper.getByTypeIdRequest.query)
423
+
424
+ // Assert
425
+ const firstCallArgs = participant.sendErrorToParticipant.getCall(0).args
426
+ expect(firstCallArgs[1]).toBe(expectedErrorCallbackEnpointType)
427
+ })
428
+
429
+ it('ensures sendRequest is called only once in FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_GET mode when oracle returns two records without SubId and with SubId', async () => {
430
+ expect.hasAssertions()
431
+ // Arrange
432
+ participant.validateParticipant = sandbox.stub().returns({})
433
+ sandbox.stub(oracle, 'oracleRequest').returns({
434
+ data: {
435
+ partyList: [
436
+ {
437
+ fspId: 'fsp1'
438
+ },
439
+ {
440
+ fspId: 'fsp1',
441
+ partySubIdOrType: 'subId'
442
+ }
443
+ ]
444
+ }
445
+ })
446
+ participant.sendRequest = sandbox.stub().resolves()
447
+ const headers = {
448
+ accept: 'application/vnd.interoperability.participants+json;version=1',
449
+ 'content-type': 'application/vnd.interoperability.participants+json;version=1.1',
450
+ date: '2019-05-24 08:52:19',
451
+ 'fspiop-source': 'payerfsp'
452
+ }
453
+ const params = { ...Helper.getByTypeIdRequest.params, SubId: 'subId' }
454
+ const expectedCallbackEnpointType = Enum.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_GET
455
+
456
+ // Act
457
+ await partiesDomain.getPartiesByTypeAndID(headers, params, Helper.getByTypeIdRequest.method, Helper.getByTypeIdRequest.query)
458
+
459
+ // Assert
460
+ const firstCallArgs = participant.sendRequest.getCall(0).args
461
+ expect(participant.sendRequest.callCount).toBe(1)
462
+ expect(firstCallArgs[2]).toBe(expectedCallbackEnpointType)
463
+ })
464
+
465
+ it('should send request to proxy if oracle returns dfsp NOT from the scheme', async () => {
466
+ Config.PROXY_CACHE_CONFIG.enabled = true
467
+ const proxyName = `proxy-${Date.now()}`
468
+ const fspId = `dfspNotFromScheme-${Date.now()}`
469
+ const oracleResponse = fixtures.oracleRequestResponseDto({
470
+ partyList: [{ fspId }]
471
+ })
472
+ sandbox.stub(oracle, 'oracleRequest').resolves(oracleResponse)
473
+ participant.validateParticipant = sandbox.stub()
474
+ .onFirstCall().resolves({}) // source
475
+ .onSecondCall().resolves(null) // oracle dfsp
476
+ participant.sendRequest = sandbox.stub().resolves()
477
+ participant.sendErrorToParticipant = sandbox.stub().resolves()
478
+
479
+ const isAdded = await proxyCache.addDfspIdToProxyMapping(fspId, proxyName)
480
+ expect(isAdded).toBe(true)
481
+
482
+ const headers = fixtures.partiesCallHeadersDto({ destination: '' })
483
+ const { params, method, query } = Helper.getByTypeIdRequest
484
+
485
+ await partiesDomain.getPartiesByTypeAndID(headers, params, method, query, null, null, proxyCache)
486
+
487
+ expect(participant.sendErrorToParticipant.callCount).toBe(0)
488
+ expect(participant.sendRequest.callCount).toBe(1)
489
+ const calledProxy = participant.sendRequest.getCall(0).args[1]
490
+ expect(calledProxy).toBe(proxyName)
491
+ })
492
+
493
+ it('handles error when `oracleRequest` returns no result', async () => {
494
+ expect.hasAssertions()
495
+ // Arrange
496
+ participant.validateParticipant = sandbox.stub().resolves({})
497
+ participant.sendErrorToParticipant = sandbox.stub().resolves(null)
498
+ oracle.oracleRequest = sandbox.stub().resolves(null)
499
+ sandbox.stub(Logger)
500
+ const expectedErrorCallbackEnpointType = Enum.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_PUT_ERROR
501
+
502
+ // Act
503
+ const headers = { ...Helper.getByTypeIdRequest.headers }
504
+ delete headers['fspiop-destination']
505
+
506
+ await partiesDomain.getPartiesByTypeAndID(headers, Helper.getByTypeIdRequest.params, Helper.getByTypeIdRequest.method, Helper.getByTypeIdRequest.query, Helper.mockSpan())
507
+
508
+ // Assert
509
+ const firstCallArgs = participant.sendErrorToParticipant.getCall(0).args
510
+ expect(firstCallArgs[1]).toBe(expectedErrorCallbackEnpointType)
511
+ })
512
+
513
+ it('should perform sendToProxies alsRequest, if no destination-header and no data in oracle response', async () => {
514
+ Config.PROXY_CACHE_CONFIG.enabled = true
515
+ const proxyNames = ['proxyA', 'proxyB']
516
+ Util.proxies.getAllProxiesNames = sandbox.stub().resolves(proxyNames)
517
+ oracle.oracleRequest = sandbox.stub().resolves(null)
518
+ participant.validateParticipant = sandbox.stub().resolves({})
519
+ participant.sendRequest = sandbox.stub().resolves()
520
+ participant.sendErrorToParticipant = sandbox.stub().resolves()
521
+
522
+ const source = `fromDfsp-${Date.now()}`
523
+ const destination = ''
524
+ const headers = fixtures.partiesCallHeadersDto({ source, destination })
525
+ const alsReq = partiesUtils.alsRequestDto(source, Helper.getByTypeIdRequest.params)
526
+
527
+ let isExists = await proxyCache.receivedSuccessResponse(alsReq) // no in cache
528
+ expect(isExists).toBe(false)
529
+
530
+ await partiesDomain.getPartiesByTypeAndID(headers, Helper.getByTypeIdRequest.params, Helper.getByTypeIdRequest.method, Helper.getByTypeIdRequest.query, Helper.mockSpan(), null, proxyCache)
531
+
532
+ isExists = await proxyCache.receivedSuccessResponse(alsReq)
533
+ expect(isExists).toBe(true)
534
+ expect(participant.sendErrorToParticipant.callCount).toBe(0)
535
+ expect(participant.sendRequest.callCount).toBe(proxyNames.length)
536
+ const calledProxies = participant.sendRequest.args.map(args => args[1])
537
+ expect(calledProxies).toEqual(proxyNames)
538
+ })
539
+
540
+ it('should send error callback, if no successful sendToProxiesList requests', async () => {
541
+ Config.PROXY_CACHE_CONFIG.enabled = true
542
+ const proxyNames = ['proxyA', 'proxyB']
543
+ Util.proxies.getAllProxiesNames = sandbox.stub().resolves(proxyNames)
544
+ oracle.oracleRequest = sandbox.stub().resolves(null)
545
+ participant.validateParticipant = sandbox.stub().resolves({})
546
+ participant.sendRequest = sandbox.stub().rejects(new Error('Some network issue'))
547
+ participant.sendErrorToParticipant = sandbox.stub().resolves()
548
+ const headers = fixtures.partiesCallHeadersDto({ destination: '' })
549
+
550
+ await partiesDomain.getPartiesByTypeAndID(headers, Helper.getByTypeIdRequest.params, Helper.getByTypeIdRequest.method, Helper.getByTypeIdRequest.query, Helper.mockSpan(), null, proxyCache)
551
+
552
+ expect(participant.sendRequest.callCount).toBe(proxyNames.length)
553
+ expect(participant.sendErrorToParticipant.callCount).toBe(1)
554
+
555
+ const { errorInformation } = participant.sendErrorToParticipant.getCall(0).args[2]
556
+ expect(errorInformation.errorCode).toBe('3200')
557
+ expect(errorInformation.errorDescription).toContain(ERROR_MESSAGES.proxyConnectionError)
558
+ })
559
+ })
560
+
561
+ describe('putPartiesByTypeAndID', () => {
562
+ beforeEach(() => {
563
+ sandbox.stub(participant)
564
+ Config.PROXY_CACHE_CONFIG.enabled = false
565
+ })
566
+
567
+ afterEach(() => {
568
+ sandbox.restore()
569
+ })
570
+
571
+ it('successfully sends the callback to the participant', async () => {
572
+ expect.hasAssertions()
573
+ // Arrange
574
+ participant.validateParticipant = sandbox.stub().resolves({
575
+ name: 'fsp1'
576
+ })
577
+ participant.sendRequest = sandbox.stub().resolves()
578
+ const payload = JSON.stringify({ testPayload: true })
579
+ const dataUri = encodePayload(payload, 'application/json')
580
+
581
+ // Act
582
+ await partiesDomain.putPartiesByTypeAndID(Helper.putByTypeIdRequest.headers, Helper.putByTypeIdRequest.params, 'put', payload, dataUri, null, proxyCache)
583
+
584
+ // Assert
585
+ expect(participant.sendRequest.callCount).toBe(1)
586
+ const sendRequestCallArgs = participant.sendRequest.getCall(0).args
587
+ expect(sendRequestCallArgs[1]).toStrictEqual('fsp1')
588
+ participant.sendRequest.reset()
589
+ })
590
+
591
+ it('successfully sends the callback to the participant when SubId is supplied', async () => {
592
+ expect.hasAssertions()
593
+ // Arrange
594
+ participant.validateParticipant = sandbox.stub().resolves({
595
+ data: {
596
+ name: 'fsp1'
597
+ }
598
+ })
599
+ participant.sendRequest = sandbox.stub().resolves()
600
+ const payload = {}
601
+ const params = { ...Helper.putByTypeIdRequest.params, SubId: 'subId' }
602
+ const expectedCallbackEnpointType = Enum.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_PUT
603
+
604
+ // Act
605
+ await partiesDomain.putPartiesByTypeAndID(Helper.putByTypeIdRequest.headers, params, 'put', payload, null, null, proxyCache)
606
+
607
+ // Assert
608
+ expect(participant.sendRequest.callCount).toBe(1)
609
+ const sendRequestCallArgs = participant.sendRequest.getCall(0).args
610
+ expect(sendRequestCallArgs[2]).toBe(expectedCallbackEnpointType)
611
+ })
612
+
613
+ it('handles error when `participant.validateParticipant()` returns no participant', async () => {
614
+ expect.hasAssertions()
615
+ // Arrange
616
+ const loggerStub = sandbox.stub(logger, 'error')
617
+ participant.sendErrorToParticipant = sandbox.stub().resolves()
618
+
619
+ const payload = JSON.stringify({ testPayload: true })
620
+ const dataUri = encodePayload(payload, 'application/json')
621
+ const { headers, params, method } = Helper.putByTypeIdRequest
622
+
623
+ // Act
624
+ await partiesDomain.putPartiesByTypeAndID(headers, params, method, payload, dataUri, proxyCache)
625
+
626
+ // Assert
627
+ expect(participant.sendErrorToParticipant.callCount).toBe(1)
628
+ const firstLoggerCallArgs = loggerStub.getCall(0).args
629
+ expect(firstLoggerCallArgs[1].message).toBe(ERROR_MESSAGES.partySourceFspNotFound)
630
+ loggerStub.reset()
631
+ participant.sendErrorToParticipant.reset()
632
+ })
633
+
634
+ it('should add proxyMapping, if source is not in scheme, and there is fspiop-proxy header', async () => {
635
+ Config.PROXY_CACHE_CONFIG.enabled = true
636
+ participant.validateParticipant = sandbox.stub().resolves(null)
637
+ participant.sendRequest = sandbox.stub().resolves()
638
+ participant.sendErrorToParticipant = sandbox.stub().resolves()
639
+
640
+ const source = `source-${Date.now()}`
641
+ let cachedProxy = await proxyCache.lookupProxyByDfspId(source)
642
+ expect(cachedProxy).toBeNull()
643
+
644
+ const proxy = `proxy-${Date.now()}`
645
+ const headers = fixtures.partiesCallHeadersDto({ source, proxy })
646
+ const payload = { test: true }
647
+ const dataUri = encodePayload(JSON.stringify(payload), 'application/json')
648
+ const { params, method } = Helper.putByTypeIdRequest
649
+
650
+ await partiesDomain.putPartiesByTypeAndID(headers, params, method, payload, dataUri, null, proxyCache)
651
+
652
+ cachedProxy = await proxyCache.lookupProxyByDfspId(source)
653
+ expect(cachedProxy).toBe(proxy)
654
+ })
655
+
656
+ it('should update oracle with partyDetails received from proxy, if previous alsReq is cached', async () => {
657
+ Config.PROXY_CACHE_CONFIG.enabled = true
658
+ const source = `source-${Date.now()}`
659
+ const destination = `payer-fsp-${Date.now()}`
660
+ const proxy = `proxy-${Date.now()}`
661
+
662
+ participant.validateParticipant = sandbox.stub()
663
+ .onFirstCall().resolves(null) // payee
664
+ .onSecondCall().resolves({ name: destination }) // payer
665
+ oracleEndpointCached.getOracleEndpointByType = sandbox.stub().resolves([
666
+ { value: 'http://oracle.endpoint' }
667
+ ])
668
+ oracle.oracleRequest = sandbox.stub().resolves()
669
+
670
+ const headers = fixtures.partiesCallHeadersDto({ source, destination, proxy })
671
+ const partyId = `testParty-${randomUUID()}`
672
+ const partyIdType = 'MSISDN'
673
+ const partyDetails = fixtures.putPartiesSuccessResponseDto({
674
+ partyId,
675
+ partyIdType,
676
+ fspId: source
677
+ })
678
+ const dataUri = encodePayload(JSON.stringify(partyDetails), 'application/json')
679
+ const params = { ID: partyId, Type: partyIdType }
680
+
681
+ const alsReq = fixtures.mockAlsRequestDto(destination, partyIdType, partyId)
682
+ const isSet = await proxyCache.setSendToProxiesList(alsReq, [proxy])
683
+ expect(isSet).toBe(true)
684
+
685
+ await partiesDomain.putPartiesByTypeAndID(headers, params, 'PUT', partyDetails, dataUri, null, proxyCache)
686
+ await sleep(1000)
687
+
688
+ expect(oracle.oracleRequest.callCount).toBe(1)
689
+ const [, method, , , payload] = oracle.oracleRequest.getCall(0).args
690
+ expect(payload.fspId).toBe(source)
691
+ expect(method).toBe(RestMethods.POST)
692
+ })
693
+
694
+ it('handles error when SubId is supplied but `participant.validateParticipant()` returns no participant', async () => {
695
+ expect.hasAssertions()
696
+ // Arrange
697
+ sandbox.stub(Logger)
698
+ participant.sendErrorToParticipant = sandbox.stub().resolves()
699
+ participant.validateParticipant = sandbox.stub()
700
+ participant.validateParticipant.withArgs('payerfsp')
701
+ .resolves({
702
+ data: {
703
+ name: 'fsp1'
704
+ }
705
+ })
706
+ participant.validateParticipant.withArgs('payeefsp').resolves(null)
707
+
708
+ const payload = JSON.stringify({ testPayload: true })
709
+ const dataUri = encodePayload(payload, 'application/json')
710
+ const params = { ...Helper.putByTypeIdRequest.params, SubId: 'subId' }
711
+ const expectedErrorCallbackEnpointType = Enum.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_PUT_ERROR
712
+
713
+ // Act
714
+ await partiesDomain.putPartiesByTypeAndID(Helper.putByTypeIdRequest.headers, params, 'put', payload, dataUri)
715
+
716
+ // Assert
717
+ expect(participant.sendErrorToParticipant.callCount).toBe(1)
718
+ const firstCallArgs = participant.sendErrorToParticipant.getCall(0).args
719
+ expect(firstCallArgs[1]).toBe(expectedErrorCallbackEnpointType)
720
+ })
721
+
722
+ it('handles error when `participant.validateParticipant()` is found and `sendErrorToParticipant()` fails', async () => {
723
+ expect.hasAssertions()
724
+ // Arrange
725
+ participant.validateParticipant = sandbox.stub()
726
+ participant.validateParticipant.withArgs('payerfsp')
727
+ .resolves({
728
+ data: {
729
+ name: 'fsp1'
730
+ }
731
+ })
732
+ participant.validateParticipant.withArgs('payeefsp').resolves(null)
733
+ participant.sendErrorToParticipant = sandbox.stub().throws('Error in sendErrorToParticipant')
734
+
735
+ const payload = JSON.stringify({ testPayload: true })
736
+ const dataUri = encodePayload(payload, 'application/json')
737
+
738
+ // Act
739
+ await partiesDomain.putPartiesByTypeAndID(Helper.putByTypeIdRequest.headers, Helper.putByTypeIdRequest.params, 'put', payload, dataUri, null, proxyCache)
740
+
741
+ // Assert
742
+ expect(participant.validateParticipant.callCount).toBe(2)
743
+ expect(participant.sendErrorToParticipant.callCount).toBe(1)
744
+ participant.validateParticipant.reset()
745
+ participant.sendErrorToParticipant.reset()
746
+ })
747
+
748
+ it('handles error when SubId is supplied, `participant.validateParticipant()` is found but `sendErrorToParticipant()` fails', async () => {
749
+ expect.hasAssertions()
750
+ // Arrange
751
+ sandbox.stub(Logger)
752
+ participant.validateParticipant = sandbox.stub()
753
+ participant.validateParticipant.withArgs('payerfsp')
754
+ .resolves({
755
+ data: {
756
+ name: 'fsp1'
757
+ }
758
+ })
759
+ participant.validateParticipant.withArgs('payeefsp').resolves(null)
760
+ participant.sendErrorToParticipant = sandbox.stub().throws('Error in sendErrorToParticipant')
761
+
762
+ const payload = JSON.stringify({ testPayload: true })
763
+ const dataUri = encodePayload(payload, 'application/json')
764
+ const params = { ...Helper.putByTypeIdRequest.params, SubId: 'subId' }
765
+ const expectedErrorCallbackEnpointType = Enum.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_PUT_ERROR
766
+
767
+ // Act
768
+ await partiesDomain.putPartiesByTypeAndID(Helper.putByTypeIdRequest.headers, params, 'put', payload, dataUri, null, proxyCache)
769
+
770
+ // Assert
771
+ expect(participant.validateParticipant.callCount).toBe(2)
772
+ expect(participant.sendErrorToParticipant.callCount).toBe(1)
773
+ const firstCallArgs = participant.sendErrorToParticipant.getCall(0).args
774
+ expect(firstCallArgs[1]).toBe(expectedErrorCallbackEnpointType)
775
+ })
776
+ })
777
+
778
+ describe('putPartiesErrorByTypeAndID', () => {
779
+ beforeEach(() => {
780
+ sandbox.stub(participant)
781
+ sandbox.stub(partiesDomain, 'getPartiesByTypeAndID')
782
+ Config.PROXY_CACHE_CONFIG.enabled = false
783
+ })
784
+
785
+ afterEach(() => {
786
+ sandbox.restore()
787
+ })
788
+
789
+ it('successfully sends error to the participant', async () => {
790
+ expect.hasAssertions()
791
+ // Arrange
792
+ participant.validateParticipant = sandbox.stub().resolves({
793
+ data: {
794
+ name: 'fsp1'
795
+ }
796
+ })
797
+ participant.sendErrorToParticipant = sandbox.stub().resolves()
798
+ const payload = JSON.stringify({ errorPayload: true })
799
+ const dataUri = encodePayload(payload, 'application/json')
800
+
801
+ // Act
802
+ await partiesDomain.putPartiesErrorByTypeAndID(Helper.putByTypeIdRequest.headers, Helper.putByTypeIdRequest.params, payload, dataUri, Helper.mockSpan())
803
+
804
+ // Assert
805
+ expect(participant.sendErrorToParticipant.callCount).toBe(1)
806
+ const sendErrorCallArgs = participant.sendErrorToParticipant.getCall(0).args
807
+ expect(sendErrorCallArgs[0]).toStrictEqual('payeefsp')
808
+ })
809
+
810
+ it('succesfully sends error to the participant when SubId is supplied', async () => {
811
+ expect.hasAssertions()
812
+ // Arrange
813
+ participant.validateParticipant = sandbox.stub().resolves({
814
+ data: {
815
+ name: 'fsp1'
816
+ }
817
+ })
818
+ participant.sendErrorToParticipant = sandbox.stub().resolves()
819
+ const payload = JSON.stringify({ errorPayload: true })
820
+ const dataUri = encodePayload(payload, 'application/json')
821
+ const params = { ...Helper.putByTypeIdRequest.params, SubId: 'subId' }
822
+ const expectedCallbackEnpointType = Enum.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_PUT_ERROR
823
+
824
+ // Act
825
+ await partiesDomain.putPartiesErrorByTypeAndID(Helper.putByTypeIdRequest.headers, params, payload, dataUri)
826
+
827
+ // Assert
828
+ expect(participant.sendErrorToParticipant.callCount).toBe(1)
829
+ const sendErrorCallArgs = participant.sendErrorToParticipant.getCall(0).args
830
+ expect(sendErrorCallArgs[0]).toStrictEqual('payeefsp')
831
+ expect(sendErrorCallArgs[1]).toBe(expectedCallbackEnpointType)
832
+ })
833
+
834
+ it('sends error to the participant when there is no destination participant', async () => {
835
+ expect.hasAssertions()
836
+ // Arrange
837
+ participant.validateParticipant = sandbox.stub().resolves(null)
838
+ participant.sendErrorToParticipant = sandbox.stub().throws(new Error('Unknown error'))
839
+ const payload = JSON.stringify({ errorPayload: true })
840
+ const dataUri = encodePayload(payload, 'application/json')
841
+
842
+ // Act
843
+ await partiesDomain.putPartiesErrorByTypeAndID(Helper.putByTypeIdRequest.headers, Helper.putByTypeIdRequest.params, payload, dataUri, null, proxyCache)
844
+
845
+ // Assert
846
+ expect(participant.sendErrorToParticipant.callCount).toBe(1)
847
+ const sendErrorCallArgs = participant.sendErrorToParticipant.getCall(0).args
848
+ expect(sendErrorCallArgs[0]).toStrictEqual('payerfsp')
849
+ })
850
+
851
+ it('handles error when `decodePayload()` fails', async () => {
852
+ expect.hasAssertions()
853
+ // Arrange)
854
+ participant.validateParticipant = sandbox.stub().resolves({
855
+ data: {
856
+ name: 'fsp1'
857
+ }
858
+ })
859
+ participant.sendErrorToParticipant = sandbox.stub().throws(new Error('Unknown error'))
860
+ const payload = JSON.stringify({ errorPayload: true })
861
+ // Send a data uri that will cause `decodePayload` to throw
862
+ const invalidDataUri = () => 'invalid uri'
863
+ const { headers, params } = Helper.putByTypeIdRequest
864
+
865
+ // Act
866
+ await partiesDomain.putPartiesErrorByTypeAndID(headers, params, payload, invalidDataUri, Helper.mockSpan(), null)
867
+
868
+ // Assert
869
+ expect(participant.sendErrorToParticipant.callCount).toBe(1)
870
+ const sendErrorCallArgs = participant.sendErrorToParticipant.getCall(0).args
871
+ expect(sendErrorCallArgs[0]).toStrictEqual(headers['fspiop-destination'])
872
+ })
873
+
874
+ it('handles error when `validateParticipant()` fails', async () => {
875
+ expect.hasAssertions()
876
+ // Arrange)
877
+ const loggerStub = sandbox.stub(logger.mlLogger, 'error')
878
+ participant.validateParticipant = sandbox.stub().throws(new Error('Validation fails'))
879
+ participant.sendErrorToParticipant = sandbox.stub().resolves({})
880
+ const payload = JSON.stringify({ errorPayload: true })
881
+ const expectedCallbackEnpointType = Enum.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_PUT_ERROR
882
+
883
+ // Act
884
+ await partiesDomain.putPartiesErrorByTypeAndID(Helper.putByTypeIdRequest.headers, Helper.putByTypeIdRequest.params, payload)
885
+
886
+ // Assert
887
+ expect(participant.sendErrorToParticipant.callCount).toBe(1)
888
+ expect(loggerStub.callCount).toBe(1)
889
+ const sendErrorCallArgs = participant.sendErrorToParticipant.getCall(0).args
890
+ expect(sendErrorCallArgs[1]).toBe(expectedCallbackEnpointType)
891
+ })
892
+
893
+ it('handles error when SubID is supplied but `validateParticipant()` fails', async () => {
894
+ expect.hasAssertions()
895
+ // Arrange
896
+
897
+ const loggerStub = sandbox.stub(logger.mlLogger, 'error')
898
+ participant.validateParticipant = sandbox.stub().throws(new Error('Validation fails'))
899
+ participant.sendErrorToParticipant = sandbox.stub().resolves({})
900
+ const payload = JSON.stringify({ errorPayload: true })
901
+ const params = { ...Helper.putByTypeIdRequest.params, SubId: 'SubId' }
902
+ const expectedCallbackEnpointType = Enum.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTIES_SUB_ID_PUT_ERROR
903
+
904
+ // Act
905
+ await partiesDomain.putPartiesErrorByTypeAndID(Helper.putByTypeIdRequest.headers, params, payload)
906
+
907
+ // Assert
908
+ expect(participant.sendErrorToParticipant.callCount).toBe(1)
909
+ expect(loggerStub.callCount).toBe(1)
910
+ const sendErrorCallArgs = participant.sendErrorToParticipant.getCall(0).args
911
+ expect(sendErrorCallArgs[1]).toBe(expectedCallbackEnpointType)
912
+ })
913
+
914
+ it('should handle notValidPayeeIdentifier case, and delete partyId from oracle', async () => {
915
+ Config.PROXY_CACHE_CONFIG.enabled = true
916
+ const errorCode = MojaloopApiErrorCodes.PAYEE_IDENTIFIER_NOT_VALID.code
917
+ const payload = fixtures.errorCallbackResponseDto({ errorCode })
918
+ const source = `source-${Date.now()}`
919
+ const proxy = `proxy-${Date.now()}`
920
+ const headers = fixtures.partiesCallHeadersDto({ source, proxy })
921
+ const { params } = Helper.putByTypeIdRequest
922
+ participant.sendRequest = sandbox.stub().resolves()
923
+ participant.sendErrorToParticipant = sandbox.stub().resolves()
924
+ oracleEndpointCached.getOracleEndpointByType = sandbox.stub().resolves([
925
+ { value: 'http://oracle.endpoint' }
926
+ ])
927
+ oracle.oracleRequest = sandbox.stub().resolves()
928
+
929
+ await partiesDomain.putPartiesErrorByTypeAndID(headers, params, payload, '', null, null, proxyCache)
930
+
931
+ expect(participant.sendRequest.callCount).toBe(0)
932
+ expect(oracle.oracleRequest.callCount).toBe(1)
933
+ const [, method] = oracle.oracleRequest.getCall(0).args
934
+ expect(method).toBe(RestMethods.DELETE)
935
+ // todo: think, how to stub getPartiesByTypeAndID call
936
+ // expect(partiesDomain.getPartiesByTypeAndID.callCount).toBe(1)
937
+ // expect(participant.sendErrorToParticipant.callCount).toBe(0)
938
+ })
939
+ })
940
+ })