account-lookup-service 17.12.3-snapshot.0 → 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 +12 -0
- package/package.json +1 -1
- package/src/domain/participants/participants.js +79 -25
- package/src/lib/config.js +1 -2
- package/src/models/oracle/facade.js +1 -4
- package/src/models/participantEndpoint/facade.js +2 -7
- package/test/unit/api/participants/{Type}/{ID}.test.js +85 -219
- package/test/unit/domain/participants/participants.test.js +42 -0
- package/test/unit/models/participantEndpoint/facade.test.js +0 -20
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
@@ -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
|
-
|
184
|
-
|
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
|
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
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
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
|
-
|
585
|
-
|
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
|
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
|
}
|
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
|
70
|
-
|
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
|
-
|
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',
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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',
|
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
|
-
|
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', () => {
|