-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f115a59
commit 4261ba3
Showing
9 changed files
with
204 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/* Business logic for application health checks */ | ||
import { Request, Response } from 'express'; | ||
import mongoose, { STATES } from 'mongoose'; | ||
import logger from '../logging/logger'; | ||
import { HEALTH_CHECK_RESPONSES } from '../constants'; | ||
|
||
/** | ||
* Business logic for application health checks | ||
*/ | ||
class HealthCheckController { | ||
/** | ||
* Performs sanity checks to determine if the app is healthy | ||
* @param req incoming request from client. | ||
* @param res response to return to client. | ||
* @returns Returns a promise indicating completion of the async function. | ||
*/ | ||
static async checkHealth(req: Request, res: Response): Promise<void> { | ||
try { | ||
/* Check if all MongoDB connections pass the ping check */ | ||
for (const connection of mongoose.connections) { | ||
if (connection.readyState === STATES.connected) { | ||
await connection.db.admin().ping(); | ||
} | ||
} | ||
|
||
/* Response to client */ | ||
res.status(200).json({ | ||
message: HEALTH_CHECK_RESPONSES._200_SUCCESS | ||
}); | ||
} catch (error) { | ||
logger.error('Error occured during database health check (failure): ' + error); | ||
res.status(503).json({ | ||
message: HEALTH_CHECK_RESPONSES._503_FAILURE | ||
}); | ||
} | ||
} | ||
} | ||
|
||
export default HealthCheckController; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* Routes for application health checks */ | ||
import express from 'express'; | ||
import rateLimit from 'express-rate-limit'; | ||
import { AUTH_RESPONSES } from '../constants'; | ||
import logger from '../logging/logger'; | ||
import Config from 'simple-app-config'; | ||
import HealthCheckController from '../controllers/healthCheckController'; | ||
|
||
const router = express.Router(); | ||
|
||
/* Rate limit register API based on IP */ | ||
const rateLimitHealthCheck = rateLimit({ | ||
windowMs: Config.get('RATE_LIMITING.HEALTH_CHECKS.HEALTH_CHECK.WINDOW'), | ||
max: Config.get('RATE_LIMITING.HEALTH_CHECKS.HEALTH_CHECK.THRESHOLD'), | ||
handler: (req, res) => { | ||
logger.info(`The register rate limit has been reached for IP ${req.ip}`); | ||
res.status(429).json({ | ||
message: AUTH_RESPONSES._429_RATE_LIMIT_EXCEEDED | ||
}); | ||
} | ||
}); | ||
|
||
/* Register route */ | ||
router.get('/healthCheck', rateLimitHealthCheck, HealthCheckController.checkHealth); | ||
|
||
export default router; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { Request, Response } from 'express'; | ||
import { MockRequest, MockResponse, createRequest, createResponse } from 'node-mocks-http'; | ||
import HealthCheckController from '../../src/controllers/healthCheckController'; | ||
import { HEALTH_CHECK_RESPONSES } from '../../src/constants'; | ||
import mongoose from 'mongoose'; | ||
import { MongoMemoryServer } from 'mongodb-memory-server'; | ||
|
||
describe('Health Check Controller Tests', () => { | ||
let request: MockRequest<Request>; | ||
let response: MockResponse<Response>; | ||
let mongoServer: MongoMemoryServer; | ||
let uri: string; | ||
|
||
beforeAll(async () => { | ||
mongoServer = await MongoMemoryServer.create(); | ||
uri = mongoServer.getUri(); | ||
await mongoose.connect(uri); | ||
}); | ||
|
||
beforeEach(() => { | ||
response = createResponse(); | ||
jest.restoreAllMocks(); | ||
}); | ||
|
||
afterAll(async () => { | ||
await mongoose.disconnect(); | ||
await mongoServer.stop(); | ||
}); | ||
|
||
describe('checkHealth', () => { | ||
it('should succeed if all DB connections are healthy', async () => { | ||
/* Create mock request */ | ||
request = createRequest(); | ||
|
||
/* Set up mocks */ | ||
mongoose.mongo.Admin.prototype.ping = jest.fn().mockResolvedValueOnce(true); | ||
|
||
/* Test against expected */ | ||
await HealthCheckController.checkHealth(request, response); | ||
expect(response.statusCode).toBe(200); | ||
expect(response._getJSONData().message).toBe(HEALTH_CHECK_RESPONSES._200_SUCCESS); | ||
}); | ||
|
||
it("should fail if any DB connections aren't healthy", async () => { | ||
/* Create mock request */ | ||
request = createRequest(); | ||
|
||
/* Set up mocks*/ | ||
mongoose.mongo.Admin.prototype.ping = jest | ||
.fn() | ||
.mockRejectedValueOnce(new Error('Failed to ping')); | ||
|
||
/* Test against expected */ | ||
await HealthCheckController.checkHealth(request, response); | ||
expect(response.statusCode).toBe(503); | ||
expect(response._getJSONData().message).toBe(HEALTH_CHECK_RESPONSES._503_FAILURE); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
/* Unit tests for the auth routes */ | ||
import request from 'supertest'; | ||
import { API_URLS_V1, AUTH_RESPONSES } from '../../src/constants'; | ||
import App from '../../src/app'; | ||
import Config from 'simple-app-config'; | ||
import HealthCheckController from '../../src/controllers/healthCheckController'; | ||
|
||
/* Mock the controller functions */ | ||
jest.mock('../../src/controllers/healthCheckController', () => ({ | ||
checkHealth: jest.fn().mockImplementation((req, res) => { | ||
res.sendStatus(200); | ||
}) | ||
})); | ||
|
||
describe('Health Check Routes Tests', () => { | ||
let appInstance: App; | ||
|
||
beforeAll(() => { | ||
appInstance = new App(); | ||
}); | ||
|
||
beforeEach(() => { | ||
jest.restoreAllMocks(); | ||
}); | ||
|
||
describe('GET /healthCheck', () => { | ||
it('should call HealthCheckController.checkHealth', async () => { | ||
/* Make the API call */ | ||
const expressInstance = appInstance.getExpressApp(); | ||
await request(expressInstance).get(`${API_URLS_V1.HEALTH_CHECK}/healthCheck`).send({}); | ||
|
||
/* Test against expected */ | ||
expect(HealthCheckController.checkHealth).toHaveBeenCalled(); | ||
}); | ||
|
||
it('should fail when the rate limit is exceeded', async () => { | ||
/* Call API `threshold` times so that next call will cause rating limiting */ | ||
const expressInstance = appInstance.getExpressApp(); | ||
const threshold: number = Config.get('RATE_LIMITING.HEALTH_CHECKS.HEALTH_CHECK.THRESHOLD'); | ||
for (let i = 0; i < threshold; i++) { | ||
await request(expressInstance).get(`${API_URLS_V1.HEALTH_CHECK}/healthCheck`).send({}); | ||
} | ||
|
||
/* Call API */ | ||
const response = await request(expressInstance) | ||
.get(`${API_URLS_V1.HEALTH_CHECK}/healthCheck`) | ||
.send({}); | ||
|
||
/* Test against expected */ | ||
expect(HealthCheckController.checkHealth).toHaveBeenCalled(); | ||
expect(response.statusCode).toBe(429); | ||
expect(response.body.message).toBe(AUTH_RESPONSES._429_RATE_LIMIT_EXCEEDED); | ||
}); | ||
}); | ||
}); |