From 93aa3bcb8529c66ad294f2a318b9793267903939 Mon Sep 17 00:00:00 2001 From: Rushabh Sancheti Date: Tue, 5 Jul 2022 16:19:24 +0530 Subject: [PATCH 1/4] feat: adding validators for auto generated apis --- .../server/api/requestGenerators.test.js | 131 ++++++++++++++---- server/api/requestGenerators.js | 8 +- server/utils/index.js | 43 ++++++ 3 files changed, 154 insertions(+), 28 deletions(-) diff --git a/__tests__/server/api/requestGenerators.test.js b/__tests__/server/api/requestGenerators.test.js index ea4fa08..e02c842 100644 --- a/__tests__/server/api/requestGenerators.test.js +++ b/__tests__/server/api/requestGenerators.test.js @@ -32,11 +32,18 @@ describe('requestGenerators tests', () => { beforeAll(() => { ENDPOINT = '/products'; }); + + const mockProduct = { + _id: '62bd7bd6151f31799cc670a6', + name: 'Bat', + price: 29, + category: 'Sports', + quantity: 10 + }; describe('generatePostRequest tests', () => { + const reqBody = mockProduct; it('should generatePostRoute should perform success response', async () => { - const reqBody = { - name: 'Jane Doe' - }; + delete reqBody['_id']; const createSpy = jest .spyOn(daoUtils, 'createItem') .mockImplementation((_, body) => Promise.resolve(body)); @@ -50,7 +57,7 @@ describe('requestGenerators tests', () => { .send(reqBody); expect(res.statusCode).toBe(200); expect(createSpy).toBeCalled(); - expect(res.body.data).toEqual(reqBody); + expect(res.body.data).toEqual(mockProduct); }); it('should generatePostRoute should perform error response', async () => { @@ -65,10 +72,23 @@ describe('requestGenerators tests', () => { Accept: 'application/json', Authorization: 'Bearer dummy-token' }) - .send({}); + .send(reqBody); expect(res.statusCode).toBe(500); expect(res.body.error).toEqual(error.message); }); + + it('should return an error from validateSchema middleware', async () => { + const res = await supertest(app) + .post(ENDPOINT) + .set({ + Accept: 'application/json', + Authorization: 'Bearer dummy-token' + }) + .send({ + name: 'Bat' + }); + expect(res.statusCode).toBe(500); + }); }); describe('generatePatchRequest tests', () => { @@ -77,7 +97,7 @@ describe('requestGenerators tests', () => { jest.spyOn(daoUtils, 'updateItem').mockResolvedValue(returnItem); const res = await supertest(app) - .patch(`${ENDPOINT}/7`) + .patch(`${ENDPOINT}/62bd7bd6151f31799cc670a6`) .set({ Accept: 'application/json', Authorization: 'Bearer dummy-token' @@ -92,15 +112,43 @@ describe('requestGenerators tests', () => { jest.spyOn(daoUtils, 'updateItem').mockRejectedValue(error); const res = await supertest(app) - .patch(`${ENDPOINT}/7`) + .patch(`${ENDPOINT}/62bd7bd6151f31799cc670a6`) .set({ Accept: 'application/json', Authorization: 'Bearer dummy-token' }) - .send({}); + .send({ name: 'Jane Doe' }); expect(res.statusCode).toBe(500); expect(res.body).toEqual({ error: error.message }); }); + + it('should return error from validateObjectId middleware', async () => { + const res = await supertest(app) + .patch(`${ENDPOINT}/62bd7bd6151f31799cc670a`) + .set({ + Accept: 'application/json', + Authorization: 'Bearer dummy-token' + }) + .send({ name: 'Jane Doe' }); + expect(res.statusCode).toBe(500); + expect(res.error.text).toEqual( + '{"error":{"message":"Invalid ObjectId"}}' + ); + }); + + it('should return error from validateReqBody middleware', async () => { + const res = await supertest(app) + .patch(`${ENDPOINT}/62bd7bd6151f31799cc670a6`) + .set({ + Accept: 'application/json', + Authorization: 'Bearer dummy-token' + }) + .send({ names: 'Jane Doe' }); + expect(res.statusCode).toBe(500); + expect(res.error.text).toEqual( + '{"error":{"message":"Request schema is invalid"}}' + ); + }); }); describe('generateFetchAllRequest tests', () => { @@ -137,18 +185,17 @@ describe('requestGenerators tests', () => { describe('generateFetchOneRequest tests', () => { it('should generate get route that fetch single item', async () => { - const returnItem = { name: 'sasuke' }; - jest.spyOn(daoUtils, 'fetchItem').mockResolvedValue(returnItem); + jest.spyOn(daoUtils, 'fetchItem').mockResolvedValue(mockProduct); const res = await supertest(app) - .get(`${ENDPOINT}/7`) + .get(`${ENDPOINT}/62bd7bd6151f31799cc670a6`) .set({ Accept: 'application/json', Authorization: 'Bearer dummy-token' - }) - .send({ name: 'shikamaru' }); + }); + expect(res.statusCode).toBe(200); - expect(res.body).toEqual({ data: returnItem }); + expect(res.body).toEqual({ data: mockProduct }); }); it('should generate get route that could return error response', async () => { @@ -156,26 +203,43 @@ describe('requestGenerators tests', () => { jest.spyOn(daoUtils, 'fetchItem').mockRejectedValue(error); const res = await supertest(app) - .get(`${ENDPOINT}/7`) + .get(`${ENDPOINT}/62bd7bd6151f31799cc670a6`) .set({ Accept: 'application/json', Authorization: 'Bearer dummy-token' - }) - .send({}); + }); expect(res.statusCode).toBe(500); expect(res.body).toEqual({ error: error.message }); }); + + it('should return error from validateObjectId', async () => { + const error = new Error('update failed'); + jest.spyOn(daoUtils, 'fetchItem').mockRejectedValue(error); + + const res = await supertest(app) + .get(`${ENDPOINT}/62bd7bd6151f31799cc670a`) + .set({ + Accept: 'application/json', + Authorization: 'Bearer dummy-token' + }); + expect(res.statusCode).toBe(500); + expect(res.error.text).toEqual( + '{"error":{"message":"Invalid ObjectId"}}' + ); + }); }); describe('generateDeleteRequest tests', () => { it('should generate delete route that could delete item', async () => { - const deleteRes = 'item delete sucess'; + const deleteRes = 'item delete success'; jest.spyOn(daoUtils, 'deleteItem').mockResolvedValue(deleteRes); - const res = await supertest(app).delete(`${ENDPOINT}/7`).set({ - Accept: 'application/json', - Authorization: 'Bearer dummy-token' - }); + const res = await supertest(app) + .delete(`${ENDPOINT}/62bd7bd6151f31799cc670a6`) + .set({ + Accept: 'application/json', + Authorization: 'Bearer dummy-token' + }); expect(res.statusCode).toBe(200); expect(res.body).toEqual({ data: deleteRes }); }); @@ -184,12 +248,27 @@ describe('requestGenerators tests', () => { const error = new Error('delete failed'); jest.spyOn(daoUtils, 'deleteItem').mockRejectedValue(error); - const res = await supertest(app).delete(`${ENDPOINT}/7`).set({ - Accept: 'application/json', - Authorization: 'Bearer dummy-token' - }); + const res = await supertest(app) + .delete(`${ENDPOINT}/62bd7bd6151f31799cc670a6`) + .set({ + Accept: 'application/json', + Authorization: 'Bearer dummy-token' + }); expect(res.statusCode).toBe(500); expect(res.body).toEqual({ error: error.message }); }); + + it('should return error from validateObjectId', async () => { + const res = await supertest(app) + .delete(`${ENDPOINT}/62bd7bd6151f31799cc670a`) + .set({ + Accept: 'application/json', + Authorization: 'Bearer dummy-token' + }); + expect(res.statusCode).toBe(500); + expect(res.error.text).toEqual( + '{"error":{"message":"Invalid ObjectId"}}' + ); + }); }); }); diff --git a/server/api/requestGenerators.js b/server/api/requestGenerators.js index 9128438..6b3d789 100644 --- a/server/api/requestGenerators.js +++ b/server/api/requestGenerators.js @@ -14,24 +14,28 @@ import { import { clientCredentialsGrant, managementClient } from 'utils/auth0'; import { REQUEST_TYPES } from './customApisMapper'; import config from 'config'; -import { checkJwt } from 'middlewares/auth'; +import { validateObjectId, validateReqBody, validateSchema } from 'utils'; export const generateRequest = (type, router, model, validator) => { - const middlewares = [...createValidatorMiddlewares(validator), checkJwt]; + const middlewares = [...createValidatorMiddlewares(validator)]; switch (type) { case REQUEST_TYPES.create: + middlewares.push(validateSchema(model)); generatePostRequest({ router, model, middlewares }); break; case REQUEST_TYPES.update: + middlewares.push(validateObjectId, validateReqBody(model)); generatePatchRequest({ router, model, middlewares }); break; case REQUEST_TYPES.fetchOne: + middlewares.push(validateObjectId); generateFetchOneRequest({ router, model, middlewares }); break; case REQUEST_TYPES.fetchAll: generateFetchAllRequest({ router, model, middlewares }); break; case REQUEST_TYPES.remove: + middlewares.push(validateObjectId); generateDeleteRequest({ router, model, middlewares }); break; } diff --git a/server/utils/index.js b/server/utils/index.js index 691017f..bf5e230 100644 --- a/server/utils/index.js +++ b/server/utils/index.js @@ -1,4 +1,6 @@ import fs from 'fs'; +import mongoose from 'mongoose'; +import { apiFailure } from './apiUtils'; export const isTestEnv = () => process.env.ENVIRONMENT_NAME === 'test' || process.env.NODE_ENV === 'test'; @@ -11,3 +13,44 @@ export const getModelFiles = modelsFolderPath => { .readdirSync(modelsFolderPath) .filter(file => fs.lstatSync(modelsFolderPath + file).isFile()); }; + +export const validateObjectId = (req, res, next) => { + if (!mongoose.Types.ObjectId.isValid(req.params._id)) { + return apiFailure(res, { message: 'Invalid ObjectId' }); + } + next(); +}; + +export const validateSchema = model => (req, res, next) => { + const doc = new model(req.body); + doc.validate(function (err) { + if (err) { + return apiFailure(res, err.errors); + } + next(); + }); +}; + +export const validateReqBody = model => (req, res, next) => { + const validKeys = Object.keys(model.schema.obj); + const keys = Object.keys(req.body); + if (keys.length) { + let isValid = true; + for (let i = 0; i < keys.length; i++) { + if (!validKeys.includes(keys[i])) { + isValid = false; + break; + } + } + if (!isValid) { + return apiFailure(res, { + message: 'Request schema is invalid' + }); + } + next(); + } else { + return apiFailure(res, { + message: 'Request schema is invalid' + }); + } +}; From 335d9c7c0da339debeedad4d72187d887360e27d Mon Sep 17 00:00:00 2001 From: Rushabh Sancheti Date: Thu, 1 Sep 2022 11:02:37 +0530 Subject: [PATCH 2/4] refactor: refactoring the code for validatereqBody function --- .gitignore | 5 +++++ server/utils/index.js | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e5f3908..945f557 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,11 @@ typings/ # dotenv environment variables file .env .env.local +.env.development +.env.docker # next.js build output .next + +dist + diff --git a/server/utils/index.js b/server/utils/index.js index bf5e230..938790f 100644 --- a/server/utils/index.js +++ b/server/utils/index.js @@ -32,12 +32,12 @@ export const validateSchema = model => (req, res, next) => { }; export const validateReqBody = model => (req, res, next) => { - const validKeys = Object.keys(model.schema.obj); + const validKeys = model.schema.obj; const keys = Object.keys(req.body); if (keys.length) { let isValid = true; for (let i = 0; i < keys.length; i++) { - if (!validKeys.includes(keys[i])) { + if (!validKeys[keys[i]]) { isValid = false; break; } From b840014486446d08172b5a4526d1b138aa51fb22 Mon Sep 17 00:00:00 2001 From: alichherawalla Date: Mon, 12 Sep 2022 10:29:46 +0000 Subject: [PATCH 3/4] 10.0.32 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5e13475..a940caa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-mongo-express", - "version": "10.0.31", + "version": "10.0.32", "description": "A basic starter web app with node, express and mongoose", "main": "index.js", "scripts": { From 4319277f1d228f043229cca805d9faa23b3c6868 Mon Sep 17 00:00:00 2001 From: Gitflow Date: Mon, 12 Sep 2022 10:31:00 +0000 Subject: [PATCH 4/4] chore: update badges --- badges/badge-branches.svg | 2 +- badges/badge-functions.svg | 2 +- badges/badge-lines.svg | 2 +- badges/badge-statements.svg | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/badges/badge-branches.svg b/badges/badge-branches.svg index c6762bd..f516bc6 100644 --- a/badges/badge-branches.svg +++ b/badges/badge-branches.svg @@ -1 +1 @@ -Coverage:branches: 97.84%Coverage:branches97.84% \ No newline at end of file +Coverage:branches: 97.31%Coverage:branches97.31% \ No newline at end of file diff --git a/badges/badge-functions.svg b/badges/badge-functions.svg index 95dc70b..e5b056d 100644 --- a/badges/badge-functions.svg +++ b/badges/badge-functions.svg @@ -1 +1 @@ -Coverage:functions: 99.25%Coverage:functions99.25% \ No newline at end of file +Coverage:functions: 99.28%Coverage:functions99.28% \ No newline at end of file diff --git a/badges/badge-lines.svg b/badges/badge-lines.svg index 03aa9e7..c264c6c 100644 --- a/badges/badge-lines.svg +++ b/badges/badge-lines.svg @@ -1 +1 @@ -Coverage:lines: 98.99%Coverage:lines98.99% \ No newline at end of file +Coverage:lines: 98.87%Coverage:lines98.87% \ No newline at end of file diff --git a/badges/badge-statements.svg b/badges/badge-statements.svg index 51e6b40..4c33556 100644 --- a/badges/badge-statements.svg +++ b/badges/badge-statements.svg @@ -1 +1 @@ -Coverage:statements: 99%Coverage:statements99% \ No newline at end of file +Coverage:statements: 98.89%Coverage:statements98.89% \ No newline at end of file