mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 06:40:31 +00:00
feat: Write integration test for users.
This commit is contained in:
@@ -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'}))
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
|
||||||
|
|||||||
@@ -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,11 +65,11 @@ export default {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
const form = { ...req.body };
|
const form = { ...req.body };
|
||||||
|
const { user } = req;
|
||||||
|
const { TenantUser } = req.models;
|
||||||
const foundUser = await SystemUser.query()
|
const foundUser = await SystemUser.query()
|
||||||
.where('email', form.email).first();
|
.where('email', form.email).first();
|
||||||
|
|
||||||
const { user } = req;
|
|
||||||
|
|
||||||
if (foundUser) {
|
if (foundUser) {
|
||||||
return res.status(400).send({
|
return res.status(400).send({
|
||||||
errors: [{ type: 'USER.EMAIL.ALREADY.REGISTERED', code: 100 }],
|
errors: [{ type: 'USER.EMAIL.ALREADY.REGISTERED', code: 100 }],
|
||||||
@@ -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();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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 || ''}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
259
server/tests/routes/inviteUsers.test.js
Normal file
259
server/tests/routes/inviteUsers.test.js
Normal 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.', () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user