Skip to content
This repository has been archived by the owner on May 9, 2021. It is now read-only.

Commit

Permalink
feat(validations): signup-validations
Browse files Browse the repository at this point in the history
- integrate express-validator
- return descriptive error messages on user signup
- write tests

[Finishes #161291004]
  • Loading branch information
sulenchy authored and augustineezinwa committed Oct 31, 2018
1 parent 87c7376 commit be41dbc
Show file tree
Hide file tree
Showing 7 changed files with 521 additions and 4 deletions.
3 changes: 2 additions & 1 deletion app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import logger from 'morgan';
import bodyParser from 'body-parser';
import winston from 'winston';
import dotenv from 'dotenv';
import validator from 'express-validator';
import routes from './routes';

const app = express();
Expand All @@ -14,7 +15,7 @@ const port = process.env.PORT || 3000;
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

app.use(validator());
app.use(routes);

// / catch 404 and forward to error handler
Expand Down
101 changes: 101 additions & 0 deletions mockdata/userMockData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
export const userDataWithEmptyName = {
email: 'augustine.ezinwa@andela.com',
password: '@August12',
fullName: ''
};
export const userDataWithNoLastName = {
email: 'augustineezinwa@gmail.com',
password: 'Inieefjuniorer3e',
fullName: ' Augustine ',
};
export const userDataWithThreeNames = {
email: 'augustineezinwa@gmail.com',
password: 'Inieefjuniorer3e',
fullName: ' Augustine Emeka Ezinwa',
};
export const userDataWithLongName = {
email: 'augustineezinwa@gmail.com',
password: 'Inieefjuniorer3e',
fullName: ' Augustinedfekjfkdfjdjf Emrewwekaerfefjduigyguyfgftfyuftc',
};
export const userDataWithInvalidName = {
email: 'augustineezinwa@gmail.com',
password: 'Inieefjuniorer3e',
fullName: ' Augustine* Ezinwa ',
};
export const userDataWithNumericName = {
email: 'augustineezinwa@gmail.com',
password: 'Inieefjuniorer3e',
fullName: ' Augustine9 Ezinwa ',
};
export const userDataWithInvalidEmail = {
email: 'augustineeainw@@@dfjkfjer.com',
password: 'Inieefjuniorer3e',
fullName: 'Augustine Ezinwa'
};
export const userDataWithInvalidPassword = {
email: 'augustineezinwa@gmail.com',
password: 'Inieefjinio',
fullName: 'Abiodun Abudu'
};
export const userDataWithShortPassword = {
email: 'augustineezinwa@gmail.com',
password: 'Inief9e',
fullName: 'Abiodun Abudu'
};
export const userDataWithWhiteSpacedPassword = {
email: 'augustineezinwa@gmail.com',
password: 'Inief 9e',
fullName: 'Abiodun Abudu'
};
export const userDataWithEmptyPassword = {
email: 'augustineezinwa@gmail.com',
password: '',
fullName: 'Abiodun Abudu'
};
export const userDataWithEmptyEmail = {
email: '',
password: 'Augustin4334',
fullName: 'Abiodun Abudu'
};
export const userDataWithInvalidPasswordAndEmail = {
email: 'augustineezinwa@@@gmail.com',
password: 'emekadonkey',
fullName: 'Abiodun Abudu'
};
export const userDataWithInvalidFields = {
email: 'augustineezinwa@@@gmail.com',
password: 'emekadonkey',
fullName: 'Abiodun Abudu*9'
};
export const userDataWithEmptyFields = {
email: '',
password: '',
fullName: '',
};
export const userDataWithAnExistingEmail = {
email: 'solomon.sulaiman@andela.com',
password: 'Inieefjun00',
fullName: 'Augustine Ezinwa',
};
export const userDataWithInvalidDataTypes = {
email: true,
password: false,
fullName: true,
bio: true,
avataUrl: true
};
export const userDataWithVeryLongBio = {
email: 'augustineezinwa@gmail.com',
password: 'Doonkey99',
fullName: 'Abiodun Abudu',
bio: `dkfgnowdnfgojwdgnpsdhfgnowephfnwfroidwhfoewfngodjwfnokn
dsonfgoewhfosnfownfonwodnfonowdfnwnfoefdjfkdfkldjfdjfdjfdjf
dfjdfjkdnfkdljfldjfnkdnfkndlfjndfnodnfldnfdnfdlnfdlkjfdf
dfldnfkdnflndlfndlnfdlnfdlnfldnfldnfldnfldnfldnfldnfld
dfldnfdnflkdnfldnfdlnfldnflndfdlnfdfdnfdnfdnfdlnfern3e394j3r`,
avatarUrl: `fpjfjwerjeojf0e3jro3j2f0owesdfnewf/r/fefdjfdjfeojr3
dfdjfjdpjfdpjfpdjfpjdpjfpdjfpkdjfnodjfdjfojdojfdojfdjfodjfodj
dfdjfdjfjdpfjkdpjfdpfjpdjfdpfjpdjfdoipjfdopjfdpjfdifjdpfjdjf
dofjdpjfpdjfpdjfdjfpdjfpdjfdpoijfdsjwfpei3er//3erfje3kfkdkf`,
};
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"node": "10.11.0"
},
"scripts": {
"test": "npm run db-init-test && cross-env NODE_ENV=test nyc mocha test/**/**/* --require babel-core/register --require babel-polyfill --exit --timeout 5000",
"test": "npm run db-init-test && cross-env NODE_ENV=test nyc mocha test/**/**/* --require babel-core/register --require babel-polyfill --exit --timeout 100000",
"start": "npm run clean && npm run build && node dist/app.js",
"start:dev": "nodemon --exec babel-node app.js",
"build": "babel . --ignore node_modules,test,coverage,dist ../valinor-ah-backend -d dist/",
Expand All @@ -33,6 +33,7 @@
"express": "^4.16.3",
"express-jwt": "^5.3.1",
"express-session": "^1.15.6",
"express-validator": "^5.3.0",
"jsonwebtoken": "^8.3.0",
"method-override": "^2.3.10",
"methods": "^1.1.2",
Expand Down
7 changes: 5 additions & 2 deletions routes/api/users.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@

import express from 'express';
import UserValidation from '../../server/middlewares/UserValidation';
import UserController from '../../server/controllers/UsersController';

const { validateUserSignUp, checkExistingEmail } = UserValidation;

const router = express.Router();

router.get('/', (req, res) => {
Expand All @@ -12,6 +14,7 @@ router.get('/', (req, res) => {
});
});

router.post('/users/signup', UserController.signUp);
router.post('/users/signup',
validateUserSignUp, checkExistingEmail, UserController.signUp);

export default router;
87 changes: 87 additions & 0 deletions server/middlewares/UserValidation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import models from '../models';

const { User } = models;
/**
* @class ValidationHelper
* @description Helps perform validations on user request body.
*/
class UserValidation {
/**
* @description - This method validates the user request body.
* @param {object} req - The request object to be validated.
* @param {object} res - Th response object to be validated.
* @param {object} next - The callback function to the next middleware.
* @returns {object} - The error object with message.
* @memberOf UserValidation
* @static
*/
static validateUserSignUp(req, res, next) {
let { fullName } = req.body;
const { avatarUrl, bio } = req.body;
req.checkBody('fullName', 'full name is too long').isLength({ max: 40 });
req.checkBody('email', 'please enter email').exists();
req.checkBody('email', 'please enter a valid email').isEmail();
req.checkBody('password', 'please enter password').exists();
req.checkBody('password', 'password must be at least 8 characters')
.isLength({ min: 8 });
req.checkBody('password', 'password must contain a letter and number')
.matches(/^((?=.*\d))(?=.*[a-zA-Z])/);
if (avatarUrl) req.checkBody('avatarUrl', 'avatarUrl is invalid').isURL();
if (bio) req.checkBody('bio', 'bio is too long').isLength({ max: 200 });
const errors = req.validationErrors();
const message = [];
fullName = fullName === undefined ? '' : fullName;
const [firstName, ...lastName] = fullName.toString().trim().split(' ');
if (lastName.join(' ').trim().includes(' ')) {
message.push('You entered more than two names');
}
if (!/^[a-zA-Z-]+$/.test(firstName)
|| !/^[a-zA-Z-]+$/.test(lastName.join(''))) {
message.push('Invalid full name');
message.push('please enter first name and last name separated by space');
}
if (!errors && !message.length) {
req.body.fullName = `${firstName} ${lastName.join('')}`;
return next();
}
if (errors) errors.forEach(err => message.push(err.msg));
return res.status(422).json({
errors: {
message
}
});
}

/**
* @description - This method checks if an email is already in the system.
* @param {object} req - The request object bearing the email.
* @param {object} res - The response object sent to the next middleware.
* @param {object} next - The callback function to the next middleware.
* @returns {object} - The error object with message.
* @memberOf UserValidation
* @static
*/
static checkExistingEmail(req, res, next) {
const { email } = req.body;
return User.findOne({
where: {
email
}
})
.then((foundEmail) => {
if (!foundEmail) return next();
return res.status(409).json({
errors: {
message: ['email is already in use']
}
});
})
.catch(err => res.status(500).json({
errors: {
message: ['error reading user table', `${err}`]
}
}));
}
}

export default UserValidation;
Loading

0 comments on commit be41dbc

Please sign in to comment.