Skip to content

Commit

Permalink
Add Redis info to health/info page (#402)
Browse files Browse the repository at this point in the history
https://eaflood.atlassian.net/browse/WATER-4099

Currently dummy info is being displayed for the Redis status. This PR will add the functionality to actually test the Redis status.
  • Loading branch information
Jozzey authored Sep 4, 2023
1 parent 004e4ce commit 3248d8d
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 27 deletions.
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,10 @@ AWS_ACCESS_KEY_ID=uploadaccesskey
AWS_SECRET_ACCESS_KEY=uploadsecretkey
AWS_MAINTENANCE_BUCKET=upload-bucket-gov-uk

# Redis config
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=

# Feature flags
ENABLE_REISSUING_BILLING_BATCHES=false
18 changes: 15 additions & 3 deletions app/services/health/info.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
const ChildProcess = require('child_process')
const util = require('util')
const exec = util.promisify(ChildProcess.exec)
const redis = require('@redis/client')

const ChargingModuleRequestLib = require('../../lib/charging-module-request.lib.js')
const RequestLib = require('../../lib/request.lib.js')
const LegacyRequestLib = require('../../lib/legacy-request.lib.js')

const redisConfig = require('../../../config/redis.config.js')
const servicesConfig = require('../../../config/services.config.js')

/**
Expand Down Expand Up @@ -62,10 +64,20 @@ async function _getVirusScannerData () {

async function _getRedisConnectivityData () {
try {
const { stdout, stderr } = await exec('redis-server --version')
return stderr ? `ERROR: ${stderr}` : stdout
const client = redis.createClient({
socket: {
host: redisConfig.host,
port: redisConfig.port
},
password: redisConfig.password
})

await client.connect()
await client.disconnect()

return 'Up and running'
} catch (error) {
return `ERROR: ${error.message}`
return 'Error connecting to Redis'
}
}

Expand Down
18 changes: 18 additions & 0 deletions config/redis.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict'

/**
* Config values used to connect to Redis
* @module RedisConfig
*/

// We require dotenv directly in each config file to support unit tests that depend on this this subset of config.
// Requiring dotenv in multiple places has no effect on the app when running for real.
require('dotenv').config()

const config = {
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
password: process.env.REDIS_PASSWORD
}

module.exports = config
50 changes: 50 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@hapi/hapi": "^21.3.2",
"@hapi/inert": "^7.1.0",
"@hapi/vision": "^7.0.2",
"@redis/client": "^1.5.9",
"@smithy/node-http-handler": "^2.0.4",
"bcryptjs": "^2.4.3",
"blipp": "^4.0.2",
Expand Down
88 changes: 64 additions & 24 deletions test/services/health/info.service.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const servicesConfig = require('../../../config/services.config.js')
// Things we need to stub
const ChargingModuleRequestLib = require('../../../app/lib/charging-module-request.lib.js')
const LegacyRequestLib = require('../../../app/lib/legacy-request.lib.js')
const redis = require('@redis/client')
const RequestLib = require('../../../app/lib/request.lib.js')

// Thing under test
Expand All @@ -40,11 +41,13 @@ describe('Info service', () => {
let chargingModuleRequestLibStub
let legacyRequestLibStub
let requestLibStub
let redisStub

beforeEach(() => {
chargingModuleRequestLibStub = Sinon.stub(ChargingModuleRequestLib, 'get')
legacyRequestLibStub = Sinon.stub(LegacyRequestLib, 'get')
requestLibStub = Sinon.stub(RequestLib, 'get')
redisStub = Sinon.stub(redis, 'createClient')

// These requests will remain unchanged throughout the tests. We do alter the ones to the AddressFacade and the
// water-api (foreground-service) though, which is why they are defined separately in each test.
Expand All @@ -61,6 +64,8 @@ describe('Info service', () => {
chargingModuleRequestLibStub
.withArgs('status')
.resolves(goodRequestResults.chargingModule)

// redisStub.returns({ connect: Sinon.fake().resolves(), disconnect: Sinon.fake().resolves() })
})

afterEach(() => {
Expand Down Expand Up @@ -88,14 +93,10 @@ describe('Info service', () => {
stdout: 'ClamAV 9.99.9/26685/Mon Oct 10 08:00:01 2022\n',
stderror: null
})
execStub
.withArgs('redis-server --version')
.resolves({
stdout: 'Redis server v=9.99.9 sha=00000000:0 malloc=jemalloc-5.2.1 bits=64 build=66bd629f924ac924\n',
stderror: null
})
const utilStub = { promisify: Sinon.stub().callsFake(() => execStub) }
InfoService = Proxyquire('../../../app/services/health/info.service', { util: utilStub })

redisStub.returns({ connect: Sinon.stub().resolves(), disconnect: Sinon.stub().resolves() })
})

it('returns details on each', async () => {
Expand All @@ -106,16 +107,62 @@ describe('Info service', () => {
])

expect(result.appData).to.have.length(10)

expect(result.virusScannerData).to.equal('ClamAV 9.99.9/26685/Mon Oct 10 08:00:01 2022\n')
expect(result.redisConnectivityData).to.equal('Up and running')
})
})

describe('when a service we check via the shell', () => {
describe('when Redis', () => {
beforeEach(async () => {
// In these scenarios everything is hunky-dory so we return 2xx responses from these services
requestLibStub
.withArgs(`${servicesConfig.addressFacade.url}/address-service/hola`)
.resolves(goodRequestResults.addressFacade)
legacyRequestLibStub.withArgs('water', 'health/info', false).resolves(goodRequestResults.app)

const execStub = Sinon
.stub()
.withArgs('clamdscan --version')
.resolves({
stdout: 'ClamAV 9.99.9/26685/Mon Oct 10 08:00:01 2022\n',
stderror: null
})
const utilStub = { promisify: Sinon.stub().callsFake(() => execStub) }
InfoService = Proxyquire('../../../app/services/health/info.service', { util: utilStub })
})

describe('is not running', () => {
beforeEach(async () => {
redisStub.returns({
connect: Sinon.stub().throwsException(new Error('Redis check went boom')),
disconnect: Sinon.stub().resolves()
})
})

it('handles the error and still returns a result for the other services', async () => {
const result = await InfoService.go()

expect(result).to.include([
'virusScannerData', 'redisConnectivityData', 'addressFacadeData', 'chargingModuleData', 'appData'
])
expect(result.appData).to.have.length(10)

expect(result.virusScannerData).to.equal('ClamAV 9.99.9/26685/Mon Oct 10 08:00:01 2022\n')
expect(result.redisConnectivityData).to.equal('Error connecting to Redis')
})
})
})

describe('when ClamAV', () => {
beforeEach(async () => {
// In these scenarios everything is hunky-dory so we return 2xx responses from these services
requestLibStub
.withArgs(`${servicesConfig.addressFacade.url}/address-service/hola`)
.resolves(goodRequestResults.addressFacade)
legacyRequestLibStub.withArgs('water', 'health/info', false).resolves(goodRequestResults.app)

redisStub.returns({ connect: Sinon.stub().resolves(), disconnect: Sinon.stub().resolves() })
})

describe('is not running', () => {
Expand All @@ -129,12 +176,6 @@ describe('Info service', () => {
stdout: null,
stderr: 'Could not connect to clamd'
})
execStub
.withArgs('redis-server --version')
.resolves({
stdout: null,
stderr: 'Could not connect to Redis'
})
const utilStub = { promisify: Sinon.stub().callsFake(() => execStub) }
InfoService = Proxyquire('../../../app/services/health/info.service', { util: utilStub })
})
Expand All @@ -148,7 +189,7 @@ describe('Info service', () => {
expect(result.appData).to.have.length(10)

expect(result.virusScannerData).to.startWith('ERROR:')
expect(result.redisConnectivityData).to.startWith('ERROR:')
expect(result.redisConnectivityData).to.equal('Up and running')
})
})

Expand All @@ -160,9 +201,6 @@ describe('Info service', () => {
.stub()
.withArgs('clamdscan --version')
.throwsException(new Error('ClamAV check went boom'))
execStub
.withArgs('redis-server --version')
.throwsException(new Error('Redis check went boom'))
const utilStub = { promisify: Sinon.stub().callsFake(() => execStub) }
InfoService = Proxyquire('../../../app/services/health/info.service', { util: utilStub })
})
Expand All @@ -176,7 +214,7 @@ describe('Info service', () => {
expect(result.appData).to.have.length(10)

expect(result.virusScannerData).to.startWith('ERROR:')
expect(result.redisConnectivityData).to.startWith('ERROR:')
expect(result.redisConnectivityData).to.equal('Up and running')
})
})
})
Expand All @@ -191,14 +229,10 @@ describe('Info service', () => {
stdout: 'ClamAV 9.99.9/26685/Mon Oct 10 08:00:01 2022\n',
stderror: null
})
execStub
.withArgs('redis-server --version')
.resolves({
stdout: 'Redis server v=9.99.9 sha=00000000:0 malloc=jemalloc-5.2.1 bits=64 build=66bd629f924ac924\n',
stderror: null
})
const utilStub = { promisify: Sinon.stub().callsFake(() => execStub) }
InfoService = Proxyquire('../../../app/services/health/info.service', { util: utilStub })

redisStub.returns({ connect: Sinon.stub().resolves(), disconnect: Sinon.stub().resolves() })
})

describe('cannot be reached because of a network error', () => {
Expand All @@ -221,6 +255,9 @@ describe('Info service', () => {

expect(result.addressFacadeData).to.startWith('ERROR:')
expect(result.appData[0].version).to.startWith('ERROR:')

expect(result.virusScannerData).to.equal('ClamAV 9.99.9/26685/Mon Oct 10 08:00:01 2022\n')
expect(result.redisConnectivityData).to.equal('Up and running')
})
})

Expand All @@ -244,6 +281,9 @@ describe('Info service', () => {

expect(result.addressFacadeData).to.startWith('ERROR:')
expect(result.appData[0].version).to.startWith('ERROR:')

expect(result.virusScannerData).to.equal('ClamAV 9.99.9/26685/Mon Oct 10 08:00:01 2022\n')
expect(result.redisConnectivityData).to.equal('Up and running')
})
})
})
Expand Down

0 comments on commit 3248d8d

Please sign in to comment.