feat: Write integration test for users.

This commit is contained in:
Ahmed Bouhuolia
2020-05-25 12:02:38 +02:00
parent 5369b3661a
commit d7e4694dfa
6 changed files with 303 additions and 17 deletions

View File

@@ -37,7 +37,8 @@ function Invite({ requestInviteAccept, requestInviteMetaByToken }) {
last_name: Yup.string().required().label(formatMessage({id:'last_name_'})), last_name: Yup.string().required().label(formatMessage({id:'last_name_'})),
phone_number: Yup.string() phone_number: Yup.string()
.matches() .matches()
.required().label(formatMessage({id:''})), .required()
.label(formatMessage({id:'phone_number'})),
password: Yup.string() password: Yup.string()
.min(4) .min(4)
.required().label(formatMessage({id:'password'})) .required().label(formatMessage({id:'password'}))

View File

@@ -1,4 +1,3 @@
import express from 'express'; import express from 'express';
import { check, validationResult } from 'express-validator'; import { check, validationResult } from 'express-validator';
import path from 'path'; import path from 'path';
@@ -176,6 +175,7 @@ export default {
await TenantUser.bindKnex(tenantDb).query().insert({ await TenantUser.bindKnex(tenantDb).query().insert({
...userInsert, ...userInsert,
invite_accepted_at: moment().format('YYYY/MM/DD HH:mm:ss'),
}); });
Logger.log('info', 'New tenant has been created.', { organizationId }); Logger.log('info', 'New tenant has been created.', { organizationId });

View File

@@ -9,6 +9,7 @@ import {
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import Mustache from 'mustache'; import Mustache from 'mustache';
import moment from 'moment';
import mail from '@/services/mail'; import mail from '@/services/mail';
import { hashPassword } from '@/utils'; import { hashPassword } from '@/utils';
import SystemUser from '@/system/models/SystemUser'; import SystemUser from '@/system/models/SystemUser';
@@ -64,10 +65,10 @@ export default {
}); });
} }
const form = { ...req.body }; const form = { ...req.body };
const foundUser = await SystemUser.query()
.where('email', form.email).first();
const { user } = req; const { user } = req;
const { TenantUser } = req.models;
const foundUser = await SystemUser.query()
.where('email', form.email).first();
if (foundUser) { if (foundUser) {
return res.status(400).send({ return res.status(400).send({
@@ -80,6 +81,10 @@ export default {
tenant_id: user.tenantId, tenant_id: user.tenantId,
token, token,
}); });
const tenantUser = await TenantUser.query().insert({
first_name: form.email,
email: form.email,
});
const { Option } = req.models; const { Option } = req.models;
const organizationOptions = await Option.query() const organizationOptions = await Option.query()
.where('key', 'organization_name'); .where('key', 'organization_name');
@@ -117,11 +122,18 @@ export default {
check('first_name').exists().trim().escape(), check('first_name').exists().trim().escape(),
check('last_name').exists().trim().escape(), check('last_name').exists().trim().escape(),
check('phone_number').exists().trim().escape(), check('phone_number').exists().trim().escape(),
// check('language').exists().isIn(['ar', 'en']),
check('password').exists().trim().escape(), check('password').exists().trim().escape(),
param('token').exists().trim().escape(), param('token').exists().trim().escape(),
], ],
async handler(req, res) { async handler(req, res) {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.boom.badData(null, {
code: 'validation_error', ...validationErrors,
});
}
const { token } = req.params; const { token } = req.params;
const inviteToken = await Invite.query() const inviteToken = await Invite.query()
.where('token', token).first(); .where('token', token).first();
@@ -160,7 +172,7 @@ export default {
const userForm = { const userForm = {
first_name: form.first_name, first_name: form.first_name,
last_name: form.last_name, last_name: form.last_name,
email: form.email, email: inviteToken.email,
phone_number: form.phone_number, phone_number: form.phone_number,
language: form.language, language: form.language,
active: 1, active: 1,
@@ -175,19 +187,29 @@ export default {
errors: [{ type: 'PHONE_NUMBER.ALREADY.EXISTS', code: 400 }], errors: [{ type: 'PHONE_NUMBER.ALREADY.EXISTS', code: 400 }],
}); });
} }
const insertUserOper = TenantUser.bindKnex(tenantDb) const insertUserOper = TenantUser.bindKnex(tenantDb)
.query().insert({ ...userForm }); .query()
.where('email', userForm.email)
.patch({
...userForm,
invite_accepted_at: moment().format('YYYY/MM/DD'),
});
const insertSysUserOper = SystemUser.query().insert({ const insertSysUserOper = SystemUser.query().insert({
...userForm, ...userForm,
password: hashedPassword, password: hashedPassword,
tenant_id: inviteToken.tenantId,
}); });
const deleteInviteTokenOper = Invite.query()
.where('token', inviteToken.token).delete();
await Promise.all([ await Promise.all([
insertUserOper, insertUserOper,
insertSysUserOper, insertSysUserOper,
deleteInviteTokenOper,
]); ]);
await Invite.query().where('token', token).delete();
return res.status(200).send(); return res.status(200).send();
}, },
}, },

View File

@@ -1,11 +1,13 @@
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import { Model } from 'objection'; import { Model, mixin } from 'objection';
import TenantModel from '@/models/TenantModel'; import TenantModel from '@/models/TenantModel';
import DateSession from '@/models/DateSession';
// import PermissionsService from '@/services/PermissionsService'; // import PermissionsService from '@/services/PermissionsService';
export default class TenantUser extends TenantModel { export default class TenantUser extends mixin(TenantModel, [DateSession]) {
// ...PermissionsService /**
* Virtual attributes.
*/
static get virtualAttributes() { static get virtualAttributes() {
return ['fullName']; return ['fullName'];
} }
@@ -49,6 +51,6 @@ export default class TenantUser extends TenantModel {
} }
fullName() { fullName() {
return `${this.firstName} ${this.lastName}`; return `${this.firstName} ${this.lastName || ''}`;
} }
} }

View File

@@ -1,8 +1,10 @@
import { Model } from 'objection'; import { Model, mixin } from 'objection';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import SystemModel from '@/system/models/SystemModel'; import SystemModel from '@/system/models/SystemModel';
import DateSession from '@/models/DateSession';
export default class SystemUser extends SystemModel {
export default class SystemUser extends mixin(SystemModel, [DateSession]) {
/** /**
* Table name. * Table name.
*/ */

View File

@@ -0,0 +1,259 @@
import knex from '@/database/knex';
import {
request,
expect,
createUser,
} from '~/testInit';
import {
tenantWebsite,
tenantFactory,
loginRes
} from '~/dbInit';
import Invite from '@/system/models/Invite'
import TenantUser from '@/models/TenantUser';
import SystemUser from '@/system/models/SystemUser';
describe('routes: `/api/invite_users`', () => {
describe('POST: `/api/invite_users/send`', () => {
it('Should response unauthorized if the user was not authorized.', async () => {
const res = await request().get('/api/invite_users/send');
expect(res.status).equals(401);
expect(res.body.message).equals('Unauthorized');
});
it('Should email be required.', async () => {
const res = await request()
.post('/api/invite/send')
.set('x-access-token', loginRes.body.token)
.set('organization-id', tenantWebsite.organizationId)
.send();
expect(res.status).equals(422);
expect(res.body.errors).include.something.deep.equals({
msg: 'Invalid value', param: 'email', location: 'body',
});
});
it('Should email not be already registered in the system database.', async () => {
const user = await createUser(tenantWebsite, {
active: false,
email: 'admin@admin.com',
});
const res = await request()
.post('/api/invite/send')
.set('x-access-token', loginRes.body.token)
.set('organization-id', tenantWebsite.organizationId)
.send({
email: 'admin@admin.com',
});
expect(res.status).equals(400);
expect(res.body.errors).include.something.deep.equals({
type: 'USER.EMAIL.ALREADY.REGISTERED', code: 100,
});
});
it('Should invite token be inserted to the master database.', async () => {
const res = await request()
.post('/api/invite/send')
.set('x-access-token', loginRes.body.token)
.set('organization-id', tenantWebsite.organizationId)
.send({
email: 'admin@admin.com'
});
const foundInviteToken = await Invite.query()
.where('email', 'admin@admin.com').first();
expect(foundInviteToken).is.not.null;
expect(foundInviteToken.token).is.not.null;
});
it('Should invite email be insereted to users tenant database.', async () => {
const res = await request()
.post('/api/invite/send')
.set('x-access-token', loginRes.body.token)
.set('organization-id', tenantWebsite.organizationId)
.send({
email: 'admin@admin.com'
});
const foundTenantUser = await TenantUser.tenant().query()
.where('email', 'admin@admin.com').first();
expect(foundTenantUser).is.not.null;
expect(foundTenantUser.email).equals('admin@admin.com');
expect(foundTenantUser.firstName).equals('admin@admin.com');
expect(foundTenantUser.createdAt).is.not.null;
});
});
describe('POST: `/api/invite/accept/:token`', () => {
let sendInviteRes;
let inviteUser;
beforeEach(async () => {
sendInviteRes = await request()
.post('/api/invite/send')
.set('x-access-token', loginRes.body.token)
.set('organization-id', tenantWebsite.organizationId)
.send({
email: 'admin@admin.com'
});
inviteUser = await Invite.query()
.where('email', 'admin@admin.com')
.first();
});
it('Should the given token be valid.', async () => {
const res = await request()
.post('/api/invite/accept/invalid_token')
.send({
first_name: 'Ahmed',
last_name: 'Bouhuolia',
password: 'hard-password',
phone_number: '0927918381',
});
expect(res.status).equals(404);
expect(res.body.errors).include.something.deep.equals({
type: 'INVITE.TOKEN.NOT.FOUND', code: 300,
});
});
it('Should first_name be required.', async () => {
const res = await request()
.post(`/api/invite/accept/${inviteUser.token}`)
.send();
expect(res.status).equals(422);
expect(res.body.errors).include.something.deep.equals({
msg: 'Invalid value', param: 'first_name', location: 'body'
});
});
it('Should last_name be required.', async () => {
const res = await request()
.post(`/api/invite/accept/${inviteUser.token}`)
.send();
expect(res.status).equals(422);
expect(res.body.errors).include.something.deep.equals({
msg: 'Invalid value', param: 'last_name', location: 'body'
});
});
it('Should phone_number be required.', async () => {
const res = await request()
.post(`/api/invite/accept/${inviteUser.token}`)
.send();
expect(res.status).equals(422);
expect(res.body.errors).include.something.deep.equals({
msg: 'Invalid value', param: 'phone_number', location: 'body'
});
});
it('Should password be required.', async () => {
const res = await request()
.post(`/api/invite/accept/${inviteUser.token}`)
.send();
expect(res.status).equals(422);
expect(res.body.errors).include.something.deep.equals({
msg: 'Invalid value', param: 'password', location: 'body'
});
});
it('Should phone number not be already registered.', async () => {
const user = await createUser(tenantWebsite);
const res = await request()
.post(`/api/invite/accept/${inviteUser.token}`)
.send({
first_name: 'Ahmed',
last_name: 'Bouhuolia',
password: 'hard-password',
phone_number: user.phone_number,
})
expect(res.status).equals(400);
expect(res.body.errors).include.something.deep.equals({
type: 'PHONE_MUMNER.ALREADY.EXISTS', code: 400,
});
});
it('Should tenant user details updated after invite accept.', async () => {
const user = await createUser(tenantWebsite);
const res = await request()
.post(`/api/invite/accept/${inviteUser.token}`)
.send({
first_name: 'Ahmed',
last_name: 'Bouhuolia',
password: 'hard-password',
phone_number: '0927918381',
});
const foundTenantUser = await TenantUser.tenant().query()
.where('email', 'admin@admin.com').first();
expect(foundTenantUser).is.not.null;
expect(foundTenantUser.id).is.not.null;
expect(foundTenantUser.email).equals('admin@admin.com');
expect(foundTenantUser.firstName).equals('Ahmed');
expect(foundTenantUser.lastName).equals('Bouhuolia');
expect(foundTenantUser.active).equals(1);
expect(foundTenantUser.inviteAcceptedAt).is.not.null;
expect(foundTenantUser.createdAt).is.not.null;
expect(foundTenantUser.updatedAt).is.not.null;
});
it('Should user details be insereted to the system database', async () => {
const user = await createUser(tenantWebsite);
const res = await request()
.post(`/api/invite/accept/${inviteUser.token}`)
.send({
first_name: 'Ahmed',
last_name: 'Bouhuolia',
password: 'hard-password',
phone_number: '0927918381',
});
const foundSystemUser = await SystemUser.query()
.where('email', 'admin@admin.com').first();
expect(foundSystemUser).is.not.null;
expect(foundSystemUser.id).is.not.null;
expect(foundSystemUser.tenantId).equals(inviteUser.tenantId);
expect(foundSystemUser.email).equals('admin@admin.com');
expect(foundSystemUser.firstName).equals('Ahmed');
expect(foundSystemUser.lastName).equals('Bouhuolia');
expect(foundSystemUser.active).equals(1);
expect(foundSystemUser.lastLoginAt).is.null;
expect(foundSystemUser.createdAt).is.not.null;
expect(foundSystemUser.updatedAt).is.null;
});
it('Should invite token be deleted after invite accept.', async () => {
const res = await request()
.post(`/api/invite/accept/${inviteUser.token}`)
.send({
first_name: 'Ahmed',
last_name: 'Bouhuolia',
password: 'hard-password',
phone_number: '0927918381',
});
const foundInviteToken = await Invite.query().where('token', inviteUser.token);
expect(foundInviteToken.length).equals(0);
});
});
describe('GET: `/api/invite_users/:token`', () => {
it('Should response token invalid.', () => {
});
});
});