account-lookup-service 17.12.3-snapshot.1 → 17.12.3

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 CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [17.12.3](https://github.com/mojaloop/account-lookup-service/compare/v17.12.2...v17.12.3) (2025-08-26)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * add validation for missing ID or SubId ([#565](https://github.com/mojaloop/account-lookup-service/issues/565)) ([8db0d1c](https://github.com/mojaloop/account-lookup-service/commit/8db0d1c1af7e858680280542f25201a655ae218e))
11
+
12
+
13
+ ### Chore
14
+
15
+ * **sbom:** update sbom [skip ci] ([f63ce0f](https://github.com/mojaloop/account-lookup-service/commit/f63ce0fc2169ed55594863f809c79183dbfd8550))
16
+
5
17
  ### [17.12.2](https://github.com/mojaloop/account-lookup-service/compare/v17.12.1...v17.12.2) (2025-08-01)
6
18
 
7
19
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "account-lookup-service",
3
3
  "description": "Account Lookup Service is used to validate Party and Participant lookups.",
4
- "version": "17.12.3-snapshot.1",
4
+ "version": "17.12.3",
5
5
  "license": "Apache-2.0",
6
6
  "author": "ModusBox",
7
7
  "contributors": [
@@ -41,6 +41,53 @@ const util = require('../../lib/util')
41
41
 
42
42
  const { FSPIOPErrorCodes } = ErrorHandler.Enums
43
43
 
44
+ /**
45
+ * @function getCallbackEndpointTypes
46
+ * @description Determines the callback and error callback endpoint types based on SubId presence
47
+ * @param {string|undefined} partySubIdOrType - The SubId parameter
48
+ * @returns {object} Object containing callbackEndpointType and errorCallbackEndpointType
49
+ */
50
+ const getCallbackEndpointTypes = (partySubIdOrType) => {
51
+ return {
52
+ callbackEndpointType: partySubIdOrType
53
+ ? Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_SUB_ID_PUT
54
+ : Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_PUT,
55
+ errorCallbackEndpointType: partySubIdOrType
56
+ ? Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_SUB_ID_PUT_ERROR
57
+ : Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_PUT_ERROR
58
+ }
59
+ }
60
+
61
+ /**
62
+ * @function validatePathParameters
63
+ * @description Validates that path parameters are not placeholder values like {ID} or {SubId}
64
+ * @param {object} params - The path parameters object
65
+ * @param {object} log - Logger instance for error logging
66
+ * @throws {FSPIOPError} When parameters contain placeholder values
67
+ */
68
+ const validatePathParameters = (params, log = logger) => {
69
+ // Validate that ID is not a placeholder value
70
+ if (params.ID && (params.ID === '{ID}' || params.ID.includes('{') || params.ID.includes('}'))) {
71
+ const errMessage = `Invalid ID parameter: ${params.ID}. ID must not be a placeholder value`
72
+ log.error(errMessage, { params })
73
+ throw ErrorHandler.Factory.createFSPIOPError(
74
+ ErrorHandler.Enums.FSPIOPErrorCodes.MALFORMED_SYNTAX,
75
+ errMessage
76
+ )
77
+ }
78
+
79
+ // Validate SubId if present
80
+ const partySubIdOrType = params.SubId
81
+ if (partySubIdOrType && (partySubIdOrType === '{SubId}' || partySubIdOrType.includes('{') || partySubIdOrType.includes('}'))) {
82
+ const errMessage = `Invalid SubId parameter: ${partySubIdOrType}. SubId must not be a placeholder value`
83
+ log.error(errMessage, { params })
84
+ throw ErrorHandler.Factory.createFSPIOPError(
85
+ ErrorHandler.Enums.FSPIOPErrorCodes.MALFORMED_SYNTAX,
86
+ errMessage
87
+ )
88
+ }
89
+ }
90
+
44
91
  /**
45
92
  * @function getParticipantsByTypeAndID
46
93
  *
@@ -62,13 +109,12 @@ const getParticipantsByTypeAndID = async (headers, params, method, query, span,
62
109
  const log = logger.child('getParticipantsByTypeAndID')
63
110
  const type = params.Type
64
111
  const partySubIdOrType = params.SubId
112
+
113
+ // Validate path parameters
114
+ validatePathParameters(params, log)
115
+
65
116
  const source = headers[Enums.Http.Headers.FSPIOP.SOURCE]
66
- const callbackEndpointType = partySubIdOrType
67
- ? Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_SUB_ID_PUT
68
- : Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_PUT
69
- const errorCallbackEndpointType = params.SubId
70
- ? Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_SUB_ID_PUT_ERROR
71
- : Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_PUT_ERROR
117
+ const { callbackEndpointType, errorCallbackEndpointType } = getCallbackEndpointTypes(partySubIdOrType)
72
118
 
73
119
  let fspiopError
74
120
  let step
@@ -180,8 +226,11 @@ const putParticipantsByTypeAndID = async (headers, params, method, payload, cach
180
226
  logger.info('putParticipantsByTypeAndID::begin')
181
227
  const type = params.Type
182
228
  const partySubIdOrType = params.SubId || undefined
183
- const callbackEndpointType = partySubIdOrType ? Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_SUB_ID_PUT : Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_PUT
184
- const errorCallbackEndpointType = partySubIdOrType ? Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_SUB_ID_PUT_ERROR : Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_PUT_ERROR
229
+
230
+ // Validate path parameters
231
+ validatePathParameters(params)
232
+
233
+ const { callbackEndpointType, errorCallbackEndpointType } = getCallbackEndpointTypes(partySubIdOrType)
185
234
  if (Object.values(Enums.Accounts.PartyAccountTypes).includes(type)) {
186
235
  step = 'validateParticipant-1'
187
236
  const requesterParticipantModel = await participant.validateParticipant(headers[Enums.Http.Headers.FSPIOP.SOURCE])
@@ -234,7 +283,7 @@ const putParticipantsByTypeAndID = async (headers, params, method, payload, cach
234
283
  logger.error('error in putParticipantsByTypeAndID:', err)
235
284
  let fspiopError
236
285
  try {
237
- const errorCallbackEndpointType = params.SubId ? Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_SUB_ID_PUT_ERROR : Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_PUT_ERROR
286
+ const { errorCallbackEndpointType } = getCallbackEndpointTypes(params.SubId)
238
287
  fspiopError = ErrorHandler.Factory.reformatFSPIOPError(err, ErrorHandler.Enums.FSPIOPErrorCodes.ADD_PARTY_INFO_ERROR)
239
288
  await participant.sendErrorToParticipant(headers[Enums.Http.Headers.FSPIOP.SOURCE], errorCallbackEndpointType,
240
289
  fspiopError.toApiErrorObject(Config.ERROR_HANDLING), headers, params)
@@ -272,6 +321,11 @@ const putParticipantsErrorByTypeAndID = async (headers, params, payload, dataUri
272
321
  let step
273
322
  try {
274
323
  const partySubIdOrType = params.SubId || undefined
324
+
325
+ // Validate path parameters
326
+ validatePathParameters(params)
327
+
328
+ // For error endpoints, we only need the error callback type
275
329
  const callbackEndpointType = partySubIdOrType
276
330
  ? Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_SUB_ID_PUT_ERROR
277
331
  : Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_PUT_ERROR
@@ -303,9 +357,7 @@ const putParticipantsErrorByTypeAndID = async (headers, params, payload, dataUri
303
357
  logger.error('error in putParticipantsErrorByTypeAndID:', err)
304
358
  try {
305
359
  const fspiopError = ErrorHandler.Factory.reformatFSPIOPError(err)
306
- const callbackEndpointType = params.SubId
307
- ? Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_SUB_ID_PUT_ERROR
308
- : Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_PUT_ERROR
360
+ const { errorCallbackEndpointType: callbackEndpointType } = getCallbackEndpointTypes(params.SubId)
309
361
  await participant.sendErrorToParticipant(
310
362
  headers[Enums.Http.Headers.FSPIOP.SOURCE],
311
363
  callbackEndpointType,
@@ -347,12 +399,11 @@ const postParticipants = async (headers, method, params, payload, span, cache) =
347
399
  logger.info('postParticipants::begin')
348
400
  const type = params.Type
349
401
  const partySubIdOrType = params.SubId
350
- const callbackEndpointType = partySubIdOrType
351
- ? Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_SUB_ID_PUT
352
- : Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_PUT
353
- const errorCallbackEndpointType = partySubIdOrType
354
- ? Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_SUB_ID_PUT_ERROR
355
- : Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_PUT_ERROR
402
+
403
+ // Validate path parameters
404
+ validatePathParameters(params)
405
+
406
+ const { callbackEndpointType, errorCallbackEndpointType } = getCallbackEndpointTypes(partySubIdOrType)
356
407
 
357
408
  if (Object.values(Enums.Accounts.PartyAccountTypes).includes(type)) {
358
409
  step = 'validateParticipant-1'
@@ -415,9 +466,7 @@ const postParticipants = async (headers, method, params, payload, span, cache) =
415
466
  util.countFspiopError(fspiopError, { operation: 'postParticipants', step })
416
467
  }
417
468
  try {
418
- const errorCallbackEndpointType = params.SubId
419
- ? Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_SUB_ID_PUT_ERROR
420
- : Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_PUT_ERROR
469
+ const { errorCallbackEndpointType } = getCallbackEndpointTypes(params.SubId)
421
470
  await participant.sendErrorToParticipant(headers[Enums.Http.Headers.FSPIOP.SOURCE], errorCallbackEndpointType,
422
471
  fspiopError.toApiErrorObject(Config.ERROR_HANDLING), headers, params, childSpan)
423
472
  } catch (exc) {
@@ -581,8 +630,11 @@ const deleteParticipants = async (headers, params, method, query, cache) => {
581
630
  log.debug('deleteParticipants::begin', { headers, params })
582
631
  const type = params.Type
583
632
  const partySubIdOrType = params.SubId || undefined
584
- const callbackEndpointType = partySubIdOrType ? Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_SUB_ID_PUT : Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_PUT
585
- const errorCallbackEndpointType = partySubIdOrType ? Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_SUB_ID_PUT_ERROR : Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_PUT_ERROR
633
+
634
+ // Validate path parameters
635
+ validatePathParameters(params, log)
636
+
637
+ const { callbackEndpointType, errorCallbackEndpointType } = getCallbackEndpointTypes(partySubIdOrType)
586
638
  if (Object.values(Enums.Accounts.PartyAccountTypes).includes(type)) {
587
639
  step = 'validateParticipant-1'
588
640
  const requesterParticipantModel = await participant.validateParticipant(headers[Enums.Http.Headers.FSPIOP.SOURCE])
@@ -624,7 +676,7 @@ const deleteParticipants = async (headers, params, method, query, cache) => {
624
676
  log.error('error in deleteParticipants', err)
625
677
  try {
626
678
  const fspiopError = ErrorHandler.Factory.reformatFSPIOPError(err, ErrorHandler.Enums.FSPIOPErrorCodes.DELETE_PARTY_INFO_ERROR)
627
- const errorCallbackEndpointType = params.SubId ? Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_SUB_ID_PUT_ERROR : Enums.EndPoints.FspEndpointTypes.FSPIOP_CALLBACK_URL_PARTICIPANT_PUT_ERROR
679
+ const { errorCallbackEndpointType } = getCallbackEndpointTypes(params.SubId)
628
680
  await participant.sendErrorToParticipant(headers[Enums.Http.Headers.FSPIOP.SOURCE], errorCallbackEndpointType, fspiopError.toApiErrorObject(Config.ERROR_HANDLING), headers, params)
629
681
  util.countFspiopError(fspiopError, { operation: 'deleteParticipants', step })
630
682
  } catch (exc) {
@@ -642,5 +694,7 @@ module.exports = {
642
694
  putParticipantsErrorByTypeAndID,
643
695
  postParticipants,
644
696
  postParticipantsBatch,
645
- deleteParticipants
697
+ deleteParticipants,
698
+ validatePathParameters,
699
+ getCallbackEndpointTypes
646
700
  }
@@ -168,9 +168,7 @@ class BasePartiesService {
168
168
 
169
169
  async sendDeleteOracleRequest (headers, params) {
170
170
  this.stepInProgress('sendDeleteOracleRequest')
171
- const result = await this.deps.oracle.oracleRequest(headers, RestMethods.DELETE, params, null, null, this.deps.cache)
172
- this.log.verbose('sendDeleteOracleRequest is done', { params })
173
- return result
171
+ return this.deps.oracle.oracleRequest(headers, RestMethods.DELETE, params, null, null, this.deps.cache)
174
172
  }
175
173
 
176
174
  async removeProxyGetPartiesTimeoutCache (alsReq) {
package/src/lib/config.js CHANGED
@@ -175,8 +175,7 @@ const config = {
175
175
  FEATURE_ENABLE_EXTENDED_PARTY_ID_TYPE: RC.FEATURE_ENABLE_EXTENDED_PARTY_ID_TYPE || false,
176
176
  PROTOCOL_VERSIONS: getProtocolVersions(DEFAULT_PROTOCOL_VERSION, RC.PROTOCOL_VERSIONS),
177
177
  PROXY_CACHE_CONFIG: RC.PROXY_CACHE,
178
- DELETE_PARTICIPANT_VALIDATION_ENABLED: RC.DELETE_PARTICIPANT_VALIDATION_ENABLED || false,
179
- HTTP_REQUEST_TIMEOUT_MS: RC.HTTP_REQUEST_TIMEOUT_MS ?? 20_000
178
+ DELETE_PARTICIPANT_VALIDATION_ENABLED: RC.DELETE_PARTICIPANT_VALIDATION_ENABLED || false
180
179
  }
181
180
 
182
181
  if (config.JWS_SIGN) {
@@ -43,10 +43,7 @@ const { Headers, RestMethods, ReturnCodes } = Enums.Http
43
43
  const sendHttpRequest = ({ method, ...restArgs }) => request.sendRequest({
44
44
  ...restArgs,
45
45
  method: method.toUpperCase(),
46
- hubNameRegex,
47
- axiosRequestOptionsOverride: {
48
- timeout: Config.HTTP_REQUEST_TIMEOUT_MS
49
- }
46
+ hubNameRegex
50
47
  })
51
48
 
52
49
  /**
@@ -37,9 +37,6 @@ const { logger } = require('../../lib')
37
37
  const { hubNameRegex } = require('../../lib/util').hubNameConfig
38
38
 
39
39
  const uriRegex = /(?:^.*)(\/(participants|parties|quotes|transfers)(\/.*)*)$/
40
- const axiosRequestOptionsOverride = {
41
- timeout: Config.HTTP_REQUEST_TIMEOUT_MS
42
- }
43
40
 
44
41
  /**
45
42
  * @module src/models/participantEndpoint/facade
@@ -118,8 +115,7 @@ exports.sendRequest = async (headers, requestedParticipant, endpointType, method
118
115
  span,
119
116
  protocolVersions,
120
117
  hubNameRegex,
121
- apiType: Config.API_TYPE,
122
- axiosRequestOptionsOverride
118
+ apiType: Config.API_TYPE
123
119
  }
124
120
  logger.debug('participant - sendRequest params:', { params })
125
121
  params.jwsSigner = defineJwsSigner(Config, headers, requestedEndpoint)
@@ -256,8 +252,7 @@ exports.sendErrorToParticipant = async (participantName, endpointType, errorInfo
256
252
  hubNameRegex,
257
253
  span,
258
254
  protocolVersions,
259
- apiType: Config.API_TYPE,
260
- axiosRequestOptionsOverride
255
+ apiType: Config.API_TYPE
261
256
  }
262
257
  logger.debug('participant - sendErrorToParticipant params: ', { params })
263
258
  params.jwsSigner = defineJwsSigner(Config, clonedHeaders, requesterErrorEndpoint)
@@ -50,6 +50,76 @@ Logger.isInfoEnabled = jest.fn(() => true)
50
50
  let server
51
51
  let sandbox
52
52
 
53
+ // Helper function to test missing parameter scenarios
54
+ const testMissingParameter = (method, urlWithMissingParam, payloadRequired = false) => {
55
+ return async () => {
56
+ const options = {
57
+ method,
58
+ url: urlWithMissingParam,
59
+ headers: Helper.defaultSwitchHeaders
60
+ }
61
+
62
+ if (payloadRequired) {
63
+ const mock = await Helper.generateMockRequest('/participants/{Type}/{ID}', method)
64
+ options.payload = mock.request.body
65
+ }
66
+
67
+ const response = await server.inject(options)
68
+
69
+ expect(response.statusCode).toBe(404)
70
+ expect(response.result.errorInformation).toBeDefined()
71
+ expect(response.result.errorInformation.errorCode).toBe('3002')
72
+ expect(response.result.errorInformation.errorDescription).toContain('Unknown URI')
73
+ }
74
+ }
75
+
76
+ // Helper function to test successful domain method calls
77
+ const testSuccessfulDomainCall = (method, domainMethod, expectedStatus, pathOverride = null) => {
78
+ return async () => {
79
+ const path = pathOverride || '/participants/{Type}/{ID}'
80
+ const mock = await Helper.generateMockRequest(path, method)
81
+ const options = {
82
+ method,
83
+ url: mock.request.path,
84
+ headers: Helper.defaultSwitchHeaders,
85
+ payload: mock.request.body
86
+ }
87
+ sandbox.stub(participants, domainMethod).resolves({})
88
+
89
+ const response = await server.inject(options)
90
+
91
+ expect(response.statusCode).toBe(expectedStatus)
92
+ expect(participants[domainMethod].callCount).toBe(1)
93
+ expect(participants[domainMethod].getCall(0).returnValue).resolves.toStrictEqual({})
94
+
95
+ participants[domainMethod].restore()
96
+ }
97
+ }
98
+
99
+ // Helper function to test domain method error handling
100
+ const testDomainMethodError = (method, domainMethod, expectedStatus, pathOverride = null) => {
101
+ return async () => {
102
+ const path = pathOverride || '/participants/{Type}/{ID}'
103
+ const mock = await Helper.generateMockRequest(path, method)
104
+ const options = {
105
+ method,
106
+ url: mock.request.path,
107
+ headers: Helper.defaultSwitchHeaders,
108
+ payload: mock.request.body
109
+ }
110
+ const throwError = new Error('Unknown error')
111
+ sandbox.stub(participants, domainMethod).rejects(throwError)
112
+
113
+ const response = await server.inject(options)
114
+
115
+ expect(response.statusCode).toBe(expectedStatus)
116
+ expect(participants[domainMethod].callCount).toBe(1)
117
+ expect(participants[domainMethod].getCall(0).returnValue).rejects.toStrictEqual(throwError)
118
+
119
+ participants[domainMethod].restore()
120
+ }
121
+ }
122
+
53
123
  describe('/participants/{Type}/{ID}', () => {
54
124
  beforeAll(async () => {
55
125
  sandbox = Sinon.createSandbox()
@@ -66,27 +136,10 @@ describe('/participants/{Type}/{ID}', () => {
66
136
  })
67
137
 
68
138
  describe('GET /participants', () => {
69
- it('returns 202 when getParticipantsByTypeAndID resolves', async () => {
70
- // Arrange
71
- const mock = await Helper.generateMockRequest('/participants/{Type}/{ID}', 'get')
72
- const options = {
73
- method: 'get',
74
- url: mock.request.path,
75
- headers: Helper.defaultSwitchHeaders
76
- }
77
- sandbox.stub(participants, 'getParticipantsByTypeAndID').resolves({})
139
+ it('returns 404 when ID parameter is missing', testMissingParameter('get', '/participants/MSISDN/'))
140
+ it('returns 404 when Type parameter is missing', testMissingParameter('get', '/participants//123456789'))
78
141
 
79
- // Act
80
- const response = await server.inject(options)
81
-
82
- // Assert
83
- expect(response.statusCode).toBe(202)
84
- expect(participants.getParticipantsByTypeAndID.callCount).toBe(1)
85
- expect(participants.getParticipantsByTypeAndID.getCall(0).returnValue).resolves.toStrictEqual({})
86
-
87
- // Cleanup
88
- participants.getParticipantsByTypeAndID.restore()
89
- })
142
+ it('returns 202 when getParticipantsByTypeAndID resolves', testSuccessfulDomainCall('get', 'getParticipantsByTypeAndID', 202))
90
143
 
91
144
  it('getParticipantsByTypeAndID sends an async 3204 for invalid party id on response with status 400', async () => {
92
145
  // Arrange
@@ -158,223 +211,36 @@ describe('/participants/{Type}/{ID}', () => {
158
211
  stubs.forEach(s => s.restore())
159
212
  })
160
213
 
161
- it('returns 202 when getParticipantsByTypeAndID rejects with an unknown error', async () => {
162
- // Arrange
163
- const mock = await Helper.generateMockRequest('/participants/{Type}/{ID}', 'get')
164
- const options = {
165
- method: 'get',
166
- url: mock.request.path,
167
- headers: Helper.defaultSwitchHeaders
168
- }
169
- const throwError = new Error('Unknown error')
170
- sandbox.stub(participants, 'getParticipantsByTypeAndID').rejects(throwError)
171
-
172
- // Act
173
- const response = await server.inject(options)
174
-
175
- // Assert
176
- expect(response.statusCode).toBe(202)
177
- expect(participants.getParticipantsByTypeAndID.callCount).toBe(1)
178
- expect(participants.getParticipantsByTypeAndID.getCall(0).returnValue).rejects.toStrictEqual(throwError)
179
-
180
- // Cleanup
181
- participants.getParticipantsByTypeAndID.restore()
182
- })
214
+ it('returns 202 when getParticipantsByTypeAndID rejects with an unknown error', testDomainMethodError('get', 'getParticipantsByTypeAndID', 202))
183
215
  })
184
216
 
185
217
  describe('POST /participants', () => {
186
- it('returns 202 when postParticipants resolves', async () => {
187
- // Arrange
188
- const mock = await Helper.generateMockRequest('/participants/{Type}/{ID}', 'post')
189
- const options = {
190
- method: 'post',
191
- url: mock.request.path,
192
- headers: Helper.defaultSwitchHeaders,
193
- payload: mock.request.body
194
- }
195
- sandbox.stub(participants, 'postParticipants').resolves({})
196
-
197
- // Act
198
- const response = await server.inject(options)
199
-
200
- // Assert
201
- expect(response.statusCode).toBe(202)
202
- expect(participants.postParticipants.callCount).toBe(1)
203
- expect(participants.postParticipants.getCall(0).returnValue).resolves.toStrictEqual({})
204
-
205
- // Cleanup
206
- participants.postParticipants.restore()
207
- })
208
-
209
- it('returns 202 when postParticipants rejects with an unknown error', async () => {
210
- // Arrange
211
- const mock = await Helper.generateMockRequest('/participants/{Type}/{ID}', 'post')
212
- const options = {
213
- method: 'post',
214
- url: mock.request.path,
215
- headers: Helper.defaultSwitchHeaders,
216
- payload: mock.request.body
217
- }
218
- const throwError = new Error('Unknown error')
219
- sandbox.stub(participants, 'postParticipants').rejects(throwError)
220
-
221
- // Act
222
- const response = await server.inject(options)
218
+ it('returns 404 when ID parameter is missing', testMissingParameter('post', '/participants/MSISDN/', true))
223
219
 
224
- // Assert
225
- expect(response.statusCode).toBe(202)
226
- expect(participants.postParticipants.callCount).toBe(1)
227
- expect(participants.postParticipants.getCall(0).returnValue).rejects.toStrictEqual(throwError)
220
+ it('returns 202 when postParticipants resolves', testSuccessfulDomainCall('post', 'postParticipants', 202))
228
221
 
229
- // Cleanup
230
- participants.postParticipants.restore()
231
- })
222
+ it('returns 202 when postParticipants rejects with an unknown error', testDomainMethodError('post', 'postParticipants', 202))
232
223
  })
233
224
 
234
225
  describe('PUT /participants', () => {
235
- it('returns 200 when putParticipantsByTypeAndID resolves', async () => {
236
- // Arrange
237
- const mock = await Helper.generateMockRequest('/participants/{Type}/{ID}', 'put')
238
- const options = {
239
- method: 'put',
240
- url: mock.request.path,
241
- headers: Helper.defaultSwitchHeaders,
242
- payload: mock.request.body
243
- }
244
- sandbox.stub(participants, 'putParticipantsByTypeAndID').resolves({})
226
+ it('returns 404 when ID parameter is missing', testMissingParameter('put', '/participants/MSISDN/', true))
245
227
 
246
- // Act
247
- const response = await server.inject(options)
248
-
249
- // Assert
250
- expect(response.statusCode).toBe(200)
251
- expect(participants.putParticipantsByTypeAndID.callCount).toBe(1)
252
- expect(participants.putParticipantsByTypeAndID.getCall(0).returnValue).resolves.toStrictEqual({})
228
+ it('returns 200 when putParticipantsByTypeAndID resolves', testSuccessfulDomainCall('put', 'putParticipantsByTypeAndID', 200))
253
229
 
254
- // Cleanup
255
- participants.putParticipantsByTypeAndID.restore()
256
- })
257
-
258
- it('returns 200 when putParticipantsByTypeAndID rejects with an unknown error', async () => {
259
- // Arrange
260
- const mock = await Helper.generateMockRequest('/participants/{Type}/{ID}', 'put')
261
- const options = {
262
- method: 'put',
263
- url: mock.request.path,
264
- headers: Helper.defaultSwitchHeaders,
265
- payload: mock.request.body
266
- }
267
- const throwError = new Error('Unknown error')
268
- sandbox.stub(participants, 'putParticipantsByTypeAndID').rejects(throwError)
269
-
270
- // Act
271
- const response = await server.inject(options)
272
-
273
- // Assert
274
- expect(response.statusCode).toBe(200)
275
- expect(participants.putParticipantsByTypeAndID.callCount).toBe(1)
276
- expect(participants.putParticipantsByTypeAndID.getCall(0).returnValue).rejects.toStrictEqual(throwError)
277
-
278
- // Cleanup
279
- participants.putParticipantsByTypeAndID.restore()
280
- })
230
+ it('returns 200 when putParticipantsByTypeAndID rejects with an unknown error', testDomainMethodError('put', 'putParticipantsByTypeAndID', 200))
281
231
  })
282
232
 
283
233
  describe('DELETE /participants', () => {
284
- it('returns 202 when deleteParticipants resolves', async () => {
285
- // Arrange
286
- const mock = await Helper.generateMockRequest('/participants/{Type}/{ID}', 'delete')
287
- const options = {
288
- method: 'delete',
289
- url: mock.request.path,
290
- headers: Helper.defaultSwitchHeaders,
291
- payload: mock.request.body
292
- }
293
- sandbox.stub(participants, 'deleteParticipants').resolves({})
294
-
295
- // Act
296
- const response = await server.inject(options)
297
-
298
- // Assert
299
- expect(response.statusCode).toBe(202)
300
- expect(participants.deleteParticipants.callCount).toBe(1)
301
- expect(participants.deleteParticipants.getCall(0).returnValue).resolves.toStrictEqual({})
234
+ it('returns 404 when ID parameter is missing', testMissingParameter('delete', '/participants/MSISDN/', true))
302
235
 
303
- // Cleanup
304
- participants.deleteParticipants.restore()
305
- })
306
-
307
- it('returns 202 when deleteParticipants rejects with an unknown error', async () => {
308
- // Arrange
309
- const mock = await Helper.generateMockRequest('/participants/{Type}/{ID}', 'delete')
310
- const options = {
311
- method: 'delete',
312
- url: mock.request.path,
313
- headers: Helper.defaultSwitchHeaders,
314
- payload: mock.request.body
315
- }
316
- const throwError = new Error('Unknown error')
317
- sandbox.stub(participants, 'deleteParticipants').rejects(throwError)
236
+ it('returns 202 when deleteParticipants resolves', testSuccessfulDomainCall('delete', 'deleteParticipants', 202))
318
237
 
319
- // Act
320
- const response = await server.inject(options)
321
-
322
- // Assert
323
- expect(response.statusCode).toBe(202)
324
- expect(participants.deleteParticipants.callCount).toBe(1)
325
- expect(participants.deleteParticipants.getCall(0).returnValue).rejects.toStrictEqual(throwError)
326
-
327
- // Cleanup
328
- participants.deleteParticipants.restore()
329
- })
238
+ it('returns 202 when deleteParticipants rejects with an unknown error', testDomainMethodError('delete', 'deleteParticipants', 202))
330
239
  })
331
240
 
332
241
  describe('PUT /error', () => {
333
- it('returns 200 when putParticipantsErrorByTypeAndID resolves', async () => {
334
- // Arrange
335
- const mock = await Helper.generateMockRequest('/participants/{Type}/{ID}/error', 'put')
336
- const options = {
337
- method: 'put',
338
- url: mock.request.path,
339
- headers: Helper.defaultSwitchHeaders,
340
- payload: mock.request.body
341
- }
342
- sandbox.stub(participants, 'putParticipantsErrorByTypeAndID').resolves({})
343
-
344
- // Act
345
- const response = await server.inject(options)
346
-
347
- // Assert
348
- expect(response.statusCode).toBe(200)
349
- expect(participants.putParticipantsErrorByTypeAndID.callCount).toBe(1)
350
- expect(participants.putParticipantsErrorByTypeAndID.getCall(0).returnValue).resolves.toStrictEqual({})
242
+ it('returns 200 when putParticipantsErrorByTypeAndID resolves', testSuccessfulDomainCall('put', 'putParticipantsErrorByTypeAndID', 200, '/participants/{Type}/{ID}/error'))
351
243
 
352
- // Cleanup
353
- participants.putParticipantsErrorByTypeAndID.restore()
354
- })
355
-
356
- it('returns 200 when putParticipantsErrorByTypeAndID rejects with an unknown error', async () => {
357
- // Arrange
358
- const mock = await Helper.generateMockRequest('/participants/{Type}/{ID}/error', 'put')
359
- const options = {
360
- method: 'put',
361
- url: mock.request.path,
362
- headers: Helper.defaultSwitchHeaders,
363
- payload: mock.request.body
364
- }
365
- const throwError = new Error('Unknown error')
366
- sandbox.stub(participants, 'putParticipantsErrorByTypeAndID').rejects(throwError)
367
-
368
- // Act
369
- const response = await server.inject(options)
370
-
371
- // Assert
372
- expect(response.statusCode).toBe(200)
373
- expect(participants.putParticipantsErrorByTypeAndID.callCount).toBe(1)
374
- expect(participants.putParticipantsErrorByTypeAndID.getCall(0).returnValue).rejects.toStrictEqual(throwError)
375
-
376
- // Cleanup
377
- participants.putParticipantsErrorByTypeAndID.restore()
378
- })
244
+ it('returns 200 when putParticipantsErrorByTypeAndID rejects with an unknown error', testDomainMethodError('put', 'putParticipantsErrorByTypeAndID', 200, '/participants/{Type}/{ID}/error'))
379
245
  })
380
246
  })
@@ -48,6 +48,48 @@ const fixtures = require('../../../fixtures')
48
48
  const { Headers } = Enums.Http
49
49
 
50
50
  describe('participant Tests', () => {
51
+ describe('validatePathParameters', () => {
52
+ it('should throw error for placeholder {ID} value', () => {
53
+ expect.hasAssertions()
54
+ const params = { ID: '{ID}', Type: 'MSISDN' }
55
+
56
+ expect(() => participantsDomain.validatePathParameters(params))
57
+ .toThrow('Invalid ID parameter: {ID}. ID must not be a placeholder value')
58
+ })
59
+
60
+ it('should throw error for ID containing curly braces', () => {
61
+ expect.hasAssertions()
62
+ const params = { ID: 'some{invalid}id', Type: 'MSISDN' }
63
+
64
+ expect(() => participantsDomain.validatePathParameters(params))
65
+ .toThrow('Invalid ID parameter: some{invalid}id. ID must not be a placeholder value')
66
+ })
67
+
68
+ it('should throw error for placeholder {SubId} value', () => {
69
+ expect.hasAssertions()
70
+ const params = { ID: '123456', Type: 'MSISDN', SubId: '{SubId}' }
71
+
72
+ expect(() => participantsDomain.validatePathParameters(params))
73
+ .toThrow('Invalid SubId parameter: {SubId}. SubId must not be a placeholder value')
74
+ })
75
+
76
+ it('should pass validation for valid parameters', () => {
77
+ expect.hasAssertions()
78
+ const params = { ID: '123456', Type: 'MSISDN' }
79
+
80
+ // Should not throw
81
+ expect(() => participantsDomain.validatePathParameters(params)).not.toThrow()
82
+ })
83
+
84
+ it('should pass validation for valid parameters with SubId', () => {
85
+ expect.hasAssertions()
86
+ const params = { ID: '123456', Type: 'MSISDN', SubId: 'validSubId' }
87
+
88
+ // Should not throw
89
+ expect(() => participantsDomain.validatePathParameters(params)).not.toThrow()
90
+ })
91
+ })
92
+
51
93
  describe('getParticipantsByTypeAndID', () => {
52
94
  let sandbox
53
95
  // Initialize Metrics for testing
@@ -188,26 +188,6 @@ describe('participantEndpoint Facade', () => {
188
188
 
189
189
  expect(mockSendRequest.mock.lastCall[0].jwsSigner).toBeTruthy()
190
190
  })
191
-
192
- it('should add default timeout to sendRequest', async () => {
193
- const mockedConfig = {
194
- JWS_SIGN: false,
195
- FSPIOP_SOURCE_TO_SIGN: mockHubName,
196
- PROTOCOL_VERSIONS: fixtures.protocolVersionsDto(),
197
- HTTP_REQUEST_TIMEOUT_MS: 1000
198
- }
199
- jest.mock('../../../../src/lib/config', () => (mockedConfig))
200
- mockGetEndpoint.mockImplementation(() => 'https://example.com/12345')
201
- mockSendRequest.mockImplementation(async () => true)
202
- const ParticipantFacade = require(`${src}/models/participantEndpoint/facade`)
203
-
204
- const headers = {}
205
- const requestedParticipant = {}
206
- const endpointType = 'URL'
207
-
208
- await ParticipantFacade.sendRequest(headers, requestedParticipant, endpointType)
209
- expect(mockSendRequest.mock.calls[0][0].axiosRequestOptionsOverride.timeout).toBe(mockedConfig.HTTP_REQUEST_TIMEOUT_MS)
210
- })
211
191
  })
212
192
 
213
193
  describe('validateParticipant', () => {