Skip to content

Commit 57e7554

Browse files
Reverted health check removal
1 parent 069e533 commit 57e7554

File tree

14 files changed

+474
-1
lines changed

14 files changed

+474
-1
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import request from 'supertest';
2+
import { getHealthCheck } from '../../../services/health-check/get-health-check';
3+
import { logInfo, logError, logDebug } from '../../../middleware/logging';
4+
import { buildTestApp } from '../../../__builders__/test-app';
5+
import { healthCheck } from '../health-check';
6+
7+
jest.mock('../../../middleware/logging');
8+
jest.mock('../../../services/health-check/get-health-check');
9+
10+
describe('GET /health', () => {
11+
const testApp = buildTestApp('/health', healthCheck);
12+
13+
it('should return HTTP status code 200', async () => {
14+
getHealthCheck.mockResolvedValue(expectedHealthCheckBase());
15+
const res = await request(testApp).get('/health');
16+
17+
expect(res.statusCode).toBe(200);
18+
expect(res.body).toEqual(expectedHealthCheckBase());
19+
expect(logDebug).toHaveBeenCalledWith('Health check completed');
20+
});
21+
22+
it('should return 500 if getHealthCheck if it cannot provide a health check', async () => {
23+
getHealthCheck.mockRejectedValue('some-error');
24+
const res = await request(testApp).get('/health');
25+
26+
expect(res.statusCode).toBe(500);
27+
expect(logInfo).not.toHaveBeenCalled();
28+
expect(logError).toHaveBeenCalledWith('Health check error', 'some-error');
29+
});
30+
});
31+
32+
const expectedHealthCheckBase = (db_writable = true, db_connected = true) => ({
33+
details: {
34+
database: getExpectedDatabase(db_writable, db_connected)
35+
}
36+
});
37+
38+
const getExpectedDatabase = (isWritable, isConnected) => {
39+
const baseConf = {
40+
connection: isConnected,
41+
writable: isWritable
42+
};
43+
44+
return !isWritable
45+
? {
46+
...baseConf,
47+
error: 'some-error'
48+
}
49+
: baseConf;
50+
};
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import express from 'express';
2+
import { getHealthCheck } from '../../services/health-check/get-health-check';
3+
import { logError, logDebug } from '../../middleware/logging';
4+
5+
export const healthCheck = express.Router();
6+
7+
healthCheck.get('/', async (req, res, next) => {
8+
try {
9+
const status = await getHealthCheck();
10+
11+
if (status.details.database) {
12+
logDebug('Health check completed');
13+
res.status(200).json(status);
14+
} else {
15+
logError('Health check failed', status);
16+
res.status(503).json(status);
17+
}
18+
} catch (err) {
19+
logError('Health check error', err);
20+
next(err);
21+
}
22+
});

services/ehr-out-service/src/app.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import swaggerUi from 'swagger-ui-express';
33

44
import { middleware } from './middleware/logging';
55
import { registrationRequests } from './api/registration-request';
6+
import { healthCheck } from './api/health-check/health-check';
67
import swaggerDocument from './swagger.json';
78

89
const app = express();
910
app.use(express.json());
1011

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

1416
// eslint-disable-next-line no-unused-vars
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { getHealthCheck } from '../get-health-check';
2+
import { config } from '../../../config';
3+
4+
describe('getHealthCheck', () => {
5+
const { nhsEnvironment } = config();
6+
7+
it('should return static health check object', async () => {
8+
const expected = {
9+
version: '1',
10+
description: 'Health of ehr-out-service',
11+
nhsEnvironment: nhsEnvironment,
12+
details: {
13+
database: {
14+
type: 'dynamodb'
15+
}
16+
}
17+
};
18+
19+
const actual = await getHealthCheck();
20+
21+
expect(actual).toMatchObject(expected);
22+
});
23+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { config } from '../../config';
2+
import { EhrTransferTracker } from '../database/dynamodb/dynamo-ehr-transfer-tracker';
3+
4+
export const getHealthCheck = async () => {
5+
const { nhsEnvironment } = config();
6+
const db = EhrTransferTracker.getInstance();
7+
8+
return {
9+
version: '1',
10+
description: 'Health of ehr-out-service',
11+
nhsEnvironment: nhsEnvironment,
12+
details: {
13+
database: {
14+
type: 'dynamodb',
15+
status: `is tableName configured: ${db.tableName !== undefined}`
16+
}
17+
}
18+
};
19+
};
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import app from '../../../app';
2+
import request from 'supertest';
3+
import { getHealthCheck } from '../../../services/health-check/get-health-check';
4+
import { logInfo, logError } from '../../../middleware/logging';
5+
6+
jest.mock('../../../config/logging');
7+
jest.mock('../../../services/health-check/get-health-check');
8+
jest.mock('../../../middleware/logging');
9+
10+
const mockErrorResponse = 'some-error';
11+
12+
describe('GET /health', () => {
13+
describe('all dependencies are available', () => {
14+
beforeEach(() => {
15+
getHealthCheck.mockReturnValue(Promise.resolve(expectedHealthCheckBase()));
16+
});
17+
18+
it('should return HTTP status code 200', (done) => {
19+
request(app).get('/health').expect(200).end(done);
20+
});
21+
22+
it('should return details of the response from getHealthCheck', (done) => {
23+
request(app)
24+
.get('/health')
25+
.expect((res) => {
26+
expect(res.body).toEqual(expectedHealthCheckBase());
27+
})
28+
.end(done);
29+
});
30+
31+
it('should call health check service with no parameters', (done) => {
32+
request(app)
33+
.get('/health')
34+
.expect(() => {
35+
expect(getHealthCheck).toHaveBeenCalledTimes(1);
36+
})
37+
.end(done);
38+
});
39+
40+
it('should call logInfo with result when all dependencies are ok', (done) => {
41+
request(app)
42+
.get('/health')
43+
.expect(() => {
44+
expect(logInfo).toHaveBeenCalledWith('Health check successful');
45+
})
46+
.end(done);
47+
});
48+
});
49+
50+
describe('S3 is not writable', () => {
51+
beforeEach(() => {
52+
getHealthCheck.mockReturnValue(Promise.resolve(expectedHealthCheckBase(false)));
53+
});
54+
55+
it('should return 503 status if s3 writable is false', (done) => {
56+
request(app).get('/health').expect(503).end(done);
57+
});
58+
59+
it('should return details of the response from getHealthCheck when s3 writable is false', (done) => {
60+
request(app)
61+
.get('/health')
62+
.expect((res) => {
63+
expect(res.body).toEqual(expectedHealthCheckBase(false));
64+
})
65+
.end(done);
66+
});
67+
68+
it('should call logError with the health check result if s3 writable is false', (done) => {
69+
request(app)
70+
.get('/health')
71+
.expect(() => {
72+
expect(logError).toHaveBeenCalledWith(
73+
'Health check failed',
74+
expectedHealthCheckBase(false)
75+
);
76+
})
77+
.end(done);
78+
});
79+
});
80+
81+
describe('s3 is not available', () => {
82+
beforeEach(() => {
83+
getHealthCheck.mockReturnValue(Promise.resolve(expectedHealthCheckBase(true, false)));
84+
});
85+
86+
it('should return 503 status if s3 available is false', (done) => {
87+
request(app).get('/health').expect(503).end(done);
88+
});
89+
90+
it('should return details of the response from getHealthCheck when the s3 available is false', (done) => {
91+
request(app)
92+
.get('/health')
93+
.expect((res) => {
94+
expect(res.body).toEqual(expectedHealthCheckBase(true, false));
95+
})
96+
.end(done);
97+
});
98+
99+
it('should call logError with the health check result if s3 available is false', (done) => {
100+
request(app)
101+
.get('/health')
102+
.expect(() => {
103+
expect(logError).toHaveBeenCalledWith(
104+
'Health check failed',
105+
expectedHealthCheckBase(true, false)
106+
);
107+
})
108+
.end(done);
109+
});
110+
});
111+
112+
describe('s3 is not available', () => {
113+
beforeEach(() => {
114+
getHealthCheck.mockReturnValue(Promise.resolve(expectedHealthCheckBase(false, false)));
115+
});
116+
117+
it('should return 503 if s3 is not writable', (done) => {
118+
request(app).get('/health').expect(503).end(done);
119+
});
120+
121+
it('should call logError with the health check result if s3 is not writable', (done) => {
122+
request(app)
123+
.get('/health')
124+
.expect(() => {
125+
expect(logError).toHaveBeenCalledWith(
126+
'Health check failed',
127+
expectedHealthCheckBase(false, false)
128+
);
129+
})
130+
.end(done);
131+
});
132+
});
133+
134+
describe('getHealthCheck throws error', () => {
135+
beforeEach(() => {
136+
getHealthCheck.mockRejectedValue(Error('some-error'));
137+
});
138+
139+
it('should return 500 if getHealthCheck if it cannot provide a health check', (done) => {
140+
request(app).get('/health').expect(500).end(done);
141+
});
142+
143+
it('should logError if getHealthCheck throws an error', (done) => {
144+
request(app)
145+
.get('/health')
146+
.expect(() => {
147+
expect(logError).toHaveBeenCalledTimes(1);
148+
expect(logError).toHaveBeenCalledWith('Health check error', expect.anything());
149+
})
150+
.end(done);
151+
});
152+
153+
it('should update the log event for any unexpected error', (done) => {
154+
getHealthCheck.mockReturnValue(Promise.resolve(expectedHealthCheckBase(false)));
155+
156+
request(app)
157+
.get('/health')
158+
.expect(() => {
159+
expect(logError).toHaveBeenCalledTimes(1);
160+
expect(logError).toHaveBeenCalledWith(
161+
'Health check failed',
162+
expectedHealthCheckBase(false)
163+
);
164+
})
165+
.end(done);
166+
});
167+
});
168+
});
169+
170+
const expectedS3Base = (isWritable, isConnected) => {
171+
const s3Base = {
172+
available: isConnected,
173+
writable: isWritable
174+
};
175+
return !isWritable
176+
? {
177+
...s3Base,
178+
error: mockErrorResponse
179+
}
180+
: s3Base;
181+
};
182+
183+
const expectedHealthCheckBase = (s3_writable = true, s3_connected = true) => ({
184+
details: {
185+
filestore: expectedS3Base(s3_writable, s3_connected)
186+
}
187+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import express from 'express';
2+
import { logError, logInfo } from '../../middleware/logging';
3+
import { getHealthCheck } from '../../services/health-check/get-health-check';
4+
5+
export const healthCheck = express.Router();
6+
7+
healthCheck.get('/', (req, res, next) => {
8+
getHealthCheck()
9+
.then((status) => {
10+
if (status.details.filestore.writable && status.details.filestore.available) {
11+
logInfo('Health check successful');
12+
res.status(200).json(status);
13+
} else {
14+
logError('Health check failed', status);
15+
res.status(503).json(status);
16+
}
17+
})
18+
.catch((err) => {
19+
logError('Health check error', err);
20+
next(err);
21+
});
22+
});

services/ehr-repo/src/app.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { options } from './config/logging';
99
import * as logging from './middleware/logging';
1010
import swaggerDocument from './swagger.json';
1111
import helmet from 'helmet';
12+
import { healthCheck } from './api/health-check/health-check';
1213

1314
httpContext.enable();
1415

@@ -24,7 +25,7 @@ app.use(
2425
})
2526
);
2627
app.use(requestLogger(options));
27-
28+
app.use('/health', logging.middleware, healthCheck);
2829
app.use('/patients', logging.middleware, patients);
2930
app.use('/messages', logging.middleware, messages);
3031
app.use('/fragments', logging.middleware, fragments);

0 commit comments

Comments
 (0)