Skip to content

Commit

Permalink
Merge pull request #10 from nestjs/feature/predefined-health-checks
Browse files Browse the repository at this point in the history
[WIP] Feature/predefined health checks
  • Loading branch information
BrunnerLivio authored Nov 7, 2018
2 parents 15c97ec + 5ecbaec commit 12fdce9
Show file tree
Hide file tree
Showing 57 changed files with 2,200 additions and 14,162 deletions.
3 changes: 3 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ tslint.json
tsconfig.json
.prettierrc
documentation
sample
.github
e2e
18 changes: 11 additions & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@

language: node_js

services:
- docker

cache:
directories:
- "node_modules"

node_js:
- "8"
- "9"
- "10"

env:
global:
- "GH_REF=github.com/BrunnerLivio/nest-terminus.git"

before_install:
- npm i -g npm@latest

install:
- npm install
script:
- npm run test
- sudo service mysql stop
- docker-compose build && docker-compose run lib
deploy:
- provider: script
# Have to use `&&` because of issue https://github.com/travis-ci/dpl/issues/673
Expand All @@ -31,7 +39,3 @@ deploy:
node_js: "10"
tags: true
tag: beta

env:
global:
- "GH_REF=github.com/BrunnerLivio/nest-terminus.git"
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM node:latest

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install

COPY . .
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.2.1/wait /wait
RUN chmod +x /wait

CMD /wait && npm test
25 changes: 25 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
version: "3"

services:
mysql:
image: mysql:5.7.22
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test
ports:
- "3306:3306"
networks:
- overlay

lib:
build:
context: .
depends_on:
- mysql
networks:
- overlay
environment:
WAIT_HOSTS: mysql:3306

networks:
overlay:
103 changes: 103 additions & 0 deletions e2e/health-checks/database.health.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { INestApplication, DynamicModule } from '@nestjs/common';
import {
DatabaseHealthIndicator,
TerminusModuleAsyncOptions,
TerminusModule,
TerminusModuleOptions,
} from '../../lib';
import { NestFactory } from '@nestjs/core';

import { TypeOrmModule } from '@nestjs/typeorm';
import Axios from 'axios';

describe('Database Health', () => {
let app: INestApplication;
const PORT = process.env.PORT || 3001;

const getTerminusOptions = (
db: DatabaseHealthIndicator,
): TerminusModuleOptions => ({
endpoints: [
{
url: '/health',
healthIndicators: [async () => db.pingCheck('database')],
},
],
});

class ApplicationModule {
static forRoot(options: TerminusModuleAsyncOptions): DynamicModule {
return {
module: ApplicationModule,
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'mysql',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
keepConnectionAlive: true,
retryAttempts: 2,
retryDelay: 1000,
}),
TerminusModule.forRootAsync(options),
],
};
}
}

async function bootstrapModule(options: TerminusModuleAsyncOptions) {
app = await NestFactory.create(ApplicationModule.forRoot(options));
await app.listen(PORT);
}

it('should check if the database is available', async () => {
await bootstrapModule({
inject: [DatabaseHealthIndicator],
useFactory: getTerminusOptions,
});

const response = await Axios.get(`http://0.0.0.0:${PORT}/health`);
expect(response.status).toBe(200);
expect(response.data).toEqual({
status: 'ok',
info: { database: { status: 'up' } },
});
});

it('should throw an error if runs into timeout error', async () => {
await bootstrapModule({
inject: [DatabaseHealthIndicator],
useFactory: (db: DatabaseHealthIndicator): TerminusModuleOptions => ({
endpoints: [
{
url: '/health',
healthIndicators: [
async () => db.pingCheck('database', { timeout: 1 }),
],
},
],
}),
});

try {
await Axios.get(`http://0.0.0.0:${PORT}/health`, {});
} catch (error) {
expect(error.response.status).toBe(503);
expect(error.response.data).toEqual({
status: 'error',
error: {
database: {
status: 'down',
message: expect.any(String),
},
},
});
}
});

afterEach(async () => {
await app.close();
});
});
151 changes: 151 additions & 0 deletions e2e/health-checks/dns.health.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { INestApplication, DynamicModule } from '@nestjs/common';
import {
TerminusModuleAsyncOptions,
TerminusModule,
TerminusModuleOptions,
DNSHealthIndicator,
} from '../../lib';
import { NestFactory } from '@nestjs/core';

import Axios from 'axios';

describe('DNS Health', () => {
let app: INestApplication;
const PORT = process.env.PORT || 3001;

const getTerminusOptions = (
dns: DNSHealthIndicator,
): TerminusModuleOptions => ({
endpoints: [
{
url: '/health',
healthIndicators: [
async () => dns.pingCheck('dns', 'https://google.com'),
],
},
],
});

class ApplicationModule {
static forRoot(options: TerminusModuleAsyncOptions): DynamicModule {
return {
module: ApplicationModule,
imports: [TerminusModule.forRootAsync(options)],
};
}
}

async function bootstrapModule(options: TerminusModuleAsyncOptions) {
app = await NestFactory.create(ApplicationModule.forRoot(options));
await app.listen(PORT);
}

it('should check if google is available', async () => {
await bootstrapModule({
inject: [DNSHealthIndicator],
useFactory: getTerminusOptions,
});

const response = await Axios.get(`http://0.0.0.0:${PORT}/health`);
expect(response.status).toBe(200);
expect(response.data).toEqual({
status: 'ok',
info: { dns: { status: 'up' } },
});
});

it('should check if correctly display a timeout error', async () => {
await bootstrapModule({
inject: [DNSHealthIndicator],
useFactory: (dns: DNSHealthIndicator): TerminusModuleOptions => ({
endpoints: [
{
url: '/health',
healthIndicators: [
async () =>
dns.pingCheck('dns', 'https://google.com', { timeout: 1 }),
],
},
],
}),
});

try {
const response = await Axios.get(`http://0.0.0.0:${PORT}/health`);
} catch (error) {
expect(error.response.status).toBe(503);
expect(error.response.data).toEqual({
status: 'error',
error: { dns: { status: 'down', message: expect.any(String) } },
});
}
});

it('should check if correctly display not found error', async () => {
await bootstrapModule({
inject: [DNSHealthIndicator],
useFactory: (dns: DNSHealthIndicator): TerminusModuleOptions => ({
endpoints: [
{
url: '/health',
healthIndicators: [
async () =>
dns.pingCheck('dns', 'https://asdfn-not-an-actual-address.com'),
],
},
],
}),
});

try {
const response = await Axios.get(`http://0.0.0.0:${PORT}/health`);
} catch (error) {
expect(error.response.status).toBe(503);
expect(error.response.data).toEqual({
status: 'error',
error: { dns: { status: 'down', message: expect.any(String) } },
});
}
});

it('should check if correctly display not found error', async () => {
await bootstrapModule({
inject: [DNSHealthIndicator],
useFactory: (dns: DNSHealthIndicator): TerminusModuleOptions => ({
endpoints: [
{
url: '/health',
healthIndicators: [
async () =>
dns.pingCheck(
'dns',
'https://pokeapi.co/api/v2/pokemon/134125',
),
],
},
],
}),
});

try {
const response = await Axios.get(`http://0.0.0.0:${PORT}/health`);
} catch (error) {
expect(error.response.status).toBe(503);
expect(error.response.data).toEqual({
status: 'error',
error: {
dns: {
status: 'down',
message: expect.any(String),
statusCode: 404,
statusText: 'Not Found',
},
},
});
}
});

afterEach(async () => {
await app.close();
});
});
3 changes: 2 additions & 1 deletion e2e/jest-e2e.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@
"coverageReporters": [
"json",
"lcov"
]
],
"testEnvironment": "node"
}
Loading

0 comments on commit 12fdce9

Please sign in to comment.