├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── config ├── custom-environment-variables.json ├── default.json └── test.json ├── controllers ├── auth.js ├── company.js ├── response.js ├── review.js ├── search.js ├── suggestion.js └── user.js ├── email ├── emailTransporter.js └── forgotPassword.js ├── index.js ├── middleware ├── admin.js ├── auth.js ├── error.js └── image.js ├── models ├── company.js ├── review.js └── user.js ├── package-lock.json ├── package.json ├── routes ├── auth.js ├── company.js ├── response.js ├── review.js ├── search.js ├── suggestion.js └── user.js ├── startup ├── config.js ├── db.js ├── logging.js ├── routes.js └── validation.js ├── tests ├── auth.test.js ├── company.test.js ├── images │ └── test.png ├── response.test.js ├── review.test.js ├── search.test.js ├── suggestion.test.js └── user.test.js └── utils └── pagination.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | uploads/ 4 | .idea/ 5 | .DS_Store 6 | logfile.log -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 10 4 | services: mongodb 5 | before_script: mkdir uploads 6 | cache: 7 | directories: 8 | - "node_modules" 9 | script: 10 | - npm test 11 | - npm run coveralls 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 A M S Rejuan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.com/rejuan/ReviewSystem.svg?token=KhpV8JP1fhyPE49az7dy&branch=master)](https://travis-ci.com/rejuan/ReviewSystem) [![Coverage Status](https://coveralls.io/repos/github/rejuan/ReviewSystem/badge.svg?branch=master)](https://coveralls.io/github/rejuan/ReviewSystem?branch=master) 2 | 3 | # Review System 4 | 5 | A fast, simple & powerful RESTful API based Review System, powered by [Node.js](https://nodejs.org/), [Express](https://expressjs.com/), [MongoDB](https://www.mongodb.com/) and [mongoose](https://mongoosejs.com/) 6 | 7 | ## Features 8 | 9 | - User - Signup/Signin/Forgot/Change password 10 | - Company - Add/Edit/Delete company 11 | - Review - Add/Edit/Delete reviews, User review list, Company review list 12 | - Response - Company owner can Add/Edit/Delete response according to any review 13 | - Search - Search by company tag or company name 14 | - Suggestion - Support autocomplete feature by tag & company suggestion 15 | - Admin - Suspend/Unsuspend user & company 16 | - Authentication & Authorisation - Used JWT based authentication & authorisation 17 | 18 | ## TO DO List 19 | 20 | - Create API documentation with swagger 21 | - Publish API documentation in apiary 22 | 23 | ## Getting Started 24 | 25 | To get you started you can simply clone the repository: 26 | 27 | ``` 28 | git clone https://github.com/rejuan/ReviewSystem.git 29 | cd ReviewSystem 30 | ``` 31 | 32 | and install the dependencies 33 | 34 | ``` 35 | npm install 36 | ``` 37 | 38 | ## Run the Application 39 | 40 | #### Run for development with nodemon 41 | 42 | ``` 43 | npm run dev 44 | ``` 45 | 46 | #### Run for production 47 | 48 | ``` 49 | npm run start 50 | ``` 51 | 52 | #### Tests 53 | 54 | ``` 55 | npm run test 56 | ``` 57 | 58 | #### Code coverage 59 | 60 | ``` 61 | npm run coverage 62 | ``` 63 | 64 | ## Authors 65 | 66 | - **A M S Rejuan** - _Initial work_ - [github](https://github.com/rejuan) 67 | 68 | See also the list of [contributors](https://github.com/rejuan/ReviewSystem/contributors) who participated in this project. 69 | 70 | ## Contributing 71 | 72 | Contributions are more than welcome! Open an [issue](https://github.com/rejuan/ReviewSystem/issues/new) and submit a pull request according to issue. 73 | 74 | 1. Navigate to the main page of the repository 75 | 1. Fork it! 76 | 1. Create your feature branch: `git checkout -b new-feature` 77 | 1. Commit your changes: `git commit -m 'Add some feature'` 78 | 1. Push to the branch: `git push origin new-feature` 79 | 1. Submit a pull request 80 | 81 | ## License 82 | 83 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 84 | -------------------------------------------------------------------------------- /config/custom-environment-variables.json: -------------------------------------------------------------------------------- 1 | { 2 | "jwtPrivateKey": "review_jwtPrivateKey" 3 | } 4 | -------------------------------------------------------------------------------- /config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "jwtPrivateKey": "1234", 3 | "db": "mongodb://localhost/review" 4 | } 5 | -------------------------------------------------------------------------------- /config/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "jwtPrivateKey": "1234", 3 | "db": "mongodb://localhost/review_test" 4 | } 5 | -------------------------------------------------------------------------------- /controllers/auth.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash"); 2 | const bcrypt = require("bcrypt"); 3 | const crypto = require("crypto"); 4 | const moment = require("moment"); 5 | const {sendMail} = require("../email/forgotPassword"); 6 | 7 | const { 8 | User, 9 | registrationValidate, 10 | signinValidation, 11 | forgotPasswordValidation, 12 | checkJwtToken, 13 | resetPasswordValidation, 14 | changePasswordValidation 15 | } = require("../models/user"); 16 | 17 | exports.registration = async (req, res) => { 18 | const {error} = registrationValidate(req.body); 19 | if (error) return res.status(400).send(error.details[0].message); 20 | 21 | let user = await User.findOne({email: req.body.email}); 22 | if (user) return res.status(400).send("User already registered"); 23 | 24 | user = new User(_.pick(req.body, ["name", "email", "password"])); 25 | const salt = await bcrypt.genSalt(10); 26 | user.password = await bcrypt.hash(user.password, salt); 27 | await user.save(); 28 | 29 | const token = user.generateAuthToken(); 30 | res 31 | .header("x-auth-token", token) 32 | .send(_.pick(user, ["_id", "name", "email"])); 33 | }; 34 | 35 | exports.signin = async (req, res) => { 36 | const {error} = signinValidation(req.body); 37 | if (error) return res.status(400).send(error.details[0].message); 38 | 39 | const user = await User.findOne({email: req.body.email}); 40 | if (!user) return res.status(400).send("Email and password doesn't match"); 41 | 42 | const validPassword = await bcrypt.compare(req.body.password, user.password); 43 | if (!validPassword) 44 | return res.status(400).send("Email and password doesn't match"); 45 | 46 | const token = user.generateAuthToken(); 47 | res 48 | .header("x-auth-token", token) 49 | .send(_.pick(user, ["_id", "name", "email"])); 50 | }; 51 | 52 | exports.forgotPassword = async (req, res) => { 53 | const {error} = forgotPasswordValidation(req.body); 54 | if (error) return res.status(400).send(error.details[0].message); 55 | 56 | const user = await User.findOne({email: req.body.email}); 57 | if (!user) return res.status(404).send("Email doesn't exist"); 58 | 59 | const forgotPassword = { 60 | forgotPassword: { 61 | token: crypto.randomBytes(20).toString('hex'), 62 | createdAt: moment().unix() 63 | } 64 | }; 65 | 66 | user.set(forgotPassword); 67 | await user.save(); 68 | await sendMail(user.name, user.email, user.generateForgotPassToken()); 69 | res.status(200).send("Success"); 70 | }; 71 | 72 | exports.resetPassword = async (req, res) => { 73 | let result = checkJwtToken(req.params.hash); 74 | if (!result) return res.status(400).send("Invalid token"); 75 | 76 | const {error} = resetPasswordValidation(req.body); 77 | if (error) return res.status(400).send(error.details[0].message); 78 | 79 | let user = await User.findOne({email: result.email}); 80 | if (!user) return res.status(404).send("Email not exist"); 81 | 82 | if (user.forgotPassword.token != result.token) return res.status(400).send("Invalid token"); 83 | 84 | const now = moment(); 85 | const was = moment(user.forgotPassword.createdAt * 1000); 86 | if (now.diff(was, 'minutes') > 5) return res.status(401).send("Expired token"); 87 | 88 | const salt = await bcrypt.genSalt(10); 89 | user.password = await bcrypt.hash(user.password, salt); 90 | await user.save(); 91 | 92 | return res.status(200).send("Changed successfully"); 93 | }; 94 | 95 | exports.changePassword = async (req, res) => { 96 | const {error} = changePasswordValidation(req.body); 97 | if (error) return res.status(400).send(error.details[0].message); 98 | 99 | if (req.body.newPassword != req.body.confirmPassword) 100 | return res.status(400).send("New password & confirm password should same"); 101 | 102 | const user = await User.findById(req.user._id); 103 | if (!user) return res.status(400).send("User doesn't exist"); 104 | 105 | const validPassword = await bcrypt.compare(req.body.currentPassword, user.password); 106 | if (!validPassword) 107 | return res.status(400).send("Email and password doesn't match"); 108 | 109 | const salt = await bcrypt.genSalt(10); 110 | user.password = await bcrypt.hash(req.body.currentPassword, salt); 111 | await user.save(); 112 | return res.status(200).send("Changed successfully"); 113 | }; 114 | -------------------------------------------------------------------------------- /controllers/company.js: -------------------------------------------------------------------------------- 1 | const multer = require('multer'); 2 | const _ = require('lodash'); 3 | const {upload} = require('../middleware/image'); 4 | const {Company, addValidate} = require("../models/company"); 5 | 6 | exports.createCompany = async (req, res) => { 7 | upload(req, res, async function (err) { 8 | if (err instanceof multer.MulterError) { 9 | return res.status(400).send(err.message); 10 | } else if (err) { 11 | return res.status(400).send(err.message); 12 | } 13 | 14 | const {error} = addValidate(req.body); 15 | if (error) return res.status(400).send(error.details[0].message); 16 | 17 | const companyData = { 18 | name: req.body.name, 19 | image: req.file.filename, 20 | contact: { 21 | mobile: req.body.mobile, 22 | address: req.body.address, 23 | website: req.body.website 24 | }, 25 | details: req.body.details, 26 | user: req.user._id 27 | }; 28 | 29 | const company = new Company(companyData); 30 | await company.save(); 31 | 32 | res.send(_.pick(company, ["_id", "name", "image", "contact", "details", "user"])); 33 | }); 34 | }; 35 | 36 | exports.updateCompany = async (req, res) => { 37 | upload(req, res, async function (err) { 38 | if (err instanceof multer.MulterError) { 39 | return res.status(400).send(err.message); 40 | } else if (err) { 41 | return res.status(400).send(err.message); 42 | } 43 | 44 | let companyData = { 45 | name: req.body.name, 46 | contact: { 47 | mobile: req.body.mobile, 48 | address: req.body.address, 49 | website: req.body.website 50 | }, 51 | details: req.body.details 52 | }; 53 | 54 | if (req.file) { 55 | companyData.image = req.file.filename; 56 | } 57 | 58 | const {error} = addValidate(req.body); 59 | if (error) return res.status(400).send(error.details[0].message); 60 | 61 | const query = { 62 | _id: req.params.id, 63 | user: req.user._id, 64 | status: 'active' 65 | }; 66 | 67 | if(req.user.userType == 'admin') { 68 | delete query.user; 69 | } 70 | 71 | const company = await Company.findOneAndUpdate(query, companyData, {new: true}); 72 | if(!company) return res.status(404).send("Not found"); 73 | 74 | res.send(_.pick(company, ["_id", "name", "image", "contact", "details", "user"])); 75 | }); 76 | }; 77 | 78 | exports.deleteCompany = async (req, res) => { 79 | const query = { 80 | _id: req.params.id, 81 | user: req.user._id, 82 | status: 'active' 83 | }; 84 | 85 | if(req.user.userType == 'admin') { 86 | delete query.user; 87 | } 88 | 89 | const company = await Company.findOneAndUpdate(query, { 90 | $set: {status: 'delete'} 91 | }, {new: true}); 92 | 93 | if(!company) return res.status(404).send('Company not exist'); 94 | 95 | res.send(_.pick(company, ["_id", "name", "image", "contact", "details", "user"])); 96 | }; 97 | 98 | exports.getCompany = async (req, res) => { 99 | const query = { 100 | _id: req.params.id, 101 | user: req.user._id, 102 | status: 'active' 103 | }; 104 | 105 | const company = await Company.findOne(query); 106 | if(!company) return res.status(404).send("Company not exist"); 107 | 108 | res.send(_.pick(company, ["_id", "name", "image", "contact", "details", "user"])); 109 | }; 110 | 111 | exports.suspendCompany = async (req, res) => { 112 | const query = { 113 | _id: req.params.id, 114 | user: req.user._id, 115 | status: 'active' 116 | }; 117 | 118 | const company = await Company.findOneAndUpdate(query, { 119 | $set: {status: 'suspend'} 120 | }, {new: true}); 121 | 122 | if(!company) return res.status(404).send('Company not exist'); 123 | 124 | res.send(_.pick(company, ["_id", "name", "image", "contact", "details", "user"])); 125 | }; 126 | 127 | exports.companyList = async (req, res) => { 128 | const query = { 129 | user: req.user._id, 130 | status: 'active' 131 | }; 132 | 133 | let company = await Company.find(query).lean(); 134 | company = company.map(function(item) { 135 | delete item.__v; 136 | delete item.status; 137 | return item; 138 | }); 139 | 140 | res.send(company); 141 | }; -------------------------------------------------------------------------------- /controllers/response.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const {Review, responseValidate, deleteResponseValidate} = require("../models/review"); 3 | const {Company} = require("../models/company"); 4 | 5 | exports.createResponse = async (req, res) => { 6 | const {error} = responseValidate(req.body); 7 | if (error) return res.status(400).send(error.details[0].message); 8 | 9 | let review = await Review.findById(req.body.id).populate('company'); 10 | if(!review) return res.status(404).send("Not found"); 11 | 12 | if(review.company.user.toString() != req.user._id) return res.status(401).send("You are not the company owner."); 13 | 14 | review.response = req.body.response; 15 | review = await review.save(); 16 | 17 | res.send(_.pick(review, ["_id", "user", "company", "star", "title", "details", "response"])); 18 | }; 19 | 20 | exports.updateResponse = async (req, res) => { 21 | const {error} = responseValidate(req.body); 22 | if (error) return res.status(400).send(error.details[0].message); 23 | 24 | let review = await Review.findById(req.body.id).populate('company'); 25 | if(!review) return res.status(404).send("Not found"); 26 | 27 | if(review.company.user.toString() != req.user._id) return res.status(401).send("You are not the company owner."); 28 | 29 | review.response = req.body.response; 30 | review = await review.save(); 31 | 32 | res.send(_.pick(review, ["_id", "user", "company", "star", "title", "details", "response"])); 33 | }; 34 | 35 | exports.deleteResponse = async (req, res) => { 36 | const {error} = deleteResponseValidate(req.body); 37 | if (error) return res.status(400).send(error.details[0].message); 38 | 39 | let review = await Review.findById(req.body.id).populate('company'); 40 | if(!review) return res.status(404).send("Not found"); 41 | 42 | if(review.company.user.toString() != req.user._id) return res.status(401).send("You are not the company owner."); 43 | 44 | review.response = null; 45 | review = await review.save(); 46 | 47 | res.send(_.pick(review, ["_id", "user", "company", "star", "title", "details", "response"])); 48 | }; -------------------------------------------------------------------------------- /controllers/review.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const {Review, validate, editValidate} = require("../models/review"); 3 | const {Company} = require("../models/company"); 4 | 5 | exports.createReview = async (req, res) => { 6 | const {error} = validate(req.body); 7 | if (error) return res.status(400).send(error.details[0].message); 8 | 9 | const reviewData = { 10 | user: req.user._id, 11 | company: req.body.company, 12 | star: req.body.star, 13 | title: req.body.title, 14 | details: req.body.details 15 | }; 16 | 17 | const company = await Company.findById(req.body.company); 18 | if(!company) return res.status(404).send("Not found"); 19 | 20 | const review = new Review(reviewData); 21 | await review.save(); 22 | 23 | res.send(_.pick(review, ["_id", "user", "company", "star", "title", "details"])); 24 | }; 25 | 26 | exports.updateReview = async (req, res) => { 27 | const {error} = editValidate(req.body); 28 | if (error) return res.status(400).send(error.details[0].message); 29 | 30 | const query = { 31 | _id: req.params.id, 32 | user: req.user._id 33 | }; 34 | 35 | const review = await Review.findOneAndUpdate(query, { 36 | $set: { 37 | star: req.body.star, 38 | title: req.body.title, 39 | details: req.body.details 40 | } 41 | }, {new: true}); 42 | 43 | if (!review) return res.status(404).send("Review not found"); 44 | res.send(_.pick(review, ["_id", "user", "company", "star", "title", "details"])); 45 | }; 46 | 47 | exports.deleteReview = async (req, res) => { 48 | let query; 49 | if (req.user.userType === 'admin') { 50 | query = { 51 | _id: req.params.id 52 | }; 53 | } else { 54 | query = { 55 | _id: req.params.id, 56 | user: req.user._id 57 | }; 58 | } 59 | 60 | const review = await Review.findOneAndUpdate(query, { 61 | $set: { 62 | status: "delete" 63 | } 64 | }, {new: true}); 65 | 66 | if (!review) return res.status(404).send("Review not found"); 67 | res.send(_.pick(review, ["_id", "user", "company", "star", "title", "details"])); 68 | }; 69 | 70 | exports.reviewList = async (req, res) => { 71 | let query; 72 | if(req.body.company) { 73 | query = { 74 | company: req.body.company, 75 | status: 'active' 76 | }; 77 | } else { 78 | query = { 79 | user: req.user._id, 80 | status: 'active' 81 | }; 82 | } 83 | 84 | let review = await Review.find(query).lean(); 85 | review = review.map(function(item) { 86 | delete item.__v; 87 | delete item.status; 88 | return item; 89 | }); 90 | 91 | res.send(review); 92 | }; -------------------------------------------------------------------------------- /controllers/search.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const {Company} = require("../models/company"); 3 | const {getPageNumber, getPageSize} = require("../utils/pagination"); 4 | 5 | exports.searchByCompany = async (req, res) => { 6 | const pageNumber = getPageNumber(req); 7 | const pageSize = getPageSize(req); 8 | const keyword = req.query.keyword; 9 | const query = {name: new RegExp('.*' + keyword + '.*', 'i')}; 10 | 11 | let company = await Company.find(query) 12 | .skip((pageNumber - 1) * pageSize) 13 | .limit(pageSize) 14 | .sort({name: 1}).lean(); 15 | 16 | company = company.map(function (item) { 17 | delete item.__v; 18 | delete item.status; 19 | return item; 20 | }); 21 | 22 | res.send(company); 23 | }; 24 | 25 | exports.searchByTag = async (req, res) => { 26 | const pageNumber = getPageNumber(req); 27 | const pageSize = getPageSize(req); 28 | const keyword = req.query.keyword; 29 | const query = {tags: new RegExp('^' + keyword , 'i')}; 30 | 31 | let company = await Company.find(query) 32 | .skip((pageNumber - 1) * pageSize) 33 | .limit(pageSize) 34 | .sort({name: 1}).lean(); 35 | 36 | company = company.map(function (item) { 37 | delete item.__v; 38 | delete item.status; 39 | return item; 40 | }); 41 | 42 | res.send(company); 43 | }; -------------------------------------------------------------------------------- /controllers/suggestion.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const {Company} = require("../models/company"); 3 | const {getPageNumber, getPageSize} = require("../utils/pagination"); 4 | 5 | exports.companySuggestion = async (req, res) => { 6 | const pageNumber = getPageNumber(req); 7 | const pageSize = getPageSize(req); 8 | const keyword = req.query.keyword; 9 | const query = [ 10 | { $match : { "name" : new RegExp('^' + keyword , 'i') }}, 11 | { $group : { "_id": "$name"}}, 12 | { $sort : { "_id" : 1 } }, 13 | { $skip : (pageNumber - 1) * pageSize}, 14 | { $limit : pageSize} 15 | ]; 16 | 17 | let company = await Company.aggregate(query); 18 | res.send(company); 19 | }; 20 | 21 | exports.tagSuggestion = async (req, res) => { 22 | const pageNumber = getPageNumber(req); 23 | const pageSize = getPageSize(req); 24 | const keyword = req.query.keyword; 25 | const query = [ 26 | { $match : { "tags" : new RegExp('^' + keyword , 'i') }}, 27 | { $unwind : "$tags"}, 28 | { $match : { "tags" : new RegExp('^' + keyword , 'i') }}, 29 | { $group : { "_id": "$tags"}}, 30 | { $sort : { "_id" : 1 } }, 31 | { $skip : (pageNumber - 1) * pageSize}, 32 | { $limit : pageSize} 33 | ]; 34 | 35 | let company = await Company.aggregate(query); 36 | res.send(company); 37 | }; -------------------------------------------------------------------------------- /controllers/user.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const {getPageSize, getPageNumber} = require("../utils/pagination"); 3 | const {User} = require("../models/user"); 4 | 5 | exports.activeUserList = async (req, res) => { 6 | const pageNumber = getPageNumber(req); 7 | const pageSize = getPageSize(req); 8 | 9 | let query = { 10 | status: 'active' 11 | }; 12 | 13 | let user = await User.find(query) 14 | .sort({_id: 1}) 15 | .skip((pageNumber - 1) * pageSize) 16 | .limit(pageSize) 17 | .lean(); 18 | 19 | user = user.map(function(item) { 20 | delete item.__v; 21 | delete item.password; 22 | return item; 23 | }); 24 | 25 | res.send(user); 26 | }; 27 | 28 | exports.suspendedUserList = async (req, res) => { 29 | const pageNumber = getPageNumber(req); 30 | const pageSize = getPageSize(req); 31 | 32 | let query = { 33 | status: 'suspend' 34 | }; 35 | 36 | let user = await User.find(query) 37 | .sort({_id: 1}) 38 | .skip((pageNumber - 1) * pageSize) 39 | .limit(pageSize) 40 | .lean(); 41 | 42 | user = user.map(function(item) { 43 | delete item.__v; 44 | delete item.password; 45 | return item; 46 | }); 47 | 48 | res.send(user); 49 | }; 50 | 51 | exports.suspendUser = async (req, res) => { 52 | let user = await User.findOne({ 53 | _id : req.params.id, 54 | status: 'active' 55 | }); 56 | 57 | if(!user) return res.status(404).send("Not found"); 58 | 59 | user.status = 'suspend'; 60 | user = await user.save(); 61 | res.send(_.pick(user, ["_id", "status", "userType", "name", "email"])); 62 | }; 63 | 64 | exports.activeUser = async (req, res) => { 65 | let user = await User.findOne({ 66 | _id : req.params.id, 67 | status: 'suspend' 68 | }); 69 | 70 | if(!user) return res.status(404).send("Not found"); 71 | 72 | user.status = 'active'; 73 | user = await user.save(); 74 | res.send(_.pick(user, ["_id", "status", "userType", "name", "email"])); 75 | }; -------------------------------------------------------------------------------- /email/emailTransporter.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require('nodemailer'); 2 | 3 | const transporter = nodemailer.createTransport({ 4 | host: 'smtp.ethereal.email', 5 | port: 587, 6 | auth: { 7 | user: 'zehmmqctsylqa7dh@ethereal.email', 8 | pass: 'w1JPVttrWQbdnysepk' 9 | } 10 | }); 11 | 12 | module.exports.transporter = transporter; -------------------------------------------------------------------------------- /email/forgotPassword.js: -------------------------------------------------------------------------------- 1 | const {transporter} = require('./emailTransporter'); 2 | 3 | function sendMail(name, email, token) { 4 | 5 | let mailOptions = { 6 | from: '"ReviewZone" ', 7 | to: `${email}`, 8 | subject: 'Forgot Password', 9 | text: `Hello ${name},\nReset your password by clicking the following url\n` + 10 | `http://localhost:3000/api/auth/resetPassword/${token}\n\nThanks,\nReviewZone`, 11 | html: `

Hello ${name},

Reset your password by clicking the following url
12 | 13 | http://localhost:3000/api/auth/resetPassword/${token} 14 |

Thanks,
ReviewZone

` 15 | }; 16 | 17 | return transporter.sendMail(mailOptions); 18 | } 19 | 20 | module.exports.sendMail = sendMail; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const winston = require("winston"); 2 | const express = require("express"); 3 | 4 | const app = express(); 5 | 6 | global.__basedir = __dirname; 7 | 8 | require("./startup/logging")(); 9 | require("./startup/routes")(app); 10 | require("./startup/db")(); 11 | require("./startup/config")(); 12 | require("./startup/validation")(); 13 | 14 | const port = process.env.PORT || 3000; 15 | const server = app.listen(port, () => 16 | winston.info(`Listening on port ${port}...`) 17 | ); 18 | 19 | module.exports = server; 20 | -------------------------------------------------------------------------------- /middleware/admin.js: -------------------------------------------------------------------------------- 1 | module.exports = function (req, res, next) { 2 | if(req.user.userType === 'admin') { 3 | next(); 4 | } else { 5 | res.status(401).send("Access denied. You are not permitted"); 6 | } 7 | } -------------------------------------------------------------------------------- /middleware/auth.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const config = require("config"); 3 | 4 | module.exports = function (req, res, next) { 5 | const token = req.header("x-auth-token"); 6 | if (!token) return res.status(401).send("Access denied. No token provided"); 7 | 8 | try { 9 | const decode = jwt.verify(token, config.get('jwtPrivateKey')); 10 | req.user = decode; 11 | next(); 12 | } catch (e) { 13 | res.status(400).send('Invalid token.'); 14 | } 15 | } -------------------------------------------------------------------------------- /middleware/error.js: -------------------------------------------------------------------------------- 1 | const winston = require("winston"); 2 | 3 | module.exports = function (err, req, res, next) { 4 | winston.error(err.message, err); 5 | 6 | res.status(500).send("Something failed"); 7 | }; 8 | -------------------------------------------------------------------------------- /middleware/image.js: -------------------------------------------------------------------------------- 1 | const multer = require('multer'); 2 | const maxSize = 1 * 1024 * 1024; 3 | 4 | const storage = multer.diskStorage({ 5 | destination: (req, file, cb) => { 6 | cb(null, __basedir + '/uploads/') 7 | }, 8 | filename: (req, file, cb) => { 9 | cb(null, Date.now() + "-" + file.originalname) 10 | } 11 | }); 12 | 13 | const imageFilter = function (req, file, cb) { 14 | if (!file.originalname.match(/\.(jpg|jpeg|png|gif)$/)) { 15 | return cb(new Error('Only image files are allowed!'), false); 16 | } 17 | cb(null, true); 18 | }; 19 | 20 | const upload = multer({ 21 | storage: storage, 22 | limits: {fileSize: maxSize}, 23 | fileFilter: imageFilter 24 | }).single("image"); 25 | 26 | exports.upload = upload; -------------------------------------------------------------------------------- /models/company.js: -------------------------------------------------------------------------------- 1 | const Joi = require("joi"); 2 | const mongoose = require("mongoose"); 3 | const Schema = mongoose.Schema; 4 | 5 | const companySchema = new Schema({ 6 | name: { 7 | type: String, 8 | required: true, 9 | minlength: 3, 10 | maxlength: 255 11 | }, 12 | image: String, 13 | contact: { 14 | mobile: String, 15 | address: String, 16 | website: String 17 | }, 18 | details: String, 19 | status: { 20 | type: String, 21 | required: true, 22 | enum: ["active", "suspend", "delete"], 23 | default: "active" 24 | }, 25 | user: { 26 | type: Schema.Types.ObjectId, 27 | ref: 'User', 28 | required: true 29 | }, 30 | tags: [String] 31 | }); 32 | 33 | const Company = mongoose.model("Company", companySchema); 34 | 35 | function addValidate(company) { 36 | const schema = { 37 | name: Joi.string() 38 | .min(3) 39 | .max(255) 40 | .required(), 41 | mobile: Joi.string(), 42 | address: Joi.string(), 43 | website: Joi.string(), 44 | details: Joi.string() 45 | }; 46 | 47 | return Joi.validate(company, schema); 48 | } 49 | 50 | exports.Company = Company; 51 | exports.addValidate = addValidate; -------------------------------------------------------------------------------- /models/review.js: -------------------------------------------------------------------------------- 1 | const Joi = require("joi"); 2 | const mongoose = require("mongoose"); 3 | const Schema = mongoose.Schema; 4 | 5 | const reviewSchema = new Schema({ 6 | user: { 7 | type: Schema.Types.ObjectId, 8 | ref: 'User', 9 | required: true 10 | }, 11 | company: { 12 | type: Schema.Types.ObjectId, 13 | ref: 'Company', 14 | required: true 15 | }, 16 | star: { 17 | type: Number, 18 | required: true, 19 | min: 1, 20 | max: 5 21 | }, 22 | title: { 23 | type: String, 24 | required: true, 25 | minLength: 3, 26 | maxLength: 30 27 | }, 28 | details: { 29 | type: String, 30 | required: true, 31 | minLength: 3, 32 | maxLength: 1024 33 | }, 34 | status: { 35 | type: String, 36 | required: true, 37 | enum: ["active", "delete"], 38 | default: "active" 39 | }, 40 | response: { 41 | type: String, 42 | minLength: 3, 43 | maxLength: 512 44 | } 45 | }); 46 | 47 | const Review = mongoose.model("Review", reviewSchema); 48 | 49 | function validate(review) { 50 | const schema = { 51 | company: Joi.objectId().required(), 52 | star: Joi.number().integer().min(1).max(5).required(), 53 | title: Joi.string().min(3).max(30).required(), 54 | details: Joi.string().min(3).max(1024).required() 55 | }; 56 | 57 | return Joi.validate(review, schema); 58 | } 59 | 60 | function editValidate(review) { 61 | const schema = { 62 | star: Joi.number().integer().min(1).max(5).required(), 63 | title: Joi.string().min(3).max(30).required(), 64 | details: Joi.string().min(3).max(1024).required() 65 | }; 66 | 67 | return Joi.validate(review, schema); 68 | } 69 | 70 | function responseValidate(response) { 71 | const schema = { 72 | id: Joi.objectId().required(), 73 | response: Joi.string().min(3).max(512).required() 74 | }; 75 | 76 | return Joi.validate(response, schema); 77 | } 78 | 79 | function deleteResponseValidate(response) { 80 | const schema = { 81 | id: Joi.objectId().required() 82 | }; 83 | 84 | return Joi.validate(response, schema); 85 | } 86 | 87 | exports.Review = Review; 88 | exports.validate = validate; 89 | exports.editValidate = editValidate; 90 | exports.responseValidate = responseValidate; 91 | exports.deleteResponseValidate = deleteResponseValidate; -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | const config = require("config"); 2 | const jwt = require("jsonwebtoken"); 3 | const Joi = require("joi"); 4 | const mongoose = require("mongoose"); 5 | 6 | const userSchema = new mongoose.Schema({ 7 | name: { 8 | type: String, 9 | required: true, 10 | minlength: 3, 11 | maxlength: 50 12 | }, 13 | email: { 14 | type: String, 15 | required: true, 16 | minlength: 5, 17 | maxlength: 255, 18 | unique: true 19 | }, 20 | password: { 21 | type: String, 22 | required: true, 23 | minlength: 5, 24 | maxlength: 1024 25 | }, 26 | forgotPassword: { 27 | token: String, 28 | createdAt: Number 29 | }, 30 | status: { 31 | type: String, 32 | required: true, 33 | enum: ["pending", "active", "suspend"], 34 | default: "pending" 35 | }, 36 | userType: { 37 | type: String, 38 | required: true, 39 | enum: ["user", "companyOwner", "admin"], 40 | default: "user" 41 | } 42 | }); 43 | 44 | userSchema.methods.generateAuthToken = function () { 45 | const token = jwt.sign( 46 | { 47 | _id: this._id, 48 | userType: this.userType 49 | }, 50 | config.get("jwtPrivateKey") 51 | ); 52 | 53 | return token; 54 | }; 55 | 56 | userSchema.methods.generateForgotPassToken = function () { 57 | const token = jwt.sign( 58 | { 59 | email: this.email, 60 | token: this.forgotPassword.token 61 | }, 62 | config.get("jwtPrivateKey") 63 | ); 64 | 65 | return token; 66 | }; 67 | 68 | const User = mongoose.model("User", userSchema); 69 | 70 | function registrationValidate(user) { 71 | const schema = { 72 | name: Joi.string() 73 | .min(3) 74 | .max(50) 75 | .required(), 76 | email: Joi.string() 77 | .min(5) 78 | .max(255) 79 | .email() 80 | .required(), 81 | password: Joi.string() 82 | .min(5) 83 | .max(255) 84 | .required() 85 | }; 86 | 87 | return Joi.validate(user, schema); 88 | } 89 | 90 | function signinValidation(user) { 91 | const schema = { 92 | email: Joi.string() 93 | .min(5) 94 | .max(255) 95 | .required() 96 | .email(), 97 | password: Joi.string() 98 | .min(5) 99 | .max(255) 100 | .required() 101 | }; 102 | 103 | return Joi.validate(user, schema); 104 | } 105 | 106 | function forgotPasswordValidation(user) { 107 | const schema = { 108 | email: Joi.string() 109 | .required() 110 | .email() 111 | }; 112 | 113 | return Joi.validate(user, schema); 114 | } 115 | 116 | function checkJwtToken(token) { 117 | try { 118 | const decode = jwt.verify(token, config.get("jwtPrivateKey")); 119 | if (!decode.hasOwnProperty("email") || !decode.hasOwnProperty("token")) { 120 | return false; 121 | } else { 122 | return decode; 123 | } 124 | } catch (Ex) { 125 | return false; 126 | } 127 | } 128 | 129 | function resetPasswordValidation(password) { 130 | const schema = { 131 | password: Joi.string() 132 | .min(5) 133 | .max(255) 134 | .required() 135 | }; 136 | 137 | return Joi.validate(password, schema); 138 | } 139 | 140 | function changePasswordValidation(body) { 141 | const schema = { 142 | currentPassword: Joi.string() 143 | .min(5) 144 | .max(255) 145 | .required(), 146 | newPassword: Joi.string() 147 | .min(5) 148 | .max(255) 149 | .required(), 150 | confirmPassword: Joi.string() 151 | .min(5) 152 | .max(255) 153 | .required() 154 | }; 155 | 156 | return Joi.validate(body, schema); 157 | } 158 | 159 | exports.User = User; 160 | exports.registrationValidate = registrationValidate; 161 | exports.signinValidation = signinValidation; 162 | exports.forgotPasswordValidation = forgotPasswordValidation; 163 | exports.checkJwtToken = checkJwtToken; 164 | exports.resetPasswordValidation = resetPasswordValidation; 165 | exports.changePasswordValidation = changePasswordValidation; 166 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reviewsystem", 3 | "version": "1.0.0", 4 | "description": "A fast, simple & powerful RESTful API based Review System", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon index.js", 8 | "start": "node index.js", 9 | "test": "jest -i --forceExit", 10 | "test:watch": "jest -i --watchAll --verbose", 11 | "coverage": "jest -i --coverage --forceExit", 12 | "coverage:watch": "jest -i --watchAll --verbose --coverage", 13 | "coveralls": "jest -i --coverage --forceExit && cat ./coverage/lcov.info | coveralls" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/rejuan/ReviewSystem.git" 18 | }, 19 | "author": "A M S Rejuan", 20 | "license": "MIT", 21 | "dependencies": { 22 | "bcrypt": "^3.0.3", 23 | "body-parser": "^1.18.3", 24 | "config": "^3.0.1", 25 | "express": "^4.16.4", 26 | "express-async-errors": "^3.1.1", 27 | "joi": "^14.3.1", 28 | "joi-objectid": "^2.0.0", 29 | "jsonwebtoken": "^8.4.0", 30 | "lodash": "^4.17.11", 31 | "moment": "^2.23.0", 32 | "mongoose": "^5.4.2", 33 | "multer": "^1.4.1", 34 | "nodemailer": "^5.0.0", 35 | "winston": "^3.1.0", 36 | "winston-mongodb": "^4.0.3" 37 | }, 38 | "devDependencies": { 39 | "coveralls": "^3.0.3", 40 | "jest": "^23.6.0", 41 | "nodemon": "^1.18.9", 42 | "supertest": "^3.3.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /routes/auth.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const authController = require("../controllers/auth"); 3 | const auth = require("../middleware/auth"); 4 | 5 | const router = express.Router(); 6 | 7 | router.post("/registration", authController.registration); 8 | 9 | router.post("/signin", authController.signin); 10 | 11 | router.post("/forgotPassword", authController.forgotPassword); 12 | 13 | router.post("/resetPassword/:hash", authController.resetPassword); 14 | 15 | router.post("/changePassword", auth, authController.changePassword); 16 | 17 | module.exports = router; 18 | -------------------------------------------------------------------------------- /routes/company.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const companyController = require("../controllers/company"); 3 | const auth = require("../middleware/auth"); 4 | const admin = require("../middleware/admin"); 5 | const router = express.Router(); 6 | 7 | router.post("/", auth, companyController.createCompany); 8 | 9 | router.patch('/:id', auth, companyController.updateCompany); 10 | 11 | router.delete('/:id', auth, companyController.deleteCompany); 12 | 13 | router.get('/:id', auth, companyController.getCompany); 14 | 15 | router.patch('/suspend/:id', [auth, admin], companyController.suspendCompany); 16 | 17 | router.get('/', auth, companyController.companyList); 18 | 19 | module.exports = router; -------------------------------------------------------------------------------- /routes/response.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const auth = require("../middleware/auth"); 3 | const responseController = require("../controllers/response"); 4 | const router = express.Router(); 5 | 6 | router.post("/", auth, responseController.createResponse); 7 | 8 | router.patch("/", auth, responseController.updateResponse); 9 | 10 | router.delete("/", auth, responseController.deleteResponse); 11 | 12 | module.exports = router; -------------------------------------------------------------------------------- /routes/review.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const auth = require("../middleware/auth"); 3 | const reviewController = require("../controllers/review"); 4 | const router = express.Router(); 5 | 6 | router.post("/", auth, reviewController.createReview); 7 | 8 | router.patch("/:id", auth, reviewController.updateReview); 9 | 10 | router.delete("/:id", auth, reviewController.deleteReview); 11 | 12 | router.get("/", auth, reviewController.reviewList); 13 | 14 | module.exports = router; -------------------------------------------------------------------------------- /routes/search.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const searchController = require("../controllers/search"); 3 | const router = express.Router(); 4 | 5 | router.get('/company', searchController.searchByCompany); 6 | 7 | router.get('/tag', searchController.searchByTag); 8 | 9 | module.exports = router; -------------------------------------------------------------------------------- /routes/suggestion.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const suggestionController = require("../controllers/suggestion"); 3 | const router = express.Router(); 4 | 5 | router.get('/company', suggestionController.companySuggestion); 6 | 7 | router.get('/tag', suggestionController.tagSuggestion); 8 | 9 | module.exports = router; -------------------------------------------------------------------------------- /routes/user.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const auth = require("../middleware/auth"); 3 | const admin = require("../middleware/admin"); 4 | const userController = require("../controllers/user"); 5 | const router = express.Router(); 6 | 7 | router.get("/active", [auth, admin] , userController.activeUserList); 8 | 9 | router.get("/suspended", [auth, admin] , userController.suspendedUserList); 10 | 11 | router.patch("/suspend/:id", [auth, admin] , userController.suspendUser); 12 | 13 | router.patch("/unsuspend/:id", [auth, admin] , userController.activeUser); 14 | 15 | module.exports = router; -------------------------------------------------------------------------------- /startup/config.js: -------------------------------------------------------------------------------- 1 | const config = require("config"); 2 | 3 | module.exports = function () { 4 | if (!config.get("jwtPrivateKey")) { 5 | throw new Error("FATAL ERROR: jwtPrivateKey is not defined"); 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /startup/db.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const winstone = require("winston"); 3 | const config = require("config"); 4 | 5 | module.exports = function () { 6 | const db = config.get("db"); 7 | mongoose 8 | .connect( 9 | db, 10 | { 11 | autoIndex: false, 12 | useNewUrlParser: true 13 | } 14 | ) 15 | .then(() => winstone.info(`${db} db server connected successfully`)); 16 | }; 17 | -------------------------------------------------------------------------------- /startup/logging.js: -------------------------------------------------------------------------------- 1 | const winston = require("winston"); 2 | require('express-async-errors'); 3 | 4 | const errorStackTracerFormat = winston.format(info => { 5 | if (info.meta && info.meta instanceof Error) { 6 | info.message = `${info.message} ${info.meta.stack}`; 7 | } 8 | return info; 9 | }); 10 | 11 | module.exports = function () { 12 | process.on("uncaughtException", ex => { 13 | winston.error(ex.message, ex); 14 | setTimeout(() => { 15 | process.exit(1); 16 | }, 4000); 17 | }); 18 | 19 | process.on("unhandledRejection", ex => { 20 | throw ex; 21 | }); 22 | 23 | winston.add( 24 | new winston.transports.File({ 25 | filename: "logfile.log", 26 | format: winston.format.combine( 27 | winston.format.splat(), 28 | errorStackTracerFormat(), 29 | winston.format.timestamp(), 30 | winston.format.json() 31 | ) 32 | }) 33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /startup/routes.js: -------------------------------------------------------------------------------- 1 | const auth = require("../routes/auth"); 2 | const company = require("../routes/company"); 3 | const review = require("../routes/review"); 4 | const search = require("../routes/search"); 5 | const suggestion = require("../routes/suggestion"); 6 | const response = require("../routes/response"); 7 | const user = require("../routes/user"); 8 | const error = require("../middleware/error"); 9 | const bodyParser = require('body-parser'); 10 | const express = require("express"); 11 | 12 | module.exports = function (app) { 13 | app.use(bodyParser.json()); 14 | app.use(bodyParser.urlencoded({ extended: true })); 15 | app.use("/api/auth", auth); 16 | app.use("/api/company", company); 17 | app.use("/api/review", review); 18 | app.use("/api/search", search); 19 | app.use("/api/suggestion", suggestion); 20 | app.use("/api/response", response); 21 | app.use("/api/user", user); 22 | app.use(error); 23 | }; 24 | -------------------------------------------------------------------------------- /startup/validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | 3 | module.exports = function() { 4 | Joi.objectId = require('joi-objectid')(Joi); 5 | } -------------------------------------------------------------------------------- /tests/auth.test.js: -------------------------------------------------------------------------------- 1 | const request = require("supertest"); 2 | const moment = require("moment"); 3 | const {User} = require("../models/user"); 4 | const jwt = require("jsonwebtoken"); 5 | const bcrypt = require("bcrypt"); 6 | const config = require("config"); 7 | const crypto = require("crypto"); 8 | const _ = require("lodash"); 9 | let server; 10 | 11 | beforeEach(() => { 12 | server = require("../index"); 13 | jest.setTimeout(30000); 14 | }); 15 | 16 | afterEach(async () => { 17 | await User.remove({}); 18 | server.close(); 19 | }); 20 | 21 | describe("/api/auth", () => { 22 | let url; 23 | 24 | const exec = (requestObjet) => { 25 | return request(server) 26 | .post(url) 27 | .send(requestObjet); 28 | }; 29 | 30 | const execWithToken = (requestObjet, token) => { 31 | return request(server) 32 | .post(url) 33 | .set('x-auth-token', token) 34 | .send(requestObjet); 35 | }; 36 | 37 | describe("POST /registration", () => { 38 | let name, email, password; 39 | 40 | beforeEach(() => { 41 | name = "test"; 42 | email = "auth@test.com"; 43 | password = "123456"; 44 | url = "/api/auth/registration"; 45 | }); 46 | 47 | it("should return 400 if user doesn't send any data", async () => { 48 | const res = await exec({}); 49 | expect(res.status).toBe(400); 50 | }); 51 | 52 | it("should return 400 if user doesn't send name", async () => { 53 | const res = await exec({email, password}); 54 | expect(res.status).toBe(400); 55 | }); 56 | 57 | it("should return 400 if user doesn't send email", async () => { 58 | const res = await exec({name, password}); 59 | expect(res.status).toBe(400); 60 | }); 61 | 62 | it("should return 400 if user doesn't send password", async () => { 63 | const res = await exec({name, email}); 64 | expect(res.status).toBe(400); 65 | }); 66 | 67 | it("should return 400 if name is less than 3", async () => { 68 | name = "aa"; 69 | const res = await exec({name, email, password}); 70 | expect(res.status).toBe(400); 71 | }); 72 | 73 | it("should return 400 if name is more than 50", async () => { 74 | name = new Array(55).join("a"); 75 | const res = await exec({name, email, password}); 76 | expect(res.status).toBe(400); 77 | }); 78 | 79 | it("should return 400 if email is less than 5", async () => { 80 | email = "@a."; 81 | const res = await exec({name, email, password}); 82 | expect(res.status).toBe(400); 83 | }); 84 | 85 | it("should return 400 if email is more than 255", async () => { 86 | email = new Array(255).join("a") + "@test.com"; 87 | const res = await exec({name, email, password}); 88 | expect(res.status).toBe(400); 89 | }); 90 | 91 | it("should return 400 if email is not valid", async () => { 92 | email = "aaaaaa"; 93 | const res = await exec({name, email, password}); 94 | expect(res.status).toBe(400); 95 | }); 96 | 97 | it("should return 400 if email already exist", async () => { 98 | const user = new User({name, email, password}); 99 | user.save(); 100 | const res = await exec({name, email, password}); 101 | expect(res.status).toBe(400); 102 | }); 103 | 104 | it("should save if input is valid", async () => { 105 | const res = await exec({name, email, password}); 106 | const user = await User.findOne({name: name}); 107 | expect(res.status).toBe(200); 108 | expect(user).not.toBeNull(); 109 | expect(user.name).toEqual(name); 110 | }); 111 | 112 | it("should return user if input is valid", async () => { 113 | const res = await exec({name, email, password}); 114 | expect(res.status).toBe(200); 115 | expect(res.body).toHaveProperty("_id"); 116 | expect(res.body).toHaveProperty("name", name); 117 | expect(res.body).toHaveProperty("email", email); 118 | }); 119 | 120 | it("should return valid jwt token if input is valid", async () => { 121 | const res = await exec({name, email, password}); 122 | expect(res.status).toBe(200); 123 | expect(res.header).toHaveProperty("x-auth-token"); 124 | let token = res.get("x-auth-token"); 125 | const decode = jwt.verify(token, config.get("jwtPrivateKey")); 126 | expect(decode).toMatchObject({ 127 | _id: res.body._id, 128 | userType: "user" 129 | }); 130 | }); 131 | }); 132 | 133 | describe("POST /signin", () => { 134 | let email, password; 135 | 136 | beforeEach(() => { 137 | url = "/api/auth/signin"; 138 | email = "auth@test.com"; 139 | password = "123456"; 140 | }); 141 | 142 | it("should return 400 if user doesn't send any data", async () => { 143 | const res = await exec({}); 144 | expect(res.status).toBe(400); 145 | }); 146 | 147 | it("should return 400 if user doesn't send email", async () => { 148 | const res = await exec({password}); 149 | expect(res.status).toBe(400); 150 | }); 151 | 152 | it("should return 400 if user doesn't send password", async () => { 153 | const res = await exec({email}); 154 | expect(res.status).toBe(400); 155 | }); 156 | 157 | it("should return 400 if email is less than 5", async () => { 158 | email = "@a."; 159 | const res = await exec({email, password}); 160 | expect(res.status).toBe(400); 161 | }); 162 | 163 | it("should return 400 if email is more than 255", async () => { 164 | email = new Array(255).join("a") + "@test.com"; 165 | const res = await exec({email, password}); 166 | expect(res.status).toBe(400); 167 | }); 168 | 169 | it("should return 400 if email is not valid", async () => { 170 | email = "aaaaaa"; 171 | const res = await exec({email, password}); 172 | expect(res.status).toBe(400); 173 | }); 174 | 175 | it("should return 400 if email not exist", async () => { 176 | const res = await exec({email, password}); 177 | expect(res.status).toBe(400); 178 | }); 179 | 180 | it("should return 400 if email and password doesn't match", async () => { 181 | const res = await exec({email, password}); 182 | expect(res.status).toBe(400); 183 | }); 184 | 185 | it("should return user if input is valid", async () => { 186 | await saveUser(name, email, password); 187 | const res = await exec({email, password}); 188 | expect(res.status).toBe(200); 189 | expect(res.body).toHaveProperty("_id"); 190 | expect(res.body).toHaveProperty("name", name); 191 | expect(res.body).toHaveProperty("email", email); 192 | }); 193 | 194 | it("should return valid jwt token if input is valid", async () => { 195 | await saveUser(name, email, password); 196 | const res = await exec({email, password}); 197 | expect(res.status).toBe(200); 198 | expect(res.header).toHaveProperty("x-auth-token"); 199 | let token = res.get("x-auth-token"); 200 | const decode = jwt.verify(token, config.get("jwtPrivateKey")); 201 | expect(decode).toMatchObject({ 202 | _id: res.body._id, 203 | userType: "user" 204 | }); 205 | }); 206 | }); 207 | 208 | describe("POST /forgotPassword", () => { 209 | let email; 210 | 211 | beforeEach(() => { 212 | url = "/api/auth/forgotPassword"; 213 | email = "auth@test.com"; 214 | }); 215 | 216 | it("should return 400 if user doesn't send any data", async () => { 217 | const res = await exec({}); 218 | expect(res.status).toBe(400); 219 | }); 220 | 221 | it("should return 400 if email is not valid", async () => { 222 | email = "aaaaa"; 223 | const res = await exec({email}); 224 | expect(res.status).toBe(400); 225 | }); 226 | 227 | it("should return 404 if email is not exist", async () => { 228 | email = "aaaaa@test.com"; 229 | const res = await exec({email}); 230 | expect(res.status).toBe(404); 231 | }); 232 | 233 | it("should return 200 if email exist", async () => { 234 | await saveUser("test", email, "12345"); 235 | 236 | const res = await exec({email}); 237 | expect(res.status).toBe(200); 238 | }); 239 | 240 | it("should add a token if email exist", async () => { 241 | await saveUser("test", email, "12345"); 242 | const res = await exec({email}); 243 | let user = await User.findOne({email}); 244 | 245 | expect(user).not.toBeNull(); 246 | expect(user).toHaveProperty('forgotPassword'); 247 | expect(user.forgotPassword).toHaveProperty('token'); 248 | }); 249 | 250 | it("should have a createdAt which is less than 5 minutes if email exist", async () => { 251 | await saveUser("test", email, "12345"); 252 | const res = await exec({email}); 253 | let user = await User.findOne({email}); 254 | 255 | expect(user).not.toBeNull(); 256 | expect(user.forgotPassword).toHaveProperty('token'); 257 | const now = moment(); 258 | const was = moment(user.forgotPassword.createdAt * 1000); 259 | expect(now.diff(was, 'minutes')).toBeLessThan(5); 260 | }); 261 | 262 | }) 263 | 264 | describe("POST /resetPassword/:hash", () => { 265 | let newPassword, name, password, email; 266 | 267 | beforeEach(() => { 268 | url = "/api/auth/resetPassword/"; 269 | newPassword = "123456"; 270 | name = "test"; 271 | email = "auth@test.com"; 272 | password = "12345"; 273 | }); 274 | 275 | it("should return 400 if no token", async () => { 276 | url = url + "1234"; 277 | const res = await exec({password}); 278 | expect(res.status).toBe(400); 279 | }); 280 | 281 | it("should return 401 if token expired", async () => { 282 | const forgotPassword = { 283 | token: crypto.randomBytes(20).toString('hex'), 284 | createdAt: moment().unix() - (6 * 60) 285 | }; 286 | let user = new User({name, email, password, forgotPassword}); 287 | await user.save(); 288 | const token = user.generateForgotPassToken(); 289 | 290 | url = url + token; 291 | password = "123456"; 292 | res = await exec({password}); 293 | expect(res.status).toBe(401); 294 | }); 295 | 296 | it("should return 400 if password less than 5", async () => { 297 | const user = await saveUser(name, email, password); 298 | const token = user.generateForgotPassToken(); 299 | user.forgotPassword.createdAt = moment().unix() - (6 * 60); 300 | await user.save(); 301 | 302 | url = url + token; 303 | password = "123"; 304 | const res = await exec({password}); 305 | expect(res.status).toBe(400); 306 | }); 307 | 308 | it("should return 400 if password more than 256", async () => { 309 | const user = await saveUser(name, email, password); 310 | const token = user.generateForgotPassToken(); 311 | user.forgotPassword.createdAt = moment().unix() - (6 * 60); 312 | await user.save(); 313 | 314 | url = url + token; 315 | password = new Array(256).fill("a"); 316 | const res = await exec({password}); 317 | expect(res.status).toBe(400); 318 | }); 319 | 320 | it("should return 400 if no password", async () => { 321 | const user = await saveUser(name, email, password); 322 | const token = user.generateForgotPassToken(); 323 | user.forgotPassword.createdAt = moment().unix() - (6 * 60); 324 | await user.save(); 325 | 326 | url = url + token; 327 | const res = await exec({}); 328 | expect(res.status).toBe(400); 329 | }); 330 | 331 | it("should return 200 if valid input", async () => { 332 | const user = await saveUser(name, email, password); 333 | const token = user.generateForgotPassToken(); 334 | 335 | url = "/api/auth/resetPassword/" + token; 336 | password = "123456"; 337 | const res = await exec({password}); 338 | expect(res.status).toBe(200); 339 | }); 340 | }) 341 | 342 | describe("POST /changePassword", () => { 343 | let name, email, currentPassword, newPassword, confirmPassword; 344 | 345 | beforeEach(() => { 346 | url = "/api/auth/changePassword"; 347 | name = "test"; 348 | email = "auth@test.com"; 349 | currentPassword = "12345"; 350 | newPassword = "1234567"; 351 | confirmPassword = "1234567"; 352 | }); 353 | 354 | it("should return 401 if no JWT", async () => { 355 | const user = await saveUser(name, email, currentPassword); 356 | const token = ""; 357 | const res = await execWithToken({}, token); 358 | expect(res.status).toBe(401); 359 | }); 360 | 361 | it("should return 400 if JWT not valid", async () => { 362 | const user = await saveUser(name, email, currentPassword); 363 | const token = "1234"; 364 | const res = await execWithToken({}, token); 365 | expect(res.status).toBe(400); 366 | }); 367 | 368 | it("should return 400 if any of the field empty", async () => { 369 | const user = await saveUser(name, email, currentPassword); 370 | const token = user.generateAuthToken(); 371 | 372 | let res = await execWithToken({currentPassword, newPassword}, token); 373 | expect(res.status).toBe(400); 374 | 375 | res = await execWithToken({currentPassword, confirmPassword}, token); 376 | expect(res.status).toBe(400); 377 | 378 | res = await execWithToken({newPassword, confirmPassword}, token); 379 | expect(res.status).toBe(400); 380 | }); 381 | 382 | it("should return 400 if new & confirm password doesn't match", async () => { 383 | const user = await saveUser(name, email, currentPassword); 384 | const token = user.generateAuthToken(); 385 | confirmPassword = "abcdef"; 386 | 387 | let res = await execWithToken({currentPassword, newPassword, confirmPassword}, token); 388 | expect(res.status).toBe(400); 389 | }); 390 | 391 | it("should return 400 if current password doesn't match", async () => { 392 | const user = await saveUser(name, email, currentPassword); 393 | const token = user.generateAuthToken(); 394 | currentPassword = "abcdef"; 395 | 396 | let res = await execWithToken({currentPassword, newPassword, confirmPassword}, token); 397 | expect(res.status).toBe(400); 398 | }); 399 | 400 | it("should return 200 if valid input", async () => { 401 | const user = await saveUser(name, email, currentPassword); 402 | const token = user.generateAuthToken(); 403 | 404 | let res = await execWithToken({currentPassword, newPassword, confirmPassword}, token); 405 | expect(res.status).toBe(200); 406 | }); 407 | 408 | }) 409 | 410 | async function saveUser(name, email, password) { 411 | const forgotPassword = { 412 | token: crypto.randomBytes(20).toString('hex'), 413 | createdAt: moment().unix() 414 | }; 415 | 416 | const user = new User({name, email, password, forgotPassword}); 417 | const salt = await bcrypt.genSalt(10); 418 | user.password = await bcrypt.hash(user.password, salt); 419 | return await user.save(); 420 | } 421 | }); 422 | -------------------------------------------------------------------------------- /tests/company.test.js: -------------------------------------------------------------------------------- 1 | const request = require("supertest"); 2 | const moment = require("moment"); 3 | const {User} = require("../models/user"); 4 | const {Company} = require("../models/company"); 5 | const bcrypt = require("bcrypt"); 6 | const crypto = require("crypto"); 7 | const _ = require("lodash"); 8 | let server; 9 | 10 | beforeEach(async () => { 11 | server = require("../index"); 12 | jest.setTimeout(30000); 13 | }); 14 | 15 | afterEach(async () => { 16 | await User.remove({}); 17 | await Company.remove({}); 18 | server.close(); 19 | }); 20 | 21 | describe("/api/company", () => { 22 | let url; 23 | 24 | describe("POST /", () => { 25 | let name, email, password; 26 | 27 | const exec = (requestObjet, token) => { 28 | return request(server) 29 | .post(url) 30 | .set('x-auth-token', token) 31 | .field('name', requestObjet.name) 32 | .attach('image', 'tests/images/test.png'); 33 | }; 34 | 35 | const execAllField = (requestObjet, token) => { 36 | return request(server) 37 | .post(url) 38 | .set('x-auth-token', token) 39 | .field('name', requestObjet.name) 40 | .field('mobile', requestObjet.mobile) 41 | .field('address', requestObjet.address) 42 | .field('website', requestObjet.website) 43 | .field('details', requestObjet.details) 44 | .attach('image', 'tests/images/test.png'); 45 | }; 46 | 47 | beforeEach(() => { 48 | name = "test"; 49 | email = "company@test.com"; 50 | password = "123456"; 51 | url = "/api/company/"; 52 | }); 53 | 54 | it("should return 401 if no JWT", async () => { 55 | const user = await saveUser(name, email, password); 56 | const token = ""; 57 | const res = await exec({name: "test company"}, token); 58 | expect(res.status).toBe(401); 59 | }); 60 | 61 | it("should return 400 if JWT not valid", async () => { 62 | const user = await saveUser(name, email, password); 63 | const token = "1234"; 64 | const res = await exec({name: "test company"}, token); 65 | expect(res.status).toBe(400); 66 | }); 67 | 68 | it("should return 400 if name field empty", async () => { 69 | const user = await saveUser(name, email, password); 70 | const token = user.generateAuthToken(); 71 | let res = await exec({name: ""}, token); 72 | expect(res.status).toBe(400); 73 | 74 | res = await exec({name: "a"}, token); 75 | expect(res.status).toBe(400); 76 | 77 | name = new Array(257).join('a'); 78 | res = await exec({name: name}, token); 79 | expect(res.status).toBe(400); 80 | }); 81 | 82 | it("should return 200 if input valid", async () => { 83 | const user = await saveUser(name, email, password); 84 | const token = user.generateAuthToken(); 85 | 86 | const companyData = { 87 | name: name, 88 | mobile: 'mobile', 89 | address: 'address', 90 | website: 'http://website.com/', 91 | details: 'details details' 92 | }; 93 | 94 | let res = await execAllField(companyData, token); 95 | expect(res.status).toBe(200); 96 | }); 97 | }); 98 | 99 | describe("before each save user and company", () => { 100 | 101 | let name, email, password, companyData, user; 102 | 103 | beforeEach(async () => { 104 | name = "test"; 105 | email = "company@test.com"; 106 | password = "123456"; 107 | user = await saveUser(name, email, password); 108 | companyData = { 109 | name: name, 110 | contact: { 111 | mobile: 'mobile', 112 | address: 'address', 113 | website: 'http://website.com/' 114 | }, 115 | details: 'details details', 116 | user: user._id.toString(), 117 | status: 'active' 118 | }; 119 | companyData = await saveCompany(companyData); 120 | }); 121 | 122 | describe("PATCH /:id", () => { 123 | 124 | beforeEach(async () => { 125 | url = "/api/company/" + companyData._id.toString(); 126 | }); 127 | 128 | const exec = (requestObjet, token) => { 129 | return request(server) 130 | .patch(url) 131 | .set('x-auth-token', token) 132 | .field('name', requestObjet.name) 133 | .attach('image', 'tests/images/test.png'); 134 | }; 135 | 136 | const execAllField = (requestObjet, token) => { 137 | return request(server) 138 | .patch(url) 139 | .set('x-auth-token', token) 140 | .field('name', requestObjet.name) 141 | .field('mobile', requestObjet.mobile) 142 | .field('address', requestObjet.address) 143 | .field('website', requestObjet.website) 144 | .field('details', requestObjet.details) 145 | .attach('image', 'tests/images/test.png'); 146 | }; 147 | 148 | it("should return 401 if no JWT", async () => { 149 | const token = ""; 150 | const res = await exec({name: "test company"}, token); 151 | expect(res.status).toBe(401); 152 | }); 153 | 154 | it("should return 400 if JWT not valid", async () => { 155 | const token = "1234"; 156 | const res = await exec({name: "test company"}, token); 157 | expect(res.status).toBe(400); 158 | }); 159 | 160 | it("should return 400 if name field empty", async () => { 161 | const token = user.generateAuthToken(); 162 | let res = await exec({name: ""}, token); 163 | expect(res.status).toBe(400); 164 | 165 | res = await exec({name: "a"}, token); 166 | expect(res.status).toBe(400); 167 | 168 | name = new Array(257).join('a'); 169 | res = await exec({name: name}, token); 170 | expect(res.status).toBe(400); 171 | }); 172 | 173 | it("should return 200 if input valid", async () => { 174 | const token = user.generateAuthToken(); 175 | name = "test edit"; 176 | let mobile = "mobile edit"; 177 | let address = "address edit"; 178 | let website = "website edit"; 179 | let details = "details edit"; 180 | 181 | let res = await execAllField({ 182 | name, mobile, address, website, details 183 | }, token); 184 | expect(res.status).toBe(200); 185 | }); 186 | 187 | it("should return 200 if input valid and admin", async () => { 188 | let adminUser = await saveUser(name, "company.admin@test.com", password); 189 | adminUser.userType = "admin"; 190 | adminUser = await adminUser.save(); 191 | 192 | const token = adminUser.generateAuthToken(); 193 | name = "test edit"; 194 | let mobile = "mobile edit"; 195 | let address = "address edit"; 196 | let website = "website edit"; 197 | let details = "details edit"; 198 | 199 | let res = await execAllField({ 200 | name, mobile, address, website, details 201 | }, token); 202 | expect(res.status).toBe(200); 203 | }); 204 | }); 205 | 206 | describe("DELETE /:id", () => { 207 | 208 | beforeEach(() => { 209 | url = "/api/company/" + companyData._id.toString(); 210 | }); 211 | 212 | const exec = (token) => { 213 | return request(server) 214 | .delete(url) 215 | .set('x-auth-token', token) 216 | .send(); 217 | }; 218 | 219 | it("should return 401 if no JWT", async () => { 220 | const token = ""; 221 | const res = await exec(token); 222 | expect(res.status).toBe(401); 223 | }); 224 | 225 | it("should return 400 if JWT not valid", async () => { 226 | const token = "1234"; 227 | const res = await exec(token); 228 | expect(res.status).toBe(400); 229 | }); 230 | 231 | it("should return 404 if company not exist", async () => { 232 | companyData.status = 'delete'; 233 | await companyData.save(); 234 | 235 | const token = user.generateAuthToken(); 236 | let res = await exec(token); 237 | expect(res.status).toBe(404); 238 | }); 239 | 240 | it("should return 200 if successfully delete", async () => { 241 | const token = user.generateAuthToken(); 242 | let res = await exec(token); 243 | expect(res.status).toBe(200); 244 | }); 245 | 246 | it("should return 200 if admin & successfully delete", async () => { 247 | let adminUser = await saveUser(name, "company.admin@test.com", password); 248 | adminUser.userType = "admin"; 249 | adminUser = await adminUser.save(); 250 | 251 | const token = adminUser.generateAuthToken(); 252 | let res = await exec(token); 253 | expect(res.status).toBe(200); 254 | }); 255 | }); 256 | 257 | describe("GET /:id", () => { 258 | 259 | beforeEach(async () => { 260 | url = "/api/company/" + companyData._id.toString(); 261 | }); 262 | 263 | const exec = (token) => { 264 | return request(server) 265 | .get(url) 266 | .set('x-auth-token', token) 267 | .send(); 268 | }; 269 | 270 | it("should return 401 if no JWT", async () => { 271 | const token = ""; 272 | const res = await exec(token); 273 | expect(res.status).toBe(401); 274 | }); 275 | 276 | it("should return 400 if JWT not valid", async () => { 277 | const token = "1234"; 278 | const res = await exec(token); 279 | expect(res.status).toBe(400); 280 | }); 281 | 282 | it("should return 404 if company not exist", async () => { 283 | companyData.status = 'delete'; 284 | await companyData.save(); 285 | 286 | const token = user.generateAuthToken(); 287 | let res = await exec(token); 288 | expect(res.status).toBe(404); 289 | }); 290 | 291 | it("should return 200 if company exist", async () => { 292 | const token = user.generateAuthToken(); 293 | let res = await exec(token); 294 | expect(res.status).toBe(200); 295 | }); 296 | }); 297 | 298 | describe("PATCH /suspend/:id", () => { 299 | 300 | beforeEach(async () => { 301 | url = "/api/company/suspend/" + companyData._id.toString(); 302 | }); 303 | 304 | const exec = (token) => { 305 | return request(server) 306 | .patch(url) 307 | .set('x-auth-token', token) 308 | .send(); 309 | }; 310 | 311 | it("should return 401 if no JWT", async () => { 312 | const token = ""; 313 | const res = await exec(token); 314 | expect(res.status).toBe(401); 315 | }); 316 | 317 | it("should return 400 if JWT not valid", async () => { 318 | const token = "1234"; 319 | const res = await exec(token); 320 | expect(res.status).toBe(400); 321 | }); 322 | 323 | it("should return 401 if user not admin", async () => { 324 | const token = user.generateAuthToken(); 325 | const res = await exec(token); 326 | expect(res.status).toBe(401); 327 | }); 328 | 329 | it("should return 404 if company not exist", async () => { 330 | user.userType = 'admin'; 331 | await user.save(); 332 | 333 | companyData.status = 'delete'; 334 | await companyData.save(); 335 | 336 | const token = user.generateAuthToken(); 337 | let res = await exec(token); 338 | expect(res.status).toBe(404); 339 | }); 340 | 341 | it("should return 200 if successfully delete", async () => { 342 | user.userType = 'admin'; 343 | await user.save(); 344 | 345 | const token = user.generateAuthToken(); 346 | let res = await exec(token); 347 | expect(res.status).toBe(200); 348 | }); 349 | }); 350 | 351 | describe("GET /", () => { 352 | 353 | beforeEach(async () => { 354 | url = "/api/company/"; 355 | }); 356 | 357 | const exec = (token) => { 358 | return request(server) 359 | .get(url) 360 | .set('x-auth-token', token) 361 | .send(); 362 | }; 363 | 364 | it("should return 401 if no JWT", async () => { 365 | const token = ""; 366 | const res = await exec(token); 367 | expect(res.status).toBe(401); 368 | }); 369 | 370 | it("should return 400 if JWT not valid", async () => { 371 | const token = "1234"; 372 | const res = await exec(token); 373 | expect(res.status).toBe(400); 374 | }); 375 | 376 | it("should return 200 if company exist", async () => { 377 | const token = user.generateAuthToken(); 378 | let res = await exec(token); 379 | expect(res.status).toBe(200); 380 | }); 381 | }); 382 | }); 383 | }); 384 | 385 | async function saveUser(name, email, password) { 386 | const forgotPassword = { 387 | token: crypto.randomBytes(20).toString('hex'), 388 | createdAt: moment().unix() 389 | }; 390 | 391 | const user = new User({name, email, password, forgotPassword}); 392 | const salt = await bcrypt.genSalt(10); 393 | user.password = await bcrypt.hash(user.password, salt); 394 | return await user.save(); 395 | } 396 | 397 | async function saveCompany(companyData) { 398 | const company = new Company(companyData); 399 | return await company.save(); 400 | } 401 | -------------------------------------------------------------------------------- /tests/images/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rejuan/ReviewSystem/519d8eed4a88074ff3b36f543ec58e204ef09270/tests/images/test.png -------------------------------------------------------------------------------- /tests/response.test.js: -------------------------------------------------------------------------------- 1 | const request = require("supertest"); 2 | const moment = require("moment"); 3 | const {User} = require("../models/user"); 4 | const {Company} = require("../models/company"); 5 | const {Review} = require("../models/review"); 6 | const bcrypt = require("bcrypt"); 7 | const crypto = require("crypto"); 8 | const _ = require("lodash"); 9 | let server; 10 | 11 | beforeEach(async () => { 12 | server = require("../index"); 13 | jest.setTimeout(30000); 14 | }); 15 | 16 | afterEach(async () => { 17 | await User.remove({}); 18 | await Company.remove({}); 19 | await Review.remove({}); 20 | server.close(); 21 | }); 22 | 23 | describe("/api/response", () => { 24 | let url, name, email, password, companyData, company, user, 25 | reviewData, review, responseData, response; 26 | 27 | beforeEach(async () => { 28 | url = "/api/response"; 29 | name = "test"; 30 | email = "response@test.com"; 31 | password = "123456"; 32 | user = await saveUser(name, email, password); 33 | companyData = { 34 | name: name, 35 | contact: { 36 | mobile: 'mobile', 37 | address: 'address', 38 | website: 'http://website.com/' 39 | }, 40 | details: 'details details', 41 | user: user._id.toString(), 42 | status: 'active' 43 | }; 44 | company = await saveCompany(companyData); 45 | reviewData = { 46 | user: user._id.toString(), 47 | company: company._id.toString(), 48 | star: 2, 49 | title: "test title", 50 | details: "details description of a company" 51 | }; 52 | 53 | review = await saveReview(reviewData); 54 | responseData = { 55 | id: review._id.toString(), 56 | response: "Thanks a lot." 57 | } 58 | }); 59 | 60 | describe("POST /", () => { 61 | 62 | const exec = (requestObjet, token) => { 63 | return request(server) 64 | .post(url) 65 | .set('x-auth-token', token) 66 | .send(requestObjet); 67 | }; 68 | 69 | it("should return 401 if no JWT", async () => { 70 | const token = ""; 71 | const res = await exec(responseData, token); 72 | expect(res.status).toBe(401); 73 | }); 74 | 75 | it("should return 400 if JWT not valid", async () => { 76 | const token = "1234"; 77 | const res = await exec(reviewData, token); 78 | expect(res.status).toBe(400); 79 | }); 80 | 81 | it("should return 404 if no review found", async () => { 82 | const token = user.generateAuthToken(); 83 | responseData.id = user._id; // wrong id 84 | const res = await exec(responseData, token); 85 | expect(res.status).toBe(404); 86 | }); 87 | 88 | it("should return 401 if not company owner", async () => { 89 | const testUser = await saveUser(name, "response1@test.com", password); 90 | const token = testUser.generateAuthToken(); 91 | const res = await exec(responseData, token); 92 | expect(res.status).toBe(401); 93 | }); 94 | 95 | it("should return 400 if no response", async () => { 96 | const token = user.generateAuthToken(); 97 | delete responseData.response; 98 | const res = await exec(responseData, token); 99 | expect(res.status).toBe(400); 100 | }); 101 | 102 | it("should return 400 if no review id", async () => { 103 | const token = user.generateAuthToken(); 104 | delete responseData.id; 105 | const res = await exec(responseData, token); 106 | expect(res.status).toBe(400); 107 | }); 108 | 109 | it("should return 400 if response less than 3", async () => { 110 | const token = user.generateAuthToken(); 111 | responseData.response = "a"; 112 | const res = await exec(responseData, token); 113 | expect(res.status).toBe(400); 114 | }); 115 | 116 | it("should return 400 if response more than 512", async () => { 117 | const token = user.generateAuthToken(); 118 | responseData.response = new Array(514).join("a"); 119 | const res = await exec(responseData, token); 120 | expect(res.status).toBe(400); 121 | }); 122 | 123 | it("should return 200 if valid input", async () => { 124 | const token = user.generateAuthToken(); 125 | const res = await exec(responseData, token); 126 | expect(res.status).toBe(200); 127 | }); 128 | 129 | }); 130 | 131 | describe("PATCH /", () => { 132 | 133 | const exec = (requestObjet, token) => { 134 | return request(server) 135 | .patch(url) 136 | .set('x-auth-token', token) 137 | .send(requestObjet); 138 | }; 139 | 140 | it("should return 401 if no JWT", async () => { 141 | const token = ""; 142 | const res = await exec(responseData, token); 143 | expect(res.status).toBe(401); 144 | }); 145 | 146 | it("should return 400 if JWT not valid", async () => { 147 | const token = "1234"; 148 | const res = await exec(reviewData, token); 149 | expect(res.status).toBe(400); 150 | }); 151 | 152 | it("should return 404 if no review found", async () => { 153 | const token = user.generateAuthToken(); 154 | responseData.id = user._id; // wrong id 155 | const res = await exec(responseData, token); 156 | expect(res.status).toBe(404); 157 | }); 158 | 159 | it("should return 401 if not company owner", async () => { 160 | const testUser = await saveUser(name, "response1@test.com", password); 161 | const token = testUser.generateAuthToken(); 162 | const res = await exec(responseData, token); 163 | expect(res.status).toBe(401); 164 | }); 165 | 166 | it("should return 400 if no response", async () => { 167 | const token = user.generateAuthToken(); 168 | delete responseData.response; 169 | const res = await exec(responseData, token); 170 | expect(res.status).toBe(400); 171 | }); 172 | 173 | it("should return 400 if no review id", async () => { 174 | const token = user.generateAuthToken(); 175 | delete responseData.id; 176 | const res = await exec(responseData, token); 177 | expect(res.status).toBe(400); 178 | }); 179 | 180 | it("should return 400 if response less than 3", async () => { 181 | const token = user.generateAuthToken(); 182 | responseData.response = "a"; 183 | const res = await exec(responseData, token); 184 | expect(res.status).toBe(400); 185 | }); 186 | 187 | it("should return 400 if response more than 512", async () => { 188 | const token = user.generateAuthToken(); 189 | responseData.response = new Array(514).join("a"); 190 | const res = await exec(responseData, token); 191 | expect(res.status).toBe(400); 192 | }); 193 | 194 | it("should return 200 if valid input", async () => { 195 | review.response = "test"; 196 | review = await review.save(); 197 | const token = user.generateAuthToken(); 198 | const res = await exec(responseData, token); 199 | expect(res.status).toBe(200); 200 | }); 201 | 202 | }); 203 | 204 | describe("DELETE /", () => { 205 | 206 | beforeEach(() => { 207 | delete responseData.response; 208 | }); 209 | 210 | const exec = (requestObjet, token) => { 211 | return request(server) 212 | .delete(url) 213 | .set('x-auth-token', token) 214 | .send(requestObjet); 215 | }; 216 | 217 | it("should return 401 if no JWT", async () => { 218 | const token = ""; 219 | const res = await exec(responseData, token); 220 | expect(res.status).toBe(401); 221 | }); 222 | 223 | it("should return 400 if JWT not valid", async () => { 224 | const token = "1234"; 225 | const res = await exec(reviewData, token); 226 | expect(res.status).toBe(400); 227 | }); 228 | 229 | it("should return 404 if no review found", async () => { 230 | const token = user.generateAuthToken(); 231 | responseData.id = user._id; // wrong id 232 | const res = await exec(responseData, token); 233 | expect(res.status).toBe(404); 234 | }); 235 | 236 | it("should return 401 if not company owner", async () => { 237 | const testUser = await saveUser(name, "response1@test.com", password); 238 | const token = testUser.generateAuthToken(); 239 | const res = await exec(responseData, token); 240 | expect(res.status).toBe(401); 241 | }); 242 | 243 | it("should return 200 if valid input", async () => { 244 | review = await review.save(); 245 | const token = user.generateAuthToken(); 246 | const res = await exec(responseData, token); 247 | expect(res.status).toBe(200); 248 | }); 249 | 250 | }); 251 | 252 | }); 253 | 254 | async function saveUser(name, email, password) { 255 | const forgotPassword = { 256 | token: crypto.randomBytes(20).toString('hex'), 257 | createdAt: moment().unix() 258 | }; 259 | 260 | const user = new User({name, email, password, forgotPassword}); 261 | const salt = await bcrypt.genSalt(10); 262 | user.password = await bcrypt.hash(user.password, salt); 263 | return await user.save(); 264 | } 265 | 266 | async function saveCompany(companyData) { 267 | const company = new Company(companyData); 268 | return await company.save(); 269 | } 270 | 271 | async function saveReview(reviewData) { 272 | const review = new Review(reviewData); 273 | return await review.save(); 274 | } 275 | -------------------------------------------------------------------------------- /tests/review.test.js: -------------------------------------------------------------------------------- 1 | const request = require("supertest"); 2 | const moment = require("moment"); 3 | const {User} = require("../models/user"); 4 | const {Company} = require("../models/company"); 5 | const {Review} = require("../models/review"); 6 | const bcrypt = require("bcrypt"); 7 | const crypto = require("crypto"); 8 | const _ = require("lodash"); 9 | let server; 10 | 11 | beforeEach(async () => { 12 | server = require("../index"); 13 | jest.setTimeout(30000); 14 | }); 15 | 16 | afterEach(async () => { 17 | await User.remove({}); 18 | await Company.remove({}); 19 | await Review.remove({}); 20 | server.close(); 21 | }); 22 | 23 | describe("/api/review", () => { 24 | let url, name, email, password, companyData, company, user, reviewData, review; 25 | 26 | beforeEach(async () => { 27 | url = "/api/review"; 28 | name = "test"; 29 | email = "review@test.com"; 30 | password = "123456"; 31 | user = await saveUser(name, email, password); 32 | companyData = { 33 | name: name, 34 | contact: { 35 | mobile: 'mobile', 36 | address: 'address', 37 | website: 'http://website.com/' 38 | }, 39 | details: 'details details', 40 | user: user._id.toString(), 41 | status: 'active' 42 | }; 43 | company = await saveCompany(companyData); 44 | reviewData = { 45 | company: company._id.toString(), 46 | star: 2, 47 | title: "test title", 48 | details: "details description of a company" 49 | }; 50 | }); 51 | 52 | describe("POST /", () => { 53 | 54 | const exec = (requestObjet, token) => { 55 | return request(server) 56 | .post(url) 57 | .set('x-auth-token', token) 58 | .send(requestObjet); 59 | }; 60 | 61 | it("should return 401 if no JWT", async () => { 62 | const token = ""; 63 | const res = await exec(reviewData, token); 64 | expect(res.status).toBe(401); 65 | }); 66 | 67 | it("should return 400 if JWT not valid", async () => { 68 | const token = "1234"; 69 | const res = await exec(reviewData, token); 70 | expect(res.status).toBe(400); 71 | }); 72 | 73 | it("should return 400 if no company", async () => { 74 | const token = user.generateAuthToken(); 75 | delete reviewData.company; 76 | const res = await exec(reviewData, token); 77 | expect(res.status).toBe(400); 78 | }); 79 | 80 | it("should return 400 if company id not valid", async () => { 81 | const token = user.generateAuthToken(); 82 | reviewData.company = "123"; 83 | const res = await exec(reviewData, token); 84 | expect(res.status).toBe(400); 85 | }); 86 | 87 | it("should return 400 if no star", async () => { 88 | const token = user.generateAuthToken(); 89 | delete reviewData.star; 90 | const res = await exec(reviewData, token); 91 | expect(res.status).toBe(400); 92 | }); 93 | 94 | it("should return 400 if star less than 1", async () => { 95 | const token = user.generateAuthToken(); 96 | reviewData.star = 0; 97 | const res = await exec(reviewData, token); 98 | expect(res.status).toBe(400); 99 | }); 100 | 101 | it("should return 400 if star more than 5", async () => { 102 | const token = user.generateAuthToken(); 103 | reviewData.star = 6; 104 | const res = await exec(reviewData, token); 105 | expect(res.status).toBe(400); 106 | }); 107 | 108 | it("should return 400 if no title", async () => { 109 | const token = user.generateAuthToken(); 110 | delete reviewData.title; 111 | const res = await exec(reviewData, token); 112 | expect(res.status).toBe(400); 113 | }); 114 | 115 | it("should return 400 if title less than 3", async () => { 116 | const token = user.generateAuthToken(); 117 | reviewData.title = "a"; 118 | const res = await exec(reviewData, token); 119 | expect(res.status).toBe(400); 120 | }); 121 | 122 | it("should return 400 if title more than 30", async () => { 123 | const token = user.generateAuthToken(); 124 | reviewData.title = new Array(32).join("a"); 125 | const res = await exec(reviewData, token); 126 | expect(res.status).toBe(400); 127 | }); 128 | 129 | it("should return 400 if no details", async () => { 130 | const token = user.generateAuthToken(); 131 | delete reviewData.details; 132 | const res = await exec(reviewData, token); 133 | expect(res.status).toBe(400); 134 | }); 135 | 136 | it("should return 400 if details less than 3", async () => { 137 | const token = user.generateAuthToken(); 138 | reviewData.details = "aa"; 139 | const res = await exec(reviewData, token); 140 | expect(res.status).toBe(400); 141 | }); 142 | 143 | it("should return 400 if details more than 1024", async () => { 144 | const token = user.generateAuthToken(); 145 | reviewData.details = new Array(1026).join("a"); 146 | const res = await exec(reviewData, token); 147 | expect(res.status).toBe(400); 148 | }); 149 | 150 | it("should return 404 if company not exist", async () => { 151 | const token = user.generateAuthToken(); 152 | reviewData.company = user._id; 153 | const res = await exec(reviewData, token); 154 | expect(res.status).toBe(404); 155 | }); 156 | 157 | it("should return 200 if valid input", async () => { 158 | const token = user.generateAuthToken(); 159 | const res = await exec(reviewData, token); 160 | expect(res.status).toBe(200); 161 | }); 162 | 163 | }); 164 | 165 | describe("PATCH /:id", () => { 166 | 167 | beforeEach(async () => { 168 | reviewData.user = user._id.toString(); 169 | review = await saveReview(reviewData); 170 | delete reviewData.user; 171 | delete reviewData.company; 172 | url = url + "/" + review._id.toString(); 173 | }); 174 | 175 | const exec = (requestObjet, token) => { 176 | return request(server) 177 | .patch(url) 178 | .set('x-auth-token', token) 179 | .send(requestObjet); 180 | }; 181 | 182 | it("should return 401 if no JWT", async () => { 183 | const token = ""; 184 | const res = await exec(reviewData, token); 185 | expect(res.status).toBe(401); 186 | }); 187 | 188 | it("should return 400 if JWT not valid", async () => { 189 | const token = "1234"; 190 | const res = await exec(reviewData, token); 191 | expect(res.status).toBe(400); 192 | }); 193 | 194 | it("should return 400 if no star", async () => { 195 | const token = user.generateAuthToken(); 196 | delete reviewData.star; 197 | const res = await exec(reviewData, token); 198 | expect(res.status).toBe(400); 199 | }); 200 | 201 | it("should return 400 if star less than 1", async () => { 202 | const token = user.generateAuthToken(); 203 | reviewData.star = 0; 204 | const res = await exec(reviewData, token); 205 | expect(res.status).toBe(400); 206 | }); 207 | 208 | it("should return 400 if star more than 5", async () => { 209 | const token = user.generateAuthToken(); 210 | reviewData.star = 6; 211 | const res = await exec(reviewData, token); 212 | expect(res.status).toBe(400); 213 | }); 214 | 215 | it("should return 400 if no title", async () => { 216 | const token = user.generateAuthToken(); 217 | delete reviewData.title; 218 | const res = await exec(reviewData, token); 219 | expect(res.status).toBe(400); 220 | }); 221 | 222 | it("should return 400 if title less than 3", async () => { 223 | const token = user.generateAuthToken(); 224 | reviewData.title = "a"; 225 | const res = await exec(reviewData, token); 226 | expect(res.status).toBe(400); 227 | }); 228 | 229 | it("should return 400 if title more than 30", async () => { 230 | const token = user.generateAuthToken(); 231 | reviewData.title = new Array(32).join("a"); 232 | const res = await exec(reviewData, token); 233 | expect(res.status).toBe(400); 234 | }); 235 | 236 | it("should return 400 if no details", async () => { 237 | const token = user.generateAuthToken(); 238 | delete reviewData.details; 239 | const res = await exec(reviewData, token); 240 | expect(res.status).toBe(400); 241 | }); 242 | 243 | it("should return 400 if details less than 3", async () => { 244 | const token = user.generateAuthToken(); 245 | reviewData.details = "aa"; 246 | const res = await exec(reviewData, token); 247 | expect(res.status).toBe(400); 248 | }); 249 | 250 | it("should return 400 if details more than 1024", async () => { 251 | const token = user.generateAuthToken(); 252 | reviewData.details = new Array(1026).join("a"); 253 | const res = await exec(reviewData, token); 254 | expect(res.status).toBe(400); 255 | }); 256 | 257 | it("should return 404 if user doesn't the owner of given id", async () => { 258 | const anotherUser = await saveUser( 259 | "test", "random@random.com", "12345"); 260 | const token = anotherUser.generateAuthToken(); 261 | 262 | reviewData.title = "edited title"; 263 | reviewData.details = "edited details"; 264 | const res = await exec(reviewData, token); 265 | expect(res.status).toBe(404); 266 | }); 267 | 268 | it("should return 200 if valid input", async () => { 269 | const token = user.generateAuthToken(); 270 | reviewData.title = "edited title"; 271 | reviewData.details = "edited details"; 272 | const res = await exec(reviewData, token); 273 | expect(res.status).toBe(200); 274 | }); 275 | 276 | }); 277 | 278 | describe("DELETE /:id", () => { 279 | 280 | beforeEach(async () => { 281 | reviewData.user = user._id.toString(); 282 | review = await saveReview(reviewData); 283 | delete reviewData.user; 284 | delete reviewData.company; 285 | url = url + "/" + review._id.toString(); 286 | }); 287 | 288 | const exec = (requestObjet, token) => { 289 | return request(server) 290 | .delete(url) 291 | .set('x-auth-token', token) 292 | .send(requestObjet); 293 | }; 294 | 295 | it("should return 401 if no JWT", async () => { 296 | const token = ""; 297 | const res = await exec(reviewData, token); 298 | expect(res.status).toBe(401); 299 | }); 300 | 301 | it("should return 400 if JWT not valid", async () => { 302 | const token = "1234"; 303 | const res = await exec(reviewData, token); 304 | expect(res.status).toBe(400); 305 | }); 306 | 307 | it("should return 404 if user doesn't the owner of given id", async () => { 308 | const anotherUser = await saveUser( 309 | "test", "random@random.com", "12345"); 310 | const token = anotherUser.generateAuthToken(); 311 | 312 | const res = await exec(reviewData, token); 313 | expect(res.status).toBe(404); 314 | }); 315 | 316 | it("should return 200 if valid input", async () => { 317 | const token = user.generateAuthToken(); 318 | const res = await exec(reviewData, token); 319 | expect(res.status).toBe(200); 320 | }); 321 | 322 | it("should return 200 if admin", async () => { 323 | let anotherUser = await saveUser( 324 | "test", "random@random.com", "12345"); 325 | anotherUser.userType = "admin"; 326 | anotherUser = await anotherUser.save(); 327 | 328 | const token = anotherUser.generateAuthToken(); 329 | const res = await exec(reviewData, token); 330 | expect(res.status).toBe(200); 331 | }); 332 | 333 | }); 334 | 335 | describe("GET /", () => { 336 | 337 | beforeEach(async () => { 338 | reviewData.user = user._id.toString(); 339 | review = await saveReview(reviewData); 340 | }); 341 | 342 | const exec = (requestObjet, token) => { 343 | return request(server) 344 | .get(url) 345 | .set('x-auth-token', token) 346 | .send(requestObjet); 347 | }; 348 | 349 | it("should return 401 if no JWT", async () => { 350 | const token = ""; 351 | const res = await exec(reviewData, token); 352 | expect(res.status).toBe(401); 353 | }); 354 | 355 | it("should return 400 if JWT not valid", async () => { 356 | const token = "1234"; 357 | const res = await exec(reviewData, token); 358 | expect(res.status).toBe(400); 359 | }); 360 | 361 | it("should return 200 if valid input for my reviews", async () => { 362 | const token = user.generateAuthToken(); 363 | delete reviewData.user; 364 | delete reviewData.company; 365 | const res = await exec(reviewData, token); 366 | expect(res.status).toBe(200); 367 | }); 368 | 369 | it("should return 200 if valid input for company reviews", async () => { 370 | const token = user.generateAuthToken(); 371 | delete reviewData.user; 372 | const res = await exec(reviewData, token); 373 | expect(res.status).toBe(200); 374 | }); 375 | }); 376 | 377 | }); 378 | 379 | async function saveUser(name, email, password) { 380 | const forgotPassword = { 381 | token: crypto.randomBytes(20).toString('hex'), 382 | createdAt: moment().unix() 383 | }; 384 | 385 | const user = new User({name, email, password, forgotPassword}); 386 | const salt = await bcrypt.genSalt(10); 387 | user.password = await bcrypt.hash(user.password, salt); 388 | return await user.save(); 389 | } 390 | 391 | async function saveCompany(companyData) { 392 | const company = new Company(companyData); 393 | return await company.save(); 394 | } 395 | 396 | async function saveReview(reviewData) { 397 | const review = new Review(reviewData); 398 | return await review.save(); 399 | } 400 | -------------------------------------------------------------------------------- /tests/search.test.js: -------------------------------------------------------------------------------- 1 | const request = require("supertest"); 2 | const moment = require("moment"); 3 | const {User} = require("../models/user"); 4 | const {Company} = require("../models/company"); 5 | const bcrypt = require("bcrypt"); 6 | const crypto = require("crypto"); 7 | const _ = require("lodash"); 8 | let server; 9 | 10 | beforeEach(async () => { 11 | server = require("../index"); 12 | jest.setTimeout(30000); 13 | }); 14 | 15 | afterEach(async () => { 16 | await User.remove({}); 17 | await Company.remove({}); 18 | server.close(); 19 | }); 20 | 21 | describe("/api/search", () => { 22 | 23 | let name, email, password, companyData, user, url; 24 | 25 | beforeEach(async () => { 26 | name = "test"; 27 | email = "company@test.com"; 28 | password = "123456"; 29 | user = await saveUser(name, email, password); 30 | companyData = { 31 | name: name, 32 | contact: { 33 | mobile: 'mobile', 34 | address: 'address', 35 | website: 'http://website.com/' 36 | }, 37 | details: 'details details', 38 | user: user._id.toString(), 39 | tags: ["can", "canada", "candy"], 40 | status: 'active' 41 | }; 42 | companyData = await saveCompany(companyData); 43 | }); 44 | 45 | describe("GET /api/search/company", () => { 46 | 47 | beforeEach(async () => { 48 | url = "/api/search/company/"; 49 | }); 50 | 51 | const exec = () => { 52 | return request(server) 53 | .get(url) 54 | .send(); 55 | }; 56 | 57 | it("should return 200 if everything fine", async () => { 58 | url = url + "?keyword=te&pageNumber=1&pageSize=1"; 59 | let res = await exec(); 60 | expect(res.status).toBe(200); 61 | }); 62 | }); 63 | 64 | describe("GET /api/search/tag", () => { 65 | 66 | beforeEach(async () => { 67 | url = "/api/search/tag/"; 68 | }); 69 | 70 | const exec = () => { 71 | return request(server) 72 | .get(url) 73 | .send(); 74 | }; 75 | 76 | it("should return 200 if everything fine", async () => { 77 | url = url + "?keyword=ca&pageNumber=1&pageSize=1"; 78 | let res = await exec(); 79 | expect(res.status).toBe(200); 80 | }); 81 | }); 82 | 83 | }); 84 | 85 | async function saveUser(name, email, password) { 86 | const forgotPassword = { 87 | token: crypto.randomBytes(20).toString('hex'), 88 | createdAt: moment().unix() 89 | }; 90 | 91 | const user = new User({name, email, password, forgotPassword}); 92 | const salt = await bcrypt.genSalt(10); 93 | user.password = await bcrypt.hash(user.password, salt); 94 | return await user.save(); 95 | } 96 | 97 | async function saveCompany(companyData) { 98 | const company = new Company(companyData); 99 | return await company.save(); 100 | } 101 | -------------------------------------------------------------------------------- /tests/suggestion.test.js: -------------------------------------------------------------------------------- 1 | const request = require("supertest"); 2 | const moment = require("moment"); 3 | const {User} = require("../models/user"); 4 | const {Company} = require("../models/company"); 5 | const bcrypt = require("bcrypt"); 6 | const crypto = require("crypto"); 7 | const _ = require("lodash"); 8 | let server; 9 | 10 | beforeEach(async () => { 11 | server = require("../index"); 12 | jest.setTimeout(30000); 13 | }); 14 | 15 | afterEach(async () => { 16 | await User.remove({}); 17 | await Company.remove({}); 18 | server.close(); 19 | }); 20 | 21 | describe("/api/suggestion", () => { 22 | 23 | let name, email, password, companyData, user, url; 24 | 25 | beforeEach(async () => { 26 | name = "test"; 27 | email = "company@test.com"; 28 | password = "123456"; 29 | user = await saveUser(name, email, password); 30 | companyData = { 31 | name: name, 32 | contact: { 33 | mobile: 'mobile', 34 | address: 'address', 35 | website: 'http://website.com/' 36 | }, 37 | details: 'details details', 38 | user: user._id.toString(), 39 | tags: ["can", "canada", "candy"], 40 | status: 'active' 41 | }; 42 | companyData = await saveCompany(companyData); 43 | }); 44 | 45 | describe("GET /api/suggestion/company", () => { 46 | 47 | beforeEach(async () => { 48 | url = "/api/suggestion/company/"; 49 | }); 50 | 51 | const exec = () => { 52 | return request(server) 53 | .get(url) 54 | .send(); 55 | }; 56 | 57 | it("should return 200 if everything fine", async () => { 58 | url = url + "?keyword=te&pageNumber=1&pageSize=10"; 59 | let res = await exec(); 60 | expect(res.status).toBe(200); 61 | }); 62 | }); 63 | 64 | describe("GET /api/suggestion/tag", () => { 65 | 66 | beforeEach(async () => { 67 | url = "/api/suggestion/tag/"; 68 | }); 69 | 70 | const exec = () => { 71 | return request(server) 72 | .get(url) 73 | .send(); 74 | }; 75 | 76 | it("should return 200 if everything fine", async () => { 77 | url = url + "?keyword=ca&pageNumber=1&pageSize=10"; 78 | let res = await exec(); 79 | expect(res.status).toBe(200); 80 | }); 81 | }); 82 | 83 | }); 84 | 85 | async function saveUser(name, email, password) { 86 | const forgotPassword = { 87 | token: crypto.randomBytes(20).toString('hex'), 88 | createdAt: moment().unix() 89 | }; 90 | 91 | const user = new User({name, email, password, forgotPassword}); 92 | const salt = await bcrypt.genSalt(10); 93 | user.password = await bcrypt.hash(user.password, salt); 94 | return await user.save(); 95 | } 96 | 97 | async function saveCompany(companyData) { 98 | const company = new Company(companyData); 99 | return await company.save(); 100 | } 101 | -------------------------------------------------------------------------------- /tests/user.test.js: -------------------------------------------------------------------------------- 1 | const request = require("supertest"); 2 | const moment = require("moment"); 3 | const {User} = require("../models/user"); 4 | const {Company} = require("../models/company"); 5 | const bcrypt = require("bcrypt"); 6 | const crypto = require("crypto"); 7 | const _ = require("lodash"); 8 | let server; 9 | 10 | beforeEach(async () => { 11 | server = require("../index"); 12 | jest.setTimeout(30000); 13 | }); 14 | 15 | afterEach(async () => { 16 | await User.remove({}); 17 | await Company.remove({}); 18 | server.close(); 19 | }); 20 | 21 | describe("/api/user", () => { 22 | 23 | let name, email, password, user, url; 24 | 25 | beforeEach(async () => { 26 | name = "test"; 27 | email = "user@test.com"; 28 | password = "123456"; 29 | user = await saveUser(name, email, password); 30 | user.userType = "admin"; 31 | user = await user.save(); 32 | }); 33 | 34 | describe("GET /api/user/active", () => { 35 | 36 | beforeEach(async () => { 37 | url = "/api/user/active/"; 38 | }); 39 | 40 | const exec = (token) => { 41 | return request(server) 42 | .get(url) 43 | .set('x-auth-token', token) 44 | .send(); 45 | }; 46 | 47 | it("should return 401 if no JWT", async () => { 48 | const token = ""; 49 | const res = await exec(token); 50 | expect(res.status).toBe(401); 51 | }); 52 | 53 | it("should return 400 if JWT not valid", async () => { 54 | const token = "1234"; 55 | const res = await exec(token); 56 | expect(res.status).toBe(400); 57 | }); 58 | 59 | it("should return 401 if not admin", async () => { 60 | user.userType = "user"; 61 | user = await user.save(); 62 | const token = user.generateAuthToken(); 63 | const res = await exec(token); 64 | expect(res.status).toBe(401); 65 | }); 66 | 67 | it("should return 200 if everything fine", async () => { 68 | url = url + "?pageNumber=1&pageSize=10"; 69 | const token = user.generateAuthToken(); 70 | let res = await exec(token); 71 | expect(res.status).toBe(200); 72 | }); 73 | }); 74 | 75 | describe("GET /api/user/suspended", () => { 76 | 77 | beforeEach(async () => { 78 | url = "/api/user/suspended/"; 79 | }); 80 | 81 | const exec = (token) => { 82 | return request(server) 83 | .get(url) 84 | .set('x-auth-token', token) 85 | .send(); 86 | }; 87 | 88 | it("should return 401 if no JWT", async () => { 89 | const token = ""; 90 | const res = await exec(token); 91 | expect(res.status).toBe(401); 92 | }); 93 | 94 | it("should return 400 if JWT not valid", async () => { 95 | const token = "1234"; 96 | const res = await exec(token); 97 | expect(res.status).toBe(400); 98 | }); 99 | 100 | it("should return 401 if not admin", async () => { 101 | user.userType = "user"; 102 | user = await user.save(); 103 | const token = user.generateAuthToken(); 104 | const res = await exec(token); 105 | expect(res.status).toBe(401); 106 | }); 107 | 108 | it("should return 200 if everything fine", async () => { 109 | let testUser = await saveUser(name, "user2@test.com", password); 110 | testUser.status = "suspend"; 111 | testUser = await testUser.save(); 112 | 113 | url = url + "?pageNumber=1&pageSize=10"; 114 | const token = user.generateAuthToken(); 115 | let res = await exec(token); 116 | expect(res.status).toBe(200); 117 | }); 118 | }); 119 | 120 | describe("PATCH /api/user/suspend/:id", () => { 121 | 122 | let testUser; 123 | 124 | beforeEach(async () => { 125 | url = "/api/user/suspend/"; 126 | testUser = await saveUser(name, "user2@test.com", password); 127 | url = url + testUser._id.toString(); 128 | }); 129 | 130 | const exec = (token) => { 131 | return request(server) 132 | .patch(url) 133 | .set('x-auth-token', token) 134 | .send(); 135 | }; 136 | 137 | it("should return 401 if no JWT", async () => { 138 | const token = ""; 139 | const res = await exec(token); 140 | expect(res.status).toBe(401); 141 | }); 142 | 143 | it("should return 400 if JWT not valid", async () => { 144 | const token = "1234"; 145 | const res = await exec(token); 146 | expect(res.status).toBe(400); 147 | }); 148 | 149 | it("should return 401 if not admin", async () => { 150 | user.userType = "user"; 151 | user = await user.save(); 152 | const token = user.generateAuthToken(); 153 | const res = await exec(token); 154 | expect(res.status).toBe(401); 155 | }); 156 | 157 | it("should return 404 if not found", async () => { 158 | testUser.status = "suspend"; 159 | testUser = await testUser.save(); 160 | const token = user.generateAuthToken(); 161 | const res = await exec(token); 162 | expect(res.status).toBe(404); 163 | }); 164 | 165 | it("should return 200 if valid input", async () => { 166 | const token = user.generateAuthToken(); 167 | const res = await exec(token); 168 | expect(res.status).toBe(200); 169 | }); 170 | }); 171 | 172 | describe("PATCH /api/user/unsuspend/:id", () => { 173 | 174 | let testUser; 175 | 176 | beforeEach(async () => { 177 | url = "/api/user/unsuspend/"; 178 | testUser = await saveUser(name, "user2@test.com", password); 179 | url = url + testUser._id.toString(); 180 | }); 181 | 182 | const exec = (token) => { 183 | return request(server) 184 | .patch(url) 185 | .set('x-auth-token', token) 186 | .send(); 187 | }; 188 | 189 | it("should return 401 if no JWT", async () => { 190 | const token = ""; 191 | const res = await exec(token); 192 | expect(res.status).toBe(401); 193 | }); 194 | 195 | it("should return 400 if JWT not valid", async () => { 196 | const token = "1234"; 197 | const res = await exec(token); 198 | expect(res.status).toBe(400); 199 | }); 200 | 201 | it("should return 401 if not admin", async () => { 202 | user.userType = "user"; 203 | user = await user.save(); 204 | const token = user.generateAuthToken(); 205 | const res = await exec(token); 206 | expect(res.status).toBe(401); 207 | }); 208 | 209 | it("should return 404 if not found", async () => { 210 | const token = user.generateAuthToken(); 211 | const res = await exec(token); 212 | expect(res.status).toBe(404); 213 | }); 214 | 215 | it("should return 200 if valid input", async () => { 216 | testUser.status = "suspend"; 217 | testUser = await testUser.save(); 218 | const token = user.generateAuthToken(); 219 | const res = await exec(token); 220 | expect(res.status).toBe(200); 221 | }); 222 | }); 223 | 224 | }); 225 | 226 | async function saveUser(name, email, password) { 227 | const forgotPassword = { 228 | token: crypto.randomBytes(20).toString('hex'), 229 | createdAt: moment().unix() 230 | }; 231 | 232 | const user = new User({name, email, password, forgotPassword}); 233 | const salt = await bcrypt.genSalt(10); 234 | user.password = await bcrypt.hash(user.password, salt); 235 | user.status = "active"; 236 | return await user.save(); 237 | } 238 | -------------------------------------------------------------------------------- /utils/pagination.js: -------------------------------------------------------------------------------- 1 | 2 | function getPageNumber(req) { 3 | if(req.query.pageNumber) { 4 | const pageNumber = parseInt(req.query.pageNumber); 5 | if (pageNumber < 1) { 6 | return 1; 7 | } else { 8 | return pageNumber; 9 | } 10 | } else { 11 | return 1; 12 | } 13 | } 14 | 15 | function getPageSize(req) { 16 | if(req.query.pageSize) { 17 | const pageSize = parseInt(req.query.pageSize); 18 | if ((pageSize < 10) || (pageSize > 50)) { 19 | return 10; 20 | } else { 21 | return pageSize; 22 | } 23 | } else { 24 | return 10; 25 | } 26 | } 27 | 28 | exports.getPageNumber = getPageNumber; 29 | exports.getPageSize = getPageSize; --------------------------------------------------------------------------------