551 lines
18 KiB
JavaScript

/* eslint-disable arrow-body-style */
/* eslint-disable no-unused-expressions */
const request = require('supertest');
const httpStatus = require('http-status');
const { expect } = require('chai');
const sinon = require('sinon');
const bcrypt = require('bcryptjs');
const { some, omitBy, isNil } = require('lodash');
const app = require('../../../index');
const User = require('../../models/user.model');
const JWT_EXPIRATION = require('../../../config/vars').jwtExpirationInterval;
/**
* root level hooks
*/
async function format(user) {
const formated = user;
// delete password
delete formated.password;
// get users from database
const dbUser = (await User.findOne({ email: user.email })).transform();
// remove null and undefined properties
return omitBy(dbUser, isNil);
}
describe('Users API', async () => {
let adminAccessToken;
let userAccessToken;
let dbUsers;
let user;
let admin;
const password = '123456';
const passwordHashed = await bcrypt.hash(password, 1);
beforeEach(async () => {
dbUsers = {
branStark: {
email: 'branstark@gmail.com',
password: passwordHashed,
name: 'Bran Stark',
role: 'admin',
},
jonSnow: {
email: 'jonsnow@gmail.com',
password: passwordHashed,
name: 'Jon Snow',
},
};
user = {
email: 'sousa.dfs@gmail.com',
password,
name: 'krish Sousa',
};
admin = {
email: 'sousa.dfs@gmail.com',
password,
name: 'krish Sousa',
role: 'admin',
};
await User.deleteMany({});
await User.insertMany([dbUsers.branStark, dbUsers.jonSnow]);
dbUsers.branStark.password = password;
dbUsers.jonSnow.password = password;
adminAccessToken = (await User.findAndGenerateToken(dbUsers.branStark)).accessToken;
userAccessToken = (await User.findAndGenerateToken(dbUsers.jonSnow)).accessToken;
});
describe('POST /v1/users', () => {
it('should create a new user when request is ok', () => {
return request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(admin)
.expect(httpStatus.CREATED)
.then((res) => {
delete admin.password;
expect(res.body).to.include(admin);
});
});
it('should create a new user and set default role to "user"', () => {
return request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(user)
.expect(httpStatus.CREATED)
.then((res) => {
expect(res.body.role).to.be.equal('user');
});
});
it('should report error when email already exists', () => {
user.email = dbUsers.branStark.email;
return request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(user)
.expect(httpStatus.CONFLICT)
.then((res) => {
const { field } = res.body.errors[0];
const { location } = res.body.errors[0];
const { messages } = res.body.errors[0];
expect(field).to.be.equal('email');
expect(location).to.be.equal('body');
expect(messages).to.include('"email" already exists');
});
});
it('should report error when email is not provided', () => {
delete user.email;
return request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(user)
.expect(httpStatus.BAD_REQUEST)
.then((res) => {
const { field } = res.body.errors[0];
const { location } = res.body.errors[0];
const { messages } = res.body.errors[0];
expect(field).to.be.equal('email');
expect(location).to.be.equal('body');
expect(messages).to.include('"email" is required');
});
});
it('should report error when password length is less than 6', () => {
user.password = '12345';
return request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(user)
.expect(httpStatus.BAD_REQUEST)
.then((res) => {
const { field } = res.body.errors[0];
const { location } = res.body.errors[0];
const { messages } = res.body.errors[0];
expect(field).to.be.equal('password');
expect(location).to.be.equal('body');
expect(messages).to.include('"password" length must be at least 6 characters long');
});
});
it('should report error when logged user is not an admin', () => {
return request(app)
.post('/v1/users')
.set('Authorization', `Bearer ${userAccessToken}`)
.send(user)
.expect(httpStatus.FORBIDDEN)
.then((res) => {
expect(res.body.code).to.be.equal(httpStatus.FORBIDDEN);
expect(res.body.message).to.be.equal('Forbidden');
});
});
});
describe('GET /v1/users', () => {
it('should get all users', () => {
return request(app)
.get('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.expect(httpStatus.OK)
.then(async (res) => {
const bran = await format(dbUsers.branStark);
const john = await format(dbUsers.jonSnow);
// before comparing it is necessary to convert String to Date
res.body[0].createdAt = new Date(res.body[0].createdAt);
res.body[1].createdAt = new Date(res.body[1].createdAt);
const includesBranStark = some(res.body, bran);
const includesjonSnow = some(res.body, john);
expect(res.body).to.be.an('array');
expect(res.body).to.have.lengthOf(2);
expect(includesBranStark).to.be.true;
expect(includesjonSnow).to.be.true;
});
});
it('should get all users with pagination', () => {
return request(app)
.get('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.query({ page: 2, perPage: 1 })
.expect(httpStatus.OK)
.then(async (res) => {
delete dbUsers.jonSnow.password;
expect(res.body).to.be.an('array');
expect(res.body[0]).to.be.an('object');
expect(res.body).to.have.lengthOf(1);
expect(res.body[0].name).to.be.equal('Jon Snow');
});
});
it('should filter users', () => {
return request(app)
.get('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.query({ email: dbUsers.jonSnow.email })
.expect(httpStatus.OK)
.then(async (res) => {
delete dbUsers.jonSnow.password;
const john = await format(dbUsers.jonSnow);
// before comparing it is necessary to convert String to Date
res.body[0].createdAt = new Date(res.body[0].createdAt);
const includesjonSnow = some(res.body, john);
expect(res.body).to.be.an('array');
expect(res.body).to.have.lengthOf(1);
expect(includesjonSnow).to.be.true;
});
});
it('should report error when pagination\'s parameters are not a number', () => {
return request(app)
.get('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
.query({ page: '?', perPage: 'whaat' })
.expect(httpStatus.BAD_REQUEST)
.then((res) => {
const { field } = res.body.errors[0];
const { location } = res.body.errors[0];
const { messages } = res.body.errors[0];
expect(field).to.be.equal('page');
expect(location).to.be.equal('query');
expect(messages).to.include('"page" must be a number');
return Promise.resolve(res);
})
.then((res) => {
const { field } = res.body.errors[1];
const { location } = res.body.errors[1];
const { messages } = res.body.errors[1];
expect(field).to.be.equal('perPage');
expect(location).to.be.equal('query');
expect(messages).to.include('"perPage" must be a number');
});
});
it('should report error if logged user is not an admin', () => {
return request(app)
.get('/v1/users')
.set('Authorization', `Bearer ${userAccessToken}`)
.expect(httpStatus.FORBIDDEN)
.then((res) => {
expect(res.body.code).to.be.equal(httpStatus.FORBIDDEN);
expect(res.body.message).to.be.equal('Forbidden');
});
});
});
describe('GET /v1/users/:userId', () => {
it('should get user', async () => {
const id = (await User.findOne({}))._id;
delete dbUsers.branStark.password;
return request(app)
.get(`/v1/users/${id}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.expect(httpStatus.OK)
.then((res) => {
expect(res.body).to.include(dbUsers.branStark);
});
});
it('should report error "User does not exist" when user does not exists', () => {
return request(app)
.get('/v1/users/56c787ccc67fc16ccc1a5e92')
.set('Authorization', `Bearer ${adminAccessToken}`)
.expect(httpStatus.NOT_FOUND)
.then((res) => {
expect(res.body.code).to.be.equal(404);
expect(res.body.message).to.be.equal('User does not exist');
});
});
it('should report error "User does not exist" when id is not a valid ObjectID', () => {
return request(app)
.get('/v1/users/palmeiras1914')
.set('Authorization', `Bearer ${adminAccessToken}`)
.expect(httpStatus.NOT_FOUND)
.then((res) => {
expect(res.body.code).to.be.equal(404);
expect(res.body.message).to.equal('User does not exist');
});
});
it('should report error when logged user is not the same as the requested one', async () => {
const id = (await User.findOne({ email: dbUsers.branStark.email }))._id;
return request(app)
.get(`/v1/users/${id}`)
.set('Authorization', `Bearer ${userAccessToken}`)
.expect(httpStatus.FORBIDDEN)
.then((res) => {
expect(res.body.code).to.be.equal(httpStatus.FORBIDDEN);
expect(res.body.message).to.be.equal('Forbidden');
});
});
});
describe('PUT /v1/users/:userId', () => {
it('should replace user', async () => {
delete dbUsers.branStark.password;
const id = (await User.findOne(dbUsers.branStark))._id;
return request(app)
.put(`/v1/users/${id}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(user)
.expect(httpStatus.OK)
.then((res) => {
delete user.password;
expect(res.body).to.include(user);
expect(res.body.role).to.be.equal('user');
});
});
it('should report error when email is not provided', async () => {
const id = (await User.findOne({}))._id;
delete user.email;
return request(app)
.put(`/v1/users/${id}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(user)
.expect(httpStatus.BAD_REQUEST)
.then((res) => {
const { field } = res.body.errors[0];
const { location } = res.body.errors[0];
const { messages } = res.body.errors[0];
expect(field).to.be.equal('email');
expect(location).to.be.equal('body');
expect(messages).to.include('"email" is required');
});
});
it('should report error user when password length is less than 6', async () => {
const id = (await User.findOne({}))._id;
user.password = '12345';
return request(app)
.put(`/v1/users/${id}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send(user)
.expect(httpStatus.BAD_REQUEST)
.then((res) => {
const { field } = res.body.errors[0];
const { location } = res.body.errors[0];
const { messages } = res.body.errors[0];
expect(field).to.be.equal('password');
expect(location).to.be.equal('body');
expect(messages).to.include('"password" length must be at least 6 characters long');
});
});
it('should report error "User does not exist" when user does not exists', () => {
return request(app)
.put('/v1/users/palmeiras1914')
.set('Authorization', `Bearer ${adminAccessToken}`)
.expect(httpStatus.NOT_FOUND)
.then((res) => {
expect(res.body.code).to.be.equal(404);
expect(res.body.message).to.be.equal('User does not exist');
});
});
it('should report error when logged user is not the same as the requested one', async () => {
const id = (await User.findOne({ email: dbUsers.branStark.email }))._id;
return request(app)
.put(`/v1/users/${id}`)
.set('Authorization', `Bearer ${userAccessToken}`)
.expect(httpStatus.FORBIDDEN)
.then((res) => {
expect(res.body.code).to.be.equal(httpStatus.FORBIDDEN);
expect(res.body.message).to.be.equal('Forbidden');
});
});
it('should not replace the role of the user (not admin)', async () => {
const id = (await User.findOne({ email: dbUsers.jonSnow.email }))._id;
const role = 'admin';
return request(app)
.put(`/v1/users/${id}`)
.set('Authorization', `Bearer ${userAccessToken}`)
.send(admin)
.expect(httpStatus.OK)
.then((res) => {
expect(res.body.role).to.not.be.equal(role);
});
});
});
describe('PATCH /v1/users/:userId', () => {
it('should update user', async () => {
delete dbUsers.branStark.password;
const id = (await User.findOne(dbUsers.branStark))._id;
const { name } = user;
return request(app)
.patch(`/v1/users/${id}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send({ name })
.expect(httpStatus.OK)
.then((res) => {
expect(res.body.name).to.be.equal(name);
expect(res.body.email).to.be.equal(dbUsers.branStark.email);
});
});
it('should not update user when no parameters were given', async () => {
delete dbUsers.branStark.password;
const id = (await User.findOne(dbUsers.branStark))._id;
return request(app)
.patch(`/v1/users/${id}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.send()
.expect(httpStatus.OK)
.then((res) => {
expect(res.body).to.include(dbUsers.branStark);
});
});
it('should report error "User does not exist" when user does not exists', () => {
return request(app)
.patch('/v1/users/palmeiras1914')
.set('Authorization', `Bearer ${adminAccessToken}`)
.expect(httpStatus.NOT_FOUND)
.then((res) => {
expect(res.body.code).to.be.equal(404);
expect(res.body.message).to.be.equal('User does not exist');
});
});
it('should report error when logged user is not the same as the requested one', async () => {
const id = (await User.findOne({ email: dbUsers.branStark.email }))._id;
return request(app)
.patch(`/v1/users/${id}`)
.set('Authorization', `Bearer ${userAccessToken}`)
.expect(httpStatus.FORBIDDEN)
.then((res) => {
expect(res.body.code).to.be.equal(httpStatus.FORBIDDEN);
expect(res.body.message).to.be.equal('Forbidden');
});
});
it('should not update the role of the user (not admin)', async () => {
const id = (await User.findOne({ email: dbUsers.jonSnow.email }))._id;
const role = 'admin';
return request(app)
.patch(`/v1/users/${id}`)
.set('Authorization', `Bearer ${userAccessToken}`)
.send({ role })
.expect(httpStatus.OK)
.then((res) => {
expect(res.body.role).to.not.be.equal(role);
});
});
});
describe('DELETE /v1/users', () => {
it('should delete user', async () => {
const id = (await User.findOne({}))._id;
return request(app)
.delete(`/v1/users/${id}`)
.set('Authorization', `Bearer ${adminAccessToken}`)
.expect(httpStatus.NO_CONTENT)
.then(() => request(app).get('/v1/users'))
.then(async () => {
const users = await User.find({});
expect(users).to.have.lengthOf(1);
});
});
it('should report error "User does not exist" when user does not exists', () => {
return request(app)
.delete('/v1/users/palmeiras1914')
.set('Authorization', `Bearer ${adminAccessToken}`)
.expect(httpStatus.NOT_FOUND)
.then((res) => {
expect(res.body.code).to.be.equal(404);
expect(res.body.message).to.be.equal('User does not exist');
});
});
it('should report error when logged user is not the same as the requested one', async () => {
const id = (await User.findOne({ email: dbUsers.branStark.email }))._id;
return request(app)
.delete(`/v1/users/${id}`)
.set('Authorization', `Bearer ${userAccessToken}`)
.expect(httpStatus.FORBIDDEN)
.then((res) => {
expect(res.body.code).to.be.equal(httpStatus.FORBIDDEN);
expect(res.body.message).to.be.equal('Forbidden');
});
});
});
describe('GET /v1/users/profile', () => {
it('should get the logged user\'s info', () => {
delete dbUsers.jonSnow.password;
return request(app)
.get('/v1/users/profile')
.set('Authorization', `Bearer ${userAccessToken}`)
.expect(httpStatus.OK)
.then((res) => {
expect(res.body).to.include(dbUsers.jonSnow);
});
});
it('should report error without stacktrace when accessToken is expired', async () => {
// fake time
const clock = sinon.useFakeTimers();
const expiredAccessToken = (await User.findAndGenerateToken(dbUsers.branStark)).accessToken;
// move clock forward by minutes set in config + 1 minute
clock.tick((JWT_EXPIRATION * 60000) + 60000);
return request(app)
.get('/v1/users/profile')
.set('Authorization', `Bearer ${expiredAccessToken}`)
.expect(httpStatus.UNAUTHORIZED)
.then((res) => {
expect(res.body.code).to.be.equal(httpStatus.UNAUTHORIZED);
expect(res.body.message).to.be.equal('jwt expired');
expect(res.body).to.not.have.a.property('stack');
});
});
});
});