Skip to content

Commit 29514b0

Browse files
committed
ADDED: Health check endpoints (requirement for Issue #2).
1 parent 3b0b10f commit 29514b0

10 files changed

+195
-1
lines changed

.nycrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"**/*.spec.ts",
99
"**/*.interfaces.ts",
1010
"src/cities/cities.ts",
11+
"src/health/health.ts",
1112
"src/swagger/swagger.ts"
1213
],
1314
"extension": [

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ This sample uses the [Serverless Application Framework](https://serverless.com/)
2929
- Test coverage report with [Istanbul](https://istanbul.js.org/) and [Coveralls](https://coveralls.io) - _so you know your weak spots._
3030
- Generated [Swagger](https://swagger.io/) documentation for the endpoints, which works well with [SwaggerHub](https://app.swaggerhub.com) - _the expected description of your API._
3131
- Multiple layers in the code to separate concerns and independently test them - _avoid monolith and complexity._
32+
- Health check endpoints - _to quickly test your service._
3233
- Dependency checks with [David](https://david-dm.org/) and [BitHound](https://www.bithound.io/) - _because the majority of your app is not your code._
3334
- Sample CRUD implementation (in progress) - _to see it all in action_.
3435

@@ -119,6 +120,10 @@ The `src/swagger` folder contains the `/swagger.json` endpoint which exports the
119120

120121
You can also reference the `swagger.json` URL when you publish your documentation via [SwaggerHub](https://app.swaggerhub.com), as you can see on the SwaggerHub page of this sample: https://app.swaggerhub.com/apis/balassy/serverless-sample.
121122

123+
### Health check endpoints
124+
125+
The `/health/check` and the `/health/check/detailed` endpoints in the `src/health` folder are provided to run quick checks on your API after deployment.
126+
122127
## Developer tasks
123128

124129
### Test the service locally
@@ -169,6 +174,8 @@ Verify that the deployment is completed successfully by opening the URL displaye
169174
https://<your_custom_domain_name>/api/swagger.json
170175
```
171176

177+
Note that this endpoint always downloads the Swagger documentation from the live, published API, even if the code is running locally!
178+
172179
If you don't want to deploy your code, just want to peek into the deployment package, you can run:
173180

174181
```bash

serverless.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,48 @@ functions:
4242
method: get
4343
cors: true
4444

45+
getHealthCheck:
46+
handler: src/health/health.getHealthCheck
47+
description: Returns the result of a quick health check of the API.
48+
49+
events:
50+
- http:
51+
path: health/check
52+
method: get
53+
cors: true
54+
documentation:
55+
summary: Returns the result of a quick health check of the API.
56+
tags:
57+
- Health
58+
description: Returns the result of a quick health check of the API.
59+
methodResponses:
60+
- statusCode: '200'
61+
description: Returned when the operation is completed successfully.
62+
responseModels:
63+
"application/json": GetHealthCheck
64+
- ${file(./swagger/error-responses/internal-server-error.yml)}
65+
66+
getHealthCheckDetailed:
67+
handler: src/health/health.getHealthCheckDetailed
68+
description: Returns the result of a detailed health check of the API.
69+
70+
events:
71+
- http:
72+
path: health/detailed
73+
method: get
74+
cors: true
75+
documentation:
76+
summary: Returns the result of a detailed health check of the API.
77+
tags:
78+
- Health
79+
description: Returns the result of a detailed health check of the API.
80+
methodResponses:
81+
- statusCode: '200'
82+
description: Returned when the operation is completed successfully.
83+
responseModels:
84+
"application/json": GetHealthCheckDetailed
85+
- ${file(./swagger/error-responses/internal-server-error.yml)}
86+
4587
getCity:
4688
handler: src/cities/cities.getCity
4789
description: Returns a single city.

src/health/health.controller.spec.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { expect } from 'chai';
2+
import { Chance } from 'chance';
3+
4+
import { ApiContext, ApiEvent, ApiHandler, ApiResponse } from '../../shared/api.interfaces';
5+
import { HttpStatusCode } from '../../shared/http-status-codes';
6+
import { callSuccess } from '../../test';
7+
import { ApiResponseParsed } from '../../test/test.interfaces';
8+
import { HealthController } from './health.controller';
9+
import { GetHealthCheckDetailedResult, GetHealthCheckResult } from './health.interfaces';
10+
11+
const chance: Chance.Chance = new Chance();
12+
13+
describe('HealthController', () => {
14+
let controller: HealthController;
15+
16+
beforeEach(() => {
17+
controller = new HealthController();
18+
});
19+
20+
describe('getHealthCheck function', () => {
21+
it('should return HTTP 200 OK', async () => {
22+
const response: ApiResponseParsed<GetHealthCheckResult> = await callSuccess<GetHealthCheckResult>(controller.getHealthCheck);
23+
expect(response.statusCode).to.equal(HttpStatusCode.Ok);
24+
});
25+
26+
it('should return success', async () => {
27+
const response: ApiResponseParsed<GetHealthCheckResult> = await callSuccess<GetHealthCheckResult>(controller.getHealthCheck);
28+
expect(response.parsedBody.success).to.equal(true);
29+
});
30+
});
31+
32+
describe('getHealthCheckDetailed function', () => {
33+
let testData: {
34+
requestId: string;
35+
};
36+
37+
beforeEach(() => {
38+
testData = {
39+
requestId: chance.word()
40+
};
41+
});
42+
43+
it('should return HTTP 200 OK', async () => {
44+
const response: ApiResponseParsed<GetHealthCheckDetailedResult> = await callSuccessDetailed(controller.getHealthCheckDetailed, testData.requestId);
45+
expect(response.statusCode).to.equal(HttpStatusCode.Ok);
46+
});
47+
48+
it('should return success', async () => {
49+
const response: ApiResponseParsed<GetHealthCheckDetailedResult> = await callSuccessDetailed(controller.getHealthCheckDetailed, testData.requestId);
50+
expect(response.parsedBody.success).to.equal(true);
51+
});
52+
53+
it('should return the request ID', async () => {
54+
const response: ApiResponseParsed<GetHealthCheckDetailedResult> = await callSuccessDetailed(controller.getHealthCheckDetailed, testData.requestId);
55+
expect(response.parsedBody.requestId).to.equal(testData.requestId);
56+
});
57+
58+
// tslint:disable-next-line arrow-return-shorthand (Long function body.)
59+
function callSuccessDetailed(handler: ApiHandler, requestId?: string): Promise<ApiResponseParsed<GetHealthCheckDetailedResult>> {
60+
// tslint:disable-next-line typedef (Well-known constructor.)
61+
return new Promise((resolve, reject) => {
62+
let event: ApiEvent = <ApiEvent> {};
63+
if (requestId) {
64+
event = <ApiEvent> {
65+
requestContext: {
66+
requestId
67+
}
68+
};
69+
}
70+
71+
handler(event, <ApiContext> {}, (error?: Error, result?: ApiResponse): void => {
72+
if (typeof result === 'undefined') {
73+
reject('No result was returned by the handler!');
74+
return;
75+
}
76+
77+
const parsedResult: ApiResponseParsed<GetHealthCheckDetailedResult> = result as ApiResponseParsed<GetHealthCheckDetailedResult>;
78+
parsedResult.parsedBody = JSON.parse(result.body) as GetHealthCheckDetailedResult;
79+
resolve(parsedResult);
80+
});
81+
});
82+
}
83+
});
84+
});

src/health/health.controller.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ApiCallback, ApiContext, ApiEvent, ApiHandler } from '../../shared/api.interfaces';
2+
import { ResponseBuilder } from '../../shared/response-builder';
3+
import { GetHealthCheckDetailedResult, GetHealthCheckResult } from './health.interfaces';
4+
5+
export class HealthController {
6+
public getHealthCheck: ApiHandler = (event: ApiEvent, context: ApiContext, callback: ApiCallback): void => {
7+
const result: GetHealthCheckResult = {
8+
success: true
9+
};
10+
11+
ResponseBuilder.ok<GetHealthCheckResult>(result, callback);
12+
}
13+
14+
public getHealthCheckDetailed: ApiHandler = (event: ApiEvent, context: ApiContext, callback: ApiCallback): void => {
15+
const result: GetHealthCheckDetailedResult = {
16+
requestId: event.requestContext.requestId,
17+
success: true
18+
};
19+
20+
ResponseBuilder.ok<GetHealthCheckDetailedResult>(result, callback);
21+
}
22+
}

src/health/health.interfaces.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export interface GetHealthCheckResult {
2+
success: boolean;
3+
}
4+
5+
export interface GetHealthCheckDetailedResult {
6+
requestId: string;
7+
success: boolean;
8+
}

src/health/health.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { ApiHandler } from '../../shared/api.interfaces';
2+
import { HealthController } from './health.controller';
3+
4+
const controller: HealthController = new HealthController();
5+
6+
export const getHealthCheck: ApiHandler = controller.getHealthCheck;
7+
export const getHealthCheckDetailed: ApiHandler = controller.getHealthCheckDetailed;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
name: GetHealthCheckDetailed
2+
contentType: "application/json"
3+
schema:
4+
type: object
5+
properties:
6+
success:
7+
type: boolean
8+
description: Returns true if the health check has found everything all right on the server.
9+
requestId:
10+
type: string
11+
description: Returns the unique identifier of the incoming HTTP request.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
name: GetHealthCheck
2+
contentType: "application/json"
3+
schema:
4+
type: object
5+
properties:
6+
success:
7+
type: boolean
8+
description: Returns true if the health check has found everything all right on the server.

swagger/documentation.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ api:
1212
tags:
1313
- name: City
1414
description: Manipulate cities.
15+
- name: Health
16+
description: Health check.
1517
models:
1618
- ${file(./swagger/error-responses/error-response.yml)}
17-
- ${file(./src/cities/swagger/get-city-response.yml)}
19+
- ${file(./src/cities/swagger/get-city-response.yml)}
20+
- ${file(./src/health/swagger/get-health-check.yml)}
21+
- ${file(./src/health/swagger/get-health-check-detailed.yml)}

0 commit comments

Comments
 (0)