init
This commit is contained in:
14
tests/fixtures/token.fixture.js
vendored
Normal file
14
tests/fixtures/token.fixture.js
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
const moment = require('moment');
|
||||
const config = require('../../src/config/config');
|
||||
const { tokenTypes } = require('../../src/config/tokens');
|
||||
const tokenService = require('../../src/services/token.service');
|
||||
const { userOne, admin } = require('./user.fixture');
|
||||
|
||||
const accessTokenExpires = moment().add(config.jwt.accessExpirationMinutes, 'minutes');
|
||||
const userOneAccessToken = tokenService.generateToken(userOne._id, accessTokenExpires, tokenTypes.ACCESS);
|
||||
const adminAccessToken = tokenService.generateToken(admin._id, accessTokenExpires, tokenTypes.ACCESS);
|
||||
|
||||
module.exports = {
|
||||
userOneAccessToken,
|
||||
adminAccessToken,
|
||||
};
|
||||
46
tests/fixtures/user.fixture.js
vendored
Normal file
46
tests/fixtures/user.fixture.js
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
const mongoose = require('mongoose');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const faker = require('faker');
|
||||
const User = require('../../src/models/user.model');
|
||||
|
||||
const password = 'password1';
|
||||
const salt = bcrypt.genSaltSync(8);
|
||||
const hashedPassword = bcrypt.hashSync(password, salt);
|
||||
|
||||
const userOne = {
|
||||
_id: mongoose.Types.ObjectId(),
|
||||
name: faker.name.findName(),
|
||||
email: faker.internet.email().toLowerCase(),
|
||||
password,
|
||||
role: 'user',
|
||||
isEmailVerified: false,
|
||||
};
|
||||
|
||||
const userTwo = {
|
||||
_id: mongoose.Types.ObjectId(),
|
||||
name: faker.name.findName(),
|
||||
email: faker.internet.email().toLowerCase(),
|
||||
password,
|
||||
role: 'user',
|
||||
isEmailVerified: false,
|
||||
};
|
||||
|
||||
const admin = {
|
||||
_id: mongoose.Types.ObjectId(),
|
||||
name: faker.name.findName(),
|
||||
email: faker.internet.email().toLowerCase(),
|
||||
password,
|
||||
role: 'admin',
|
||||
isEmailVerified: false,
|
||||
};
|
||||
|
||||
const insertUsers = async (users) => {
|
||||
await User.insertMany(users.map((user) => ({ ...user, password: hashedPassword })));
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
userOne,
|
||||
userTwo,
|
||||
admin,
|
||||
insertUsers,
|
||||
};
|
||||
587
tests/integration/auth.test.js
Normal file
587
tests/integration/auth.test.js
Normal file
@@ -0,0 +1,587 @@
|
||||
const request = require('supertest');
|
||||
const faker = require('faker');
|
||||
const httpStatus = require('http-status');
|
||||
const httpMocks = require('node-mocks-http');
|
||||
const moment = require('moment');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const app = require('../../src/app');
|
||||
const config = require('../../src/config/config');
|
||||
const auth = require('../../src/middlewares/auth');
|
||||
const { tokenService, emailService } = require('../../src/services');
|
||||
const ApiError = require('../../src/utils/ApiError');
|
||||
const setupTestDB = require('../utils/setupTestDB');
|
||||
const { User, Token } = require('../../src/models');
|
||||
const { roleRights } = require('../../src/config/roles');
|
||||
const { tokenTypes } = require('../../src/config/tokens');
|
||||
const { userOne, admin, insertUsers } = require('../fixtures/user.fixture');
|
||||
const { userOneAccessToken, adminAccessToken } = require('../fixtures/token.fixture');
|
||||
|
||||
setupTestDB();
|
||||
|
||||
describe('Auth routes', () => {
|
||||
describe('POST /v1/auth/register', () => {
|
||||
let newUser;
|
||||
beforeEach(() => {
|
||||
newUser = {
|
||||
name: faker.name.findName(),
|
||||
email: faker.internet.email().toLowerCase(),
|
||||
password: 'password1',
|
||||
};
|
||||
});
|
||||
|
||||
test('should return 201 and successfully register user if request data is ok', async () => {
|
||||
const res = await request(app).post('/v1/auth/register').send(newUser).expect(httpStatus.CREATED);
|
||||
|
||||
expect(res.body.user).not.toHaveProperty('password');
|
||||
expect(res.body.user).toEqual({
|
||||
id: expect.anything(),
|
||||
name: newUser.name,
|
||||
email: newUser.email,
|
||||
role: 'user',
|
||||
isEmailVerified: false,
|
||||
});
|
||||
|
||||
const dbUser = await User.findById(res.body.user.id);
|
||||
expect(dbUser).toBeDefined();
|
||||
expect(dbUser.password).not.toBe(newUser.password);
|
||||
expect(dbUser).toMatchObject({ name: newUser.name, email: newUser.email, role: 'user', isEmailVerified: false });
|
||||
|
||||
expect(res.body.tokens).toEqual({
|
||||
access: { token: expect.anything(), expires: expect.anything() },
|
||||
refresh: { token: expect.anything(), expires: expect.anything() },
|
||||
});
|
||||
});
|
||||
|
||||
test('should return 400 error if email is invalid', async () => {
|
||||
newUser.email = 'invalidEmail';
|
||||
|
||||
await request(app).post('/v1/auth/register').send(newUser).expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
|
||||
test('should return 400 error if email is already used', async () => {
|
||||
await insertUsers([userOne]);
|
||||
newUser.email = userOne.email;
|
||||
|
||||
await request(app).post('/v1/auth/register').send(newUser).expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
|
||||
test('should return 400 error if password length is less than 8 characters', async () => {
|
||||
newUser.password = 'passwo1';
|
||||
|
||||
await request(app).post('/v1/auth/register').send(newUser).expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
|
||||
test('should return 400 error if password does not contain both letters and numbers', async () => {
|
||||
newUser.password = 'password';
|
||||
|
||||
await request(app).post('/v1/auth/register').send(newUser).expect(httpStatus.BAD_REQUEST);
|
||||
|
||||
newUser.password = '11111111';
|
||||
|
||||
await request(app).post('/v1/auth/register').send(newUser).expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /v1/auth/login', () => {
|
||||
test('should return 200 and login user if email and password match', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const loginCredentials = {
|
||||
email: userOne.email,
|
||||
password: userOne.password,
|
||||
};
|
||||
|
||||
const res = await request(app).post('/v1/auth/login').send(loginCredentials).expect(httpStatus.OK);
|
||||
|
||||
expect(res.body.user).toEqual({
|
||||
id: expect.anything(),
|
||||
name: userOne.name,
|
||||
email: userOne.email,
|
||||
role: userOne.role,
|
||||
isEmailVerified: userOne.isEmailVerified,
|
||||
});
|
||||
|
||||
expect(res.body.tokens).toEqual({
|
||||
access: { token: expect.anything(), expires: expect.anything() },
|
||||
refresh: { token: expect.anything(), expires: expect.anything() },
|
||||
});
|
||||
});
|
||||
|
||||
test('should return 401 error if there are no users with that email', async () => {
|
||||
const loginCredentials = {
|
||||
email: userOne.email,
|
||||
password: userOne.password,
|
||||
};
|
||||
|
||||
const res = await request(app).post('/v1/auth/login').send(loginCredentials).expect(httpStatus.UNAUTHORIZED);
|
||||
|
||||
expect(res.body).toEqual({ code: httpStatus.UNAUTHORIZED, message: 'Incorrect email or password' });
|
||||
});
|
||||
|
||||
test('should return 401 error if password is wrong', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const loginCredentials = {
|
||||
email: userOne.email,
|
||||
password: 'wrongPassword1',
|
||||
};
|
||||
|
||||
const res = await request(app).post('/v1/auth/login').send(loginCredentials).expect(httpStatus.UNAUTHORIZED);
|
||||
|
||||
expect(res.body).toEqual({ code: httpStatus.UNAUTHORIZED, message: 'Incorrect email or password' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /v1/auth/logout', () => {
|
||||
test('should return 204 if refresh token is valid', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
|
||||
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
|
||||
await tokenService.saveToken(refreshToken, userOne._id, expires, tokenTypes.REFRESH);
|
||||
|
||||
await request(app).post('/v1/auth/logout').send({ refreshToken }).expect(httpStatus.NO_CONTENT);
|
||||
|
||||
const dbRefreshTokenDoc = await Token.findOne({ token: refreshToken });
|
||||
expect(dbRefreshTokenDoc).toBe(null);
|
||||
});
|
||||
|
||||
test('should return 400 error if refresh token is missing from request body', async () => {
|
||||
await request(app).post('/v1/auth/logout').send().expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
|
||||
test('should return 404 error if refresh token is not found in the database', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
|
||||
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
|
||||
|
||||
await request(app).post('/v1/auth/logout').send({ refreshToken }).expect(httpStatus.NOT_FOUND);
|
||||
});
|
||||
|
||||
test('should return 404 error if refresh token is blacklisted', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
|
||||
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
|
||||
await tokenService.saveToken(refreshToken, userOne._id, expires, tokenTypes.REFRESH, true);
|
||||
|
||||
await request(app).post('/v1/auth/logout').send({ refreshToken }).expect(httpStatus.NOT_FOUND);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /v1/auth/refresh-tokens', () => {
|
||||
test('should return 200 and new auth tokens if refresh token is valid', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
|
||||
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
|
||||
await tokenService.saveToken(refreshToken, userOne._id, expires, tokenTypes.REFRESH);
|
||||
|
||||
const res = await request(app).post('/v1/auth/refresh-tokens').send({ refreshToken }).expect(httpStatus.OK);
|
||||
|
||||
expect(res.body).toEqual({
|
||||
access: { token: expect.anything(), expires: expect.anything() },
|
||||
refresh: { token: expect.anything(), expires: expect.anything() },
|
||||
});
|
||||
|
||||
const dbRefreshTokenDoc = await Token.findOne({ token: res.body.refresh.token });
|
||||
expect(dbRefreshTokenDoc).toMatchObject({ type: tokenTypes.REFRESH, user: userOne._id, blacklisted: false });
|
||||
|
||||
const dbRefreshTokenCount = await Token.countDocuments();
|
||||
expect(dbRefreshTokenCount).toBe(1);
|
||||
});
|
||||
|
||||
test('should return 400 error if refresh token is missing from request body', async () => {
|
||||
await request(app).post('/v1/auth/refresh-tokens').send().expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
|
||||
test('should return 401 error if refresh token is signed using an invalid secret', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
|
||||
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH, 'invalidSecret');
|
||||
await tokenService.saveToken(refreshToken, userOne._id, expires, tokenTypes.REFRESH);
|
||||
|
||||
await request(app).post('/v1/auth/refresh-tokens').send({ refreshToken }).expect(httpStatus.UNAUTHORIZED);
|
||||
});
|
||||
|
||||
test('should return 401 error if refresh token is not found in the database', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
|
||||
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
|
||||
|
||||
await request(app).post('/v1/auth/refresh-tokens').send({ refreshToken }).expect(httpStatus.UNAUTHORIZED);
|
||||
});
|
||||
|
||||
test('should return 401 error if refresh token is blacklisted', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
|
||||
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
|
||||
await tokenService.saveToken(refreshToken, userOne._id, expires, tokenTypes.REFRESH, true);
|
||||
|
||||
await request(app).post('/v1/auth/refresh-tokens').send({ refreshToken }).expect(httpStatus.UNAUTHORIZED);
|
||||
});
|
||||
|
||||
test('should return 401 error if refresh token is expired', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const expires = moment().subtract(1, 'minutes');
|
||||
const refreshToken = tokenService.generateToken(userOne._id, expires);
|
||||
await tokenService.saveToken(refreshToken, userOne._id, expires, tokenTypes.REFRESH);
|
||||
|
||||
await request(app).post('/v1/auth/refresh-tokens').send({ refreshToken }).expect(httpStatus.UNAUTHORIZED);
|
||||
});
|
||||
|
||||
test('should return 401 error if user is not found', async () => {
|
||||
const expires = moment().add(config.jwt.refreshExpirationDays, 'days');
|
||||
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
|
||||
await tokenService.saveToken(refreshToken, userOne._id, expires, tokenTypes.REFRESH);
|
||||
|
||||
await request(app).post('/v1/auth/refresh-tokens').send({ refreshToken }).expect(httpStatus.UNAUTHORIZED);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /v1/auth/forgot-password', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(emailService.transport, 'sendMail').mockResolvedValue();
|
||||
});
|
||||
|
||||
test('should return 204 and send reset password email to the user', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const sendResetPasswordEmailSpy = jest.spyOn(emailService, 'sendResetPasswordEmail');
|
||||
|
||||
await request(app).post('/v1/auth/forgot-password').send({ email: userOne.email }).expect(httpStatus.NO_CONTENT);
|
||||
|
||||
expect(sendResetPasswordEmailSpy).toHaveBeenCalledWith(userOne.email, expect.any(String));
|
||||
const resetPasswordToken = sendResetPasswordEmailSpy.mock.calls[0][1];
|
||||
const dbResetPasswordTokenDoc = await Token.findOne({ token: resetPasswordToken, user: userOne._id });
|
||||
expect(dbResetPasswordTokenDoc).toBeDefined();
|
||||
});
|
||||
|
||||
test('should return 400 if email is missing', async () => {
|
||||
await insertUsers([userOne]);
|
||||
|
||||
await request(app).post('/v1/auth/forgot-password').send().expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
|
||||
test('should return 404 if email does not belong to any user', async () => {
|
||||
await request(app).post('/v1/auth/forgot-password').send({ email: userOne.email }).expect(httpStatus.NOT_FOUND);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /v1/auth/reset-password', () => {
|
||||
test('should return 204 and reset the password', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
|
||||
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
|
||||
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
|
||||
|
||||
await request(app)
|
||||
.post('/v1/auth/reset-password')
|
||||
.query({ token: resetPasswordToken })
|
||||
.send({ password: 'password2' })
|
||||
.expect(httpStatus.NO_CONTENT);
|
||||
|
||||
const dbUser = await User.findById(userOne._id);
|
||||
const isPasswordMatch = await bcrypt.compare('password2', dbUser.password);
|
||||
expect(isPasswordMatch).toBe(true);
|
||||
|
||||
const dbResetPasswordTokenCount = await Token.countDocuments({ user: userOne._id, type: tokenTypes.RESET_PASSWORD });
|
||||
expect(dbResetPasswordTokenCount).toBe(0);
|
||||
});
|
||||
|
||||
test('should return 400 if reset password token is missing', async () => {
|
||||
await insertUsers([userOne]);
|
||||
|
||||
await request(app).post('/v1/auth/reset-password').send({ password: 'password2' }).expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
|
||||
test('should return 401 if reset password token is blacklisted', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
|
||||
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
|
||||
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD, true);
|
||||
|
||||
await request(app)
|
||||
.post('/v1/auth/reset-password')
|
||||
.query({ token: resetPasswordToken })
|
||||
.send({ password: 'password2' })
|
||||
.expect(httpStatus.UNAUTHORIZED);
|
||||
});
|
||||
|
||||
test('should return 401 if reset password token is expired', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const expires = moment().subtract(1, 'minutes');
|
||||
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
|
||||
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
|
||||
|
||||
await request(app)
|
||||
.post('/v1/auth/reset-password')
|
||||
.query({ token: resetPasswordToken })
|
||||
.send({ password: 'password2' })
|
||||
.expect(httpStatus.UNAUTHORIZED);
|
||||
});
|
||||
|
||||
test('should return 401 if user is not found', async () => {
|
||||
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
|
||||
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
|
||||
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
|
||||
|
||||
await request(app)
|
||||
.post('/v1/auth/reset-password')
|
||||
.query({ token: resetPasswordToken })
|
||||
.send({ password: 'password2' })
|
||||
.expect(httpStatus.UNAUTHORIZED);
|
||||
});
|
||||
|
||||
test('should return 400 if password is missing or invalid', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
|
||||
const resetPasswordToken = tokenService.generateToken(userOne._id, expires, tokenTypes.RESET_PASSWORD);
|
||||
await tokenService.saveToken(resetPasswordToken, userOne._id, expires, tokenTypes.RESET_PASSWORD);
|
||||
|
||||
await request(app).post('/v1/auth/reset-password').query({ token: resetPasswordToken }).expect(httpStatus.BAD_REQUEST);
|
||||
|
||||
await request(app)
|
||||
.post('/v1/auth/reset-password')
|
||||
.query({ token: resetPasswordToken })
|
||||
.send({ password: 'short1' })
|
||||
.expect(httpStatus.BAD_REQUEST);
|
||||
|
||||
await request(app)
|
||||
.post('/v1/auth/reset-password')
|
||||
.query({ token: resetPasswordToken })
|
||||
.send({ password: 'password' })
|
||||
.expect(httpStatus.BAD_REQUEST);
|
||||
|
||||
await request(app)
|
||||
.post('/v1/auth/reset-password')
|
||||
.query({ token: resetPasswordToken })
|
||||
.send({ password: '11111111' })
|
||||
.expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /v1/auth/send-verification-email', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(emailService.transport, 'sendMail').mockResolvedValue();
|
||||
});
|
||||
|
||||
test('should return 204 and send verification email to the user', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const sendVerificationEmailSpy = jest.spyOn(emailService, 'sendVerificationEmail');
|
||||
|
||||
await request(app)
|
||||
.post('/v1/auth/send-verification-email')
|
||||
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
||||
.expect(httpStatus.NO_CONTENT);
|
||||
|
||||
expect(sendVerificationEmailSpy).toHaveBeenCalledWith(userOne.email, expect.any(String));
|
||||
const verifyEmailToken = sendVerificationEmailSpy.mock.calls[0][1];
|
||||
const dbVerifyEmailToken = await Token.findOne({ token: verifyEmailToken, user: userOne._id });
|
||||
|
||||
expect(dbVerifyEmailToken).toBeDefined();
|
||||
});
|
||||
|
||||
test('should return 401 error if access token is missing', async () => {
|
||||
await insertUsers([userOne]);
|
||||
|
||||
await request(app).post('/v1/auth/send-verification-email').send().expect(httpStatus.UNAUTHORIZED);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /v1/auth/verify-email', () => {
|
||||
test('should return 204 and verify the email', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const expires = moment().add(config.jwt.verifyEmailExpirationMinutes, 'minutes');
|
||||
const verifyEmailToken = tokenService.generateToken(userOne._id, expires);
|
||||
await tokenService.saveToken(verifyEmailToken, userOne._id, expires, tokenTypes.VERIFY_EMAIL);
|
||||
|
||||
await request(app)
|
||||
.post('/v1/auth/verify-email')
|
||||
.query({ token: verifyEmailToken })
|
||||
.send()
|
||||
.expect(httpStatus.NO_CONTENT);
|
||||
|
||||
const dbUser = await User.findById(userOne._id);
|
||||
|
||||
expect(dbUser.isEmailVerified).toBe(true);
|
||||
|
||||
const dbVerifyEmailToken = await Token.countDocuments({
|
||||
user: userOne._id,
|
||||
type: tokenTypes.VERIFY_EMAIL,
|
||||
});
|
||||
expect(dbVerifyEmailToken).toBe(0);
|
||||
});
|
||||
|
||||
test('should return 400 if verify email token is missing', async () => {
|
||||
await insertUsers([userOne]);
|
||||
|
||||
await request(app).post('/v1/auth/verify-email').send().expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
|
||||
test('should return 401 if verify email token is blacklisted', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const expires = moment().add(config.jwt.verifyEmailExpirationMinutes, 'minutes');
|
||||
const verifyEmailToken = tokenService.generateToken(userOne._id, expires);
|
||||
await tokenService.saveToken(verifyEmailToken, userOne._id, expires, tokenTypes.VERIFY_EMAIL, true);
|
||||
|
||||
await request(app)
|
||||
.post('/v1/auth/verify-email')
|
||||
.query({ token: verifyEmailToken })
|
||||
.send()
|
||||
.expect(httpStatus.UNAUTHORIZED);
|
||||
});
|
||||
|
||||
test('should return 401 if verify email token is expired', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const expires = moment().subtract(1, 'minutes');
|
||||
const verifyEmailToken = tokenService.generateToken(userOne._id, expires);
|
||||
await tokenService.saveToken(verifyEmailToken, userOne._id, expires, tokenTypes.VERIFY_EMAIL);
|
||||
|
||||
await request(app)
|
||||
.post('/v1/auth/verify-email')
|
||||
.query({ token: verifyEmailToken })
|
||||
.send()
|
||||
.expect(httpStatus.UNAUTHORIZED);
|
||||
});
|
||||
|
||||
test('should return 401 if user is not found', async () => {
|
||||
const expires = moment().add(config.jwt.verifyEmailExpirationMinutes, 'minutes');
|
||||
const verifyEmailToken = tokenService.generateToken(userOne._id, expires);
|
||||
await tokenService.saveToken(verifyEmailToken, userOne._id, expires, tokenTypes.VERIFY_EMAIL);
|
||||
|
||||
await request(app)
|
||||
.post('/v1/auth/verify-email')
|
||||
.query({ token: verifyEmailToken })
|
||||
.send()
|
||||
.expect(httpStatus.UNAUTHORIZED);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Auth middleware', () => {
|
||||
test('should call next with no errors if access token is valid', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${userOneAccessToken}` } });
|
||||
const next = jest.fn();
|
||||
|
||||
await auth()(req, httpMocks.createResponse(), next);
|
||||
|
||||
expect(next).toHaveBeenCalledWith();
|
||||
expect(req.user._id).toEqual(userOne._id);
|
||||
});
|
||||
|
||||
test('should call next with unauthorized error if access token is not found in header', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const req = httpMocks.createRequest();
|
||||
const next = jest.fn();
|
||||
|
||||
await auth()(req, httpMocks.createResponse(), next);
|
||||
|
||||
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
|
||||
expect(next).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ statusCode: httpStatus.UNAUTHORIZED, message: 'Please authenticate' })
|
||||
);
|
||||
});
|
||||
|
||||
test('should call next with unauthorized error if access token is not a valid jwt token', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const req = httpMocks.createRequest({ headers: { Authorization: 'Bearer randomToken' } });
|
||||
const next = jest.fn();
|
||||
|
||||
await auth()(req, httpMocks.createResponse(), next);
|
||||
|
||||
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
|
||||
expect(next).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ statusCode: httpStatus.UNAUTHORIZED, message: 'Please authenticate' })
|
||||
);
|
||||
});
|
||||
|
||||
test('should call next with unauthorized error if the token is not an access token', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const expires = moment().add(config.jwt.accessExpirationMinutes, 'minutes');
|
||||
const refreshToken = tokenService.generateToken(userOne._id, expires, tokenTypes.REFRESH);
|
||||
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${refreshToken}` } });
|
||||
const next = jest.fn();
|
||||
|
||||
await auth()(req, httpMocks.createResponse(), next);
|
||||
|
||||
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
|
||||
expect(next).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ statusCode: httpStatus.UNAUTHORIZED, message: 'Please authenticate' })
|
||||
);
|
||||
});
|
||||
|
||||
test('should call next with unauthorized error if access token is generated with an invalid secret', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const expires = moment().add(config.jwt.accessExpirationMinutes, 'minutes');
|
||||
const accessToken = tokenService.generateToken(userOne._id, expires, tokenTypes.ACCESS, 'invalidSecret');
|
||||
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${accessToken}` } });
|
||||
const next = jest.fn();
|
||||
|
||||
await auth()(req, httpMocks.createResponse(), next);
|
||||
|
||||
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
|
||||
expect(next).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ statusCode: httpStatus.UNAUTHORIZED, message: 'Please authenticate' })
|
||||
);
|
||||
});
|
||||
|
||||
test('should call next with unauthorized error if access token is expired', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const expires = moment().subtract(1, 'minutes');
|
||||
const accessToken = tokenService.generateToken(userOne._id, expires, tokenTypes.ACCESS);
|
||||
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${accessToken}` } });
|
||||
const next = jest.fn();
|
||||
|
||||
await auth()(req, httpMocks.createResponse(), next);
|
||||
|
||||
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
|
||||
expect(next).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ statusCode: httpStatus.UNAUTHORIZED, message: 'Please authenticate' })
|
||||
);
|
||||
});
|
||||
|
||||
test('should call next with unauthorized error if user is not found', async () => {
|
||||
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${userOneAccessToken}` } });
|
||||
const next = jest.fn();
|
||||
|
||||
await auth()(req, httpMocks.createResponse(), next);
|
||||
|
||||
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
|
||||
expect(next).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ statusCode: httpStatus.UNAUTHORIZED, message: 'Please authenticate' })
|
||||
);
|
||||
});
|
||||
|
||||
test('should call next with forbidden error if user does not have required rights and userId is not in params', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const req = httpMocks.createRequest({ headers: { Authorization: `Bearer ${userOneAccessToken}` } });
|
||||
const next = jest.fn();
|
||||
|
||||
await auth('anyRight')(req, httpMocks.createResponse(), next);
|
||||
|
||||
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
|
||||
expect(next).toHaveBeenCalledWith(expect.objectContaining({ statusCode: httpStatus.FORBIDDEN, message: 'Forbidden' }));
|
||||
});
|
||||
|
||||
test('should call next with no errors if user does not have required rights but userId is in params', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const req = httpMocks.createRequest({
|
||||
headers: { Authorization: `Bearer ${userOneAccessToken}` },
|
||||
params: { userId: userOne._id.toHexString() },
|
||||
});
|
||||
const next = jest.fn();
|
||||
|
||||
await auth('anyRight')(req, httpMocks.createResponse(), next);
|
||||
|
||||
expect(next).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
test('should call next with no errors if user has required rights', async () => {
|
||||
await insertUsers([admin]);
|
||||
const req = httpMocks.createRequest({
|
||||
headers: { Authorization: `Bearer ${adminAccessToken}` },
|
||||
params: { userId: userOne._id.toHexString() },
|
||||
});
|
||||
const next = jest.fn();
|
||||
|
||||
await auth(...roleRights.get('admin'))(req, httpMocks.createResponse(), next);
|
||||
|
||||
expect(next).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
14
tests/integration/docs.test.js
Normal file
14
tests/integration/docs.test.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const request = require('supertest');
|
||||
const httpStatus = require('http-status');
|
||||
const app = require('../../src/app');
|
||||
const config = require('../../src/config/config');
|
||||
|
||||
describe('Auth routes', () => {
|
||||
describe('GET /v1/docs', () => {
|
||||
test('should return 404 when running in production', async () => {
|
||||
config.env = 'production';
|
||||
await request(app).get('/v1/docs').send().expect(httpStatus.NOT_FOUND);
|
||||
config.env = process.env.NODE_ENV;
|
||||
});
|
||||
});
|
||||
});
|
||||
625
tests/integration/user.test.js
Normal file
625
tests/integration/user.test.js
Normal file
@@ -0,0 +1,625 @@
|
||||
const request = require('supertest');
|
||||
const faker = require('faker');
|
||||
const httpStatus = require('http-status');
|
||||
const app = require('../../src/app');
|
||||
const setupTestDB = require('../utils/setupTestDB');
|
||||
const { User } = require('../../src/models');
|
||||
const { userOne, userTwo, admin, insertUsers } = require('../fixtures/user.fixture');
|
||||
const { userOneAccessToken, adminAccessToken } = require('../fixtures/token.fixture');
|
||||
|
||||
setupTestDB();
|
||||
|
||||
describe('User routes', () => {
|
||||
describe('POST /v1/users', () => {
|
||||
let newUser;
|
||||
|
||||
beforeEach(() => {
|
||||
newUser = {
|
||||
name: faker.name.findName(),
|
||||
email: faker.internet.email().toLowerCase(),
|
||||
password: 'password1',
|
||||
role: 'user',
|
||||
};
|
||||
});
|
||||
|
||||
test('should return 201 and successfully create new user if data is ok', async () => {
|
||||
await insertUsers([admin]);
|
||||
|
||||
const res = await request(app)
|
||||
.post('/v1/users')
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.send(newUser)
|
||||
.expect(httpStatus.CREATED);
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).toEqual({
|
||||
id: expect.anything(),
|
||||
name: newUser.name,
|
||||
email: newUser.email,
|
||||
role: newUser.role,
|
||||
isEmailVerified: false,
|
||||
});
|
||||
|
||||
const dbUser = await User.findById(res.body.id);
|
||||
expect(dbUser).toBeDefined();
|
||||
expect(dbUser.password).not.toBe(newUser.password);
|
||||
expect(dbUser).toMatchObject({ name: newUser.name, email: newUser.email, role: newUser.role, isEmailVerified: false });
|
||||
});
|
||||
|
||||
test('should be able to create an admin as well', async () => {
|
||||
await insertUsers([admin]);
|
||||
newUser.role = 'admin';
|
||||
|
||||
const res = await request(app)
|
||||
.post('/v1/users')
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.send(newUser)
|
||||
.expect(httpStatus.CREATED);
|
||||
|
||||
expect(res.body.role).toBe('admin');
|
||||
|
||||
const dbUser = await User.findById(res.body.id);
|
||||
expect(dbUser.role).toBe('admin');
|
||||
});
|
||||
|
||||
test('should return 401 error if access token is missing', async () => {
|
||||
await request(app).post('/v1/users').send(newUser).expect(httpStatus.UNAUTHORIZED);
|
||||
});
|
||||
|
||||
test('should return 403 error if logged in user is not admin', async () => {
|
||||
await insertUsers([userOne]);
|
||||
|
||||
await request(app)
|
||||
.post('/v1/users')
|
||||
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
||||
.send(newUser)
|
||||
.expect(httpStatus.FORBIDDEN);
|
||||
});
|
||||
|
||||
test('should return 400 error if email is invalid', async () => {
|
||||
await insertUsers([admin]);
|
||||
newUser.email = 'invalidEmail';
|
||||
|
||||
await request(app)
|
||||
.post('/v1/users')
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.send(newUser)
|
||||
.expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
|
||||
test('should return 400 error if email is already used', async () => {
|
||||
await insertUsers([admin, userOne]);
|
||||
newUser.email = userOne.email;
|
||||
|
||||
await request(app)
|
||||
.post('/v1/users')
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.send(newUser)
|
||||
.expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
|
||||
test('should return 400 error if password length is less than 8 characters', async () => {
|
||||
await insertUsers([admin]);
|
||||
newUser.password = 'passwo1';
|
||||
|
||||
await request(app)
|
||||
.post('/v1/users')
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.send(newUser)
|
||||
.expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
|
||||
test('should return 400 error if password does not contain both letters and numbers', async () => {
|
||||
await insertUsers([admin]);
|
||||
newUser.password = 'password';
|
||||
|
||||
await request(app)
|
||||
.post('/v1/users')
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.send(newUser)
|
||||
.expect(httpStatus.BAD_REQUEST);
|
||||
|
||||
newUser.password = '1111111';
|
||||
|
||||
await request(app)
|
||||
.post('/v1/users')
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.send(newUser)
|
||||
.expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
|
||||
test('should return 400 error if role is neither user nor admin', async () => {
|
||||
await insertUsers([admin]);
|
||||
newUser.role = 'invalid';
|
||||
|
||||
await request(app)
|
||||
.post('/v1/users')
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.send(newUser)
|
||||
.expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /v1/users', () => {
|
||||
test('should return 200 and apply the default query options', async () => {
|
||||
await insertUsers([userOne, userTwo, admin]);
|
||||
|
||||
const res = await request(app)
|
||||
.get('/v1/users')
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.send()
|
||||
.expect(httpStatus.OK);
|
||||
|
||||
expect(res.body).toEqual({
|
||||
results: expect.any(Array),
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
totalResults: 3,
|
||||
});
|
||||
expect(res.body.results).toHaveLength(3);
|
||||
expect(res.body.results[0]).toEqual({
|
||||
id: userOne._id.toHexString(),
|
||||
name: userOne.name,
|
||||
email: userOne.email,
|
||||
role: userOne.role,
|
||||
isEmailVerified: userOne.isEmailVerified,
|
||||
});
|
||||
});
|
||||
|
||||
test('should return 401 if access token is missing', async () => {
|
||||
await insertUsers([userOne, userTwo, admin]);
|
||||
|
||||
await request(app).get('/v1/users').send().expect(httpStatus.UNAUTHORIZED);
|
||||
});
|
||||
|
||||
test('should return 403 if a non-admin is trying to access all users', async () => {
|
||||
await insertUsers([userOne, userTwo, admin]);
|
||||
|
||||
await request(app)
|
||||
.get('/v1/users')
|
||||
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
||||
.send()
|
||||
.expect(httpStatus.FORBIDDEN);
|
||||
});
|
||||
|
||||
test('should correctly apply filter on name field', async () => {
|
||||
await insertUsers([userOne, userTwo, admin]);
|
||||
|
||||
const res = await request(app)
|
||||
.get('/v1/users')
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.query({ name: userOne.name })
|
||||
.send()
|
||||
.expect(httpStatus.OK);
|
||||
|
||||
expect(res.body).toEqual({
|
||||
results: expect.any(Array),
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
totalResults: 1,
|
||||
});
|
||||
expect(res.body.results).toHaveLength(1);
|
||||
expect(res.body.results[0].id).toBe(userOne._id.toHexString());
|
||||
});
|
||||
|
||||
test('should correctly apply filter on role field', async () => {
|
||||
await insertUsers([userOne, userTwo, admin]);
|
||||
|
||||
const res = await request(app)
|
||||
.get('/v1/users')
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.query({ role: 'user' })
|
||||
.send()
|
||||
.expect(httpStatus.OK);
|
||||
|
||||
expect(res.body).toEqual({
|
||||
results: expect.any(Array),
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
totalResults: 2,
|
||||
});
|
||||
expect(res.body.results).toHaveLength(2);
|
||||
expect(res.body.results[0].id).toBe(userOne._id.toHexString());
|
||||
expect(res.body.results[1].id).toBe(userTwo._id.toHexString());
|
||||
});
|
||||
|
||||
test('should correctly sort the returned array if descending sort param is specified', async () => {
|
||||
await insertUsers([userOne, userTwo, admin]);
|
||||
|
||||
const res = await request(app)
|
||||
.get('/v1/users')
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.query({ sortBy: 'role:desc' })
|
||||
.send()
|
||||
.expect(httpStatus.OK);
|
||||
|
||||
expect(res.body).toEqual({
|
||||
results: expect.any(Array),
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
totalResults: 3,
|
||||
});
|
||||
expect(res.body.results).toHaveLength(3);
|
||||
expect(res.body.results[0].id).toBe(userOne._id.toHexString());
|
||||
expect(res.body.results[1].id).toBe(userTwo._id.toHexString());
|
||||
expect(res.body.results[2].id).toBe(admin._id.toHexString());
|
||||
});
|
||||
|
||||
test('should correctly sort the returned array if ascending sort param is specified', async () => {
|
||||
await insertUsers([userOne, userTwo, admin]);
|
||||
|
||||
const res = await request(app)
|
||||
.get('/v1/users')
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.query({ sortBy: 'role:asc' })
|
||||
.send()
|
||||
.expect(httpStatus.OK);
|
||||
|
||||
expect(res.body).toEqual({
|
||||
results: expect.any(Array),
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
totalResults: 3,
|
||||
});
|
||||
expect(res.body.results).toHaveLength(3);
|
||||
expect(res.body.results[0].id).toBe(admin._id.toHexString());
|
||||
expect(res.body.results[1].id).toBe(userOne._id.toHexString());
|
||||
expect(res.body.results[2].id).toBe(userTwo._id.toHexString());
|
||||
});
|
||||
|
||||
test('should correctly sort the returned array if multiple sorting criteria are specified', async () => {
|
||||
await insertUsers([userOne, userTwo, admin]);
|
||||
|
||||
const res = await request(app)
|
||||
.get('/v1/users')
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.query({ sortBy: 'role:desc,name:asc' })
|
||||
.send()
|
||||
.expect(httpStatus.OK);
|
||||
|
||||
expect(res.body).toEqual({
|
||||
results: expect.any(Array),
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalPages: 1,
|
||||
totalResults: 3,
|
||||
});
|
||||
expect(res.body.results).toHaveLength(3);
|
||||
|
||||
const expectedOrder = [userOne, userTwo, admin].sort((a, b) => {
|
||||
if (a.role < b.role) {
|
||||
return 1;
|
||||
}
|
||||
if (a.role > b.role) {
|
||||
return -1;
|
||||
}
|
||||
return a.name < b.name ? -1 : 1;
|
||||
});
|
||||
|
||||
expectedOrder.forEach((user, index) => {
|
||||
expect(res.body.results[index].id).toBe(user._id.toHexString());
|
||||
});
|
||||
});
|
||||
|
||||
test('should limit returned array if limit param is specified', async () => {
|
||||
await insertUsers([userOne, userTwo, admin]);
|
||||
|
||||
const res = await request(app)
|
||||
.get('/v1/users')
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.query({ limit: 2 })
|
||||
.send()
|
||||
.expect(httpStatus.OK);
|
||||
|
||||
expect(res.body).toEqual({
|
||||
results: expect.any(Array),
|
||||
page: 1,
|
||||
limit: 2,
|
||||
totalPages: 2,
|
||||
totalResults: 3,
|
||||
});
|
||||
expect(res.body.results).toHaveLength(2);
|
||||
expect(res.body.results[0].id).toBe(userOne._id.toHexString());
|
||||
expect(res.body.results[1].id).toBe(userTwo._id.toHexString());
|
||||
});
|
||||
|
||||
test('should return the correct page if page and limit params are specified', async () => {
|
||||
await insertUsers([userOne, userTwo, admin]);
|
||||
|
||||
const res = await request(app)
|
||||
.get('/v1/users')
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.query({ page: 2, limit: 2 })
|
||||
.send()
|
||||
.expect(httpStatus.OK);
|
||||
|
||||
expect(res.body).toEqual({
|
||||
results: expect.any(Array),
|
||||
page: 2,
|
||||
limit: 2,
|
||||
totalPages: 2,
|
||||
totalResults: 3,
|
||||
});
|
||||
expect(res.body.results).toHaveLength(1);
|
||||
expect(res.body.results[0].id).toBe(admin._id.toHexString());
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /v1/users/:userId', () => {
|
||||
test('should return 200 and the user object if data is ok', async () => {
|
||||
await insertUsers([userOne]);
|
||||
|
||||
const res = await request(app)
|
||||
.get(`/v1/users/${userOne._id}`)
|
||||
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
||||
.send()
|
||||
.expect(httpStatus.OK);
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).toEqual({
|
||||
id: userOne._id.toHexString(),
|
||||
email: userOne.email,
|
||||
name: userOne.name,
|
||||
role: userOne.role,
|
||||
isEmailVerified: userOne.isEmailVerified,
|
||||
});
|
||||
});
|
||||
|
||||
test('should return 401 error if access token is missing', async () => {
|
||||
await insertUsers([userOne]);
|
||||
|
||||
await request(app).get(`/v1/users/${userOne._id}`).send().expect(httpStatus.UNAUTHORIZED);
|
||||
});
|
||||
|
||||
test('should return 403 error if user is trying to get another user', async () => {
|
||||
await insertUsers([userOne, userTwo]);
|
||||
|
||||
await request(app)
|
||||
.get(`/v1/users/${userTwo._id}`)
|
||||
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
||||
.send()
|
||||
.expect(httpStatus.FORBIDDEN);
|
||||
});
|
||||
|
||||
test('should return 200 and the user object if admin is trying to get another user', async () => {
|
||||
await insertUsers([userOne, admin]);
|
||||
|
||||
await request(app)
|
||||
.get(`/v1/users/${userOne._id}`)
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.send()
|
||||
.expect(httpStatus.OK);
|
||||
});
|
||||
|
||||
test('should return 400 error if userId is not a valid mongo id', async () => {
|
||||
await insertUsers([admin]);
|
||||
|
||||
await request(app)
|
||||
.get('/v1/users/invalidId')
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.send()
|
||||
.expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
|
||||
test('should return 404 error if user is not found', async () => {
|
||||
await insertUsers([admin]);
|
||||
|
||||
await request(app)
|
||||
.get(`/v1/users/${userOne._id}`)
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.send()
|
||||
.expect(httpStatus.NOT_FOUND);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /v1/users/:userId', () => {
|
||||
test('should return 204 if data is ok', async () => {
|
||||
await insertUsers([userOne]);
|
||||
|
||||
await request(app)
|
||||
.delete(`/v1/users/${userOne._id}`)
|
||||
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
||||
.send()
|
||||
.expect(httpStatus.NO_CONTENT);
|
||||
|
||||
const dbUser = await User.findById(userOne._id);
|
||||
expect(dbUser).toBeNull();
|
||||
});
|
||||
|
||||
test('should return 401 error if access token is missing', async () => {
|
||||
await insertUsers([userOne]);
|
||||
|
||||
await request(app).delete(`/v1/users/${userOne._id}`).send().expect(httpStatus.UNAUTHORIZED);
|
||||
});
|
||||
|
||||
test('should return 403 error if user is trying to delete another user', async () => {
|
||||
await insertUsers([userOne, userTwo]);
|
||||
|
||||
await request(app)
|
||||
.delete(`/v1/users/${userTwo._id}`)
|
||||
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
||||
.send()
|
||||
.expect(httpStatus.FORBIDDEN);
|
||||
});
|
||||
|
||||
test('should return 204 if admin is trying to delete another user', async () => {
|
||||
await insertUsers([userOne, admin]);
|
||||
|
||||
await request(app)
|
||||
.delete(`/v1/users/${userOne._id}`)
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.send()
|
||||
.expect(httpStatus.NO_CONTENT);
|
||||
});
|
||||
|
||||
test('should return 400 error if userId is not a valid mongo id', async () => {
|
||||
await insertUsers([admin]);
|
||||
|
||||
await request(app)
|
||||
.delete('/v1/users/invalidId')
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.send()
|
||||
.expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
|
||||
test('should return 404 error if user already is not found', async () => {
|
||||
await insertUsers([admin]);
|
||||
|
||||
await request(app)
|
||||
.delete(`/v1/users/${userOne._id}`)
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.send()
|
||||
.expect(httpStatus.NOT_FOUND);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PATCH /v1/users/:userId', () => {
|
||||
test('should return 200 and successfully update user if data is ok', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const updateBody = {
|
||||
name: faker.name.findName(),
|
||||
email: faker.internet.email().toLowerCase(),
|
||||
password: 'newPassword1',
|
||||
};
|
||||
|
||||
const res = await request(app)
|
||||
.patch(`/v1/users/${userOne._id}`)
|
||||
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
||||
.send(updateBody)
|
||||
.expect(httpStatus.OK);
|
||||
|
||||
expect(res.body).not.toHaveProperty('password');
|
||||
expect(res.body).toEqual({
|
||||
id: userOne._id.toHexString(),
|
||||
name: updateBody.name,
|
||||
email: updateBody.email,
|
||||
role: 'user',
|
||||
isEmailVerified: false,
|
||||
});
|
||||
|
||||
const dbUser = await User.findById(userOne._id);
|
||||
expect(dbUser).toBeDefined();
|
||||
expect(dbUser.password).not.toBe(updateBody.password);
|
||||
expect(dbUser).toMatchObject({ name: updateBody.name, email: updateBody.email, role: 'user' });
|
||||
});
|
||||
|
||||
test('should return 401 error if access token is missing', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const updateBody = { name: faker.name.findName() };
|
||||
|
||||
await request(app).patch(`/v1/users/${userOne._id}`).send(updateBody).expect(httpStatus.UNAUTHORIZED);
|
||||
});
|
||||
|
||||
test('should return 403 if user is updating another user', async () => {
|
||||
await insertUsers([userOne, userTwo]);
|
||||
const updateBody = { name: faker.name.findName() };
|
||||
|
||||
await request(app)
|
||||
.patch(`/v1/users/${userTwo._id}`)
|
||||
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
||||
.send(updateBody)
|
||||
.expect(httpStatus.FORBIDDEN);
|
||||
});
|
||||
|
||||
test('should return 200 and successfully update user if admin is updating another user', async () => {
|
||||
await insertUsers([userOne, admin]);
|
||||
const updateBody = { name: faker.name.findName() };
|
||||
|
||||
await request(app)
|
||||
.patch(`/v1/users/${userOne._id}`)
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.send(updateBody)
|
||||
.expect(httpStatus.OK);
|
||||
});
|
||||
|
||||
test('should return 404 if admin is updating another user that is not found', async () => {
|
||||
await insertUsers([admin]);
|
||||
const updateBody = { name: faker.name.findName() };
|
||||
|
||||
await request(app)
|
||||
.patch(`/v1/users/${userOne._id}`)
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.send(updateBody)
|
||||
.expect(httpStatus.NOT_FOUND);
|
||||
});
|
||||
|
||||
test('should return 400 error if userId is not a valid mongo id', async () => {
|
||||
await insertUsers([admin]);
|
||||
const updateBody = { name: faker.name.findName() };
|
||||
|
||||
await request(app)
|
||||
.patch(`/v1/users/invalidId`)
|
||||
.set('Authorization', `Bearer ${adminAccessToken}`)
|
||||
.send(updateBody)
|
||||
.expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
|
||||
test('should return 400 if email is invalid', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const updateBody = { email: 'invalidEmail' };
|
||||
|
||||
await request(app)
|
||||
.patch(`/v1/users/${userOne._id}`)
|
||||
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
||||
.send(updateBody)
|
||||
.expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
|
||||
test('should return 400 if email is already taken', async () => {
|
||||
await insertUsers([userOne, userTwo]);
|
||||
const updateBody = { email: userTwo.email };
|
||||
|
||||
await request(app)
|
||||
.patch(`/v1/users/${userOne._id}`)
|
||||
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
||||
.send(updateBody)
|
||||
.expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
|
||||
test('should not return 400 if email is my email', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const updateBody = { email: userOne.email };
|
||||
|
||||
await request(app)
|
||||
.patch(`/v1/users/${userOne._id}`)
|
||||
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
||||
.send(updateBody)
|
||||
.expect(httpStatus.OK);
|
||||
});
|
||||
|
||||
test('should return 400 if password length is less than 8 characters', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const updateBody = { password: 'passwo1' };
|
||||
|
||||
await request(app)
|
||||
.patch(`/v1/users/${userOne._id}`)
|
||||
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
||||
.send(updateBody)
|
||||
.expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
|
||||
test('should return 400 if password does not contain both letters and numbers', async () => {
|
||||
await insertUsers([userOne]);
|
||||
const updateBody = { password: 'password' };
|
||||
|
||||
await request(app)
|
||||
.patch(`/v1/users/${userOne._id}`)
|
||||
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
||||
.send(updateBody)
|
||||
.expect(httpStatus.BAD_REQUEST);
|
||||
|
||||
updateBody.password = '11111111';
|
||||
|
||||
await request(app)
|
||||
.patch(`/v1/users/${userOne._id}`)
|
||||
.set('Authorization', `Bearer ${userOneAccessToken}`)
|
||||
.send(updateBody)
|
||||
.expect(httpStatus.BAD_REQUEST);
|
||||
});
|
||||
});
|
||||
});
|
||||
168
tests/unit/middlewares/error.test.js
Normal file
168
tests/unit/middlewares/error.test.js
Normal file
@@ -0,0 +1,168 @@
|
||||
const mongoose = require('mongoose');
|
||||
const httpStatus = require('http-status');
|
||||
const httpMocks = require('node-mocks-http');
|
||||
const { errorConverter, errorHandler } = require('../../../src/middlewares/error');
|
||||
const ApiError = require('../../../src/utils/ApiError');
|
||||
const config = require('../../../src/config/config');
|
||||
const logger = require('../../../src/config/logger');
|
||||
|
||||
describe('Error middlewares', () => {
|
||||
describe('Error converter', () => {
|
||||
test('should return the same ApiError object it was called with', () => {
|
||||
const error = new ApiError(httpStatus.BAD_REQUEST, 'Any error');
|
||||
const next = jest.fn();
|
||||
|
||||
errorConverter(error, httpMocks.createRequest(), httpMocks.createResponse(), next);
|
||||
|
||||
expect(next).toHaveBeenCalledWith(error);
|
||||
});
|
||||
|
||||
test('should convert an Error to ApiError and preserve its status and message', () => {
|
||||
const error = new Error('Any error');
|
||||
error.statusCode = httpStatus.BAD_REQUEST;
|
||||
const next = jest.fn();
|
||||
|
||||
errorConverter(error, httpMocks.createRequest(), httpMocks.createResponse(), next);
|
||||
|
||||
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
|
||||
expect(next).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
statusCode: error.statusCode,
|
||||
message: error.message,
|
||||
isOperational: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('should convert an Error without status to ApiError with status 500', () => {
|
||||
const error = new Error('Any error');
|
||||
const next = jest.fn();
|
||||
|
||||
errorConverter(error, httpMocks.createRequest(), httpMocks.createResponse(), next);
|
||||
|
||||
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
|
||||
expect(next).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
statusCode: httpStatus.INTERNAL_SERVER_ERROR,
|
||||
message: error.message,
|
||||
isOperational: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('should convert an Error without message to ApiError with default message of that http status', () => {
|
||||
const error = new Error();
|
||||
error.statusCode = httpStatus.BAD_REQUEST;
|
||||
const next = jest.fn();
|
||||
|
||||
errorConverter(error, httpMocks.createRequest(), httpMocks.createResponse(), next);
|
||||
|
||||
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
|
||||
expect(next).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
statusCode: error.statusCode,
|
||||
message: httpStatus[error.statusCode],
|
||||
isOperational: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('should convert a Mongoose error to ApiError with status 400 and preserve its message', () => {
|
||||
const error = new mongoose.Error('Any mongoose error');
|
||||
const next = jest.fn();
|
||||
|
||||
errorConverter(error, httpMocks.createRequest(), httpMocks.createResponse(), next);
|
||||
|
||||
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
|
||||
expect(next).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
statusCode: httpStatus.BAD_REQUEST,
|
||||
message: error.message,
|
||||
isOperational: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('should convert any other object to ApiError with status 500 and its message', () => {
|
||||
const error = {};
|
||||
const next = jest.fn();
|
||||
|
||||
errorConverter(error, httpMocks.createRequest(), httpMocks.createResponse(), next);
|
||||
|
||||
expect(next).toHaveBeenCalledWith(expect.any(ApiError));
|
||||
expect(next).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
statusCode: httpStatus.INTERNAL_SERVER_ERROR,
|
||||
message: httpStatus[httpStatus.INTERNAL_SERVER_ERROR],
|
||||
isOperational: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error handler', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(logger, 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
test('should send proper error response and put the error message in res.locals', () => {
|
||||
const error = new ApiError(httpStatus.BAD_REQUEST, 'Any error');
|
||||
const res = httpMocks.createResponse();
|
||||
const sendSpy = jest.spyOn(res, 'send');
|
||||
|
||||
errorHandler(error, httpMocks.createRequest(), res);
|
||||
|
||||
expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({ code: error.statusCode, message: error.message }));
|
||||
expect(res.locals.errorMessage).toBe(error.message);
|
||||
});
|
||||
|
||||
test('should put the error stack in the response if in development mode', () => {
|
||||
config.env = 'development';
|
||||
const error = new ApiError(httpStatus.BAD_REQUEST, 'Any error');
|
||||
const res = httpMocks.createResponse();
|
||||
const sendSpy = jest.spyOn(res, 'send');
|
||||
|
||||
errorHandler(error, httpMocks.createRequest(), res);
|
||||
|
||||
expect(sendSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ code: error.statusCode, message: error.message, stack: error.stack })
|
||||
);
|
||||
config.env = process.env.NODE_ENV;
|
||||
});
|
||||
|
||||
test('should send internal server error status and message if in production mode and error is not operational', () => {
|
||||
config.env = 'production';
|
||||
const error = new ApiError(httpStatus.BAD_REQUEST, 'Any error', false);
|
||||
const res = httpMocks.createResponse();
|
||||
const sendSpy = jest.spyOn(res, 'send');
|
||||
|
||||
errorHandler(error, httpMocks.createRequest(), res);
|
||||
|
||||
expect(sendSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
code: httpStatus.INTERNAL_SERVER_ERROR,
|
||||
message: httpStatus[httpStatus.INTERNAL_SERVER_ERROR],
|
||||
})
|
||||
);
|
||||
expect(res.locals.errorMessage).toBe(error.message);
|
||||
config.env = process.env.NODE_ENV;
|
||||
});
|
||||
|
||||
test('should preserve original error status and message if in production mode and error is operational', () => {
|
||||
config.env = 'production';
|
||||
const error = new ApiError(httpStatus.BAD_REQUEST, 'Any error');
|
||||
const res = httpMocks.createResponse();
|
||||
const sendSpy = jest.spyOn(res, 'send');
|
||||
|
||||
errorHandler(error, httpMocks.createRequest(), res);
|
||||
|
||||
expect(sendSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
code: error.statusCode,
|
||||
message: error.message,
|
||||
})
|
||||
);
|
||||
config.env = process.env.NODE_ENV;
|
||||
});
|
||||
});
|
||||
});
|
||||
61
tests/unit/models/plugins/paginate.plugin.test.js
Normal file
61
tests/unit/models/plugins/paginate.plugin.test.js
Normal file
@@ -0,0 +1,61 @@
|
||||
const mongoose = require('mongoose');
|
||||
const setupTestDB = require('../../../utils/setupTestDB');
|
||||
const paginate = require('../../../../src/models/plugins/paginate.plugin');
|
||||
|
||||
const projectSchema = mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
projectSchema.virtual('tasks', {
|
||||
ref: 'Task',
|
||||
localField: '_id',
|
||||
foreignField: 'project',
|
||||
});
|
||||
|
||||
projectSchema.plugin(paginate);
|
||||
const Project = mongoose.model('Project', projectSchema);
|
||||
|
||||
const taskSchema = mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
project: {
|
||||
type: mongoose.SchemaTypes.ObjectId,
|
||||
ref: 'Project',
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
taskSchema.plugin(paginate);
|
||||
const Task = mongoose.model('Task', taskSchema);
|
||||
|
||||
setupTestDB();
|
||||
|
||||
describe('paginate plugin', () => {
|
||||
describe('populate option', () => {
|
||||
test('should populate the specified data fields', async () => {
|
||||
const project = await Project.create({ name: 'Project One' });
|
||||
const task = await Task.create({ name: 'Task One', project: project._id });
|
||||
|
||||
const taskPages = await Task.paginate({ _id: task._id }, { populate: 'project' });
|
||||
|
||||
expect(taskPages.results[0].project).toHaveProperty('_id', project._id);
|
||||
});
|
||||
|
||||
test('should populate nested fields', async () => {
|
||||
const project = await Project.create({ name: 'Project One' });
|
||||
const task = await Task.create({ name: 'Task One', project: project._id });
|
||||
|
||||
const projectPages = await Project.paginate({ _id: project._id }, { populate: 'tasks.project' });
|
||||
const { tasks } = projectPages.results[0];
|
||||
|
||||
expect(tasks).toHaveLength(1);
|
||||
expect(tasks[0]).toHaveProperty('_id', task._id);
|
||||
expect(tasks[0].project).toHaveProperty('_id', project._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
89
tests/unit/models/plugins/toJSON.plugin.test.js
Normal file
89
tests/unit/models/plugins/toJSON.plugin.test.js
Normal file
@@ -0,0 +1,89 @@
|
||||
const mongoose = require('mongoose');
|
||||
const { toJSON } = require('../../../../src/models/plugins');
|
||||
|
||||
describe('toJSON plugin', () => {
|
||||
let connection;
|
||||
|
||||
beforeEach(() => {
|
||||
connection = mongoose.createConnection();
|
||||
});
|
||||
|
||||
it('should replace _id with id', () => {
|
||||
const schema = mongoose.Schema();
|
||||
schema.plugin(toJSON);
|
||||
const Model = connection.model('Model', schema);
|
||||
const doc = new Model();
|
||||
expect(doc.toJSON()).not.toHaveProperty('_id');
|
||||
expect(doc.toJSON()).toHaveProperty('id', doc._id.toString());
|
||||
});
|
||||
|
||||
it('should remove __v', () => {
|
||||
const schema = mongoose.Schema();
|
||||
schema.plugin(toJSON);
|
||||
const Model = connection.model('Model', schema);
|
||||
const doc = new Model();
|
||||
expect(doc.toJSON()).not.toHaveProperty('__v');
|
||||
});
|
||||
|
||||
it('should remove createdAt and updatedAt', () => {
|
||||
const schema = mongoose.Schema({}, { timestamps: true });
|
||||
schema.plugin(toJSON);
|
||||
const Model = connection.model('Model', schema);
|
||||
const doc = new Model();
|
||||
expect(doc.toJSON()).not.toHaveProperty('createdAt');
|
||||
expect(doc.toJSON()).not.toHaveProperty('updatedAt');
|
||||
});
|
||||
|
||||
it('should remove any path set as private', () => {
|
||||
const schema = mongoose.Schema({
|
||||
public: { type: String },
|
||||
private: { type: String, private: true },
|
||||
});
|
||||
schema.plugin(toJSON);
|
||||
const Model = connection.model('Model', schema);
|
||||
const doc = new Model({ public: 'some public value', private: 'some private value' });
|
||||
expect(doc.toJSON()).not.toHaveProperty('private');
|
||||
expect(doc.toJSON()).toHaveProperty('public');
|
||||
});
|
||||
|
||||
it('should remove any nested paths set as private', () => {
|
||||
const schema = mongoose.Schema({
|
||||
public: { type: String },
|
||||
nested: {
|
||||
private: { type: String, private: true },
|
||||
},
|
||||
});
|
||||
schema.plugin(toJSON);
|
||||
const Model = connection.model('Model', schema);
|
||||
const doc = new Model({
|
||||
public: 'some public value',
|
||||
nested: {
|
||||
private: 'some nested private value',
|
||||
},
|
||||
});
|
||||
expect(doc.toJSON()).not.toHaveProperty('nested.private');
|
||||
expect(doc.toJSON()).toHaveProperty('public');
|
||||
});
|
||||
|
||||
it('should also call the schema toJSON transform function', () => {
|
||||
const schema = mongoose.Schema(
|
||||
{
|
||||
public: { type: String },
|
||||
private: { type: String },
|
||||
},
|
||||
{
|
||||
toJSON: {
|
||||
transform: (doc, ret) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
delete ret.private;
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
schema.plugin(toJSON);
|
||||
const Model = connection.model('Model', schema);
|
||||
const doc = new Model({ public: 'some public value', private: 'some private value' });
|
||||
expect(doc.toJSON()).not.toHaveProperty('private');
|
||||
expect(doc.toJSON()).toHaveProperty('public');
|
||||
});
|
||||
});
|
||||
57
tests/unit/models/user.model.test.js
Normal file
57
tests/unit/models/user.model.test.js
Normal file
@@ -0,0 +1,57 @@
|
||||
const faker = require('faker');
|
||||
const { User } = require('../../../src/models');
|
||||
|
||||
describe('User model', () => {
|
||||
describe('User validation', () => {
|
||||
let newUser;
|
||||
beforeEach(() => {
|
||||
newUser = {
|
||||
name: faker.name.findName(),
|
||||
email: faker.internet.email().toLowerCase(),
|
||||
password: 'password1',
|
||||
role: 'user',
|
||||
};
|
||||
});
|
||||
|
||||
test('should correctly validate a valid user', async () => {
|
||||
await expect(new User(newUser).validate()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
test('should throw a validation error if email is invalid', async () => {
|
||||
newUser.email = 'invalidEmail';
|
||||
await expect(new User(newUser).validate()).rejects.toThrow();
|
||||
});
|
||||
|
||||
test('should throw a validation error if password length is less than 8 characters', async () => {
|
||||
newUser.password = 'passwo1';
|
||||
await expect(new User(newUser).validate()).rejects.toThrow();
|
||||
});
|
||||
|
||||
test('should throw a validation error if password does not contain numbers', async () => {
|
||||
newUser.password = 'password';
|
||||
await expect(new User(newUser).validate()).rejects.toThrow();
|
||||
});
|
||||
|
||||
test('should throw a validation error if password does not contain letters', async () => {
|
||||
newUser.password = '11111111';
|
||||
await expect(new User(newUser).validate()).rejects.toThrow();
|
||||
});
|
||||
|
||||
test('should throw a validation error if role is unknown', async () => {
|
||||
newUser.role = 'invalid';
|
||||
await expect(new User(newUser).validate()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('User toJSON()', () => {
|
||||
test('should not return user password when toJSON is called', () => {
|
||||
const newUser = {
|
||||
name: faker.name.findName(),
|
||||
email: faker.internet.email().toLowerCase(),
|
||||
password: 'password1',
|
||||
role: 'user',
|
||||
};
|
||||
expect(new User(newUser).toJSON()).not.toHaveProperty('password');
|
||||
});
|
||||
});
|
||||
});
|
||||
18
tests/utils/setupTestDB.js
Normal file
18
tests/utils/setupTestDB.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const mongoose = require('mongoose');
|
||||
const config = require('../../src/config/config');
|
||||
|
||||
const setupTestDB = () => {
|
||||
beforeAll(async () => {
|
||||
await mongoose.connect(config.mongoose.url, config.mongoose.options);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await Promise.all(Object.values(mongoose.connection.collections).map(async (collection) => collection.deleteMany()));
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await mongoose.disconnect();
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = setupTestDB;
|
||||
Reference in New Issue
Block a user