From 8a07aa450ff09d09cd8b12e97c752a0dc0bce218 Mon Sep 17 00:00:00 2001 From: Iadivin Date: Fri, 26 Apr 2024 11:41:00 +0200 Subject: [PATCH] ch: Setup CI workflow for testing, coverage reporting, building and linting --- .eslintrc.js | 45 +++++++++++++++++++++------------ .github/workflows/ci.yml | 33 ++++++++++++++++++++++++ README.md | 2 ++ jest.config.ts | 32 +++++++++++------------ package.json | 2 ++ src/__test__/route.test.ts | 12 ++++----- src/index.ts | 36 +++++++++++++------------- src/middlewares/errorHandler.ts | 37 ++++++++++++--------------- tsconfig.json | 5 +++- 9 files changed, 125 insertions(+), 79 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.eslintrc.js b/.eslintrc.js index 47d4e4f..350033b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,32 +2,45 @@ module.exports = { root: true, parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint'], - extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], + plugins: ['@typescript-eslint', 'jest'], + env: { + 'jest/globals': true, + }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:jest/recommended', + ], rules: { '@typescript-eslint/no-explicit-any': 'off', - "@typescript-eslint/no-unused-vars": [ - "warn", + '@typescript-eslint/no-unused-vars': [ + 'warn', { - "args": "all", - "argsIgnorePattern": "^_", - "caughtErrors": "all", - "caughtErrorsIgnorePattern": "^_", - "destructuredArrayIgnorePattern": "^_", - "varsIgnorePattern": "^_", - "ignoreRestSiblings": true - } + args: 'all', + argsIgnorePattern: '^_', + caughtErrors: 'all', + caughtErrorsIgnorePattern: '^_', + destructuredArrayIgnorePattern: '^_', + varsIgnorePattern: '^_', + ignoreRestSiblings: true, + }, ], 'no-undef': 'warn', 'semi': ['warn', 'always'], 'no-multi-spaces': 'warn', 'no-trailing-spaces': 'warn', 'space-before-function-paren': ['warn', 'always'], - 'func-style': ['warn', 'declaration', { 'allowArrowFunctions': true }], + 'func-style': ['warn', 'declaration', { allowArrowFunctions: true }], 'camelcase': 'warn', - '@typescript-eslint/explicit-function-return-type': ['warn', { allowExpressions: true }], - '@typescript-eslint/explicit-member-accessibility': ['warn', { accessibility: 'explicit' }], + '@typescript-eslint/explicit-function-return-type': [ + 'warn', + { allowExpressions: true }, + ], + '@typescript-eslint/explicit-member-accessibility': [ + 'warn', + { accessibility: 'explicit' }, + ], 'no-unused-vars': 'warn', 'no-extra-semi': 'warn', }, -}; \ No newline at end of file +}; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1cbda74 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,33 @@ +name: knights-ecomm-be CI + +on: [push, pull_request] + +jobs: + build-lint-test-coverage: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + + - name: Install dependencies + run: npm install + + - name: Run ESLint and Prettier + run: npm run lint + + - name: Build project + run: npm run build --if-present + + - name: Run tests + run: npm test + + - name: Upload coverage report to Coveralls + uses: coverallsapp/github-action@v2.2.3 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index 7e2ae64..1d78fa6 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,9 @@ functionalities for the frontend, such as storing, retrieving, deleting data and List of endpoints exposed by the service ## Setup + - to use loggers in program use below functions + ```bash logger.error('This is an error message'); logger.warn('This is a warning message'); diff --git a/jest.config.ts b/jest.config.ts index 3d8dfdc..296ffe3 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,18 +1,18 @@ /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ export default { - preset: "ts-jest", - testEnvironment: "node", - testMatch: ["**/**/*.test.ts"], - verbose: true, - forceExit: true, - clearMocks: true, - resetMocks: true, - restoreMocks: true, - collectCoverageFrom: [ - "src/**/*.{ts,tsx}", // Include all JavaScript/JSX files in the src directory - ], - coveragePathIgnorePatterns: [ - "/node_modules/", // Exclude the node_modules directory - "/__tests__/", // Exclude the tests directory - ], - }; + preset: 'ts-jest', + testEnvironment: 'node', + testMatch: ['**/**/*.test.ts'], + verbose: true, + forceExit: true, + clearMocks: true, + resetMocks: true, + restoreMocks: true, + collectCoverageFrom: [ + 'src/**/*.{ts,tsx}', // Include all JavaScript/JSX files in the src directory + ], + coveragePathIgnorePatterns: [ + '/node_modules/', // Exclude the node_modules directory + '/__tests__/', // Exclude the tests directory + ], +}; diff --git a/package.json b/package.json index 70d4bec..3809b93 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@types/jest": "^29.5.12", "@types/jsend": "^1.0.32", "@types/morgan": "^1.9.9", + "@types/supertest": "^6.0.2", "@types/winston": "^2.4.4", "@typescript-eslint/eslint-plugin": "^7.7.1", "@typescript-eslint/parser": "^7.7.1", @@ -53,6 +54,7 @@ "eslint-config-prettier": "^9.1.0", "eslint-plugin-custom-plugin": "^1.0.0", "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-jest": "^28.3.0", "eslint-plugin-prettier": "^5.1.3", "jest": "^29.7.0", "prettier": "^3.2.5", diff --git a/src/__test__/route.test.ts b/src/__test__/route.test.ts index af92a44..7fa4402 100644 --- a/src/__test__/route.test.ts +++ b/src/__test__/route.test.ts @@ -1,14 +1,12 @@ import request from 'supertest'; -import {app, server} from '../index'; // update this with the path to your app file +import { app, server } from '../index'; // update this with the path to your app file describe('GET /', () => { - afterAll(done => { - server.close(done); - }); + afterAll(done => { + server.close(done); + }); it('responds with "Knights Ecommerce API"', done => { - request(app) - .get('/') - .expect(200, 'Knights Ecommerce API', done); + request(app).get('/').expect(200, 'Knights Ecommerce API', done); }); }); diff --git a/src/index.ts b/src/index.ts index c090dcb..9e97fde 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,10 @@ -import express, { Request, Response } from "express"; -import cors from "cors"; -import dotenv from "dotenv"; -import router from "./routes"; -import { addDocumentation } from "./startups/docs"; +import express, { Request, Response } from 'express'; +import cors from 'cors'; +import dotenv from 'dotenv'; +import router from './routes'; +import { addDocumentation } from './startups/docs'; -import {CustomError,errorHandler} from "./middlewares/errorHandler"; +import { CustomError, errorHandler } from './middlewares/errorHandler'; import morgan from 'morgan'; dotenv.config(); @@ -12,18 +12,20 @@ export const app = express(); const port = process.env.PORT as string; app.use(express.json()); -app.use(cors({ origin: "*" })); +app.use(cors({ origin: '*' })); -app.all('*', (req: Request,res: Response,next) =>{ - const error = new CustomError(`Can't find ${req.originalUrl} on the server!`,404); - error.status = 'fail'; - next(error); +app.get('/', (req: Request, res: Response) => { + res.send('Knights Ecommerce API'); }); -addDocumentation(app); -app.get("/api/v1", (req: Request, res: Response) => { - res.send("Knights Ecommerce API"); +app.all('*', (req: Request, res: Response, next) => { + const error = new CustomError(`Can't find ${req.originalUrl} on the server!`, 404); + error.status = 'fail'; + next(error); }); + +addDocumentation(app); + app.use(router); app.use(errorHandler); @@ -31,8 +33,6 @@ app.use(errorHandler); const morganFormat = ':method :url :status :response-time ms - :res[content-length]'; app.use(morgan(morganFormat)); - - -app.listen(port, () => { - console.log(`[server]: Server is running at http://localhost:${port}`); +export const server = app.listen(port, () => { + console.log(`[server]: Server is running at http://localhost:${port}`); }); diff --git a/src/middlewares/errorHandler.ts b/src/middlewares/errorHandler.ts index 74cca91..e4e967f 100644 --- a/src/middlewares/errorHandler.ts +++ b/src/middlewares/errorHandler.ts @@ -1,30 +1,25 @@ import { Request, Response } from 'express'; class CustomError extends Error { - statusCode: number; - status: string; + statusCode: number; + status: string; - constructor (message: string, statusCode: number) { - super(message); - this.statusCode = statusCode; - this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error'; - Error.captureStackTrace(this, this.constructor); - } + constructor (message: string, statusCode: number) { + super(message); + this.statusCode = statusCode; + this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error'; + Error.captureStackTrace(this, this.constructor); + } } -const errorHandler = ( - err: CustomError, - req: Request, - res: Response, - -) => { - err.statusCode = err.statusCode || 500; - err.status = err.status || 'error'; - res.status(err.statusCode).json({ - status: err.statusCode, - message: err.message - }); - console.error(err.stack); +const errorHandler = (err: CustomError, req: Request, res: Response) => { + err.statusCode = err.statusCode || 500; + err.status = err.status || 'error'; + res.status(err.statusCode).json({ + status: err.statusCode, + message: err.message, + }); + console.error(err.stack); }; export { CustomError, errorHandler }; diff --git a/tsconfig.json b/tsconfig.json index bfaf5b0..ab7370d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,7 +29,10 @@ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - "types": ["node"] /* Specify type package names to be included without being referenced in a source file. */, + "types": [ + "node", + "jest" + ] /* Specify type package names to be included without being referenced in a source file. */, // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */