Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import request from 'supertest';
import { getHealthCheck } from '../../../services/health-check/get-health-check';
import { logInfo, logError, logDebug } from '../../../middleware/logging';
import { buildTestApp } from '../../../__builders__/test-app';
import { healthCheck } from '../health-check';

jest.mock('../../../middleware/logging');
jest.mock('../../../services/health-check/get-health-check');

describe('GET /health', () => {
const testApp = buildTestApp('/health', healthCheck);

it('should return HTTP status code 200', async () => {
getHealthCheck.mockResolvedValue(expectedHealthCheckBase());
const res = await request(testApp).get('/health');

expect(res.statusCode).toBe(200);
expect(res.body).toEqual(expectedHealthCheckBase());
expect(logDebug).toHaveBeenCalledWith('Health check completed');
});

it('should return 500 if getHealthCheck if it cannot provide a health check', async () => {
getHealthCheck.mockRejectedValue('some-error');
const res = await request(testApp).get('/health');

expect(res.statusCode).toBe(500);
expect(logInfo).not.toHaveBeenCalled();
expect(logError).toHaveBeenCalledWith('Health check error', 'some-error');
});
});

const expectedHealthCheckBase = (db_writable = true, db_connected = true) => ({
details: {
database: getExpectedDatabase(db_writable, db_connected)
}
});

const getExpectedDatabase = (isWritable, isConnected) => {
const baseConf = {
connection: isConnected,
writable: isWritable
};

return !isWritable
? {
...baseConf,
error: 'some-error'
}
: baseConf;
};
22 changes: 22 additions & 0 deletions services/ehr-out-service/src/api/health-check/health-check.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import express from 'express';
import { getHealthCheck } from '../../services/health-check/get-health-check';
import { logError, logDebug } from '../../middleware/logging';

export const healthCheck = express.Router();

healthCheck.get('/', async (req, res, next) => {
try {
const status = await getHealthCheck();

if (status.details.database) {
logDebug('Health check completed');
res.status(200).json(status);
} else {
logError('Health check failed', status);
res.status(503).json(status);
}
} catch (err) {
logError('Health check error', err);
next(err);
}
});
2 changes: 2 additions & 0 deletions services/ehr-out-service/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import swaggerUi from 'swagger-ui-express';

import { middleware } from './middleware/logging';
import { registrationRequests } from './api/registration-request';
import { healthCheck } from './api/health-check/health-check';
import swaggerDocument from './swagger.json';

const app = express();
app.use(express.json());

app.use('/swagger', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
app.use('/health', middleware, healthCheck);
app.use('/registration-requests', middleware, registrationRequests);

// eslint-disable-next-line no-unused-vars
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { getHealthCheck } from '../get-health-check';
import { config } from '../../../config';

describe('getHealthCheck', () => {
const { nhsEnvironment } = config();

it('should return static health check object', async () => {
const expected = {
version: '1',
description: 'Health of ehr-out-service',
nhsEnvironment: nhsEnvironment,
details: {
database: {
type: 'dynamodb'
}
}
};

const actual = await getHealthCheck();

expect(actual).toMatchObject(expected);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { config } from '../../config';
import { EhrTransferTracker } from '../database/dynamodb/dynamo-ehr-transfer-tracker';

export const getHealthCheck = async () => {
const { nhsEnvironment } = config();
const db = EhrTransferTracker.getInstance();

return {
version: '1',
description: 'Health of ehr-out-service',
nhsEnvironment: nhsEnvironment,
details: {
database: {
type: 'dynamodb',
status: `is tableName configured: ${db.tableName !== undefined}`
}
}
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import app from '../../../app';
import request from 'supertest';
import { getHealthCheck } from '../../../services/health-check/get-health-check';
import { logInfo, logError } from '../../../middleware/logging';

jest.mock('../../../config/logging');
jest.mock('../../../services/health-check/get-health-check');
jest.mock('../../../middleware/logging');

const mockErrorResponse = 'some-error';

describe('GET /health', () => {
describe('all dependencies are available', () => {
beforeEach(() => {
getHealthCheck.mockReturnValue(Promise.resolve(expectedHealthCheckBase()));
});

it('should return HTTP status code 200', (done) => {
request(app).get('/health').expect(200).end(done);
});

it('should return details of the response from getHealthCheck', (done) => {
request(app)
.get('/health')
.expect((res) => {
expect(res.body).toEqual(expectedHealthCheckBase());
})
.end(done);
});

it('should call health check service with no parameters', (done) => {
request(app)
.get('/health')
.expect(() => {
expect(getHealthCheck).toHaveBeenCalledTimes(1);
})
.end(done);
});

it('should call logInfo with result when all dependencies are ok', (done) => {
request(app)
.get('/health')
.expect(() => {
expect(logInfo).toHaveBeenCalledWith('Health check successful');
})
.end(done);
});
});

describe('S3 is not writable', () => {
beforeEach(() => {
getHealthCheck.mockReturnValue(Promise.resolve(expectedHealthCheckBase(false)));
});

it('should return 503 status if s3 writable is false', (done) => {
request(app).get('/health').expect(503).end(done);
});

it('should return details of the response from getHealthCheck when s3 writable is false', (done) => {
request(app)
.get('/health')
.expect((res) => {
expect(res.body).toEqual(expectedHealthCheckBase(false));
})
.end(done);
});

it('should call logError with the health check result if s3 writable is false', (done) => {
request(app)
.get('/health')
.expect(() => {
expect(logError).toHaveBeenCalledWith(
'Health check failed',
expectedHealthCheckBase(false)
);
})
.end(done);
});
});

describe('s3 is not available', () => {
beforeEach(() => {
getHealthCheck.mockReturnValue(Promise.resolve(expectedHealthCheckBase(true, false)));
});

it('should return 503 status if s3 available is false', (done) => {
request(app).get('/health').expect(503).end(done);
});

it('should return details of the response from getHealthCheck when the s3 available is false', (done) => {
request(app)
.get('/health')
.expect((res) => {
expect(res.body).toEqual(expectedHealthCheckBase(true, false));
})
.end(done);
});

it('should call logError with the health check result if s3 available is false', (done) => {
request(app)
.get('/health')
.expect(() => {
expect(logError).toHaveBeenCalledWith(
'Health check failed',
expectedHealthCheckBase(true, false)
);
})
.end(done);
});
});

describe('s3 is not available', () => {
beforeEach(() => {
getHealthCheck.mockReturnValue(Promise.resolve(expectedHealthCheckBase(false, false)));
});

it('should return 503 if s3 is not writable', (done) => {
request(app).get('/health').expect(503).end(done);
});

it('should call logError with the health check result if s3 is not writable', (done) => {
request(app)
.get('/health')
.expect(() => {
expect(logError).toHaveBeenCalledWith(
'Health check failed',
expectedHealthCheckBase(false, false)
);
})
.end(done);
});
});

describe('getHealthCheck throws error', () => {
beforeEach(() => {
getHealthCheck.mockRejectedValue(Error('some-error'));
});

it('should return 500 if getHealthCheck if it cannot provide a health check', (done) => {
request(app).get('/health').expect(500).end(done);
});

it('should logError if getHealthCheck throws an error', (done) => {
request(app)
.get('/health')
.expect(() => {
expect(logError).toHaveBeenCalledTimes(1);
expect(logError).toHaveBeenCalledWith('Health check error', expect.anything());
})
.end(done);
});

it('should update the log event for any unexpected error', (done) => {
getHealthCheck.mockReturnValue(Promise.resolve(expectedHealthCheckBase(false)));

request(app)
.get('/health')
.expect(() => {
expect(logError).toHaveBeenCalledTimes(1);
expect(logError).toHaveBeenCalledWith(
'Health check failed',
expectedHealthCheckBase(false)
);
})
.end(done);
});
});
});

const expectedS3Base = (isWritable, isConnected) => {
const s3Base = {
available: isConnected,
writable: isWritable
};
return !isWritable
? {
...s3Base,
error: mockErrorResponse
}
: s3Base;
};

const expectedHealthCheckBase = (s3_writable = true, s3_connected = true) => ({
details: {
filestore: expectedS3Base(s3_writable, s3_connected)
}
});
22 changes: 22 additions & 0 deletions services/ehr-repo/src/api/health-check/health-check.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import express from 'express';
import { logError, logInfo } from '../../middleware/logging';
import { getHealthCheck } from '../../services/health-check/get-health-check';

export const healthCheck = express.Router();

healthCheck.get('/', (req, res, next) => {
getHealthCheck()
.then((status) => {
if (status.details.filestore.writable && status.details.filestore.available) {
logInfo('Health check successful');
res.status(200).json(status);
} else {
logError('Health check failed', status);
res.status(503).json(status);
}
})
.catch((err) => {
logError('Health check error', err);
next(err);
});
});
3 changes: 2 additions & 1 deletion services/ehr-repo/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { options } from './config/logging';
import * as logging from './middleware/logging';
import swaggerDocument from './swagger.json';
import helmet from 'helmet';
import { healthCheck } from './api/health-check/health-check';

httpContext.enable();

Expand All @@ -24,7 +25,7 @@ app.use(
})
);
app.use(requestLogger(options));

app.use('/health', logging.middleware, healthCheck);
app.use('/patients', logging.middleware, patients);
app.use('/messages', logging.middleware, messages);
app.use('/fragments', logging.middleware, fragments);
Expand Down
Loading
Loading