Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PP 10451 handle selfservice 504 ledger gateway timeout #4150

Merged
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
8f7886a
PP-10451 Handle Selfservice 504 on Ledger Gateway Timeout
olatomgds Oct 20, 2023
8a38f4b
PP-10451 Handling Gateway timeout error in the self-service front-en…
olatomgds Oct 24, 2023
122fa7c
PP-10451 Handle gateway timeout error during transaction search.
olatomgds Oct 24, 2023
c4d013c
PP-10451 Handle gateway timeout error during transactions download.
olatomgds Oct 25, 2023
a2d9566
PP-10451 Handle gateway timeout error during transactions download.
olatomgds Oct 26, 2023
7bcfcc4
PP-10451 Handle gateway timeout error during transactions download.
olatomgds Oct 26, 2023
4dae539
PP-10451 Handle gateway timeout error during transactions download.
olatomgds Oct 26, 2023
bf0df4a
PP-10451 Handle gateway timeout error during transactions download.
olatomgds Oct 27, 2023
90ec075
PP-10451 Handle gateway timeout error during transactions download.
olatomgds Oct 31, 2023
b7f7daf
PP-10451 Handle gateway timeout error during transactions download.
olatomgds Oct 31, 2023
3eb1604
PP-10451 Handle gateway timeout error during transactions download.
olatomgds Nov 1, 2023
e741213
PP-10451 Handle gateway timeout error during transactions download.
olatomgds Nov 1, 2023
3fbd411
PP-10451 Handle gateway timeout error during transaction search.
olatomgds Oct 24, 2023
208ec91
PP-10451 Handle gateway timeout error during transactions download.
olatomgds Oct 25, 2023
01b56bf
PP-10451 Handle gateway timeout error during transactions download.
olatomgds Oct 26, 2023
6a01f3f
PP-10451 Handle gateway timeout error during transactions download.
olatomgds Oct 27, 2023
ebedcdb
PP-10451 Handle gateway timeout error during transactions download.
olatomgds Oct 31, 2023
fa9d21f
PP-10451 Handle gateway timeout error during transactions download.
olatomgds Oct 31, 2023
6482761
PP-10451 Handle gateway timeout error during transactions download.
olatomgds Nov 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions app/controllers/invite-user.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,7 @@ async function invite (req, res, next) {
lodash.set(req, 'session.pageData', { invitee })
res.redirect(303, formatServicePathsFor(paths.service.teamMembers.invite, externalServiceId))
} else if (!role) {
req.flash('genericError', 'Select the team member’s permission level')
lodash.set(req, 'session.pageData', { invitee })
res.redirect(303, formatServicePathsFor(paths.service.teamMembers.invite, externalServiceId))
next(new Error(`Cannot identify role from user input ${roleId}`))
} else {
try {
await userService.createInviteToJoinService(invitee, senderId, externalServiceId, role.name)
Expand Down
23 changes: 0 additions & 23 deletions app/controllers/invite-user.controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,4 @@ describe('invite user controller', () => {
sinon.assert.calledWith(req.flash, 'genericError', 'Enter a valid email address')
sinon.assert.calledWith(res.redirect, 303, `/service/${externalServiceId}/team-members/invite`)
})

it('should error if a role is not recognised', async () => {
const externalServiceId = 'some-external-service-id'
const unknownRoleId = '999'
const req = {
user: { externalId: 'some-ext-id', serviceIds: ['1'] },
body: {
'invitee-email': '[email protected]',
'role-input': unknownRoleId
},
service: {
externalId: externalServiceId
},
flash: sinon.stub()
}
const res = {
redirect: sinon.stub()
}

await inviteUserController.invite(req, res)
sinon.assert.calledWith(req.flash, 'genericError', 'Select the team member’s permission level')
sinon.assert.calledWith(res.redirect, 303, `/service/${externalServiceId}/team-members/invite`)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,14 @@ const fetchTransactionCsvWithHeader = function fetchTransactionCsvWithHeader (re
transactionService.logCsvFileStreamComplete(timestampStreamStart, filters, [accountId], req.user, false, req.account.type === 'live')
res.end()
}
const error = () => renderErrorView(req, res, 'Unable to download list of transactions.')
const error = () => {
const code = (res || {}).statusCode
if (code === 504) {
renderErrorView(req, res, 'Your request has timed out. Please apply more filters and try again.')
} else {
renderErrorView(req, res, 'Unable to download list of transactions.')
}
}
olatomgds marked this conversation as resolved.
Show resolved Hide resolved
const client = new Stream(data, complete, error)

res.setHeader('Content-disposition', `attachment; filename="${name}"`)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,16 @@
'use strict'

const nock = require('nock')
const sinon = require('sinon')

const paths = require('../../paths')
const getQueryStringForParams = require('../../utils/get-query-string-for-params')
const formatAccountPathsFor = require('../../utils/format-account-paths-for')
const { validGatewayAccountResponse } = require('../../../test/fixtures/gateway-account.fixtures')
const transactionListController = require('./transaction-list.controller')

// Setup
const gatewayAccountId = '651342'
const ledgerSearchParameters = {}
const EXTERNAL_GATEWAY_ACCOUNT_ID = 'an-external-id'
const LEDGER_TRANSACTION_PATH = '/v1/transaction?account_id=' + gatewayAccountId
const requestId = 'unique-request-id'
const headers = { 'x-request-id': requestId }
const ledgerMock = nock(process.env.LEDGER_URL, { reqheaders: headers })

function ledgerMockResponds (code, data, searchParameters) {
const queryString = getQueryStringForParams(searchParameters)
return ledgerMock.get(LEDGER_TRANSACTION_PATH + '&' + queryString)
.reply(code, data)
}

describe('The /transactions endpoint', () => {
const account = validGatewayAccountResponse(
Expand All @@ -42,40 +30,17 @@ describe('The /transactions endpoint', () => {
const res = {}
let next

afterEach(() => {
nock.cleanAll()
})

beforeEach(() => {
next = sinon.spy()
})

describe('Error getting transactions', () => {
it('should show error message on a bad request while retrieving the list of transactions', async () => {
const errorMessage = 'There is a problem with the payments platform. Please contact the support team.'
ledgerMockResponds(400, { 'message': errorMessage }, ledgerSearchParameters)

await transactionListController(req, res, next)
const expectedError = sinon.match.instanceOf(Error)
.and(sinon.match.has('message', 'Unable to retrieve list of transactions or card types'))
sinon.assert.calledWith(next, expectedError)
})

it('should show a generic error message on a ledger service error while retrieving the list of transactions', async () => {
ledgerMockResponds(500, { 'message': 'some error from connector' }, ledgerSearchParameters)

await transactionListController(req, res, next)
const expectedError = sinon.match.instanceOf(Error)
.and(sinon.match.has('message', 'Unable to retrieve list of transactions or card types'))
sinon.assert.calledWith(next, expectedError)
})

it('should show internal error message if any error happens while retrieving the list of transactions', async () => {
// No ledgerMock defined on purpose to mock a network failure

// No mocking defined on purpose to mock a network failure,
// This integration test will cover server errors outside the 500 and 504 defined in the Cypress test
await transactionListController(req, res, next)
const expectedError = sinon.match.instanceOf(Error)
.and(sinon.match.has('message', 'Unable to retrieve list of transactions or card types'))
.and(sinon.match.has('message', 'Unable to retrieve list of transactions or card types.'))
sinon.assert.calledWith(next, expectedError)
})
})
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/transactions/transaction-list.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ module.exports = async function showTransactionList (req, res, next) {
transactionService.search([accountId], filters.result),
client.getAllCardTypes()
])
} catch (err) {
return next(new Error('Unable to retrieve list of transactions or card types'))
} catch (error) {
return next(error)
}

const transactionsDownloadLink = formatAccountPathsFor(router.paths.account.transactions.download, req.account.external_id)
Expand Down
20 changes: 19 additions & 1 deletion app/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@ class DomainError extends Error {
}
}

class GatewayTimeoutError extends Error {
constructor (message) {
super(message)
this.name = this.constructor.name
Error.captureStackTrace(this, this.constructor)
}
}

class GenericServerError extends Error {
constructor (message) {
super(message)
this.name = this.constructor.name
Error.captureStackTrace(this, this.constructor)
}
}

/**
* Thrown when there is no authentication session for the user.
*/
Expand Down Expand Up @@ -96,5 +112,7 @@ module.exports = {
RegistrationSessionMissingError,
InvalidRegistationStateError,
InvalidConfigurationError,
ExpiredInviteError
ExpiredInviteError,
GatewayTimeoutError,
GenericServerError
}
13 changes: 12 additions & 1 deletion app/middleware/error-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const {
InvalidRegistationStateError,
InvalidConfigurationError,
ExpiredInviteError,
RESTClientError
RESTClientError, GatewayTimeoutError, GenericServerError
olatomgds marked this conversation as resolved.
Show resolved Hide resolved
} = require('../errors')
const paths = require('../paths')
const { renderErrorView, response } = require('../utils/response')
Expand Down Expand Up @@ -95,6 +95,17 @@ module.exports = function errorHandler (err, req, res, next) {
stack: err.stack
})
}

if (err instanceof GatewayTimeoutError) {
logger.info('Gateway Time out Error occurred on Transactions Search Page. Rendering error page')
return renderErrorView(req, res, err.message, 504)
}
olatomgds marked this conversation as resolved.
Show resolved Hide resolved

if (err instanceof GenericServerError) {
logger.info('General Error occurred on Transactions Search Page. Rendering error page')
return renderErrorView(req, res, err.message, 500)
}
olatomgds marked this conversation as resolved.
Show resolved Hide resolved

Sentry.captureException(err)
renderErrorView(req, res, 'There is a problem with the payments platform. Please contact the support team.', 500)
}
15 changes: 12 additions & 3 deletions app/services/transaction.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const getQueryStringForParams = require('../utils/get-query-string-for-params')
const userService = require('../services/user.service')
const transactionView = require('../utils/transaction-view')
const errorIdentifier = require('../models/error-identifier')
const { GatewayTimeoutError, GenericServerError } = require('../errors')

const connector = new ConnectorClient(process.env.CONNECTOR_URL)

Expand All @@ -22,10 +23,9 @@ const connectorRefundFailureReasons = {

const searchLedger = async function searchLedger (gatewayAccountIds = [], filters) {
try {
const transactions = await Ledger.transactions(gatewayAccountIds, filters)
return transactions
return await Ledger.transactions(gatewayAccountIds, filters)
} catch (error) {
throw new Error('GET_FAILED')
throw handleErrorForFailedSearch(error)
olatomgds marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down Expand Up @@ -148,6 +148,15 @@ function getStatusCodeForError (err, response) {
return status
}

function handleErrorForFailedSearch (err, response) {
const code = (response || {}).statusCode || (err || {}).errorCode
olatomgds marked this conversation as resolved.
Show resolved Hide resolved
if (code === 504) {
return new GatewayTimeoutError('Your request has timed out. Please apply more filters and try again.')
olatomgds marked this conversation as resolved.
Show resolved Hide resolved
} else {
return new GenericServerError('Unable to retrieve list of transactions or card types.')
}
}

module.exports = {
search: searchLedger,
csvSearchUrl,
Expand Down
4 changes: 2 additions & 2 deletions app/views/team-members/team-member-invite.njk
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ Invite a new team member - GOV.UK Pay
label: {
classes: "govuk-label--s"
},
checked: false,
checked: true,
hint: {
html: "View transactions<br>
Cannot refund payments<br>
Expand Down Expand Up @@ -155,7 +155,7 @@ Invite a new team member - GOV.UK Pay
label: {
classes: "govuk-label--s"
},
checked: false,
checked: true,
hint: {
html: "View transactions<br>
Cannot refund payments<br>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@
"chai-arrays": "2.2.0",
"chai-as-promised": "7.1.1",
"cheerio": "1.0.0-rc.12",
"chokidar-cli": "*",
"chokidar-cli": "latest",
olatomgds marked this conversation as resolved.
Show resolved Hide resolved
"csrf": "^3.1.0",
"cypress": "^12.16.0",
"dotenv": "16.3.1",
Expand Down
6 changes: 3 additions & 3 deletions test/cypress/integration/demo-payment/mock-cards-stripe.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ function setupYourPspStubs (opts = {}) {
gatewayAccountId,
gatewayAccountExternalId,
type: 'test',
paymentProvider: 'stripe',
paymentProvider: 'stripe'
})

const stripeAccountSetup = stripeAccountSetupStubs.getGatewayAccountStripeSetupSuccess({
gatewayAccountId,
gatewayAccountId
})

const stubs = [
user,
gatewayAccountByExternalId,
stripeAccountSetup,
stripeAccountSetup
]

cy.task('setupStubs', stubs)
Expand Down
Loading