Skip to content

Commit

Permalink
PP-11681 Replace request library with axios. (#4180)
Browse files Browse the repository at this point in the history
- Initial set up.
- Use latest version of 'pay-js-commons' library containing 'axios-base-client' code.
- Add 'clients/base' files for use with axis-base-client calls.
  • Loading branch information
JFSGDS authored Feb 26, 2024
1 parent 29b8497 commit ef521eb
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 14 deletions.
40 changes: 40 additions & 0 deletions app/services/clients/base/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use strict'

const requestLogger = require('./request-logger')
const { getRequestCorrelationIDField } = require('./request-context')
const { CORRELATION_HEADER } = require('../../../../config')

function transformRequestAddHeaders () {
const correlationId = getRequestCorrelationIDField()
const headers = {}
if (correlationId) {
headers[CORRELATION_HEADER] = correlationId
}
return headers
}

function onRequestStart (context) {
requestLogger.logRequestStart(context)
}

function onSuccessResponse (context) {
requestLogger.logRequestEnd(context)
}

function onFailureResponse (context) {
requestLogger.logRequestEnd(context)
requestLogger.logRequestFailure(context)
}

function configureClient (client, baseUrl) {
client.configure(baseUrl, {
transformRequestAddHeaders,
onRequestStart,
onSuccessResponse,
onFailureResponse
})
}

module.exports = {
configureClient
}
90 changes: 90 additions & 0 deletions app/services/clients/base/config.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
'use strict'

const nock = require('nock')
const sinon = require('sinon')
const proxyquire = require('proxyquire')
const { expect } = require('chai')
const { Client } = require('@govuk-pay/pay-js-commons/lib/utils/axios-base-client/axios-base-client')

const baseUrl = 'http://localhost:8000'
const app = 'an-app'

const logInfoSpy = sinon.spy()

function getConfigWithMocks (correlationId) {
const config = proxyquire('./config.js', {
'./request-context': {
getRequestCorrelationIDField: () => correlationId
},
'./request-logger': proxyquire('./request-logger', {
'../../../utils/logger': () => ({
info: logInfoSpy
})
})
})
return config
}

describe('Client config', () => {
beforeEach(() => {
logInfoSpy.resetHistory()
})

describe('Headers', () => {
it('should add correlation ID as header when correlation ID exists on request context', async () => {
const client = new Client(app)
const config = getConfigWithMocks('abc123')

config.configureClient(client, baseUrl)

nock(baseUrl)
.get('/')
.reply(200)

const response = await client.get('/', 'foo')

expect(response.status).to.equal(200)
expect(response.request.headers).to.have.property('x-request-id', 'abc123')
})

it('should not add correlation ID as header when correlation ID does not exist on request context', async () => {
const client = new Client(app)
const config = getConfigWithMocks()
config.configureClient(client, baseUrl)

nock(baseUrl)
.get('/')
.reply(200)

const response = await client.get('/', 'foo')
expect(response.status).to.equal(200)
expect(response.request.headers).to.not.have.key('x-request-id')
})
})

describe('Logging', () => {
it('should log request start', async () => {
const client = new Client(app)
const config = getConfigWithMocks('abc123')
config.configureClient(client, baseUrl)

nock(baseUrl)
.get('/')
.reply(200)

const response = await client.get('/', 'do something', {
additionalLoggingFields: { foo: 'bar' }
})

expect(response.status).to.equal(200)

sinon.assert.calledWith(logInfoSpy, 'Calling an-app to do something', {
service: app,
method: 'get',
url: '/',
description: 'do something',
foo: 'bar'
})
})
})
})
38 changes: 38 additions & 0 deletions app/services/clients/base/request-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict'

const { CORRELATION_ID } = require('@govuk-pay/pay-js-commons').logging.keys

const { AsyncLocalStorage } = require('async_hooks')
const { CORRELATION_HEADER } = require('./config')

const asyncLocalStorage = new AsyncLocalStorage()

function requestContextMiddleware (req, res, next) {
asyncLocalStorage.run({}, () => {
asyncLocalStorage.getStore()[CORRELATION_ID] = req.headers[CORRELATION_HEADER]
next()
})
}

function addField (key, value) {
if (asyncLocalStorage.getStore()) {
asyncLocalStorage.getStore()[key] = value
}
}

function getRequestCorrelationIDField () {
if (asyncLocalStorage.getStore()) {
return asyncLocalStorage.getStore()[CORRELATION_ID]
}
}

function getLoggingFields () {
return asyncLocalStorage.getStore()
}

module.exports = {
requestContextMiddleware,
addField,
getRequestCorrelationIDField,
getLoggingFields
}
53 changes: 53 additions & 0 deletions app/services/clients/base/request-logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const logger = require('../../../utils/logger')(__filename)

module.exports = {
logRequestStart: context => {
logger.info(`Calling ${context.service} to ${context.description}`, {
service: context.service,
method: context.method,
url: context.url,
description: context.description,
...context.additionalLoggingFields
})
},

logRequestEnd: (context) => {
const responseTime = (context.startTime && new Date() - context.startTime) || context.responseTime
logger.info(`${context.method} to ${context.url} ended - elapsed time: ${responseTime} ms`, {
service: context.service,
method: context.method,
url: context.url,
description: context.description,
response_time: responseTime,
status: context.status,
...context.additionalLoggingFields
})
},

logRequestFailure: (context) => {
let message = `Calling ${context.service} to ${context.description} failed`
if (context.retry) {
message = message + ' - request will be retried'
}

logger.info(message, {
service: context.service,
method: context.method,
url: context.url,
description: context.description,
status: context.status,
...context.additionalLoggingFields
})
},

logRequestError: (context, error) => {
logger.error(`Calling ${context.service} to ${context.description} threw exception`, {
service: context.service,
method: context.method,
url: context.url,
description: context.description,
error: error,
...context.additionalLoggingFields
})
}
}
28 changes: 15 additions & 13 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 package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
}
},
"dependencies": {
"@govuk-pay/pay-js-commons": "4.0.10",
"@govuk-pay/pay-js-commons": "4.0.16",
"@govuk-pay/pay-js-metrics": "^1.0.6",
"@sentry/node": "6.12.0",
"accessible-autocomplete": "2.0.4",
Expand Down

0 comments on commit ef521eb

Please sign in to comment.