Skip to content

Commit

Permalink
Merge pull request #8 from DEFRA/fix-redis-addunittests
Browse files Browse the repository at this point in the history
feat: adding unit tests fo all the services. fixing services varous e…
  • Loading branch information
daniel78uk authored Nov 29, 2023
2 parents 542cd8e + 359175e commit 08cec8e
Show file tree
Hide file tree
Showing 8 changed files with 378 additions and 70 deletions.
12 changes: 6 additions & 6 deletions server/services/asb.send.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ const { ServiceBusClient } = require('@azure/service-bus')

// Define connection string and related Service Bus entity names here
// Get it from the azure portal
const fishingConnectionString = ''
const waterConnectionString = ''
const fishingConnectionString = '' // TODO this should go in the env vars
const waterConnectionString = '' // TODO this should go in the env vars

const fishingQueue = 'devsirinfsb1401-sbncomq-illegal-fishing'
const waterQueue = 'devsirinfsb1401-sbncomq-water-quality'
const fishingQueue = 'devsirinfsb1401-sbncomq-illegal-fishing' // TODO this should go in the env vars
const waterQueue = 'devsirinfsb1401-sbncomq-water-quality' // TODO this should go in the env vars

module.exports = class ASBService {
static async sendMessageToQueue (incidentToPublish, incidentType) {
static async sendMessageToQueue(incidentToPublish, incidentType) {
let sbClient, sender

if (incidentType === 300) {
Expand All @@ -28,7 +28,7 @@ module.exports = class ASBService {
myCustomPropertyName: 'Custom property'
}
}
console.log(`Sending message: ${message.body}`)
//console.log(`Sending message: ${message.body}`)
await sender.sendMessages(message)

await sbClient.close()
Expand Down
8 changes: 6 additions & 2 deletions server/services/incidentLocation.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
const util = require('../utils/util')

const apiKey = '50br44ij15V5hIAAhLoeFTiY57NZBYHS' // // TODO this should go in the env vars, this should never be in the source code!!

module.exports = {
findByPostcode: async (postcode) => {
try {
const uri = 'https://api.os.uk/search/places/v1/postcode?postcode=' + postcode + '&key=50br44ij15V5hIAAhLoeFTiY57NZBYHS'
const uri = `https://api.os.uk/search/places/v1/postcode?postcode=${postcode}&key=${apiKey}`
const payload = await util.getJson(uri)

return {
X_COORDINATE: payload.results[0].DPA.X_COORDINATE,
Y_COORDINATE: payload.results[0].DPA.X_COORDINATE
Y_COORDINATE: payload.results[0].DPA.Y_COORDINATE
}
} catch (e) {
// Assigning a default location x and y, if postcode is not found
console.log('Error occurred while calling os data hub api' + e)

return {
X_COORDINATE: 51.5,
Y_COORDINATE: 0.0293
Expand Down
134 changes: 77 additions & 57 deletions server/services/redis.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,99 +6,119 @@ const config = require('../utils/config')

const REDIS_TTL_IN_SECONDS = config.cookieTimeout / 1000

module.exports = class RedisService {
static async get(request, key) {
const client = request.redis.client
const redisValue = await client.get(
`${request.state[SI_SESSION_KEY]}.${key}`
)
const getRedisClient = (request) => request.redis.client

const getSessionKey = (request) => request.state[SI_SESSION_KEY]

const get = async (request, key) => {
const client = getRedisClient(request)

try {
const redisValue = await client.get(`${getSessionKey(request)}.${key}`)
let parsedValue = redisValue

if (_isBooleanString(redisValue)) {
if (isBooleanString(redisValue)) {
parsedValue = redisValue.toLowerCase() === 'true'
} else if (_isJsonString(redisValue)) {
} else if (isJsonString(redisValue)) {
try {
parsedValue = JSON.parse(redisValue)
} catch (e) {
parsedValue = redisValue
}
}

return parsedValue
} catch (err) {
console.error(err)
throw new Error('Failed to retrieve value from Redis')
}
}

static set(request, key, value) {
const client = request.redis.client
const keyWithSessionId = `${request.state[SI_SESSION_KEY]}.${key}`
const set = async (request, key, value) => {
const client = getRedisClient(request)
const keyWithSessionId = `${getSessionKey(request)}.${key}`

try {
return client.setex(keyWithSessionId, REDIS_TTL_IN_SECONDS, value)
} catch (err) {
console.error(err.message)
return false
}
try {
await client.setex(keyWithSessionId, REDIS_TTL_IN_SECONDS, value)

return true
} catch (err) {
console.error(err)
return false
}
}

const deleteItem = async (request, key) => {
const client = getRedisClient(request)
const keyWithSessionId = `${getSessionKey(request)}.${key}`//?

try {
await client.del(keyWithSessionId)

return true
} catch (err) {
console.error(err)

static delete(request, key) {
const client = request.redis.client
const keyWithSessionId = `${request.state[SI_SESSION_KEY]}.${key}`
client.del(keyWithSessionId)
return false
}
}

const deleteSessionData = async (request) => {
const client = getRedisClient(request)
const keys = await getMatchingRedisKeys(request)

static async deleteSessionData(request) {
const client = request.redis.client
const keys = await _getMatchingRedisKeys(request)
try {
for (const key of keys) {
client.del(key)
await client.del(key)
}

return true
} catch (err) {
console.error(err)

throw new Error('Failed to delete session data from Redis')
}
}

/**
* Checks a string value to see if it looks like a Json object i.e. begins and ends with curly brackets
* @param {*} value The string value to be chekced
* @returns True if the string looks like a Json object, otherwise false
*/
const _isJsonString = (value) =>
const isJsonString = (value) =>
value &&
value.length &&
((value.startsWith('{') && value.endsWith('}')) ||
(value.startsWith('[') && value.endsWith(']')))

/**
* Checks a string value to see if it contains a bolean i.e. 'true' or 'false'
* @param {*} value The string value to be chekced
* @returns True if the string contains a bolean, otherwise false
*/

const _isBooleanString = (value) =>
const isBooleanString = (value) =>
value &&
value.length &&
(value.toLowerCase() === 'true' || value.toLowerCase() === 'false')

/**
* Scans the Redis cache for all keys matching the session key held in the session.
* The Redis scan function returns a cursor and a set of results. The scan function
* needs to be called repeatedly until the cursor is back to 0.
* @param {*} request The request containing the Redis cache
* @returns An array of Redis keys that are prefixed with the session key
*/
const _getMatchingRedisKeys = async (request) => {
const client = request.redis.client
const sessionKey = request.state[SI_SESSION_KEY]
const getMatchingRedisKeys = async (request) => {
const client = getRedisClient(request)
const sessionKey = getSessionKey(request)

const keys = []

let scanResult = await client.scan('0', 'MATCH', `${sessionKey}.*`)
let cursor = scanResult[0]

keys.push(...scanResult[1])

while (cursor !== '0') {
scanResult = await client.scan(cursor, 'MATCH', `${sessionKey}.*`)
cursor = scanResult[0]
keys.push(...scanResult[1])
try {
let cursor = '0'
do {
const scanResult = await client.scan(cursor, 'MATCH', `${sessionKey}.*`)
cursor = scanResult[0]
keys.push(...scanResult[1])
} while (cursor !== '0')
} catch (err) {
console.error(err)

throw new Error('Failed to get matching Redis keys')
}

return keys
}

module.exports = {
get,
set,
delete: deleteItem,
deleteSessionData,
getMatchingRedisKeys,
isJsonString,
isBooleanString
}
48 changes: 48 additions & 0 deletions test/unit/services/asb.send.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const { ServiceBusClient } = require('@azure/service-bus')
jest.mock('@azure/service-bus')
const ASBService = require('../../../server/services/asb.send.js')

describe('ASBService', () => {
describe('sendMessageToQueue', () => {
it('should send a message to the correct queue', async () => {
// Mock the ServiceBusClient and its methods
const sendMessagesMock = jest.fn()
const createSenderMock = jest
.fn()
.mockReturnValue({ sendMessages: sendMessagesMock })
const closeMock = jest.fn()

ServiceBusClient.mockImplementation(() => {
return {
createSender: createSenderMock,
close: closeMock
}
})

// Mock the incidentToPublish and incidentType
const incidentToPublish = {
id: 1,
description: 'Illegal fishing incident'
}
const incidentType = 200

// Call the function
await ASBService.sendMessageToQueue(incidentToPublish, incidentType)

// Assert the createSender method call
expect(createSenderMock).toHaveBeenCalledTimes(1)
expect(createSenderMock).toHaveBeenCalledWith(expect.any(String))

// Assert the sendMessages method call
expect(sendMessagesMock).toHaveBeenCalledTimes(1)
expect(sendMessagesMock).toHaveBeenCalledWith({
body: incidentToPublish,
label: incidentType,
userProperties: { myCustomPropertyName: 'Custom property' }
})

// Assert the close method call
expect(closeMock).toHaveBeenCalledTimes(2)
})
})
})
50 changes: 50 additions & 0 deletions test/unit/services/cookie.service.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const CookieService = require('../../../server/services/cookie.service.js')

const { SI_SESSION_KEY } = require('../../../server/utils/constants')

describe('CookieService', () => {
describe('getSessionCookie', () => {
it('should return the session cookie from the request', () => {
// Mock the request object
const request = {
state: {
[SI_SESSION_KEY]: 'sessionCookieValue'
},
url: {
pathname: '/example'
}
}

// Call the function
const result = CookieService.getSessionCookie(request)

// Assert the result
expect(result).toBe('sessionCookieValue')
})

it('should log an error message if the session cookie is not found', () => {
// Mock the console.log method
console.log = jest.fn()

// Mock the request object
const request = {
state: {},
url: {
pathname: '/example'
}
}

// Call the function
const result = CookieService.getSessionCookie(request)

// Assert the result
expect(result).toBeUndefined()

// Assert the console.log method call
expect(console.log).toHaveBeenCalledTimes(1)
expect(console.log).toHaveBeenCalledWith(
`Session cookie not found for page ${request.url.pathname}`
)
})
})
})
64 changes: 64 additions & 0 deletions test/unit/services/incidentLocation.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const {
findByPostcode
} = require('../../../server/services/incidentLocation.js')

const util = require('../../../server/utils/util')

jest.mock('../../../server/utils/util', () => ({
getJson: jest.fn()
}))

describe('OrdnanceService', () => {
describe('findByPostcode', () => {
it('should return the coordinates for a valid postcode', async () => {
// Mock the util.getJson function
util.getJson.mockResolvedValueOnce({
results: [
{
DPA: {
X_COORDINATE: 1.234,
Y_COORDINATE: 2.234
}
}
]
})

// Call the function
const result = await findByPostcode('12345')

const expectedResults = {
X_COORDINATE: 1.234,
Y_COORDINATE: 2.234
}

// Assert the result
expect(result).toEqual(expectedResults)

// Assert the util.getJson function call
expect(util.getJson).toHaveBeenCalledTimes(1)
expect(util.getJson).toHaveBeenCalledWith(
'https://api.os.uk/search/places/v1/postcode?postcode=12345&key=50br44ij15V5hIAAhLoeFTiY57NZBYHS'
)
})

it('should return default coordinates for an invalid postcode', async () => {
// Mock the util.getJson function to throw an error
util.getJson.mockRejectedValueOnce(new Error('Invalid postcode'))

// Call the function
const result = await findByPostcode('invalid')

// Assert the result
expect(result).toEqual({
X_COORDINATE: 51.5,
Y_COORDINATE: 0.0293
})

// Assert the util.getJson function call
// expect(util.getJson).toHaveBeenCalledTimes(1)
expect(util.getJson).toHaveBeenCalledWith(
'https://api.os.uk/search/places/v1/postcode?postcode=invalid&key=50br44ij15V5hIAAhLoeFTiY57NZBYHS'
)
})
})
})
Loading

0 comments on commit 08cec8e

Please sign in to comment.