Skip to content

Commit

Permalink
feat(Heatlh) add ep and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
pablojvritx committed Oct 2, 2023
1 parent 7ebbe93 commit 9bea4cf
Show file tree
Hide file tree
Showing 24 changed files with 337 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,4 @@ dist

test-report.xml
debug.json
config/*.env
File renamed without changes.
7 changes: 6 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,10 @@ module.exports = {
reporters: ['default'],
testResultsProcessor: 'jest-sonar-reporter',
coverageReporters: ['text', 'html', 'lcov', 'clover'],
coveragePathIgnorePatterns: ['/node_modules/', '/test/']
coveragePathIgnorePatterns: [
'/node_modules/',
'/test/',
'/start.ts',
'/server.ts'
]
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"scripts": {
"check-dependencies": "madge --circular ./src",
"dev:api": "cross-env NODE_ENV=dev ts-node-dev --ignore-watch node_modules ./src/apps/apiApp/backend/start.ts",
"start:api": "cross-env NODE_ENV=production ts-node ./src/apps/apiApp/backend/start.ts",
"lint": "eslint . --ext .ts --max-warnings=0",
"lint:fix": "eslint . --ext .ts --fix",
"pre-commit": "lint-staged",
Expand Down
1 change: 1 addition & 0 deletions sonar-project.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
sonar.projectKey=vinjatovix_ts-api
sonar.organization=vinjatovix
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.coverage.exclusions=**/tests/**/*, **/node_modules/**/*, **/start.ts


# This is the name and version displayed in the SonarCloud UI.
Expand Down
20 changes: 20 additions & 0 deletions src/apps/apiApp/backend/ApiApp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Server } from './server';

export class ApiApp {
server?: Server;

async start() {
const port: string = process.env.PORT ?? '0';
this.server = new Server(port);

return this.server.listen();
}

async stop() {
return this.server?.stop();
}

get httpServer() {
return this.server?.getHTTPServer();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Request, Response } from 'express';
import { Controller } from '../../../shared/controllers/Controller';

export class GetStatusController implements Controller {
async run(_req: Request, res: Response): Promise<void> {
res.status(200).json({ status: 'OK' });
}
}
2 changes: 2 additions & 0 deletions src/apps/apiApp/backend/dependency-injection/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
imports:
- { resource: ./apps/application.yml }
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
imports:
- { resource: ./application.yml }
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
imports:
- { resource: ./application.yml }
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
imports:
- { resource: ./application.yml }
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
services:
Apps.apiApp.controllers.health.GetStatusController:
class: ../../controllers/health/GetStatusController
arguments: []
9 changes: 9 additions & 0 deletions src/apps/apiApp/backend/dependency-injection/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ContainerBuilder, YamlFileLoader } from 'node-dependency-injection';

const container = new ContainerBuilder();
const loader = new YamlFileLoader(container);
const env = process.env.NODE_ENV ?? 'dev';

loader.load(`${__dirname}/application_${env}.yml`);

export default container;
14 changes: 14 additions & 0 deletions src/apps/apiApp/backend/routes/health/health.routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Request, Response, Router } from 'express';
import { GetStatusController } from '../../controllers/health/GetStatusController';
import container from '../../dependency-injection';

const prefix = '/api/v1/health';

export const register = (router: Router) => {
const controller: GetStatusController = container.get(
'Apps.apiApp.controllers.health.GetStatusController'
);
router.get(`${prefix}/http`, (req: Request, res: Response) => {
controller.run(req, res);
});
};
18 changes: 18 additions & 0 deletions src/apps/apiApp/backend/routes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Router } from 'express';
import { globSync } from 'glob';

export function registerRoutes(router: Router) {
const routes = globSync('**/*.routes.*', {
cwd: __dirname,
ignore: '**/index.ts'
});
routes.forEach((route) => {
_register(route, router);
});
}

const _register = (routePath: string, router: Router) => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const route = require(__dirname + '/' + routePath);
route.register(router);
};
80 changes: 80 additions & 0 deletions src/apps/apiApp/backend/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import dotenv from 'dotenv';
import bodyParser from 'body-parser';
import errorHandler from 'errorhandler';
import express, { Request, Response, NextFunction } from 'express';
import Router from 'express-promise-router';
import helmet from 'helmet';
import * as http from 'http';
import httpStatus from 'http-status';
import cors from 'cors';
import { registerRoutes } from './routes';

dotenv.config();
const corsOptions = {
origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true
};

export class Server {
private express: express.Express;
private port: string;
private httpServer?: http.Server;

constructor(port: string) {
this.port = port;
this.express = express();
this.express.use(cors(corsOptions));
this.express.use(bodyParser.json());
this.express.use(bodyParser.urlencoded({ extended: true }));
this.express.use(helmet.xssFilter());
this.express.use(helmet.noSniff());
this.express.use(helmet.hidePoweredBy());
this.express.use(helmet.frameguard({ action: 'deny' }));
const router = Router();
router.use(errorHandler());
this.express.use(router);

registerRoutes(router);

router.use(
(err: Error, _req: Request, res: Response, _next: NextFunction): void => {
console.log(err);
res.status(httpStatus.INTERNAL_SERVER_ERROR).send(err.message);
}
);
}

async listen(): Promise<void> {
return new Promise((resolve) => {
const env = this.express.get('env') as string;
this.httpServer = this.express.listen(this.port, () => {
console.log(
` Mock Backend App is running at http://localhost:${this.port} in ${env} mode`
);
console.log(' Press CTRL-C to stop\n');
resolve();
});
});
}

getHTTPServer() {
return this.httpServer;
}

async stop(): Promise<void> {
return new Promise((resolve, reject) => {
if (this.httpServer) {
this.httpServer.close((error) => {
if (error) {
reject(error);
return;
}
resolve();
});
} else {
resolve();
}
});
}
}
22 changes: 22 additions & 0 deletions src/apps/apiApp/backend/start.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import dotenv from 'dotenv';
import path from 'path';
import { ApiApp } from './ApiApp';

dotenv.config({
path: path.resolve(
__dirname,
`../../../../config/${process.env.NODE_ENV}.env`
)
});

try {
new ApiApp().start();
} catch (e) {
console.log(e);
process.exit(1);
}

process.on('uncaughtException', (err) => {
console.log('uncaughtException', err);
process.exit(1);
});
5 changes: 5 additions & 0 deletions src/apps/apiApp/shared/controllers/Controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Request, Response } from 'express';

export interface Controller {
run(req: Request, res: Response): Promise<void>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import request from 'supertest';
import { describe, beforeAll, afterAll, it, expect } from '@jest/globals';
import { ApiApp } from '../../../../../../src/apps/apiApp/backend/ApiApp';

let app: ApiApp;

describe('Health Check Endpoint', () => {
beforeAll(async () => {
app = new ApiApp();
await app.start();
});

afterAll(async () => {
await app.stop();
});

it('should return status 200 and "OK"', async () => {
const { status, body } = await request(app.httpServer).get(
'/api/v1/health/http'
);

expect(status).toBe(200);
expect(body).toMatchObject({ status: 'OK' });
});
});
21 changes: 21 additions & 0 deletions tests/Contexts/api/backEnd/health/unit/getStatusController.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Request, Response } from 'express';
import { GetStatusController } from '../../../../../../src/apps/apiApp/backend/controllers/health/GetStatusController';

describe('GetStatusController', () => {
let controller: GetStatusController;
let mockResponse: Partial<Response>;

beforeEach(() => {
controller = new GetStatusController();
mockResponse = {
status: jest.fn().mockReturnThis(),
json: jest.fn()
};
});

it('should send a status of 200 with the message "OK"', async () => {
await controller.run({} as Request, mockResponse as Response);
expect(mockResponse.status).toHaveBeenCalledWith(200);
expect(mockResponse.json).toHaveBeenCalledWith({ status: 'OK' });
});
});
40 changes: 40 additions & 0 deletions tests/Contexts/api/backEnd/unit/apiApp.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ApiApp } from '../../../../../src/apps/apiApp/backend/ApiApp';
import { Server } from '../../../../../src/apps/apiApp/backend/server';

describe('ApiApp', () => {
let apiApp: ApiApp;
const listenSpy = jest.spyOn(Server.prototype, 'listen');
const stopSpy = jest.spyOn(Server.prototype, 'stop');

beforeAll(() => {
apiApp = new ApiApp();
});

afterEach(async () => {
if (apiApp.httpServer?.listening) {
await apiApp.stop();
}
});

it('should start the server', async () => {
await apiApp.start();

expect(listenSpy).toHaveBeenCalled();
});

it('should stop the server', async () => {
await apiApp.start();

await apiApp.stop();

expect(stopSpy).toHaveBeenCalled();
});

it('should return the HTTP server', async () => {
await apiApp.start();

const httpServer = apiApp.httpServer;

expect(httpServer).toBeDefined();
});
});
15 changes: 15 additions & 0 deletions tests/apps/apiApp/backend/features/health/getStatus.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Feature: Api Health Check
In order to verify the application's availability
As a health check client
I want check the API status

Scenario: Performing a health check
Given a GET request to "/api/v1/health/http"
Then the response status code should be 200
Then the response body should be
"""
{
"status": "OK"
}
"""

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { AfterAll, BeforeAll, Given, Then } from '@cucumber/cucumber';
import request from 'supertest';
import { ApiApp } from '../../../../../../src/apps/apiApp/backend/ApiApp';
import chai from 'chai';

const expect = chai.expect;
let _request: request.Test;
let app: ApiApp;

BeforeAll(async () => {
app = new ApiApp();
await app.start();
});

AfterAll(async () => {
await app.stop();
});

Given('a GET request to {string}', (route: string) => {
_request = request(app.httpServer).get(route);
});

Then('the response status code should be {int}', async (status: number) => {
const response = await _request;
expect(response.status).to.be.equal(status);
});

Then('the response body should be', async (docString: string) => {
const response = await _request;
const expectedResponseBody = JSON.parse(docString);
expect(response.body).to.deep.equal(expectedResponseBody);
});

Then('the response body should not be', async (docString: string) => {
const response = await _request;
const expectedResponseBody = JSON.parse(docString);
expect(response.body).to.not.deep.equal(expectedResponseBody);
});
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
"noUnusedLocals": true,
"outDir": "./dist"
},
"include": ["src/**/**.ts"],
"include": ["src/**/*.ts", "tests/**/*.ts"],
"exclude": ["node_modules"]
}

0 comments on commit 9bea4cf

Please sign in to comment.