Skip to content

Commit

Permalink
feat: add /consentRequests/{ID}/error handler (#47)
Browse files Browse the repository at this point in the history
* feat: add consentRequest/{ID}/error handler

* chore: fix test

* chore: resolve audit
  • Loading branch information
kleyow authored Mar 31, 2021
1 parent b909d7a commit 5e6f56c
Show file tree
Hide file tree
Showing 12 changed files with 407 additions and 9 deletions.
16 changes: 16 additions & 0 deletions audit-resolve.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,22 @@
"decision": "ignore",
"madeAt": 1615381391877,
"expiresAt": 1617973359417
},
"1654|@mojaloop/central-services-health>@mojaloop/central-services-shared>@mojaloop/event-sdk>grpc>protobufjs>yargs>y18n": {
"decision": "fix",
"madeAt": 1617135289860
},
"1654|@mojaloop/central-services-shared>@mojaloop/event-sdk>grpc>protobufjs>yargs>y18n": {
"decision": "fix",
"madeAt": 1617135289860
},
"1654|@mojaloop/event-sdk>grpc>protobufjs>yargs>y18n": {
"decision": "fix",
"madeAt": 1617135289860
},
"1654|@mojaloop/central-services-shared>widdershins>yargs>y18n": {
"decision": "fix",
"madeAt": 1617135306288
}
},
"rules": {},
Expand Down
13 changes: 10 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/domain/consentRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import * as types from '~/interface/types'

/**
* @function forwardConsentRequestsIDRequestError
* @description Generic function to handle sending `PUT .../consentRequests/error` back to the FSPIOP-Source
* @description Generic function to handle sending `PUT .../consentRequests/{ID}/error` back to the FSPIOP-Source
* @param {string} path Callback endpoint path
* @param {string} consentRequestsRequestId the ID of the consentRequest
* @param {HapiUtil.Dictionary<string>} headers Headers object of the request
Expand Down
2 changes: 2 additions & 0 deletions src/interface/api-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ paths:
$ref: '../../node_modules/@mojaloop/api-snippets/thirdparty/openapi3/paths/consentRequests.yaml'
/consentRequests/{ID}:
$ref: '../../node_modules/@mojaloop/api-snippets/thirdparty/openapi3/paths/consentRequests_ID.yaml'
/consentRequests/{ID}/error:
$ref: '../../node_modules/@mojaloop/api-snippets/thirdparty/openapi3/paths/consentRequests_ID_error.yaml'
/consents:
$ref: '../../node_modules/@mojaloop/api-snippets/thirdparty/openapi3/paths/consents.yaml'
/consents/{ID}:
Expand Down
47 changes: 47 additions & 0 deletions src/interface/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,53 @@ paths:
$ref: '#/components/responses/501'
'503':
$ref: '#/components/responses/503'
'/consentRequests/{ID}/error':
put:
tags:
- consentRequests
operationId: NotifyErrorConsentRequests
summary: NotifyErrorConsentRequests
description: >
DFSP responds to the PISP if something went wrong with validating an OTP
or secret.
parameters:
- $ref: '#/components/parameters/ID'
- $ref: '#/components/parameters/Content-Length'
- $ref: '#/components/parameters/Content-Type'
- $ref: '#/components/parameters/Date'
- $ref: '#/components/parameters/X-Forwarded-For'
- $ref: '#/components/parameters/FSPIOP-Source'
- $ref: '#/components/parameters/FSPIOP-Destination'
- $ref: '#/components/parameters/FSPIOP-Encryption'
- $ref: '#/components/parameters/FSPIOP-Signature'
- $ref: '#/components/parameters/FSPIOP-URI'
- $ref: '#/components/parameters/FSPIOP-HTTP-Method'
requestBody:
description: Error information returned.
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorInformationObject'
responses:
'200':
$ref: '#/components/responses/200'
'400':
$ref: '#/components/responses/400'
'401':
$ref: '#/components/responses/401'
'403':
$ref: '#/components/responses/403'
'404':
$ref: '#/components/responses/404'
'405':
$ref: '#/components/responses/405'
'406':
$ref: '#/components/responses/406'
'501':
$ref: '#/components/responses/501'
'503':
$ref: '#/components/responses/503'
/consents:
post:
tags:
Expand Down
87 changes: 87 additions & 0 deletions src/server/handlers/consentRequests/{ID}/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*****
License
--------------
Copyright © 2020 Mojaloop Foundation The Mojaloop files are made available by the Mojaloop Foundation
under the Apache License, Version 2.0 (the 'License') and you may not
use these files except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 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. Contributors
--------------
This is the official list of the Mojaloop project contributors for this file. Names of the original
copyright holders (individuals or organizations) should be listed with a '*' in the first column.
People who have contributed from an organization can be listed under the organization that actually
holds the copyright for their contributions (see the Gates Foundation organization for an example).
Those individuals should have their names indented and be marked with a '-'. Email address can be
added optionally within square brackets <email>.
* Gates Foundation
- Name Surname <name.surname@gatesfoundation.com>
- Kevin Leyow <sridhar.voruganti@modusbox.com>
--------------
******/
'use strict'

import { Request, ResponseToolkit, ResponseObject } from '@hapi/hapi'
import Logger from '@mojaloop/central-services-logger'
import { ReformatFSPIOPError, APIErrorObject } from '@mojaloop/central-services-error-handling'
import { Enum } from '@mojaloop/central-services-shared'
import { AuditEventAction } from '@mojaloop/event-sdk'
import { forwardConsentRequestsIdRequestError } from '~/domain/consentRequests'
import { getSpanTags } from '~/shared/util'

/**
* summary: NotifyErrorConsentRequests
* description: The HTTP request PUT /consentRequests/{ID}/error is used to inform the client
* about accounts error.
* parameters: body, accept, content-length, content-type, date, x-forwarded-for, fspiop-source,
* fspiop-destination, fspiop-encryption,fspiop-signature, fspiop-uri fspiop-http-method
* produces: application/json
* responses: 200, 400, 401, 403, 404, 405, 406, 501, 503
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const put = async (_context: unknown, request: Request, h: ResponseToolkit): Promise<ResponseObject> => {
const span = (request as any).span
const consentRequestsId: string = request.params.ID
const payload = request.payload as APIErrorObject

try {
const tags: { [id: string]: string } = getSpanTags(
request,
Enum.Events.Event.Type.CONSENT_REQUEST,
Enum.Events.Event.Action.PUT,
{ consentRequestsId })

span?.setTags(tags)
await span?.audit({
headers: request.headers,
payload: request.payload
}, AuditEventAction.start)

// Note: calling async function without `await`
forwardConsentRequestsIdRequestError(
Enum.EndPoints.FspEndpointTemplates.TP_CONSENT_REQUEST_PUT_ERROR,
consentRequestsId,
request.headers,
payload,
span
)
.catch(err => {
// Do nothing with the error - forwardConsentRequestsIdRequestError takes care of async errors
Logger.error('ConsentRequests::put:error - forwardConsentRequestsIdRequestError async handler threw an unhandled error')
Logger.error(ReformatFSPIOPError(err))
})

return h.response().code(Enum.Http.ReturnCodes.OK.CODE)
} catch (err) {
const fspiopError = ReformatFSPIOPError(err)
Logger.error(fspiopError)
throw fspiopError
}
}

export default {
put
}
9 changes: 9 additions & 0 deletions src/server/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import Authorizations from './thirdpartyRequests/transactions/{ID}/authorization
import ConsentRequestsId from './consentRequests/{ID}'
import Accounts from './accounts/{ID}'
import AccountsByUserIdError from './accounts/{ID}/error'
import ConsentRequestsByIdError from './consentRequests/{ID}/error'
import { wrapWithHistogram } from '~/shared/histogram'
const OpenapiBackend = Util.OpenapiBackend

Expand Down Expand Up @@ -164,6 +165,14 @@ export default {
['success']
]
),
NotifyErrorConsentRequests: wrapWithHistogram(
ConsentRequestsByIdError.put,
[
'consentRequestsId_error_put',
'Put consentRequestsId error request',
['success']
]
),
validationFail: OpenapiBackend.validationFail,
notFound: OpenapiBackend.notFound,
methodNotAllowed: OpenapiBackend.methodNotAllowed
Expand Down
5 changes: 5 additions & 0 deletions test/features/consentRequests.feature
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,8 @@ Scenario: PatchConsentRequest
Given thirdparty-api-adapter server
When I send a 'PatchConsentRequest' request
Then I get a response with a status code of '202'

Scenario: NotifyErrorConsentRequests
Given thirdparty-api-adapter server
When I send a 'NotifyErrorConsentRequests' request
Then I get a response with a status code of '200'
41 changes: 41 additions & 0 deletions test/step-definitions/consentRequests.step.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const feature = loadFeature(featurePath)

const mockForwardConsentRequestsRequest = jest.spyOn(ConsentRequests, 'forwardConsentRequestsRequest')
const mockForwardConsentRequestsIdRequest = jest.spyOn(ConsentRequests, 'forwardConsentRequestsIdRequest')
const mockForwardConsentRequestsIdErrorRequest = jest.spyOn(ConsentRequests, 'forwardConsentRequestsIdRequestError')
const mockData = JSON.parse(JSON.stringify(TestData))

defineFeature(feature, (test): void => {
Expand Down Expand Up @@ -261,4 +262,44 @@ defineFeature(feature, (test): void => {
expect(mockForwardConsentRequestsIdRequest).toHaveBeenCalledWith(...expected)
})
})

test('NotifyErrorConsentRequests', ({ given, when, then }): void => {

const consentRequestsError = mockData.genericThirdpartyError
const reqHeaders = Object.assign(consentRequestsError.headers, {
date: 'Tue, 02 Mar 2021 10:10:10 GMT',
accept: 'application/json'
})
const request = {
method: 'PUT',
url: '/consentRequests/b82348b9-81f6-42ea-b5c4-80667d5740fe/error',
headers: reqHeaders,
payload: consentRequestsError.payload
}

given('thirdparty-api-adapter server', async (): Promise<Server> => {
server = await ThirdPartyAPIAdapterService.run(Config)
return server
})

when('I send a \'NotifyErrorConsentRequests\' request', async (): Promise<ServerInjectResponse> => {
mockForwardConsentRequestsIdErrorRequest.mockResolvedValueOnce()
response = await server.inject(request)
return response
})

then('I get a response with a status code of \'200\'', (): void => {
const expected = [
'/consentRequests/{{ID}}/error',
'b82348b9-81f6-42ea-b5c4-80667d5740fe',
expect.objectContaining(request.headers),
request.payload,
undefined
]

expect(response.statusCode).toBe(200)
expect(response.result).toBeNull()
expect(mockForwardConsentRequestsIdErrorRequest).toHaveBeenCalledWith(...expected)
})
})
})
23 changes: 23 additions & 0 deletions test/unit/data/mockData.json
Original file line number Diff line number Diff line change
Expand Up @@ -525,5 +525,28 @@
"payload": {
"authToken": "141321"
}
},
"consentRequestsThirdpartyError": {
"headers": {
"fspiop-source": "dfspA",
"fspiop-destination": "pispA"
},
"params": {
"ID": "a5bbfd51-d9fc-4084-961a-c2c2221a31e0"
},
"payload": {
"errorInformation": {
"errorCode": "6000",
"errorDescription": "Generic third party error",
"extensionList": {
"extension": [
{
"key": "test",
"value": "test"
}
]
}
}
}
}
}
Loading

0 comments on commit 5e6f56c

Please sign in to comment.