From 10f636d2bc2d3f42dae150902698a73ba04dd080 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 17 May 2020 07:08:12 +0200 Subject: [PATCH] WIP pass the failed tests. --- {server/tests => common}/lib/Lexer.test.js | 0 .../lib/Metable/MetableModel.test.js | 0 .../lib/MetableCollection.test.js | 0 {server/tests => common}/lib/Parser.test.js | 0 .../lib/ResourceCustomFieldRepository.test.js | 0 .../lib/ViewRolesBuilder.test.js | 0 .../models/AccountBalance.test.js | 0 .../tests => common}/models/Expense.test.js | 0 .../models/Permission.test.js | 0 .../models/ResourceField.test.js | 0 {server/tests => common}/models/Role.test.js | 0 .../routes/accountOpeningBalance.js | 0 .../routes/authorization.js | 0 .../budget.test.js => common/routes/budget.js | 0 .../routes/budget_reports.js | 0 .../routes/expenses.js | 0 .../fields.test.js => common/routes/fields.js | 0 .../oauth.test.js => common/routes/oauth.js | 0 .../routes/resources.js | 0 .../roles.test.js => common/routes/roles.js | 0 common/routes/users.test.js | 425 +++++++++++++++ .../services/JournalPoster.test.js | 0 server/config/systemKnexfile.js | 1 + server/src/database/factories/index.js | 510 +++++++++--------- server/src/database/factories/system.js | 16 + server/src/database/knex.js | 1 + server/src/database/manager.js | 4 - .../20190423085247_create_roles_table.js | 4 +- .../20190822214242_create_users_table.js | 2 +- ...0190822214247_create_oauth_tokens_table.js | 16 - ...190822214301_create_oauth_clients_table.js | 11 - .../20190822214302_create_settings_table.js | 2 +- .../20190822214303_create_items_table.js | 3 +- .../20190822214304_create_accounts_table.js | 4 +- ...190822214316_create_item_metadata_table.js | 11 - ...190822214904_create_account_types_table.js | 2 +- ...0822214905_create_resource_fields_table.js | 2 +- .../20190822214905_create_views_columns.js | 2 + ...20190822214905_create_views_roles_table.js | 2 +- .../20190822214905_create_views_table.js | 2 +- ...2647_create_accounts_transactions_table.js | 2 +- ...0105195823_create_manual_journals_table.js | 2 +- .../20200419171451_create_currencies_table.js | 2 +- ...00419191832_create_exchange_rates_table.js | 2 +- server/src/http/controllers/Accounts.js | 47 ++ server/src/http/controllers/Authentication.js | 16 +- server/src/http/controllers/ExchangeRates.js | 40 ++ server/src/http/controllers/ItemCategories.js | 50 +- server/src/http/controllers/Items.js | 14 +- server/src/lib/KnexFactory/index.js | 55 ++ server/src/models/Option.js | 5 +- server/src/system/TenantEnvironment.js | 12 + server/src/system/TenantsManager.js | 74 +++ server/tests/dbInit.js | 37 ++ server/tests/docker-compose.yml | 5 +- server/tests/models/Account.test.js | 25 +- server/tests/models/AccountType.test.js | 14 +- server/tests/models/Item.test.js | 26 +- server/tests/models/ItemCategories.test.js | 18 +- server/tests/models/Option.test.js | 11 +- server/tests/models/Resource.test.js | 16 +- server/tests/models/Setting.test.js | 197 ------- server/tests/models/User.test.js | 43 +- server/tests/models/View.test.js | 30 +- server/tests/mysql-tmpfs.sh | 31 ++ server/tests/routes/accounting.test.js | 149 ++--- server/tests/routes/accounts.test.js | 305 +++++++---- server/tests/routes/auth.test.js | 125 +++-- server/tests/routes/currencies.test.js | 51 +- server/tests/routes/exchange_rates.test.js | 84 ++- .../tests/routes/financial_statements.test.js | 97 +++- server/tests/routes/items.test.js | 185 ++++--- server/tests/routes/itemsCategories.test.js | 125 +++-- server/tests/routes/options.test.js | 35 +- server/tests/routes/users.test.js | 292 ++-------- server/tests/routes/views.test.js | 225 ++++---- server/tests/testInit.js | 100 ++-- 77 files changed, 2164 insertions(+), 1403 deletions(-) rename {server/tests => common}/lib/Lexer.test.js (100%) rename {server/tests => common}/lib/Metable/MetableModel.test.js (100%) rename {server/tests => common}/lib/MetableCollection.test.js (100%) rename {server/tests => common}/lib/Parser.test.js (100%) rename {server/tests => common}/lib/ResourceCustomFieldRepository.test.js (100%) rename {server/tests => common}/lib/ViewRolesBuilder.test.js (100%) rename {server/tests => common}/models/AccountBalance.test.js (100%) rename {server/tests => common}/models/Expense.test.js (100%) rename {server/tests => common}/models/Permission.test.js (100%) rename {server/tests => common}/models/ResourceField.test.js (100%) rename {server/tests => common}/models/Role.test.js (100%) rename server/tests/routes/accountOpeningBalance.test.js => common/routes/accountOpeningBalance.js (100%) rename server/tests/routes/authorization.test.js => common/routes/authorization.js (100%) rename server/tests/routes/budget.test.js => common/routes/budget.js (100%) rename server/tests/routes/budget_reports.test.js => common/routes/budget_reports.js (100%) rename server/tests/routes/expenses.test.js => common/routes/expenses.js (100%) rename server/tests/routes/fields.test.js => common/routes/fields.js (100%) rename server/tests/routes/oauth.test.js => common/routes/oauth.js (100%) rename server/tests/routes/resources.test.js => common/routes/resources.js (100%) rename server/tests/routes/roles.test.js => common/routes/roles.js (100%) create mode 100644 common/routes/users.test.js rename {server/tests => common}/services/JournalPoster.test.js (100%) create mode 100644 server/src/database/factories/system.js delete mode 100644 server/src/database/migrations/20190822214247_create_oauth_tokens_table.js delete mode 100644 server/src/database/migrations/20190822214301_create_oauth_clients_table.js delete mode 100644 server/src/database/migrations/20190822214316_create_item_metadata_table.js create mode 100644 server/src/lib/KnexFactory/index.js create mode 100644 server/src/system/TenantEnvironment.js create mode 100644 server/tests/dbInit.js delete mode 100644 server/tests/models/Setting.test.js create mode 100644 server/tests/mysql-tmpfs.sh diff --git a/server/tests/lib/Lexer.test.js b/common/lib/Lexer.test.js similarity index 100% rename from server/tests/lib/Lexer.test.js rename to common/lib/Lexer.test.js diff --git a/server/tests/lib/Metable/MetableModel.test.js b/common/lib/Metable/MetableModel.test.js similarity index 100% rename from server/tests/lib/Metable/MetableModel.test.js rename to common/lib/Metable/MetableModel.test.js diff --git a/server/tests/lib/MetableCollection.test.js b/common/lib/MetableCollection.test.js similarity index 100% rename from server/tests/lib/MetableCollection.test.js rename to common/lib/MetableCollection.test.js diff --git a/server/tests/lib/Parser.test.js b/common/lib/Parser.test.js similarity index 100% rename from server/tests/lib/Parser.test.js rename to common/lib/Parser.test.js diff --git a/server/tests/lib/ResourceCustomFieldRepository.test.js b/common/lib/ResourceCustomFieldRepository.test.js similarity index 100% rename from server/tests/lib/ResourceCustomFieldRepository.test.js rename to common/lib/ResourceCustomFieldRepository.test.js diff --git a/server/tests/lib/ViewRolesBuilder.test.js b/common/lib/ViewRolesBuilder.test.js similarity index 100% rename from server/tests/lib/ViewRolesBuilder.test.js rename to common/lib/ViewRolesBuilder.test.js diff --git a/server/tests/models/AccountBalance.test.js b/common/models/AccountBalance.test.js similarity index 100% rename from server/tests/models/AccountBalance.test.js rename to common/models/AccountBalance.test.js diff --git a/server/tests/models/Expense.test.js b/common/models/Expense.test.js similarity index 100% rename from server/tests/models/Expense.test.js rename to common/models/Expense.test.js diff --git a/server/tests/models/Permission.test.js b/common/models/Permission.test.js similarity index 100% rename from server/tests/models/Permission.test.js rename to common/models/Permission.test.js diff --git a/server/tests/models/ResourceField.test.js b/common/models/ResourceField.test.js similarity index 100% rename from server/tests/models/ResourceField.test.js rename to common/models/ResourceField.test.js diff --git a/server/tests/models/Role.test.js b/common/models/Role.test.js similarity index 100% rename from server/tests/models/Role.test.js rename to common/models/Role.test.js diff --git a/server/tests/routes/accountOpeningBalance.test.js b/common/routes/accountOpeningBalance.js similarity index 100% rename from server/tests/routes/accountOpeningBalance.test.js rename to common/routes/accountOpeningBalance.js diff --git a/server/tests/routes/authorization.test.js b/common/routes/authorization.js similarity index 100% rename from server/tests/routes/authorization.test.js rename to common/routes/authorization.js diff --git a/server/tests/routes/budget.test.js b/common/routes/budget.js similarity index 100% rename from server/tests/routes/budget.test.js rename to common/routes/budget.js diff --git a/server/tests/routes/budget_reports.test.js b/common/routes/budget_reports.js similarity index 100% rename from server/tests/routes/budget_reports.test.js rename to common/routes/budget_reports.js diff --git a/server/tests/routes/expenses.test.js b/common/routes/expenses.js similarity index 100% rename from server/tests/routes/expenses.test.js rename to common/routes/expenses.js diff --git a/server/tests/routes/fields.test.js b/common/routes/fields.js similarity index 100% rename from server/tests/routes/fields.test.js rename to common/routes/fields.js diff --git a/server/tests/routes/oauth.test.js b/common/routes/oauth.js similarity index 100% rename from server/tests/routes/oauth.test.js rename to common/routes/oauth.js diff --git a/server/tests/routes/resources.test.js b/common/routes/resources.js similarity index 100% rename from server/tests/routes/resources.test.js rename to common/routes/resources.js diff --git a/server/tests/routes/roles.test.js b/common/routes/roles.js similarity index 100% rename from server/tests/routes/roles.test.js rename to common/routes/roles.js diff --git a/common/routes/users.test.js b/common/routes/users.test.js new file mode 100644 index 000000000..172c75afc --- /dev/null +++ b/common/routes/users.test.js @@ -0,0 +1,425 @@ +import knex from '@/database/knex'; +import { + request, + expect, + create, + make, + login, + createTenantFactory, + createTenant, +} from '~/testInit'; + +let tenantDb; +let tenantFactory; + +describe.only('routes: `/routes`', () => { + beforeEach(async () => { + tenantDb = await createTenant(); + tenantFactory = createTenantFactory(tenantDb); + + loginRes = await login(); + }); + afterEach(() => { + loginRes = null; + }); + + describe('GET: `/users`', () => { + it('Should response unauthorized if the user was not authorized.', async () => { + const res = await request().get('/api/users'); + + expect(res.status).equals(401); + expect(res.body.message).equals('unauthorized'); + }); + + it('Should retrieve the stored users with pagination meta.', async () => { + await create('user'); + + const res = await request() + .get('/api/users') + .set('x-access-token', loginRes.body.token) + .send(); + + expect(res.body.users.results.length).equals(2); + expect(res.body.users.total).equals(2); + }); + }); + + describe('POST: `/users`', () => { + it('Should create a new user if the user was not authorized.', async () => { + const res = await request().post('/api/users'); + + expect(res.status).equals(401); + expect(res.body.message).equals('unauthorized'); + }); + + it('Should `first_name` be required.', async () => { + const res = await request() + .post('/api/users') + .set('x-access-token', loginRes.body.token) + .send(); + + expect(res.status).equals(422); + + const foundFirstNameParam = res.body.errors.find((error) => error.param === 'first_name'); + expect(!!foundFirstNameParam).equals(true); + }); + + it('Should `last_name` be required.', async () => { + const res = await request() + .post('/api/users') + .set('x-access-token', loginRes.body.token) + .send(); + + expect(res.status).equals(422); + + const foundFirstNameParam = res.body.errors.find((error) => error.param === 'last_name'); + expect(!!foundFirstNameParam).equals(true); + }); + + it('Should `email` be required.', async () => { + const res = await request() + .post('/api/users') + .set('x-access-token', loginRes.body.token) + .send(); + + expect(res.status).equals(422); + + const foundEmailParam = res.body.errors.find((error) => error.param === 'email'); + expect(!!foundEmailParam).equals(true); + }); + + it('Should be `email` be valid format.', async () => { + const user = make('user'); + const res = await request() + .post('/api/users') + .set('x-access-token', loginRes.body.token) + .send({ + first_name: user.first_name, + last_name: user.last_name, + email: 'email', + phone_number: user.phone_number, + status: 1, + }); + + expect(res.status).equals(422); + + const foundEmailParam = res.body.errors.find((error) => error.param === 'email'); + expect(!!foundEmailParam).equals(true); + }); + + it('Should `phone_number` be valid format.', async () => { + const user = make('user'); + const res = await request() + .post('/api/users') + .set('x-access-token', loginRes.body.token) + .send({ + first_name: user.first_name, + last_name: user.last_name, + email: user.email, + phone_number: 'phone_number', + status: 1, + }); + + expect(res.status).equals(422); + + const phoneNumberParam = res.body.errors.find((error) => error.param === 'phone_number'); + expect(!!phoneNumberParam).equals(true); + }); + + it('Should `password` be required.', async () => { + const res = await request() + .post('/api/users') + .set('x-access-token', loginRes.body.token) + .send(); + + expect(res.status).equals(422); + + const passwordParam = res.body.errors.find((error) => error.param === 'password'); + expect(!!passwordParam).equals(true); + }); + + it('Should password be equals confirm_password.', async () => { + const res = await request() + .post('/api/users') + .set('x-access-token', loginRes.body.token) + .send({ + password: '123123', + }); + + expect(res.status).equals(422); + + const passwordParam = res.body.errors.find((error) => error.param === 'password'); + expect(!!passwordParam).equals(true); + }); + + it('Should `status` be boolean', async () => { + const res = await request() + .post('/api/users') + .set('x-access-token', loginRes.body.token) + .send({ + status: 'not_boolean', + }); + + expect(res.status).equals(422); + + const statusParam = res.body.errors.find((error) => error.param === 'status'); + expect(!!statusParam).equals(true); + }); + + it('Should response bad request in case email was already exist.', async () => { + const user = await create('user'); + const res = await request() + .post('/api/users') + .set('x-access-token', loginRes.body.token) + .send({ + first_name: user.firstName, + last_name: user.lastName, + email: user.email, + phone_number: user.phoneNumber, + password: '123123123', + confirm_password: '123123123', + status: 1, + }); + + expect(res.status).equals(400); + expect(res.body.errors).include.something.that.deep.equals({ + type: 'EMAIL_ALREADY_EXIST', code: 100, + }); + }); + + it('Should response bad request in case phone number was already exist.', async () => { + const user = await create('user', { phone_number: '0927918381' }); + + const res = await request() + .post('/api/users') + .set('x-access-token', loginRes.body.token) + .send({ + first_name: user.firstName, + last_name: user.lastName, + email: user.email, + phone_number: '0927918381', + password: user.password, + confirm_password: user.password, + status: 1, + }); + + expect(res.status).equals(400); + expect(res.body.errors).include.something.that.deep.equals({ + type: 'PHONE_NUMBER_ALREADY_EXIST', code: 120, + }); + }); + + it('Should response success with correct data type.', async () => { + const user = await make('user', { phone_number: '0920000000' }); + const res = await request() + .post('/api/users') + .set('x-access-token', loginRes.body.token) + .send({ + first_name: user.firstName, + last_name: user.lastName, + email: user.email, + phone_number: '0920000000', + password: user.password, + confirm_password: user.password, + status: 1, + }); + + expect(res.status).equals(200); + expect(res.body.user.id).equals(2); + }); + }); + + describe('POST: `/users/:id`', () => { + it('Should create a new user if the user was not authorized.', async () => { + const user = await create('user'); + const res = await request().post(`/api/users/${user.id}`); + + expect(res.status).equals(401); + expect(res.body.message).equals('unauthorized'); + }); + + it('Should `first_name` be required.', async () => { + const user = await create('user'); + const res = await request() + .post(`/api/users/${user.id}`) + .set('x-access-token', loginRes.body.token) + .send(); + + expect(res.status).equals(422); + + const foundFirstNameParam = res.body.errors.find((error) => error.param === 'first_name'); + expect(!!foundFirstNameParam).equals(true); + }); + + it('Should `last_name` be required.', async () => { + const user = await create('user'); + const res = await request() + .post(`/api/users/${user.id}`) + .set('x-access-token', loginRes.body.token) + .send(); + + expect(res.status).equals(422); + + const foundFirstNameParam = res.body.errors.find((error) => error.param === 'last_name'); + expect(!!foundFirstNameParam).equals(true); + }); + + it('Should `email` be required.', async () => { + const user = await create('user'); + const res = await request() + .post(`/api/users/${user.id}`) + .set('x-access-token', loginRes.body.token) + .send(); + + expect(res.status).equals(422); + + const foundEmailParam = res.body.errors.find((error) => error.param === 'email'); + expect(!!foundEmailParam).equals(true); + }); + + it('Should be `email` be valid format.', async () => { + const user = await create('user'); + const res = await request() + .post(`/api/users/${user.id}`) + .set('x-access-token', loginRes.body.token) + .send({ + first_name: user.first_name, + last_name: user.last_name, + email: 'email', + phone_number: user.phone_number, + status: 1, + }); + + expect(res.status).equals(422); + + const foundEmailParam = res.body.errors.find((error) => error.param === 'email'); + expect(!!foundEmailParam).equals(true); + }); + + it('Should `phone_number` be valid format.', async () => { + const user = create('user'); + const res = await request() + .post(`/api/users/${user.id}`) + .set('x-access-token', loginRes.body.token) + .send({ + first_name: user.first_name, + last_name: user.last_name, + email: user.email, + phone_number: 'phone_number', + status: 1, + }); + + expect(res.status).equals(422); + + const phoneNumberParam = res.body.errors.find((error) => error.param === 'phone_number'); + expect(!!phoneNumberParam).equals(true); + }); + + it('Should `password` be required.', async () => { + const user = create('user'); + const res = await request() + .post(`/api/users/${user.id}`) + .set('x-access-token', loginRes.body.token) + .send(); + + expect(res.status).equals(422); + + const passwordParam = res.body.errors.find((error) => error.param === 'password'); + expect(!!passwordParam).equals(true); + }); + + it('Should password be equals confirm_password.', async () => { + const user = create('user'); + const res = await request() + .post(`/api/users/${user.id}`) + .set('x-access-token', loginRes.body.token) + .send({ + password: '123123', + }); + + expect(res.status).equals(422); + + const passwordParam = res.body.errors.find((error) => error.param === 'password'); + expect(!!passwordParam).equals(true); + }); + + it('Should `status` be boolean', async () => { + const user = create('user'); + const res = await request() + .post(`/api/users/${user.id}`) + .set('x-access-token', loginRes.body.token) + .send({ + status: 'not_boolean', + }); + + expect(res.status).equals(422); + + const statusParam = res.body.errors.find((error) => error.param === 'status'); + expect(!!statusParam).equals(true); + }); + }); + + describe('GET: `/users/:id`', () => { + it('Should not success if the user was not authorized.', () => { + + }); + + it('Should response not found if the user was not exist.', async () => { + const res = await request() + .get('/api/users/10') + .set('x-access-token', loginRes.body.token) + .send(); + + expect(res.status).equals(404); + }); + + it('Should response success if the user was exist.', async () => { + const user = await create('user'); + const res = await request() + .get(`/api/users/${user.id}`) + .set('x-access-token', loginRes.body.token) + .send(); + + expect(res.status).equals(200); + }); + }); + + describe('DELETE: `/users/:id`', () => { + it('Should not success if the user was not authorized.', () => { + + }); + + it('Should response not found if the user was not exist.', async () => { + const res = await request() + .delete('/api/users/10') + .set('x-access-token', loginRes.body.token) + .send(); + + expect(res.status).equals(404); + expect(res.body.errors).include.something.that.deep.equals({ + type: 'USER_NOT_FOUND', code: 100, + }); + }); + + it('Should response success if the user was exist.', async () => { + const user = await create('user'); + const res = await request() + .delete(`/api/users/${user.id}`) + .set('x-access-token', loginRes.body.token) + .send(); + + expect(res.status).equals(200); + }); + + it('Should delete the give user from the storage.', async () => { + const user = await create('user'); + await request() + .delete(`/api/users/${user.id}`) + .set('x-access-token', loginRes.body.token) + .send(); + + const storedUsers = await knex('users').where('id', user.id); + expect(storedUsers).to.have.lengthOf(0); + }); + }); +}); diff --git a/server/tests/services/JournalPoster.test.js b/common/services/JournalPoster.test.js similarity index 100% rename from server/tests/services/JournalPoster.test.js rename to common/services/JournalPoster.test.js diff --git a/server/config/systemKnexfile.js b/server/config/systemKnexfile.js index 373cce76d..9fef0a0f0 100644 --- a/server/config/systemKnexfile.js +++ b/server/config/systemKnexfile.js @@ -12,6 +12,7 @@ const configEnv = { migrations: { directory: config.system.migrations_dir, }, + pool: { min: 0, max: 7 }, }; module.exports = { diff --git a/server/src/database/factories/index.js b/server/src/database/factories/index.js index 8f9e73296..e7ceb8525 100644 --- a/server/src/database/factories/index.js +++ b/server/src/database/factories/index.js @@ -1,282 +1,284 @@ -import knexFactory from 'knex-factory'; +import KnexFactory from '@/lib/KnexFactory'; import faker from 'faker'; -import knex from '@/database/knex'; import { hashPassword } from '@/utils'; -const factory = knexFactory(knex); -factory.define('user', 'users', async () => { - const hashedPassword = await hashPassword('admin'); - return { - first_name: faker.name.firstName(), - last_name: faker.name.lastName(), - email: faker.internet.email(), - phone_number: faker.phone.phoneNumberFormat().replace('-', ''), - active: 1, - password: hashedPassword, - }; -}); +export default (tenantDb) => { + const factory = new KnexFactory(tenantDb); -factory.define('password_reset', 'password_resets', async () => { - const user = await faker.create('user'); - return { - user_id: user.id, - token: faker.lorem.slug, - }; -}); + factory.define('user', 'users', async () => { + // const hashedPassword = await hashPassword('admin'); -factory.define('account_type', 'account_types', async () => ({ - name: faker.lorem.words(2), - normal: 'debit', -})); + return { + first_name: faker.name.firstName(), + last_name: faker.name.lastName(), + email: faker.internet.email(), + phone_number: faker.phone.phoneNumberFormat().replace('-', ''), + active: 1, + // password: hashedPassword, + }; + }); -factory.define('account_balance', 'account_balances', async () => { - const account = await factory.create('account'); + factory.define('password_reset', 'password_resets', async () => { + return { + user_id: null, + token: faker.lorem.slug, + }; + }); - return { - account_id: account.id, - amount: faker.random.number(), - currency_code: 'USD', - }; -}); + factory.define('account_type', 'account_types', async () => ({ + name: faker.lorem.words(2), + normal: 'debit', + })); -factory.define('account', 'accounts', async () => { - const accountType = await factory.create('account_type'); - return { + factory.define('account_balance', 'account_balances', async () => { + const account = await factory.create('account'); + + return { + account_id: account.id, + amount: faker.random.number(), + currency_code: 'USD', + }; + }); + + factory.define('account', 'accounts', async () => { + const accountType = await factory.create('account_type'); + return { + name: faker.lorem.word(), + code: faker.random.number(), + account_type_id: accountType.id, + description: faker.lorem.paragraph(), + }; + }); + + factory.define('account_transaction', 'accounts_transactions', async () => { + const account = await factory.create('account'); + const user = await factory.create('user'); + + return { + account_id: account.id, + credit: faker.random.number(), + debit: 0, + user_id: user.id, + }; + }); + + factory.define('manual_journal', 'manual_journals', async () => { + const user = await factory.create('user'); + + return { + journal_number: faker.random.number(), + transaction_type: '', + amount: faker.random.number(), + date: faker.date.future, + status: 1, + user_id: user.id, + }; + }); + + factory.define('item_category', 'items_categories', () => ({ + name: faker.name.firstName(), + description: faker.lorem.text(), + parent_category_id: null, + })); + + factory.define('item_metadata', 'items_metadata', async () => { + const item = await factory.create('item'); + + return { + key: faker.lorem.slug(), + value: faker.lorem.word(), + item_id: item.id, + }; + }); + + factory.define('item', 'items', async () => { + const category = await factory.create('item_category'); + const costAccount = await factory.create('account'); + const sellAccount = await factory.create('account'); + const inventoryAccount = await factory.create('account'); + return { + name: faker.lorem.word(), + note: faker.lorem.paragraph(), + cost_price: faker.random.number(), + sell_price: faker.random.number(), + cost_account_id: costAccount.id, + sell_account_id: sellAccount.id, + inventory_account_id: inventoryAccount.id, + category_id: category.id, + }; + }); + + factory.define('setting', 'settings', async () => { + const user = await factory.create('user'); + return { + key: faker.lorem.slug(), + user_id: user.id, + type: 'string', + value: faker.lorem.words(), + group: 'default', + }; + }); + + factory.define('role', 'roles', async () => ({ name: faker.lorem.word(), - code: faker.random.number(), - account_type_id: accountType.id, - description: faker.lorem.paragraph(), - }; -}); - -factory.define('account_transaction', 'accounts_transactions', async () => { - const account = await factory.create('account'); - const user = await factory.create('user'); - - return { - account_id: account.id, - credit: faker.random.number(), - debit: 0, - user_id: user.id, - }; -}); - -factory.define('manual_journal', 'manual_journals', async () => { - const user = await factory.create('user'); - - return { - journal_number: faker.random.number(), - transaction_type: '', - amount: faker.random.number(), - date: faker.date.future, - status: 1, - user_id: user.id, - }; -}); - -factory.define('item_category', 'items_categories', () => ({ - name: faker.name.firstName(), - description: faker.lorem.text(), - parent_category_id: null, -})); - -factory.define('item_metadata', 'items_metadata', async () => { - const item = await factory.create('item'); - - return { - key: faker.lorem.slug(), - value: faker.lorem.word(), - item_id: item.id, - }; -}); - -factory.define('item', 'items', async () => { - const category = await factory.create('item_category'); - const costAccount = await factory.create('account'); - const sellAccount = await factory.create('account'); - const inventoryAccount = await factory.create('account'); - return { - name: faker.lorem.word(), - note: faker.lorem.paragraph(), - cost_price: faker.random.number(), - sell_price: faker.random.number(), - cost_account_id: costAccount.id, - sell_account_id: sellAccount.id, - inventory_account_id: inventoryAccount.id, - category_id: category.id, - }; -}); - -factory.define('setting', 'settings', async () => { - const user = await factory.create('user'); - return { - key: faker.lorem.slug(), - user_id: user.id, - type: 'string', - value: faker.lorem.words(), - group: 'default', - }; -}); - -factory.define('role', 'roles', async () => ({ - name: faker.lorem.word(), - description: faker.lorem.words(), - predefined: false, -})); - -factory.define('user_has_role', 'user_has_roles', async () => { - const user = await factory.create('user'); - const role = await factory.create('role'); - - return { - user_id: user.id, - role_id: role.id, - }; -}); - -factory.define('permission', 'permissions', async () => { - const permissions = ['create', 'edit', 'delete', 'view', 'owner']; - const randomPermission = permissions[Math.floor(Math.random() * permissions.length)]; - - return { - name: randomPermission, - }; -}); - -factory.define('role_has_permission', 'role_has_permissions', async () => { - const permission = await factory.create('permission'); - const role = await factory.create('role'); - const resource = await factory.create('resource'); - - return { - role_id: role.id, - permission_id: permission.id, - resource_id: resource.id, - }; -}); - -factory.define('resource', 'resources', () => ({ - name: faker.lorem.word(), -})); - -factory.define('view', 'views', async () => { - const resource = await factory.create('resource'); - return { - name: faker.lorem.word(), - resource_id: resource.id, + description: faker.lorem.words(), predefined: false, - }; -}); + })); -factory.define('resource_field', 'resource_fields', async () => { - const resource = await factory.create('resource'); - const dataTypes = ['select', 'date', 'text']; + factory.define('user_has_role', 'user_has_roles', async () => { + const user = await factory.create('user'); + const role = await factory.create('role'); - return { - label_name: faker.lorem.words(), - key: faker.lorem.slug(), - data_type: dataTypes[Math.floor(Math.random() * dataTypes.length)], - help_text: faker.lorem.words(), - default: faker.lorem.word(), - resource_id: resource.id, - active: true, - columnable: true, - predefined: false, - }; -}); + return { + user_id: user.id, + role_id: role.id, + }; + }); -factory.define('resource_custom_field_metadata', 'resource_custom_fields_metadata', async () => { - const resource = await factory.create('resource'); + factory.define('permission', 'permissions', async () => { + const permissions = ['create', 'edit', 'delete', 'view', 'owner']; + const randomPermission = permissions[Math.floor(Math.random() * permissions.length)]; - return { - resource_id: resource.id, - resource_item_id: 1, - key: faker.lorem.words(), - value: faker.lorem.words(), - }; -}); + return { + name: randomPermission, + }; + }); -factory.define('view_role', 'view_roles', async () => { - const view = await factory.create('view'); - const field = await factory.create('resource_field'); + factory.define('role_has_permission', 'role_has_permissions', async () => { + const permission = await factory.create('permission'); + const role = await factory.create('role'); + const resource = await factory.create('resource'); - return { - view_id: view.id, - index: faker.random.number(), - field_id: field.id, - value: '', - comparator: '', - }; -}); + return { + role_id: role.id, + permission_id: permission.id, + resource_id: resource.id, + }; + }); -factory.define('view_column', 'view_has_columns', async () => { - const view = await factory.create('view'); - const field = await factory.create('resource_field'); + factory.define('resource', 'resources', () => ({ + name: faker.lorem.word(), + })); - return { - field_id: field.id, - view_id: view.id, - // index: 1, - }; -}); + factory.define('view', 'views', async () => { + const resource = await factory.create('resource'); + return { + name: faker.lorem.word(), + resource_id: resource.id, + predefined: false, + }; + }); -factory.define('expense', 'expenses', async () => { - const paymentAccount = await factory.create('account'); - const expenseAccount = await factory.create('account'); - const user = await factory.create('user'); + factory.define('resource_field', 'resource_fields', async () => { + const resource = await factory.create('resource'); + const dataTypes = ['select', 'date', 'text']; - return { - payment_account_id: paymentAccount.id, - expense_account_id: expenseAccount.id, - user_id: user.id, - amount: faker.random.number(), - currency_code: 'USD', - }; -}); + return { + label_name: faker.lorem.words(), + key: faker.lorem.slug(), + data_type: dataTypes[Math.floor(Math.random() * dataTypes.length)], + help_text: faker.lorem.words(), + default: faker.lorem.word(), + resource_id: resource.id, + active: true, + columnable: true, + predefined: false, + }; + }); -factory.define('option', 'options', async () => { - return { - key: faker.lorem.slug(), - value: faker.lorem.slug(), - group: faker.lorem.slug(), - }; -}); + factory.define('resource_custom_field_metadata', 'resource_custom_fields_metadata', async () => { + const resource = await factory.create('resource'); -factory.define('currency', 'currencies', async () => { - return { - currency_name: faker.lorem.slug(), - currency_code: 'USD', - }; -}); + return { + resource_id: resource.id, + resource_item_id: 1, + key: faker.lorem.words(), + value: faker.lorem.words(), + }; + }); -factory.define('exchange_rate', 'exchange_rates', async () => { - return { - date: '2020-02-02', - currency_code: 'USD', - exchange_rate: faker.random.number(), - }; -}); + factory.define('view_role', 'view_roles', async () => { + const view = await factory.create('view'); + const field = await factory.create('resource_field'); -factory.define('budget', 'budgets', async () => { - return { - name: faker.lorem.slug(), - fiscal_year: '2020', - period: 'month', - account_types: 'profit_loss', - }; -}); + return { + view_id: view.id, + index: faker.random.number(), + field_id: field.id, + value: '', + comparator: '', + }; + }); -factory.define('budget_entry', 'budget_entries', async () => { - const budget = await factory.create('budget'); - const account = await factory.create('account'); + factory.define('view_column', 'view_has_columns', async () => { + const view = await factory.create('view'); + const field = await factory.create('resource_field'); - return { - account_id: account.id, - budget_id: budget.id, - amount: 1000, - order: 1, - }; -}); + return { + field_id: field.id, + view_id: view.id, + // index: 1, + }; + }); -export default factory; + factory.define('expense', 'expenses', async () => { + const paymentAccount = await factory.create('account'); + const expenseAccount = await factory.create('account'); + const user = await factory.create('user'); + + return { + payment_account_id: paymentAccount.id, + expense_account_id: expenseAccount.id, + user_id: user.id, + amount: faker.random.number(), + currency_code: 'USD', + }; + }); + + factory.define('option', 'options', async () => { + return { + key: faker.lorem.slug(), + value: faker.lorem.slug(), + group: faker.lorem.slug(), + }; + }); + + factory.define('currency', 'currencies', async () => { + return { + currency_name: faker.lorem.slug(), + currency_code: 'USD', + }; + }); + + factory.define('exchange_rate', 'exchange_rates', async () => { + return { + date: '2020-02-02', + currency_code: 'USD', + exchange_rate: faker.random.number(), + }; + }); + + factory.define('budget', 'budgets', async () => { + return { + name: faker.lorem.slug(), + fiscal_year: '2020', + period: 'month', + account_types: 'profit_loss', + }; + }); + + factory.define('budget_entry', 'budget_entries', async () => { + const budget = await factory.create('budget'); + const account = await factory.create('account'); + + return { + account_id: account.id, + budget_id: budget.id, + amount: 1000, + order: 1, + }; + }); + + return factory; +} diff --git a/server/src/database/factories/system.js b/server/src/database/factories/system.js new file mode 100644 index 000000000..ccde0f943 --- /dev/null +++ b/server/src/database/factories/system.js @@ -0,0 +1,16 @@ +import KnexFactory from '@/lib/KnexFactory'; +import systemDb from '@/database/knex'; +import faker from 'faker'; + +export default () => { + const factory = new KnexFactory(systemDb); + + factory.define('password_reset', 'password_resets', async () => { + return { + email: faker.lorem.email, + token: faker.lorem.slug, + }; + }); + + return factory; +}; \ No newline at end of file diff --git a/server/src/database/knex.js b/server/src/database/knex.js index 7ef83dbab..4ee9662e6 100644 --- a/server/src/database/knex.js +++ b/server/src/database/knex.js @@ -3,6 +3,7 @@ import { knexSnakeCaseMappers } from 'objection'; import knexfile from '@/../config/systemKnexfile'; const config = knexfile[process.env.NODE_ENV]; + const knex = Knex({ ...config, ...knexSnakeCaseMappers({ upperCase: true }), diff --git a/server/src/database/manager.js b/server/src/database/manager.js index 5b173aaf4..675954d19 100644 --- a/server/src/database/manager.js +++ b/server/src/database/manager.js @@ -4,10 +4,6 @@ import config from '@/../config/config'; const knexConfig = knexfile[process.env.NODE_ENV]; -console.log({ - superUser: config.manager.superUser, - superPassword: config.manager.superPassword, -}); const dbManager = knexManager.databaseManagerFactory({ knex: knexConfig, dbManager: { diff --git a/server/src/database/migrations/20190423085247_create_roles_table.js b/server/src/database/migrations/20190423085247_create_roles_table.js index fe19f9eb8..4f1c6c435 100644 --- a/server/src/database/migrations/20190423085247_create_roles_table.js +++ b/server/src/database/migrations/20190423085247_create_roles_table.js @@ -5,6 +5,8 @@ exports.up = (knex) => knex.schema.createTable('roles', (table) => { table.string('description'); table.boolean('predefined').default(false); table.timestamps(); -}); +}).raw('ALTER TABLE `ROLES` AUTO_INCREMENT = 1000'); + + exports.down = (knex) => knex.schema.dropTable('roles'); diff --git a/server/src/database/migrations/20190822214242_create_users_table.js b/server/src/database/migrations/20190822214242_create_users_table.js index 5b62e461c..fb9ee35f6 100644 --- a/server/src/database/migrations/20190822214242_create_users_table.js +++ b/server/src/database/migrations/20190822214242_create_users_table.js @@ -13,7 +13,7 @@ exports.up = function (knex) { table.date('invite_accepted_at'); table.timestamps(); - }); + }).raw('ALTER TABLE `USERS` AUTO_INCREMENT = 1000');; }; exports.down = function (knex) { diff --git a/server/src/database/migrations/20190822214247_create_oauth_tokens_table.js b/server/src/database/migrations/20190822214247_create_oauth_tokens_table.js deleted file mode 100644 index 0b7ded2ef..000000000 --- a/server/src/database/migrations/20190822214247_create_oauth_tokens_table.js +++ /dev/null @@ -1,16 +0,0 @@ - -exports.up = function(knex) { - return knex.schema.createTable('oauth_tokens', (table) => { - table.increments(); - table.string('access_token'); - table.date('access_token_expires_on'); - table.integer('client_id').unsigned(); - table.string('refresh_token'); - table.date('refresh_token_expires_on'); - table.integer('user_id').unsigned(); - }); -}; - -exports.down = function(knex) { - return knex.schema.dropTableIfExists('oauth_tokens'); -}; diff --git a/server/src/database/migrations/20190822214301_create_oauth_clients_table.js b/server/src/database/migrations/20190822214301_create_oauth_clients_table.js deleted file mode 100644 index af117714c..000000000 --- a/server/src/database/migrations/20190822214301_create_oauth_clients_table.js +++ /dev/null @@ -1,11 +0,0 @@ - -exports.up = function(knex) { - return knex.schema.createTable('oauth_clients', (table) => { - table.increments(); - table.integer('client_id').unsigned(); - table.string('client_secret'); - table.string('redirect_uri'); - }); -}; - -exports.down = (knex) => knex.schema.dropTableIfExists('oauth_clients'); diff --git a/server/src/database/migrations/20190822214302_create_settings_table.js b/server/src/database/migrations/20190822214302_create_settings_table.js index bd7c0b43a..b6aba4bef 100644 --- a/server/src/database/migrations/20190822214302_create_settings_table.js +++ b/server/src/database/migrations/20190822214302_create_settings_table.js @@ -7,7 +7,7 @@ exports.up = function (knex) { table.string('type'); table.string('key'); table.string('value'); - }); + }).raw('ALTER TABLE `SETTINGS` AUTO_INCREMENT = 2000'); }; exports.down = (knex) => knex.schema.dropTableIfExists('settings'); diff --git a/server/src/database/migrations/20190822214303_create_items_table.js b/server/src/database/migrations/20190822214303_create_items_table.js index c01186c82..cbd014c97 100644 --- a/server/src/database/migrations/20190822214303_create_items_table.js +++ b/server/src/database/migrations/20190822214303_create_items_table.js @@ -4,6 +4,7 @@ exports.up = function (knex) { table.increments(); table.string('name'); table.string('type'); + table.string('sku'); table.decimal('cost_price').unsigned(); table.decimal('sell_price').unsigned(); table.string('currency_code', 3); @@ -15,7 +16,7 @@ exports.up = function (knex) { table.integer('category_id').unsigned(); table.integer('user_id').unsigned(); table.timestamps(); - }); + }).raw('ALTER TABLE `ITEMS` AUTO_INCREMENT = 1000');; }; exports.down = (knex) => knex.schema.dropTableIfExists('items'); diff --git a/server/src/database/migrations/20190822214304_create_accounts_table.js b/server/src/database/migrations/20190822214304_create_accounts_table.js index 3878bb1d5..47a188c27 100644 --- a/server/src/database/migrations/20190822214304_create_accounts_table.js +++ b/server/src/database/migrations/20190822214304_create_accounts_table.js @@ -1,7 +1,7 @@ exports.up = function (knex) { return knex.schema.createTable('accounts', (table) => { - table.increments(); + table.bigIncrements('id').comment('Auto-generated id');; table.string('name'); table.integer('account_type_id'); table.integer('parent_account_id'); @@ -11,7 +11,7 @@ exports.up = function (knex) { table.integer('index').unsigned(); table.boolean('predefined').defaultTo(false); table.timestamps(); - }).then(() => { + }).raw('ALTER TABLE `ACCOUNTS` AUTO_INCREMENT = 1000').then(() => { return knex.seed.run({ specific: 'seed_accounts.js', }); diff --git a/server/src/database/migrations/20190822214316_create_item_metadata_table.js b/server/src/database/migrations/20190822214316_create_item_metadata_table.js deleted file mode 100644 index 9f2b2dec0..000000000 --- a/server/src/database/migrations/20190822214316_create_item_metadata_table.js +++ /dev/null @@ -1,11 +0,0 @@ - -exports.up = function (knex) { - return knex.schema.createTable('items_metadata', (table) => { - table.increments(); - table.string('key'); - table.string('value'); - table.integer('item_id').unsigned(); - }); -}; - -exports.down = (knex) => knex.schema.dropTableIfExists('items_metadata'); diff --git a/server/src/database/migrations/20190822214904_create_account_types_table.js b/server/src/database/migrations/20190822214904_create_account_types_table.js index 378045e2d..d5a4ae25e 100644 --- a/server/src/database/migrations/20190822214904_create_account_types_table.js +++ b/server/src/database/migrations/20190822214904_create_account_types_table.js @@ -7,7 +7,7 @@ exports.up = (knex) => { table.string('root_type'); table.boolean('balance_sheet'); table.boolean('income_sheet'); - }).then(() => { + }).raw('ALTER TABLE `ACCOUNT_TYPES` AUTO_INCREMENT = 1000').then(() => { return knex.seed.run({ specific: 'seed_account_types.js', }); diff --git a/server/src/database/migrations/20190822214905_create_resource_fields_table.js b/server/src/database/migrations/20190822214905_create_resource_fields_table.js index 82b3137d5..eaed02ad8 100644 --- a/server/src/database/migrations/20190822214905_create_resource_fields_table.js +++ b/server/src/database/migrations/20190822214905_create_resource_fields_table.js @@ -14,7 +14,7 @@ exports.up = function (knex) { table.integer('index'); table.json('options'); table.integer('resource_id').unsigned(); - }).then(() => { + }).raw('ALTER TABLE `RESOURCE_FIELDS` AUTO_INCREMENT = 1000').then(() => { return knex.seed.run({ specific: 'seed_resources_fields.js', }); diff --git a/server/src/database/migrations/20190822214905_create_views_columns.js b/server/src/database/migrations/20190822214905_create_views_columns.js index 38f1851a2..500fbc27d 100644 --- a/server/src/database/migrations/20190822214905_create_views_columns.js +++ b/server/src/database/migrations/20190822214905_create_views_columns.js @@ -5,6 +5,8 @@ exports.up = function (knex) { table.integer('view_id').unsigned(); table.integer('field_id').unsigned(); table.integer('index').unsigned(); + }).raw('ALTER TABLE `ITEMS_CATEGORIES` AUTO_INCREMENT = 1000').then(() => { + }); }; diff --git a/server/src/database/migrations/20190822214905_create_views_roles_table.js b/server/src/database/migrations/20190822214905_create_views_roles_table.js index 740f14997..59f9ccf1f 100644 --- a/server/src/database/migrations/20190822214905_create_views_roles_table.js +++ b/server/src/database/migrations/20190822214905_create_views_roles_table.js @@ -7,7 +7,7 @@ exports.up = function (knex) { table.string('comparator'); table.string('value'); table.integer('view_id').unsigned(); - }).then(() => { + }).raw('ALTER TABLE `VIEW_ROLES` AUTO_INCREMENT = 1000').then(() => { return knex.seed.run({ specific: 'seed_views_roles.js', }); diff --git a/server/src/database/migrations/20190822214905_create_views_table.js b/server/src/database/migrations/20190822214905_create_views_table.js index 2c91ddf04..5c524ac72 100644 --- a/server/src/database/migrations/20190822214905_create_views_table.js +++ b/server/src/database/migrations/20190822214905_create_views_table.js @@ -7,7 +7,7 @@ exports.up = function (knex) { table.integer('resource_id').unsigned().references('id').inTable('resources'); table.boolean('favourite'); table.string('roles_logic_expression'); - }).then(() => { + }).raw('ALTER TABLE `VIEWS` AUTO_INCREMENT = 1000').then(() => { return knex.seed.run({ specific: 'seed_views.js', }); diff --git a/server/src/database/migrations/20200104232647_create_accounts_transactions_table.js b/server/src/database/migrations/20200104232647_create_accounts_transactions_table.js index 2ca5339e3..c85eb42bb 100644 --- a/server/src/database/migrations/20200104232647_create_accounts_transactions_table.js +++ b/server/src/database/migrations/20200104232647_create_accounts_transactions_table.js @@ -13,7 +13,7 @@ exports.up = function(knex) { table.integer('user_id').unsigned(); table.date('date'); table.timestamps(); - }); + }).raw('ALTER TABLE `ACCOUNTS_TRANSACTIONS` AUTO_INCREMENT = 1000'); }; exports.down = function(knex) { diff --git a/server/src/database/migrations/20200105195823_create_manual_journals_table.js b/server/src/database/migrations/20200105195823_create_manual_journals_table.js index 9fef2ee22..72e619fad 100644 --- a/server/src/database/migrations/20200105195823_create_manual_journals_table.js +++ b/server/src/database/migrations/20200105195823_create_manual_journals_table.js @@ -12,7 +12,7 @@ exports.up = function(knex) { table.string('attachment_file'); table.integer('user_id').unsigned(); table.timestamps(); - }); + }).raw('ALTER TABLE `MANUAL_JOURNALS` AUTO_INCREMENT = 1000'); }; exports.down = function(knex) { diff --git a/server/src/database/migrations/20200419171451_create_currencies_table.js b/server/src/database/migrations/20200419171451_create_currencies_table.js index 43aa21da2..8c18ac5bc 100644 --- a/server/src/database/migrations/20200419171451_create_currencies_table.js +++ b/server/src/database/migrations/20200419171451_create_currencies_table.js @@ -4,7 +4,7 @@ exports.up = function(knex) { table.increments(); table.string('currency_name'); table.string('currency_code', 4); - }) + }).raw('ALTER TABLE `CURRENCIES` AUTO_INCREMENT = 1000'); }; exports.down = function(knex) { diff --git a/server/src/database/migrations/20200419191832_create_exchange_rates_table.js b/server/src/database/migrations/20200419191832_create_exchange_rates_table.js index fdf4509d0..50225d970 100644 --- a/server/src/database/migrations/20200419191832_create_exchange_rates_table.js +++ b/server/src/database/migrations/20200419191832_create_exchange_rates_table.js @@ -5,7 +5,7 @@ exports.up = function(knex) { table.string('currency_code', 4); table.decimal('exchange_rate'); table.date('date'); - }); + }).raw('ALTER TABLE `EXCHANGE_RATES` AUTO_INCREMENT = 1000'); }; exports.down = function(knex) { diff --git a/server/src/http/controllers/Accounts.js b/server/src/http/controllers/Accounts.js index 069850920..8f11a0101 100644 --- a/server/src/http/controllers/Accounts.js +++ b/server/src/http/controllers/Accounts.js @@ -68,6 +68,10 @@ export default { this.transferToAnotherAccount.validation, asyncMiddleware(this.transferToAnotherAccount.handler)); + router.post('/bulk/:type(activate|inactivate)', + this.bulkInactivateAccounts.validation, + asyncMiddleware(this.bulkInactivateAccounts.handler)); + return router; }, @@ -541,4 +545,47 @@ export default { return res.status(200).send(); }, }, + + /** + * Bulk acvtivate/inactivate the given accounts. + */ + bulkInactivateAccounts: { + validation: [ + query('ids').isArray({ min: 2 }), + query('ids.*').isNumeric().toInt(), + param('type').exists().isIn(['activate', 'inactivate']), + ], + async handler(req, res) { + const validationErrors = validationResult(req); + + if (!validationErrors.isEmpty()) { + return res.boom.badData(null, { + code: 'validation_error', ...validationErrors, + }); + } + const filter = { + ids: [], + ...req.query, + }; + const { Account } = req.models; + const { type } = req.params; + + const storedAccounts = await Account.query().whereIn('id', filter.ids); + const storedAccountsIds = storedAccounts.map((account) => account.id); + const notFoundAccounts = difference(filter.ids, storedAccountsIds); + + if (notFoundAccounts.length > 0) { + return res.status(400).send({ + errors: [{ type: 'ACCOUNTS.NOT.FOUND', code: 200 }], + }); + } + const updatedAccounts = await Account.query() + .whereIn('id', storedAccountsIds) + .patch({ + active: type === 'activate' ? 1 : 0, + }); + + return res.status(200).send({ ids: storedAccountsIds }); + } + } }; diff --git a/server/src/http/controllers/Authentication.js b/server/src/http/controllers/Authentication.js index 53590c69a..1cb22316b 100644 --- a/server/src/http/controllers/Authentication.js +++ b/server/src/http/controllers/Authentication.js @@ -7,6 +7,7 @@ import Mustache from 'mustache'; import jwt from 'jsonwebtoken'; import { pick } from 'lodash'; import uniqid from 'uniqid'; +import moment from 'moment'; import Logger from '@/services/Logger'; import asyncMiddleware from '@/http/middleware/asyncMiddleware'; import SystemUser from '@/system/models/SystemUser'; @@ -19,6 +20,7 @@ import TenantsManager from '@/system/TenantsManager'; import TenantModel from '@/models/TenantModel'; import PasswordReset from '@/system/models/PasswordReset'; + export default { /** * Constructor method. @@ -51,7 +53,7 @@ export default { login: { validation: [ check('crediential').exists().isEmail(), - check('password').exists().isLength({ min: 4 }), + check('password').exists().isLength({ min: 5 }), ], async handler(req, res) { const validationErrors = validationResult(req); @@ -87,7 +89,17 @@ export default { errors: [{ type: 'USER_INACTIVE', code: 110 }], }); } - // user.update({ last_login_at: new Date() }); + const lastLoginAt = moment().format('YYYY/MM/DD HH:mm:ss'); + + const updateTenantUser = TenantUser.tenant().query() + .where('id', user.id) + .update({ last_login_at: lastLoginAt }); + + const updateSystemUser = SystemUser.query() + .where('id', user.id) + .update({ last_login_at: lastLoginAt }); + + await Promise.all([updateTenantUser, updateSystemUser]); const token = jwt.sign( { email: user.email, _id: user.id }, diff --git a/server/src/http/controllers/ExchangeRates.js b/server/src/http/controllers/ExchangeRates.js index cac0e97d1..7d955da7c 100644 --- a/server/src/http/controllers/ExchangeRates.js +++ b/server/src/http/controllers/ExchangeRates.js @@ -6,6 +6,7 @@ import { validationResult, } from 'express-validator'; import moment from 'moment'; +import { difference } from 'lodash'; import asyncMiddleware from '@/http/middleware/asyncMiddleware'; export default { @@ -27,6 +28,10 @@ export default { this.editExchangeRate.validation, asyncMiddleware(this.editExchangeRate.handler)); + router.delete('/bulk', + this.bulkDeleteExchangeRates.validation, + asyncMiddleware(this.bulkDeleteExchangeRates.handler)); + router.delete('/:id', this.deleteExchangeRate.validation, asyncMiddleware(this.deleteExchangeRate.handler)); @@ -166,4 +171,39 @@ export default { return res.status(200).send({ id }); }, }, + + bulkDeleteExchangeRates: { + validation: [ + query('ids').isArray({ min: 2 }), + query('ids.*').isNumeric().toInt(), + ], + async handler(req, res) { + const validationErrors = validationResult(req); + + if (!validationErrors.isEmpty()) { + return res.boom.badData(null, { + code: 'validation_error', ...validationErrors, + }); + } + + const filter = { + ids: [], + ...req.query, + }; + const { ExchangeRate } = req.models; + + const exchangeRates = await ExchangeRate.query().whereIn('id', filter.ids); + const exchangeRatesIds = exchangeRates.map((category) => category.id); + const notFoundExRates = difference(filter.ids, exchangeRatesIds); + + if (notFoundExRates.length > 0) { + return res.status(400).send({ + errors: [{ type: 'EXCHANGE.RATES.IS.NOT.FOUND', code: 200, ids: notFoundExRates }], + }); + } + await ExchangeRate.query().whereIn('id', exchangeRatesIds).delete(); + + return res.status(200).send({ ids: exchangeRatesIds }); + }, + }, } \ No newline at end of file diff --git a/server/src/http/controllers/ItemCategories.js b/server/src/http/controllers/ItemCategories.js index b3da48ecc..77e7ed11d 100644 --- a/server/src/http/controllers/ItemCategories.js +++ b/server/src/http/controllers/ItemCategories.js @@ -5,6 +5,7 @@ import { validationResult, query, } from 'express-validator'; +import { difference } from 'lodash'; import asyncMiddleware from '../middleware/asyncMiddleware'; import { DynamicFilter, @@ -15,6 +16,7 @@ import { mapFilterRolesToDynamicFilter, } from '@/lib/ViewRolesBuilder'; + export default { /** * Router constructor method. @@ -31,6 +33,10 @@ export default { this.newCategory.validation, asyncMiddleware(this.newCategory.handler)); + router.delete('/bulk', + this.bulkDeleteCategories.validation, + asyncMiddleware(this.bulkDeleteCategories.handler)); + router.delete('/:id', this.deleteItem.validation, asyncMiddleware(this.deleteItem.handler)); @@ -43,6 +49,8 @@ export default { this.getList.validation, asyncMiddleware(this.getList.handler)); + + return router; }, @@ -145,7 +153,7 @@ export default { .where('id', id) .update({ ...form }); - return res.status(200).send({ id: updateItemCategory }); + return res.status(200).send({ id }); }, }, @@ -274,8 +282,46 @@ export default { errors: [{ type: 'CATEGORY_NOT_FOUND', code: 100 }], }); } - return res.status(200).send({ category: item.toJSON() }); }, }, + + + /** + * Bulk delete the given item categories. + */ + bulkDeleteCategories: { + validation: [ + query('ids').isArray({ min: 2 }), + query('ids.*').isNumeric().toInt(), + ], + async handler(req, res) { + const validationErrors = validationResult(req); + + if (!validationErrors.isEmpty()) { + return res.boom.badData(null, { + code: 'validation_error', ...validationErrors, + }); + } + const filter = { + ids: [], + ...req.query, + }; + const { ItemCategory } = req.models; + + const itemCategories = await ItemCategory.query().whereIn('id', filter.ids); + const itemCategoriesIds = itemCategories.map((category) => category.id); + const notFoundCategories = difference(filter.ids, itemCategoriesIds); + + if (notFoundCategories.length > 0) { + return res.status(400).send({ + errors: [{ type: 'ITEM.CATEGORIES.IDS.NOT.FOUND', code: 200 }], + }); + } + + await ItemCategory.query().whereIn('id', filter.ids).delete(); + + return res.status(200).send({ ids: filter.ids }); + }, + }, }; diff --git a/server/src/http/controllers/Items.js b/server/src/http/controllers/Items.js index 4650b694b..b8c6e1916 100644 --- a/server/src/http/controllers/Items.js +++ b/server/src/http/controllers/Items.js @@ -17,6 +17,7 @@ import Logger from '@/services/Logger'; const fsPromises = fs.promises; + export default { /** * Router constructor. @@ -80,6 +81,7 @@ export default { code: 'validation_error', ...validationErrors, }); } + const { user } = req; const form = { custom_fields: [], media_ids: [], @@ -129,8 +131,10 @@ export default { itemCategory, inventoryAccount, ] = await Promise.all([ - costAccountPromise, sellAccountPromise, - itemCategoryPromise, inventoryAccountPromise, + costAccountPromise, + sellAccountPromise, + itemCategoryPromise, + inventoryAccountPromise, ]); if (!costAccount) { errorReasons.push({ type: 'COST_ACCOUNT_NOT_FOUND', code: 100 }); @@ -152,11 +156,14 @@ export default { const item = await Item.query().insertAndFetch({ name: form.name, type: form.type, + sku: form.sku, cost_price: form.cost_price, sell_price: form.sell_price, sell_account_id: form.sell_account_id, cost_account_id: form.cost_account_id, currency_code: form.currency_code, + category_id: form.category_id, + user_id: user.id, note: form.note, }); @@ -239,7 +246,7 @@ export default { errorReasons.push({ type: 'ITEM_CATEGORY_NOT_FOUND', code: 140 }); } - const { attachment } = req.files; + const attachment = req.files && req.files.attachment ? req.files.attachment : null; const attachmentsMimes = ['image/png', 'image/jpeg']; // Validate the attachment. @@ -277,7 +284,6 @@ export default { cost_account_id: form.cost_account_id, category_id: form.category_id, note: form.note, - attachment_file: (attachment) ? item.attachmentFile : null, }); // Save links of new inserted media that associated to the item model. diff --git a/server/src/lib/KnexFactory/index.js b/server/src/lib/KnexFactory/index.js new file mode 100644 index 000000000..866af6eae --- /dev/null +++ b/server/src/lib/KnexFactory/index.js @@ -0,0 +1,55 @@ +const { extend, isFunction, isObject } = require('lodash'); + +export default class KnexFactory { + + constructor(knex) { + this.knex = knex; + + this.factories = []; + } + + define(name, tableName, defaultAttributes) { + this.factories[name] = { tableName, defaultAttributes }; + } + + async build(factoryName, attributes) { + const factory = this.factories[factoryName]; + + if (!factory) { + throw `Unkown factory: ${factoryName}`; + } + let { defaultAttributes } = factory; + const insertData = {}; + + if( 'function' === typeof defaultAttributes) { + defaultAttributes = await defaultAttributes(); + } + extend(insertData, defaultAttributes, attributes); + + for (let k in insertData) { + const v = insertData[k]; + + if (isFunction(v)) { + insertData[k] = await v(); + } else { + insertData[k] = await v; + } + if (isObject(insertData[k]) && insertData[k].id) { + insertData[k] = insertData[k].id; + } + }; + + return insertData; + } + + async create(factoryName, attributes) { + const factory = this.factories[factoryName]; + const insertData = await this.build(factoryName, attributes); + const { tableName } = factory; + + const [id] = await this.knex(tableName).insert(insertData); + const record = await this.knex(tableName).where({ id }).first(); + + return record; + } +} \ No newline at end of file diff --git a/server/src/models/Option.js b/server/src/models/Option.js index 0f9386906..d2e1a786f 100644 --- a/server/src/models/Option.js +++ b/server/src/models/Option.js @@ -18,13 +18,16 @@ export default class Option extends mixin(TenantModel, [mixin]) { static query(...args) { return super.query(...args).runAfter((result) => { if (result instanceof MetableCollection) { - result.setModel(Option); + result.setModel(this.tenant()); result.setExtraColumns(['group']); } return result; }); } + /** + * Model collection. + */ static get collection() { return MetableCollection; } diff --git a/server/src/system/TenantEnvironment.js b/server/src/system/TenantEnvironment.js new file mode 100644 index 000000000..5af74c39c --- /dev/null +++ b/server/src/system/TenantEnvironment.js @@ -0,0 +1,12 @@ + + +export default class TenantEnviroment { + + static get currentTenant() { + return this.currentTenantWebsite; + } + + static set currentTenant(website) { + this.currentTenantWebsite = website; + } +} \ No newline at end of file diff --git a/server/src/system/TenantsManager.js b/server/src/system/TenantsManager.js index 4f843228b..ffde9d466 100644 --- a/server/src/system/TenantsManager.js +++ b/server/src/system/TenantsManager.js @@ -2,6 +2,20 @@ import Knex from 'knex'; import { knexSnakeCaseMappers } from 'objection'; import Tenant from '@/system/models/Tenant'; import config from '@/../config/config'; +import TenantModel from '@/models/TenantModel'; +import uniqid from 'uniqid'; +import dbManager from '@/database/manager'; +import { omit } from 'lodash'; + +import SystemUser from '@/system/models/SystemUser'; +import TenantUser from '@/models/TenantUser'; +// import TenantModel from '@/models/TenantModel'; + +// const TenantWebsite: { +// tenantDb: Knex, +// tenantId: Number, +// tenantOrganizationId: String, +// } export default class TenantsManager { @@ -16,6 +30,65 @@ export default class TenantsManager { return tenant; } + /** + * Creates a new tenant database. + * @param {Integer} uniqId + * @return {TenantWebsite} + */ + static async createTenant(uniqId) { + const organizationId = uniqId || uniqid(); + const tenantOrganization = await Tenant.query().insert({ + organization_id: organizationId, + }); + + const tenantDbName = `bigcapital_tenant_${organizationId}`; + await dbManager.createDb(tenantDbName); + + const tenantDb = TenantsManager.knexInstance(organizationId); + await tenantDb.migrate.latest(); + + return { + tenantDb, + tenantId: tenantOrganization.id, + organizationId, + }; + } + + /** + * Drop tenant database of the given tenant website. + * @param {TenantWebsite} tenantWebsite + */ + static async dropTenant(tenantWebsite) { + const tenantDbName = `bigcapital_tenant_${tenantWebsite.organizationId}`; + await dbManager.dropDb(tenantDbName); + + await SystemUser.query() + .where('tenant_id', tenantWebsite.tenantId); + } + + /** + * Creates a user that associate to the given tenant. + */ + static async createTenantUser(tenantWebsite, user) { + const userInsert = { ...user }; + + const systemUser = await SystemUser.query().insert({ + ...user, + tenant_id: tenantWebsite.tenantId, + }); + TenantModel.knexBinded = tenantWebsite.tenantDb; + + const tenantUser = await TenantUser.bindKnex(tenantWebsite.tenantDb) + .query() + .insert({ + ...omit(userInsert, ['password']), + }); + return { + ...tenantUser, + ...systemUser + }; + } + /** * Retrieve all tenants metadata from system storage. */ @@ -43,6 +116,7 @@ export default class TenantsManager { seeds: { directory: config.tenant.seeds_dir, }, + pool: { min: 0, max: 5 }, }; } diff --git a/server/tests/dbInit.js b/server/tests/dbInit.js new file mode 100644 index 000000000..98456918b --- /dev/null +++ b/server/tests/dbInit.js @@ -0,0 +1,37 @@ +import { + request, + expect, + createTenantFactory, + createTenant, + bindTenantModel, + login, + systemFactory, + dropTenant, +} from '~/testInit'; + +let tenantWebsite; +let tenantFactory; +let loginRes; + +beforeEach(async () => { + tenantWebsite = await createTenant(); + tenantFactory = createTenantFactory(tenantWebsite.tenantDb); + + bindTenantModel(tenantWebsite.tenantDb); + loginRes = await login(tenantWebsite); +}); + +afterEach(async () => { + await dropTenant(tenantWebsite); + + loginRes = null; + tenantFactory = null; + tenantWebsite = null; +}); + +export { + tenantWebsite, + tenantFactory, + systemFactory, + loginRes, +}; \ No newline at end of file diff --git a/server/tests/docker-compose.yml b/server/tests/docker-compose.yml index 4e5a15d76..52c7a1c24 100644 --- a/server/tests/docker-compose.yml +++ b/server/tests/docker-compose.yml @@ -1,4 +1,4 @@ -on: '2' + services: mysql: image: mysql/mysql-server:5.7 @@ -9,3 +9,6 @@ services: - MYSQL_DATABASE=moosher_test - MYSQL_USER=moosher - MYSQL_PASSWORD=moosher + tmpfs: + - /var/lib/mysql/:rw,noexec,nosuid,size=600m + - /tmp/:rw,noexec,nosuid,size=50m diff --git a/server/tests/models/Account.test.js b/server/tests/models/Account.test.js index acadbd622..4faacc8e6 100644 --- a/server/tests/models/Account.test.js +++ b/server/tests/models/Account.test.js @@ -1,13 +1,20 @@ -import { create, expect } from '~/testInit'; +import { + expect, +} from '~/testInit'; import Account from '@/models/Account'; import AccountType from '@/models/AccountType'; +import { + tenantFactory, + tenantWebsite +} from '~/dbInit'; + describe('Model: Account', () => { it('Should account model belongs to the associated account type model.', async () => { - const accountType = await create('account_type'); - const account = await create('account', { account_type_id: accountType.id }); + const accountType = await tenantFactory.create('account_type'); + const account = await tenantFactory.create('account', { account_type_id: accountType.id }); - const accountModel = await Account.query() + const accountModel = await Account.tenant().query() .where('id', account.id) .withGraphFetched('type') .first(); @@ -16,9 +23,9 @@ describe('Model: Account', () => { }); it('Should account model has one balance model that associated to the account model.', async () => { - const accountBalance = await create('account_balance'); + const accountBalance = await tenantFactory.create('account_balance'); - const accountModel = await Account.query() + const accountModel = await Account.tenant().query() .where('id', accountBalance.accountId) .withGraphFetched('balance') .first(); @@ -27,10 +34,10 @@ describe('Model: Account', () => { }); it('Should account model has many transactions models that associated to the account model.', async () => { - const account = await create('account'); - const accountTransaction = await create('account_transaction', { account_id: account.id }); + const account = await tenantFactory.create('account'); + const accountTransaction = await tenantFactory.create('account_transaction', { account_id: account.id }); - const accountModel = await Account.query().where('id', account.id).first(); + const accountModel = await Account.tenant().query().where('id', account.id).first(); const transactionsModels = await accountModel.$relatedQuery('transactions'); expect(transactionsModels.length).equals(1); diff --git a/server/tests/models/AccountType.test.js b/server/tests/models/AccountType.test.js index e5428e71f..e1bbab474 100644 --- a/server/tests/models/AccountType.test.js +++ b/server/tests/models/AccountType.test.js @@ -1,14 +1,20 @@ import { create, expect } from '~/testInit'; import '@/models/Account'; import AccountType from '@/models/AccountType'; +import { + tenantWebsite, + tenantFactory, + loginRes +} from '~/dbInit'; + describe('Model: AccountType', () => { it('Shoud account type model has many associated accounts.', async () => { - const accountType = await create('account_type'); - await create('account', { account_type_id: accountType.id }); - await create('account', { account_type_id: accountType.id }); + const accountType = await tenantFactory.create('account_type'); + await tenantFactory.create('account', { account_type_id: accountType.id }); + await tenantFactory.create('account', { account_type_id: accountType.id }); - const accountTypeModel = await AccountType.query().where('id', accountType.id).first(); + const accountTypeModel = await AccountType.tenant().query().where('id', accountType.id).first(); const typeAccounts = await accountTypeModel.$relatedQuery('accounts'); expect(typeAccounts.length).equals(2); diff --git a/server/tests/models/Item.test.js b/server/tests/models/Item.test.js index e76b17070..0b0713cf8 100644 --- a/server/tests/models/Item.test.js +++ b/server/tests/models/Item.test.js @@ -2,27 +2,21 @@ import { create, expect } from '~/testInit'; import Item from '@/models/Item'; // eslint-disable-next-line no-unused-vars import itemCategory from '@/models/ItemCategory'; -import '@/models/ItemMetadata'; +import { + tenantWebsite, + tenantFactory, + loginRes +} from '~/dbInit'; + describe('Model: Item', () => { it('Should item model belongs to the associated category model.', async () => { - const category = await create('item_category'); - const item = await create('item', { category_id: category.id }); + const category = await tenantFactory.create('item_category'); + const item = await tenantFactory.create('item', { category_id: category.id }); - const itemModel = await Item.query().where('id', item.id); + const itemModel = await Item.tenant().query().where('id', item.id).first(); const itemCategoryModel = await itemModel.$relatedQuery('category'); - expect(itemCategoryModel.attributes.id).equals(category.id); - }); - - it('Should item model has many metadata that assciated to the item model.', async () => { - const item = await create('item'); - await create('item_metadata', { item_id: item.id }); - await create('item_metadata', { item_id: item.id }); - - const itemModel = await Item.query().where('id', item.id); - const itemMetadataCollection = await itemModel.$relatedQuery('metadata'); - - expect(itemMetadataCollection.length).equals(2); + expect(itemCategoryModel.id).equals(category.id); }); }); diff --git a/server/tests/models/ItemCategories.test.js b/server/tests/models/ItemCategories.test.js index 7be3fcbb6..3a71a2eed 100644 --- a/server/tests/models/ItemCategories.test.js +++ b/server/tests/models/ItemCategories.test.js @@ -1,15 +1,23 @@ import { create, expect } from '~/testInit'; import '@/models/Item'; import ItemCategory from '@/models/ItemCategory'; +import { + tenantWebsite, + tenantFactory, + loginRes +} from '~/dbInit'; + describe('Model: ItemCategories', () => { it('Shoud item category model has many associated items.', async () => { - const category = await create('item_category'); - await create('item', { category_id: category.id }); - await create('item', { category_id: category.id }); + const category = await tenantFactory.create('item_category'); + await tenantFactory.create('item', { category_id: category.id }); + await tenantFactory.create('item', { category_id: category.id }); - const categoryModel = await ItemCategory.where('id', category.id).fetch(); - const categoryItems = await categoryModel.items().fetch(); + const categoryModel = await ItemCategory.tenant().query() + .where('id', category.id).first(); + + const categoryItems = await categoryModel.$relatedQuery('items'); expect(categoryItems.length).equals(2); }); diff --git a/server/tests/models/Option.test.js b/server/tests/models/Option.test.js index fade7f49c..b8122afc9 100644 --- a/server/tests/models/Option.test.js +++ b/server/tests/models/Option.test.js @@ -1,12 +1,17 @@ import { create, expect } from '~/testInit'; import Option from '@/models/Option'; import MetableCollection from '@/lib/Metable/MetableCollection'; +import { + tenantFactory, + tenantWebsite, +} from '~/dbInit'; + describe('Model: Option', () => { it('Should result collection be instance of `MetableCollection` class.', async () => { - await create('option'); - await create('option'); - const options = await Option.query(); + await tenantFactory.create('option'); + await tenantFactory.create('option'); + const options = await Option.tenant().query(); expect(options).to.be.an.instanceof(MetableCollection); }); diff --git a/server/tests/models/Resource.test.js b/server/tests/models/Resource.test.js index 24be2df48..6d7b03c79 100644 --- a/server/tests/models/Resource.test.js +++ b/server/tests/models/Resource.test.js @@ -2,22 +2,28 @@ import { create, expect } from '~/testInit'; import Resource from '@/models/Resource'; import '@/models/View'; import '@/models/ResourceField'; +import { + tenantWebsite, + tenantFactory, + loginRes +} from '~/dbInit'; + describe('Model: Resource', () => { it('Resource model may has many associated views.', async () => { - const view = await create('view'); - await create('view', { resource_id: view.resourceId }); + const view = await tenantFactory.create('view'); + await tenantFactory.create('view', { resource_id: view.resourceId }); - const resourceModel = await Resource.query().findById(view.resourceId); + const resourceModel = await Resource.tenant().query().findById(view.resourceId); const resourceViews = await resourceModel.$relatedQuery('views'); expect(resourceViews).to.have.lengthOf(2); }); it('Resource model may has many fields.', async () => { - const resourceField = await create('resource_field'); + const resourceField = await tenantFactory.create('resource_field'); - const resourceModel = await Resource.query().findById(resourceField.resourceId); + const resourceModel = await Resource.tenant().query().findById(resourceField.resourceId); const resourceFields = await resourceModel.$relatedQuery('fields'); expect(resourceFields).to.have.lengthOf(1); diff --git a/server/tests/models/Setting.test.js b/server/tests/models/Setting.test.js deleted file mode 100644 index 365c20709..000000000 --- a/server/tests/models/Setting.test.js +++ /dev/null @@ -1,197 +0,0 @@ -import sinon from 'sinon'; -import { create, expect } from '~/testInit'; -import Setting from '@/models/Setting'; -import knex from '../../src/database/knex'; - -describe('Model: Setting', () => { - afterEach(() => { - Setting.purgeMetadata(); - }); - - describe('Setting.AllMeta()', async () => { - it('Should fetch all metadata from storage in the first call.', async () => { - await create('setting'); - const querySpy = sinon.spy(Setting, 'query'); - - const metadata = await Setting.allMeta(); - - expect(querySpy.calledOnce).equals(true); - expect(metadata).to.have.lengthOf(1); - - querySpy.restore(); - }); - - it('Should get all meta data from stored cache in the second call.', async () => { - await create('setting'); - const querySpy = sinon.spy(Setting, 'query'); - - await Setting.allMeta(); - await Setting.allMeta(); - - expect(querySpy.calledOnce).equals(true); - expect(Setting.metadata).to.have.lengthOf(1); - - querySpy.restore(); - }); - }); - - describe('Setting.getMeta()', () => { - it('Should fetch metadata of the given key from storage.', async () => { - const setting = await create('setting'); - const metadata = await Setting.getMeta(setting.key); - - expect(metadata).equals(setting.value); - }); - - it('Should retrieve the default value if the metadata key was not found.', async () => { - const metadata = await Setting.getMeta('setting', 'default'); - expect(metadata).equals('default'); - }); - - it('Should get the same metadata key from cache in the second call.', async () => { - const setting = await create('setting'); - await create('setting'); - - const querySpy = sinon.spy(Setting, 'query'); - - await Setting.getMeta(setting.key); - expect(querySpy.calledOnce).equals(true); - - await Setting.getMeta(setting.key); - expect(querySpy.calledOnce).equals(true); - - querySpy.restore(); - }); - - it('Should get the different metadata key from storage.', async () => { - const setting = await create('setting'); - const settingAnother = await create('setting'); - - const querySpy = sinon.spy(Setting, 'query'); - - await Setting.getMeta(setting.key); - expect(querySpy.calledOnce).equals(true); - - await Setting.getMeta(settingAnother.key); - expect(querySpy.calledOnce).equals(true); - - querySpy.restore(); - }); - - it('Should hard fetching the metadata from the storage when passing `force` parameter.', async () => { - const setting = await create('setting'); - await create('setting'); - - const querySpy = sinon.spy(Setting, 'query'); - - await Setting.allMeta(); - expect(querySpy.calledOnce).equals(true); - expect(Setting.metadata).to.have.lengthOf(2); - - await Setting.getMeta(setting.key, null, true); - expect(querySpy.calledTwice).equals(true); - expect(Setting.metadata).to.have.lengthOf(2); - - querySpy.restore(); - }); - }); - - describe('Setting.setMeta()', () => { - it('Should mark the given metadata as updated in the stack.', async () => { - const setting = await create('setting'); - await Setting.setMeta(setting.key, 'Ahmed'); - - const foundMeta = Setting.metadata.find((metadata) => ( - metadata.key === setting.key && metadata.markAsUpdated === true - && metadata.value === 'Ahmed' - )); - expect(!!foundMeta).equals(true); - }); - - it('Should mark the set metadata as inserted metadata in the stack.', async () => { - await create('setting'); - await Setting.setMeta('key', 'value'); - - const foundMeta = Setting.metadata.find((metadata) => ( - metadata.key === 'key' && metadata.markAsInserted === true - && metadata.value === 'value' - )); - expect(!!foundMeta).equals(true); - }); - - it('Should fetch the metadata from the storage in case the metadata was exist.', async () => { - const setting = await create('setting'); - const querySpy = sinon.spy(Setting, 'query'); - - await Setting.setMeta(setting.key, 'value'); - expect(querySpy.calledOnce).equals(true); - - await Setting.setMeta(setting.key, 'updated-value'); - expect(querySpy.calledOnce).equals(true); - }); - - it('Should mark the updated bluk metadata as updated in the stock.', async () => { - - }); - - it('Should mark the inserted bluk metadata as inserted in the stock.', async () => { - - }); - }); - - describe('Setting.removeMeta()', () => { - it('Should mark the given metadata as deleted', async () => { - const setting = await create('setting'); - await Setting.removeMeta(setting.key); - - const foundMeta = Setting.metadata.find((metadata) => ( - metadata.key === setting.key && metadata.markAsDeleted === true - )); - expect(!!foundMeta).equals(true); - }); - - it('Should not query the storage when found cached the metadata.', async () => { - const setting = await create('setting'); - await Setting.allMeta(); - - const querySpy = sinon.spy(Setting, 'query'); - - await Setting.removeMeta(setting.key); - expect(querySpy.calledOnce).equals(false); - - querySpy.restore(); - }); - }); - - describe('Setting.saveMeta()', () => { - it('Should insert the metadata that set to the stock.', async () => { - await Setting.setMeta('key', 'value'); - await Setting.saveMeta(); - - const storedMetadata = await knex('settings'); - expect(storedMetadata).to.have.lengthOf(1); - }); - - it('Should update the metadata that updated in the stock.', async () => { - const setting = await create('setting'); - - await Setting.setMeta(setting.key, 'value'); - await Setting.saveMeta(); - - const storedMetadata = await knex('settings'); - - expect(storedMetadata).to.have.lengthOf(1); - expect(storedMetadata[0].value).equals('value'); - }); - - it('Should delete the metadata that removed from the stock.', async () => { - const setting = await create('setting'); - - await Setting.removeMeta(setting.key); - await Setting.saveMeta(); - - const storedMetadata = await knex('settings'); - expect(storedMetadata).to.have.lengthOf(0); - }); - }); -}); diff --git a/server/tests/models/User.test.js b/server/tests/models/User.test.js index e68cda30e..f6633bdbd 100644 --- a/server/tests/models/User.test.js +++ b/server/tests/models/User.test.js @@ -1,46 +1,23 @@ import { create, expect } from '~/testInit'; -import User from '@/models/User'; +import User from '@/models/TenantUser'; import '@/models/Role'; +import { + tenantWebsite, + tenantFactory, + loginRes +} from '~/dbInit'; + describe('Model: User', () => { describe('relations', () => { it('User model may has many associated roles.', async () => { - const userHasRole = await create('user_has_role'); - await create('user_has_role', { user_id: userHasRole.user_id }); + const userHasRole = await tenantFactory.create('user_has_role'); + await tenantFactory.create('user_has_role', { user_id: userHasRole.user_id }); - const userModel = await User.query().where('id', userHasRole.userId).first(); + const userModel = await User.tenant().query().where('id', userHasRole.userId).first(); const userRoles = await userModel.$relatedQuery('roles'); expect(userRoles).to.have.lengthOf(1); }); }); - - describe('hasPermissions', () => { - it('Should return true in case user has the given permissions.', async () => { - const resource = await create('resource'); - const permission = await create('permission'); - const roleHasPerms = await create('role_has_permission', { - resource_id: resource.id, - permission_id: permission.id, - }); - const userHasRole = await create('user_has_role', { role_id: roleHasPerms.role_id }); - await create('user_has_role', { user_id: userHasRole.user_id }); - - const userModel = await User.where('id', userHasRole.user_id).fetch(); - const hasPermission = await userModel.hasPermissions(resource.name, [permission.name]); - - expect(hasPermission).to.equals(true); - }); - - it('Should return false in case user has no the given permissions.', async () => { - const roleHasPerms = await create('role_has_permission'); - const userHasRole = await create('user_has_role', { role_id: roleHasPerms.role_id }); - await create('user_has_role', { user_id: userHasRole.user_id }); - - const userModel = await User.where('id', userHasRole.user_id).fetch(); - const hasPermission = await userModel.hasPermissions('resource', ['permission']); - - expect(hasPermission).to.equals(false); - }); - }); }); diff --git a/server/tests/models/View.test.js b/server/tests/models/View.test.js index 034178b86..3a2d5d55b 100644 --- a/server/tests/models/View.test.js +++ b/server/tests/models/View.test.js @@ -3,37 +3,43 @@ import View from '@/models/View'; import Resource from '@/models/Resource'; import ResourceField from '@/models/ResourceField'; import ViewRole from '@/models/ViewRole'; +import { + tenantWebsite, + tenantFactory, + loginRes +} from '~/dbInit'; + describe('Model: View', () => { it('View model may has many associated resource.', async () => { - const view = await create('view'); + const view = await tenantFactory.create('view'); - const viewModel = await View.query().findById(view.id); + const viewModel = await View.tenant().query().findById(view.id); const viewResource = await viewModel.$relatedQuery('resource'); - const foundResource = await Resource.query().findById(view.resourceId); + const foundResource = await Resource.tenant().query().findById(view.resourceId); expect(viewResource.id).equals(foundResource.id); expect(viewResource.name).equals(foundResource.name); }); it('View model may has many associated view roles.', async () => { - const view = await create('view'); - await create('view_role', { view_id: view.id }); - await create('view_role', { view_id: view.id }); + const view = await tenantFactory.create('view'); + await tenantFactory.create('view_role', { view_id: view.id }); + await tenantFactory.create('view_role', { view_id: view.id }); - const viewModel = await View.query().findById(view.id); - const viewRoles = await viewModel.$relatedQuery('viewRoles'); + const viewModel = await View.tenant().query().findById(view.id); + const viewRoles = await viewModel.$relatedQuery('roles'); expect(viewRoles).to.have.lengthOf(2); }); it('View model may has many associated view columns', async () => { - const view = await create('view'); - await create('view_column', { view_id: view.id }); - await create('view_column', { view_id: view.id }); + const view = await tenantFactory.create('view'); + await tenantFactory.create('view_column', { view_id: view.id }); + await tenantFactory.create('view_column', { view_id: view.id }); - const viewModel = await View.query().findById(view.id); + const viewModel = await View.tenant().query().findById(view.id); const viewColumns = await viewModel.$relatedQuery('columns'); expect(viewColumns).to.have.lengthOf(2); diff --git a/server/tests/mysql-tmpfs.sh b/server/tests/mysql-tmpfs.sh new file mode 100644 index 000000000..7ee993230 --- /dev/null +++ b/server/tests/mysql-tmpfs.sh @@ -0,0 +1,31 @@ +MYSQL_USER="database_test" +MYSQL_DATABASE="database_test" +MYSQL_CONTAINER_NAME="database_test" + +MYSQL_ROOT_PASSWORD="root" +MYSQL_PASSWORD="root" + +echo "Start the testing MySql database..." + +docker \ + run \ + --detach \ + --env MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} \ + --env MYSQL_USER=${MYSQL_USER} \ + --env MYSQL_PASSWORD=${MYSQL_PASSWORD} \ + --env MYSQL_DATABASE=${MYSQL_DATABASE} \ + --name ${MYSQL_CONTAINER_NAME} \ + --publish 3306:3306 \ + --tmpfs /var/lib/mysql:rw,noexec,nosuid,size=600m \ + mysql:5.7; + +echo "Sleeping for 10 seconds to allow time for the DB to be provisioned:" +for i in `seq 1 10`; +do + echo "." + sleep 1 +done + +echo "Database '${MYSQL_DATABASE}' running." +echo " Username: ${MYSQL_USER}" +echo " Password: ${MYSQL_PASSWORD}" diff --git a/server/tests/routes/accounting.test.js b/server/tests/routes/accounting.test.js index 2b4070f31..34734a75b 100644 --- a/server/tests/routes/accounting.test.js +++ b/server/tests/routes/accounting.test.js @@ -1,30 +1,26 @@ import { request, expect, - create, - login, } from '~/testInit'; import moment from 'moment'; import ManualJournal from '@/models/ManualJournal'; import AccountTransaction from '@/models/AccountTransaction'; import AccountBalance from '@/models/AccountBalance'; +import { + tenantWebsite, + tenantFactory, + loginRes +} from '~/dbInit'; -let loginRes; describe('routes: `/accounting`', () => { - beforeEach(async () => { - loginRes = await login(); - }); - afterEach(() => { - loginRes = null; - }); - describe('route: `/accounting/make-journal-entries`', async () => { it('Should sumation of credit or debit does not equal zero.', async () => { - const account = await create('account'); + const account = await tenantFactory.create('account'); const res = await request() .post('/api/accounting/make-journal-entries') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ date: new Date().toISOString(), journal_number: '123', @@ -50,10 +46,11 @@ describe('routes: `/accounting`', () => { }); it('Should all credit entries equal debit.', async () => { - const account = await create('account'); + const account = await tenantFactory.create('account'); const res = await request() .post('/api/accounting/make-journal-entries') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ date: new Date().toISOString(), journal_number: '123', @@ -79,12 +76,13 @@ describe('routes: `/accounting`', () => { }); it('Should journal reference be not exists.', async () => { - const manualJournal = await create('manual_journal'); - const account = await create('account'); + const manualJournal = await tenantFactory.create('manual_journal'); + const account = await tenantFactory.create('account'); const res = await request() .post('/api/accounting/make-journal-entries') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ date: new Date().toISOString(), journal_number: manualJournal.journalNumber, @@ -113,6 +111,7 @@ describe('routes: `/accounting`', () => { const res = await request() .post('/api/accounting/make-journal-entries') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ date: new Date().toISOString(), journal_number: '123', @@ -138,12 +137,13 @@ describe('routes: `/accounting`', () => { }); it('Should discard journal entries that has null credit and debit amount.', async () => { - const account1 = await create('account'); - const account2 = await create('account'); + const account1 = await tenantFactory.create('account'); + const account2 = await tenantFactory.create('account'); const res = await request() .post('/api/accounting/make-journal-entries') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ date: new Date().toISOString(), journal_number: '1000', @@ -169,12 +169,13 @@ describe('routes: `/accounting`', () => { }); it('Should store manual journal transaction to the storage.', async () => { - const account1 = await create('account'); - const account2 = await create('account'); + const account1 = await tenantFactory.create('account'); + const account2 = await tenantFactory.create('account'); const res = await request() .post('/api/accounting/make-journal-entries') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ date: new Date('2020-2-2').toISOString(), journal_number: '1000', @@ -192,7 +193,7 @@ describe('routes: `/accounting`', () => { ], }); - const foundManualJournal = await ManualJournal.query(); + const foundManualJournal = await ManualJournal.tenant().query(); expect(foundManualJournal.length).equals(1); @@ -202,17 +203,19 @@ describe('routes: `/accounting`', () => { expect(foundManualJournal[0].amount).equals(1000); expect(moment(foundManualJournal[0].date).format('YYYY-MM-DD')).equals('2020-02-02'); expect(foundManualJournal[0].description).equals('Description here.'); - expect(foundManualJournal[0].userId).to.be.a('integer'); + expect(foundManualJournal[0].userId).to.be.a('number'); }); it('Should store journal transactions to the storage.', async () => { - const account1 = await create('account'); - const account2 = await create('account'); + const account1 = await tenantFactory.create('account'); + const account2 = await tenantFactory.create('account'); const res = await request() .post('/api/accounting/make-journal-entries') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ + journal_number: '1', date: new Date('2020-1-1').toISOString(), reference: '1000', memo: 'Description here.', @@ -230,7 +233,7 @@ describe('routes: `/accounting`', () => { ], }); - const foundAccountsTransactions = await AccountTransaction.query(); + const foundAccountsTransactions = await AccountTransaction.tenant().query(); expect(foundAccountsTransactions.length).equals(2); @@ -238,14 +241,14 @@ describe('routes: `/accounting`', () => { expect(foundAccountsTransactions[0].debit).equals(null); expect(foundAccountsTransactions[0].accountId).equals(account1.id); expect(foundAccountsTransactions[0].note).equals('First note'); - expect(foundAccountsTransactions[0].transactionType).equals('Journal'); + expect(foundAccountsTransactions[0].referenceType).equals('Journal'); expect(foundAccountsTransactions[0].userId).equals(1); expect(foundAccountsTransactions[1].credit).equals(null); expect(foundAccountsTransactions[1].debit).equals(1000); expect(foundAccountsTransactions[1].accountId).equals(account2.id); expect(foundAccountsTransactions[1].note).equals('Second note'); - expect(foundAccountsTransactions[1].transactionType).equals('Journal'); + expect(foundAccountsTransactions[1].referenceType).equals('Journal'); expect(foundAccountsTransactions[1].userId).equals(1); }); }); @@ -255,17 +258,19 @@ describe('routes: `/accounting`', () => { const res = await request() .post('/api/manual-journal/1000') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(404); }); it('Should sumation of credit or debit be equal zero.', async () => { - const manualJournal = await create('manual_journal'); + const manualJournal = await tenantFactory.create('manual_journal'); const res = await request() - .post(`/api/accounting/manual-journal/${manualJournal.id}`) + .post(`/api/accounting/manual-journals/${manualJournal.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ date: new Date().toISOString(), journal_number: '123', @@ -292,11 +297,12 @@ describe('routes: `/accounting`', () => { }); it('Should all credit and debit sumation be equal.', async () => { - const manualJournal = await create('manual_journal'); + const manualJournal = await tenantFactory.create('manual_journal'); const res = await request() - .post(`/api/accounting/manual-journal/${manualJournal.id}`) + .post(`/api/accounting/manual-journals/${manualJournal.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ date: new Date().toISOString(), journal_number: '123', @@ -322,8 +328,8 @@ describe('routes: `/accounting`', () => { }); it('Should response journal number already exists in case another one on the storage.', async () => { - const manualJournal = await create('manual_journal'); - const manualJournal2 = await create('manual_journal'); + const manualJournal = await tenantFactory.create('manual_journal'); + const manualJournal2 = await tenantFactory.create('manual_journal'); const jsonBody = { date: new Date().toISOString(), @@ -343,8 +349,9 @@ describe('routes: `/accounting`', () => { }; const res = await request() - .post(`/api/accounting/manual-journal/${manualJournal.id}`) + .post(`/api/accounting/manual-journals/${manualJournal.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ ...jsonBody, journal_number: manualJournal2.journalNumber, @@ -356,8 +363,8 @@ describe('routes: `/accounting`', () => { }); it('Should not response journal number exists in case was unique number.', async () => { - const manualJournal = await create('manual_journal'); - const manualJournal2 = await create('manual_journal'); + const manualJournal = await tenantFactory.create('manual_journal'); + const manualJournal2 = await tenantFactory.create('manual_journal'); const jsonBody = { date: new Date().toISOString(), @@ -376,8 +383,9 @@ describe('routes: `/accounting`', () => { ], }; const res = await request() - .post(`/api/accounting/manual-journal/${manualJournal.id}`) + .post(`/api/accounting/manual-journals/${manualJournal.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ ...jsonBody, journal_number: manualJournal.journalNumber, @@ -390,8 +398,8 @@ describe('routes: `/accounting`', () => { }) it('Should response error in case account id not exists in one of the given entries.', async () => { - const manualJournal = await create('manual_journal'); - const manualJournal2 = await create('manual_journal'); + const manualJournal = await tenantFactory.create('manual_journal'); + const manualJournal2 = await tenantFactory.create('manual_journal'); const jsonBody = { date: new Date().toISOString(), @@ -410,8 +418,9 @@ describe('routes: `/accounting`', () => { ], }; const res = await request() - .post(`/api/accounting/manual-journal/${manualJournal.id}`) + .post(`/api/accounting/manual-journals/${manualJournal.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ ...jsonBody, journal_number: manualJournal.journalNumber, @@ -424,13 +433,14 @@ describe('routes: `/accounting`', () => { }); it('Should update the given manual journal transaction in the storage.', async () => { - const manualJournal = await create('manual_journal'); - const account1 = await create('account'); - const account2 = await create('account'); + const manualJournal = await tenantFactory.create('manual_journal'); + const account1 = await tenantFactory.create('account'); + const account2 = await tenantFactory.create('account'); const res = await request() - .post(`/api/accounting/manual-journal/${manualJournal.id}`) + .post(`/api/accounting/manual-journals/${manualJournal.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ journal_number: '123', date: new Date().toISOString(), @@ -450,7 +460,7 @@ describe('routes: `/accounting`', () => { ], }); - const foundManualJournal = await ManualJournal.query() + const foundManualJournal = await ManualJournal.tenant().query() .where('id', manualJournal.id); expect(foundManualJournal.length).equals(1); @@ -460,21 +470,22 @@ describe('routes: `/accounting`', () => { }); it('Should update account transactions that associated to the manual journal transaction.', async () => { - const manualJournal = await create('manual_journal'); - const account1 = await create('account'); - const account2 = await create('account'); - const transaction = await create('account_transaction', { + const manualJournal = await tenantFactory.create('manual_journal'); + const account1 = await tenantFactory.create('account'); + const account2 = await tenantFactory.create('account'); + const transaction = await tenantFactory.create('account_transaction', { reference_type: 'Journal', reference_id: manualJournal.id, }); - const transaction2 = await create('account_transaction', { + const transaction2 = await tenantFactory.create('account_transaction', { reference_type: 'Journal', reference_id: manualJournal.id, }); const res = await request() - .post(`/api/accounting/manual-journal/${manualJournal.id}`) + .post(`/api/accounting/manual-journals/${manualJournal.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ journal_number: '123', date: new Date().toISOString(), @@ -496,7 +507,7 @@ describe('routes: `/accounting`', () => { ], }); - const foundTransactions = await AccountTransaction.query(); + const foundTransactions = await AccountTransaction.tenant().query(); expect(foundTransactions.length).equals(2); expect(foundTransactions[0].credit).equals(0); @@ -516,6 +527,7 @@ describe('routes: `/accounting`', () => { const res = await request() .delete('/api/accounting/manual-journals/1000') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(404); @@ -525,33 +537,36 @@ describe('routes: `/accounting`', () => { }); it('Should delete manual journal transactions from storage.', async () => { - const manualJournal = await create('manual_journal'); + const manualJournal = await tenantFactory.create('manual_journal'); const res = await request() .delete(`/api/accounting/manual-journals/${manualJournal.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); - const foundManualTransaction = await ManualJournal.query() + + const foundManualTransaction = await ManualJournal.tenant().query() .where('id', manualJournal.id).first(); expect(foundManualTransaction).equals(undefined); }); it('Should delete associated transactions of journal transaction.', async () => { - const manualJournal = await create('manual_journal'); - const transaction1 = await create('account_transaction', { + const manualJournal = await tenantFactory.create('manual_journal'); + const transaction1 = await tenantFactory.create('account_transaction', { reference_type: 'Journal', reference_id: manualJournal.id, }); - const transaction2 = await create('account_transaction', { + const transaction2 = await tenantFactory.create('account_transaction', { reference_type: 'Journal', reference_id: manualJournal.id, }); const res = await request() .delete(`/api/accounting/manual-journals/${manualJournal.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); - const foundTransactions = await AccountTransaction.query(); + const foundTransactions = await AccountTransaction.tenant().query(); expect(foundTransactions.length).equals(0); }); @@ -565,6 +580,7 @@ describe('routes: `/accounting`', () => { const res = await request() .delete('/api/accounting/manual-journals/100') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(404); @@ -582,12 +598,13 @@ describe('routes: `/accounting`', () => { describe('route: `accounting/manual-journals`', async () => { it('Should retrieve all manual journals with pagination meta.', async () => { - const manualJournal1 = await create('manual_journal'); - const manualJournal2 = await create('manual_journal'); + const manualJournal1 = await tenantFactory.create('manual_journal'); + const manualJournal2 = await tenantFactory.create('manual_journal'); const res = await request() .get('/api/accounting/manual-journals') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(200); @@ -599,11 +616,12 @@ describe('routes: `/accounting`', () => { describe('route: POST `accounting/manual-journals/:id/publish`', () => { it('Should response not found in case the manual journal id was not exists.', async () => { - const manualJournal = await create('manual_journal'); + const manualJournal = await tenantFactory.create('manual_journal'); const res = await request() .post('/api/accounting/manual-journals/123/publish') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(404); @@ -613,10 +631,12 @@ describe('routes: `/accounting`', () => { }); it('Should response published ready.', async () => { - const manualJournal = await create('manual_journal', { status: 0 }); + const manualJournal = await tenantFactory.create('manual_journal', { status: 1 }); + const res = await request() .post(`/api/accounting/manual-journals/${manualJournal.id}/publish`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(400); @@ -626,13 +646,13 @@ describe('routes: `/accounting`', () => { }); it('Should update all accounts transactions to not draft.', async () => { - const manualJournal = await create('manual_journal'); - const transaction = await create('account_transaction', { + const manualJournal = await tenantFactory.create('manual_journal', { status: 0 }); + const transaction = await tenantFactory.create('account_transaction', { reference_type: 'Journal', reference_id: manualJournal.id, draft: 1, }); - const transaction2 = await create('account_transaction', { + const transaction2 = await tenantFactory.create('account_transaction', { reference_type: 'Journal', reference_id: manualJournal.id, draft: 1, @@ -640,9 +660,10 @@ describe('routes: `/accounting`', () => { const res = await request() .post(`/api/accounting/manual-journals/${manualJournal.id}/publish`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); - const foundTransactions = await AccountTransaction.query() + const foundTransactions = await AccountTransaction.tenant().query() .whereIn('id', [transaction.id, transaction2.id]); expect(foundTransactions[0].draft).equals(0); diff --git a/server/tests/routes/accounts.test.js b/server/tests/routes/accounts.test.js index 9acb11299..e3bf051d4 100644 --- a/server/tests/routes/accounts.test.js +++ b/server/tests/routes/accounts.test.js @@ -1,21 +1,24 @@ -import { request, expect, create, login } from '~/testInit'; +import { + request, + expect, +} from '~/testInit'; import Account from '@/models/Account'; +import { + tenantWebsite, + tenantFactory, + loginRes +} from '~/dbInit'; -let loginRes; describe('routes: /accounts/', () => { - beforeEach(async () => { - loginRes = await login(); - }); - afterEach(() => { - loginRes = null; - }); describe('POST `/accounts`', () => { it('Should `name` be required.', async () => { const res = await request() .post('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); + expect(res.status).equals(422); expect(res.body.code).equals('validation_error'); }); @@ -24,6 +27,7 @@ describe('routes: /accounts/', () => { const res = await request() .post('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -34,6 +38,7 @@ describe('routes: /accounts/', () => { const res = await request() .post('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -41,10 +46,11 @@ describe('routes: /accounts/', () => { }); it('Should response type not found in case `account_type_id` was not exist.', async () => { - const account = await create('account'); + const account = await tenantFactory.create('account'); const res = await request() .post('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'Account Name', description: account.description, @@ -59,10 +65,11 @@ describe('routes: /accounts/', () => { }); it('Should account code be unique in the storage.', async () => { - const account = await create('account'); + const account = await tenantFactory.create('account'); const res = await request() .post('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: account.name, description: account.description, @@ -77,10 +84,11 @@ describe('routes: /accounts/', () => { }); it('Should response success with correct data form.', async () => { - const account = await create('account'); + const account = await tenantFactory.create('account'); const res = await request() .post('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'Name', description: 'description here', @@ -93,9 +101,11 @@ describe('routes: /accounts/', () => { }); it('Should store account data in the storage.', async () => { - const account = await create('account'); + const account = await tenantFactory.create('account'); + const res = await request().post('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'Account Name', description: 'desc here', @@ -103,12 +113,10 @@ describe('routes: /accounts/', () => { parent_account_id: account.id, }); - - - const accountModel = await Account.query() + const accountModel = await Account.tenant().query() .where('name', 'Account Name') .first(); - + expect(accountModel).a.an('object'); expect(accountModel.description).equals('desc here'); expect(accountModel.accountTypeId).equals(account.accountTypeId); @@ -118,10 +126,11 @@ describe('routes: /accounts/', () => { describe('POST `/accounts/:id`', () => { it('Should `name` be required.', async () => { - const account = await create('account'); + const account = await tenantFactory.create('account'); const res = await request() .post(`/api/accounts/${account.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -129,10 +138,11 @@ describe('routes: /accounts/', () => { }); it('Should `account_type_id` be required.', async () => { - const account = await create('account'); + const account = await tenantFactory.create('account'); const res = await request() .post(`/api/accounts/${account.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -140,10 +150,11 @@ describe('routes: /accounts/', () => { }); it('Should max length of `code` be limited.', async () => { - const account = await create('account'); + const account = await tenantFactory.create('account'); const res = await request() .post(`/api/accounts/${account.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -154,17 +165,19 @@ describe('routes: /accounts/', () => { const res = await request() .post('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); }); it('Should account code be unique in the storage.', async () => { - await create('account', { code: 'ABCD' }); - const account = await create('account'); + await tenantFactory.create('account', { code: 'ABCD' }); + const account = await tenantFactory.create('account'); const res = await request() .post(`/api/accounts/${account.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'name', code: 'ABCD', @@ -178,10 +191,11 @@ describe('routes: /accounts/', () => { }); it('Should response success with correct data form.', async () => { - const account = await create('account'); + const account = await tenantFactory.create('account'); const res = await request() .post('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'Name', description: 'description here', @@ -195,26 +209,15 @@ describe('routes: /accounts/', () => { }); describe('GET: `/accounts`', () => { - // it('Should retrieve accounts resource not found.', async () => { - // const res = await request() - // .get('/api/accounts') - // .set('x-access-token', loginRes.body.token) - // .send(); - - // expect(res.status).equals(400); - // expect(res.body.errors).include.something.that.deep.equals({ - // type: 'ACCOUNTS_RESOURCE_NOT_FOUND', code: 200, - // }); - // }); - it('Should retrieve chart of accounts', async () => { - await create('resource', { name: 'accounts' }); - const account = await create('account'); - await create('account', { parent_account_id: account.id }); + await tenantFactory.create('resource', { name: 'accounts' }); + const account = await tenantFactory.create('account'); + await tenantFactory.create('account', { parent_account_id: account.id }); const res = await request() .get('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(200); @@ -222,9 +225,9 @@ describe('routes: /accounts/', () => { }); it('Should retrieve accounts based on view roles conditionals of the custom view.', async () => { - const resource = await create('resource', { name: 'accounts' }); + const resource = await tenantFactory.create('resource', { name: 'accounts' }); - const accountTypeField = await create('resource_field', { + const accountTypeField = await tenantFactory.create('resource_field', { label_name: 'Account type', key: 'type', resource_id: resource.id, @@ -232,28 +235,28 @@ describe('routes: /accounts/', () => { predefined: true, }); - const accountNameField = await create('resource_field', { + const accountNameField = await tenantFactory.create('resource_field', { label_name: 'Account Name', key: 'name', resource_id: resource.id, active: true, predefined: true, }); - const accountsView = await create('view', { + const accountsView = await tenantFactory.create('view', { name: 'Accounts View', resource_id: resource.id, roles_logic_expression: '1 AND 2', }); - const accountType = await create('account_type'); + const accountType = await tenantFactory.create('account_type'); - await create('view_role', { + await tenantFactory.create('view_role', { view_id: accountsView.id, index: 1, field_id: accountTypeField.id, value: accountType.name, comparator: 'equals', }); - await create('view_role', { + await tenantFactory.create('view_role', { view_id: accountsView.id, index: 2, field_id: accountNameField.id, @@ -261,14 +264,17 @@ describe('routes: /accounts/', () => { comparator: 'contains', }); - await create('account', { name: 'account-1', account_type_id: accountType.id }); - await create('account', { name: 'account-2', account_type_id: accountType.id }); - await create('account', { name: 'account-3' }); + await tenantFactory.create('account', { name: 'account-1', account_type_id: accountType.id }); + await tenantFactory.create('account', { name: 'account-2', account_type_id: accountType.id }); + await tenantFactory.create('account', { name: 'account-3' }); const res = await request() .get('/api/accounts') .set('x-access-token', loginRes.body.token) - .query({ custom_view_id: accountsView.id }) + .set('organization-id', tenantWebsite.organizationId) + .query({ + custom_view_id: accountsView.id + }) .send(); expect(res.body.accounts.length).equals(2); @@ -279,23 +285,23 @@ describe('routes: /accounts/', () => { }); it('Should retrieve accounts based on view roles conditionals with relation join column.', async () => { - const resource = await create('resource', { name: 'accounts' }); + const resource = await tenantFactory.create('resource', { name: 'accounts' }); - const accountTypeField = await create('resource_field', { + const accountTypeField = await tenantFactory.create('resource_field', { label_name: 'Account type', key: 'type', resource_id: resource.id, active: true, predefined: true, }); - const accountsView = await create('view', { + const accountsView = await tenantFactory.create('view', { name: 'Accounts View', resource_id: resource.id, roles_logic_expression: '1', }); - const accountType = await create('account_type'); - const accountsViewRole = await create('view_role', { + const accountType = await tenantFactory.create('account_type'); + const accountsViewRole = await tenantFactory.create('view_role', { view_id: accountsView.id, index: 1, field_id: accountTypeField.id, @@ -303,13 +309,14 @@ describe('routes: /accounts/', () => { comparator: 'equals', }); - await create('account', { account_type_id: accountType.id }); - await create('account'); - await create('account'); + await tenantFactory.create('account', { account_type_id: accountType.id }); + await tenantFactory.create('account'); + await tenantFactory.create('account'); const res = await request() .get('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ custom_view_id: accountsView.id }) @@ -320,15 +327,16 @@ describe('routes: /accounts/', () => { }); it('Should retrieve accounts and child accounts in nested set graph.', async () => { - const resource = await create('resource', { name: 'accounts' }); + const resource = await tenantFactory.create('resource', { name: 'accounts' }); - const account1 = await create('account'); - const account2 = await create('account', { parent_account_id: account1.id }); - const account3 = await create('account', { parent_account_id: account2.id }); + const account1 = await tenantFactory.create('account'); + const account2 = await tenantFactory.create('account', { parent_account_id: account1.id }); + const account3 = await tenantFactory.create('account', { parent_account_id: account2.id }); const res = await request() .get('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ display_type: 'tree' }) .send(); @@ -346,15 +354,16 @@ describe('routes: /accounts/', () => { }); it('Should retrieve bad request when `filter_roles.*.field_key` not found in accounts resource.', async () => { - const resource = await create('resource', { name: 'accounts' }); + const resource = await tenantFactory.create('resource', { name: 'accounts' }); - const account1 = await create('account', { name: 'ahmed' }); - const account2 = await create('account'); - const account3 = await create('account'); + const account1 = await tenantFactory.create('account', { name: 'ahmed' }); + const account2 = await tenantFactory.create('account'); + const account3 = await tenantFactory.create('account'); const res = await request() .get('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ stringified_filter_roles: JSON.stringify([{ condition: 'AND', @@ -379,27 +388,28 @@ describe('routes: /accounts/', () => { }); it('Should retrieve filtered accounts according to the given account type filter condition.', async () => { - const resource = await create('resource', { name: 'accounts' }); - const keyField = await create('resource_field', { + const resource = await tenantFactory.create('resource', { name: 'accounts' }); + const keyField = await tenantFactory.create('resource_field', { key: 'type', resource_id: resource.id, }); - const nameFiled = await create('resource_field', { + const nameFiled = await tenantFactory.create('resource_field', { key: 'name', resource_id: resource.id, }); - const accountType = await create('account_type'); + const accountType = await tenantFactory.create('account_type'); - const account1 = await create('account', { + const account1 = await tenantFactory.create('account', { name: 'ahmed', account_type_id: accountType.id }); - const account2 = await create('account'); - const account3 = await create('account'); + const account2 = await tenantFactory.create('account'); + const account3 = await tenantFactory.create('account'); const res = await request() .get('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ stringified_filter_roles: JSON.stringify([{ condition: '&&', @@ -418,19 +428,20 @@ describe('routes: /accounts/', () => { }); it('Shoud retrieve filtered accounts according to the given account description filter condition.', async () => { - const resource = await create('resource', { name: 'accounts' }); - const resourceField = await create('resource_field', { + const resource = await tenantFactory.create('resource', { name: 'accounts' }); + const resourceField = await tenantFactory.create('resource_field', { key: 'description', resource_id: resource.id, }); - const account1 = await create('account', { name: 'ahmed', description: 'here' }); - const account2 = await create('account'); - const account3 = await create('account'); + const account1 = await tenantFactory.create('account', { name: 'ahmed', description: 'here' }); + const account2 = await tenantFactory.create('account'); + const account3 = await tenantFactory.create('account'); const res = await request() .get('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ stringified_filter_roles: JSON.stringify([{ condition: 'AND', @@ -445,24 +456,25 @@ describe('routes: /accounts/', () => { }); it('Should retrieve filtered accounts based on given filter roles between OR conditions.', async () => { - const resource = await create('resource', { name: 'accounts' }); - const resourceField = await create('resource_field', { + const resource = await tenantFactory.create('resource', { name: 'accounts' }); + const resourceField = await tenantFactory.create('resource_field', { key: 'description', resource_id: resource.id, }); - const resourceCodeField = await create('resource_field', { + const resourceCodeField = await tenantFactory.create('resource_field', { key: 'code', resource_id: resource.id, }); - const account1 = await create('account', { name: 'ahmed', description: 'target' }); - const account2 = await create('account', { description: 'target' }); - const account3 = await create('account'); + const account1 = await tenantFactory.create('account', { name: 'ahmed', description: 'target' }); + const account2 = await tenantFactory.create('account', { description: 'target' }); + const account3 = await tenantFactory.create('account'); const res = await request() .get('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ stringified_filter_roles: JSON.stringify([{ condition: '&&', @@ -484,26 +496,26 @@ describe('routes: /accounts/', () => { }); it('Should retrieve filtered accounts from custom view and filter roles.', async () => { - const resource = await create('resource', { name: 'accounts' }); - const accountTypeField = await create('resource_field', { + const resource = await tenantFactory.create('resource', { name: 'accounts' }); + const accountTypeField = await tenantFactory.create('resource_field', { key: 'type', resource_id: resource.id, }); - const accountDescriptionField = await create('resource_field', { + const accountDescriptionField = await tenantFactory.create('resource_field', { key: 'description', resource_id: resource.id, }); - const accountType = await create('account_type', { name: 'type-name' }); + const accountType = await tenantFactory.create('account_type', { name: 'type-name' }); - const account1 = await create('account', { name: 'ahmed-1' }); - const account2 = await create('account', { name: 'ahmed-2', account_type_id: accountType.id, description: 'target' }); - const account3 = await create('account', { name: 'ahmed-3' }); + const account1 = await tenantFactory.create('account', { name: 'ahmed-1' }); + const account2 = await tenantFactory.create('account', { name: 'ahmed-2', account_type_id: accountType.id, description: 'target' }); + const account3 = await tenantFactory.create('account', { name: 'ahmed-3' }); - const accountsView = await create('view', { + const accountsView = await tenantFactory.create('view', { name: 'Accounts View', resource_id: resource.id, roles_logic_expression: '1', }); - const accountsViewRole = await create('view_role', { + const accountsViewRole = await tenantFactory.create('view_role', { view_id: accountsView.id, field_id: accountTypeField.id, index: 1, @@ -514,6 +526,7 @@ describe('routes: /accounts/', () => { const res = await request() .get('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ custom_view_id: accountsView.id, stringified_filter_roles: JSON.stringify([{ @@ -530,12 +543,13 @@ describe('routes: /accounts/', () => { }); it('Should validate the given `column_sort_order` column on the accounts resource.', async () => { - const resource = await create('resource', { name: 'accounts' }); + const resource = await tenantFactory.create('resource', { name: 'accounts' }); const res = await request() .get('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ - column_sort_order: 'not_found', + column_sort_by: 'not_found', sort_order: 'desc', }); @@ -545,18 +559,19 @@ describe('routes: /accounts/', () => { }); it('Should sorting the given `column_sort_order` column on asc direction,', async () => { - const resource = await create('resource', { name: 'accounts' }); - const resourceField = await create('resource_field', { + const resource = await tenantFactory.create('resource', { name: 'accounts' }); + const resourceField = await tenantFactory.create('resource_field', { key: 'name', resource_id: resource.id, }); - const accounts1 = await create('account', { name: 'A' }); - const accounts2 = await create('account', { name: 'B' }); + const accounts1 = await tenantFactory.create('account', { name: 'A' }); + const accounts2 = await tenantFactory.create('account', { name: 'B' }); const res = await request() .get('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ - column_sort_order: 'name', + column_sort_by: 'name', sort_order: 'asc', }); @@ -567,25 +582,25 @@ describe('routes: /accounts/', () => { }); it('Should sorting the given `column_sort_order` columnw with relation on another table on asc direction.', async () => { - const resource = await create('resource', { name: 'accounts' }); - const resourceField = await create('resource_field', { + const resource = await tenantFactory.create('resource', { name: 'accounts' }); + const resourceField = await tenantFactory.create('resource_field', { key: 'type', resource_id: resource.id, }); - const accounts1 = await create('account', { name: 'A' }); - const accounts2 = await create('account', { name: 'B' }); + const accounts1 = await tenantFactory.create('account', { name: 'A' }); + const accounts2 = await tenantFactory.create('account', { name: 'B' }); const res = await request() .get('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ - column_sort_order: 'name', + column_sort_by: 'name', sort_order: 'asc', }); expect(res.body.accounts[0].name).equals('A'); expect(res.body.accounts[1].name).equals('B'); }); - }); describe('DELETE: `/accounts`', () => { @@ -593,28 +608,31 @@ describe('routes: /accounts/', () => { const res = await request() .delete('/api/accounts/10') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(404); }); it('Should delete the give account from the storage.', async () => { - const account = await create('account'); + const account = await tenantFactory.create('account'); await request() .delete(`/api/accounts/${account.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); - const foundAccounts = await Account.query().where('id', account.id); + const foundAccounts = await Account.tenant().query().where('id', account.id); expect(foundAccounts).to.have.lengthOf(0); }); it('Should not delete the given account in case account has associated transactions.', async () => { - const accountTransaction = await create('account_transaction'); + const accountTransaction = await tenantFactory.create('account_transaction'); const res = await request() .delete(`/api/accounts/${accountTransaction.accountId}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(400); @@ -624,12 +642,12 @@ describe('routes: /accounts/', () => { }); }); - describe('DELETE: `/accounts?ids=`', () => { it('Should response in case on of accounts ids was not exists.', async () => { const res = await request() .delete('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ ids: [100, 200], }) @@ -642,31 +660,33 @@ describe('routes: /accounts/', () => { }); it('Should response bad request in case one of accounts has transactions.', async () => { - const accountTransaction = await create('account_transaction'); - const accountTransaction2 = await create('account_transaction'); + const accountTransaction = await tenantFactory.create('account_transaction'); + const accountTransaction2 = await tenantFactory.create('account_transaction'); const res = await request() .delete('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ ids: [accountTransaction.accountId, accountTransaction2.accountId], }) .send(); expect(res.body.errors).include.something.that.deep.equals({ - type: 'ACCOUNTS.HAS.TRANSACTIONS', + type: 'ACCOUNT.HAS.ASSOCIATED.TRANSACTIONS', code: 300, ids: [accountTransaction.accountId, accountTransaction2.accountId], }); }); it('Should delete the given accounts from the storage.', async () => { - const account1 = await create('account'); - const account2 = await create('account'); + const account1 = await tenantFactory.create('account'); + const account2 = await tenantFactory.create('account'); const res = await request() .delete('/api/accounts') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ ids: [account1.id, account2.id], }) @@ -674,9 +694,66 @@ describe('routes: /accounts/', () => { expect(res.status).equals(200); - const foundAccounts = await Account.query() + const foundAccounts = await Account.tenant().query() .whereIn('id', [account1.id, account2.id]); + expect(foundAccounts.length).equals(0); }); }); + + describe('POST: `/api/accounts/bulk/activate|inactivate', () => { + it('Should response if there one of accounts ids were not found.', async () => { + const res = await request() + .post('/api/accounts/bulk/activate') + .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) + .query({ + ids: [123123, 321321], + }) + .send(); + + expect(res.status).equals(400); + expect(res.body.errors).include.something.that.deep.equals({ + type: 'ACCOUNTS.NOT.FOUND', code: 200, + }); + }); + + it('Should activate all the given accounts.', async () => { + const accountA = await tenantFactory.create('account', { active: 1 }); + const accountB = await tenantFactory.create('account', { active: 1 }); + + const res = await request() + .post('/api/accounts/bulk/inactivate') + .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) + .query({ + ids: [accountA.id, accountB.id], + }) + .send(); + + const updatedAccounts = await Account.tenant().query().whereIn('id', [accountA.id, accountB.id]); + + expect(updatedAccounts[0].active).equals(0); + expect(updatedAccounts[1].active).equals(0); + }); + + it('Should inactivate all the given accounts.', async () => { + const accountA = await tenantFactory.create('account', { active: 0 }); + const accountB = await tenantFactory.create('account', { active: 0 }); + + const res = await request() + .post('/api/accounts/bulk/activate') + .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) + .query({ + ids: [accountA.id, accountB.id], + }) + .send(); + + const updatedAccounts = await Account.tenant().query().whereIn('id', [accountA.id, accountB.id]); + + expect(updatedAccounts[0].active).equals(1); + expect(updatedAccounts[1].active).equals(1); + }); + }); }); diff --git a/server/tests/routes/auth.test.js b/server/tests/routes/auth.test.js index 14fc9e2e6..fffeeaf30 100644 --- a/server/tests/routes/auth.test.js +++ b/server/tests/routes/auth.test.js @@ -1,6 +1,16 @@ -import { request, expect, create } from '~/testInit'; +import { request, expect, createUser } from '~/testInit'; import { hashPassword } from '@/utils'; import knex from '@/database/knex'; +import { + tenantWebsite, + tenantFactory, + systemFactory, + loginRes +} from '~/dbInit'; +import TenantUser from '@/models/TenantUser'; +import PasswordReset from '@/system/models/PasswordReset'; +import SystemUser from '@/system/models/SystemUser'; + describe('routes: /auth/', () => { describe('POST `/api/auth/login`', () => { @@ -59,33 +69,35 @@ describe('routes: /auth/', () => { }); it('Should not authenticate in case user was not active.', async () => { - const user = await create('user', { active: false }); - const res = await request().post('/api/auth/login').send({ - crediential: user.email, - password: 'incorrect_password', + const user = await createUser(tenantWebsite, { + active: false, + email: 'admin@admin.com', }); + const res = await request().post('/api/auth/login').send({ + crediential: 'admin@admin.com', + password: 'admin', + }); expect(res.status).equals(400); expect(res.body.errors).include.something.that.deep.equals({ - type: 'INCORRECT_PASSWORD', code: 110, + type: 'USER_INACTIVE', code: 110, }); }); it('Should authenticate with correct email and password and active user.', async () => { - const user = await create('user', { - password: hashPassword('admin'), + const user = await createUser(tenantWebsite, { + email: 'admin@admin.com', }); const res = await request().post('/api/auth/login').send({ crediential: user.email, password: 'admin', }); - expect(res.status).equals(200); }); it('Should autheticate success with correct phone number and password.', async () => { const password = await hashPassword('admin'); - const user = await create('user', { + const user = await createUser(tenantWebsite, { phone_number: '0920000000', password, }); @@ -98,15 +110,20 @@ describe('routes: /auth/', () => { }); it('Should last login date be saved after success login.', async () => { - const user = await create('user', { - password: hashPassword('admin'), + const user = await createUser(tenantWebsite, { + email: 'admin@admin.com', }); const res = await request().post('/api/auth/login').send({ crediential: user.email, password: 'admin', }); - + const foundUserAfterUpdate = await TenantUser.tenant().query() + .where('email', user.email) + .where('first_name', user.first_name) + .first(); + expect(res.status).equals(200); + expect(foundUserAfterUpdate.lastLoginAt).to.not.be.null; }); }); @@ -132,17 +149,18 @@ describe('routes: /auth/', () => { email: 'admin@admin.com', }); - expect(res.status).equals(422); + expect(res.status).equals(400); expect(res.body.errors).include.something.that.deep.equals({ - type: 'EMAIL_NOT_FOUND', code: 100, + type: 'EMAIL.NOT.REGISTERED', code: 200, }); }); it('Should delete all already tokens that associate to the given email.', async () => { - const user = await create('user'); + const user = await createUser(tenantWebsite); const token = '123123'; await knex('password_resets').insert({ email: user.email, token }); + await request().post('/api/auth/send_reset_password').send({ email: user.email, }); @@ -153,7 +171,7 @@ describe('routes: /auth/', () => { }); it('Should store new token associate with the given email.', async () => { - const user = await create('user'); + const user = await createUser(tenantWebsite); await request().post('/api/auth/send_reset_password').send({ email: user.email, }); @@ -164,7 +182,7 @@ describe('routes: /auth/', () => { }); it('Should response success if the email was exist.', async () => { - const user = await create('user'); + const user = await createUser(tenantWebsite); const res = await request().post('/api/auth/send_reset_password').send({ email: user.email, }); @@ -183,63 +201,88 @@ describe('routes: /auth/', () => { }); it('Should `password` be required.', async () => { - const passwordReset = await create('password_reset'); - const res = await request().post(`/api/reset/${passwordReset.token}`).send(); + const user = await createUser(tenantWebsite); + const passwordReset = await systemFactory.create('password_reset', { + email: user.email, + }); + + const res = await request() + .post(`/api/auth/reset/${passwordReset.token}`) + .send(); expect(res.status).equals(422); - expect(res.body.code).equals('VALIDATION_ERROR'); + expect(res.body.code).equals('validation_error'); const paramsErrors = res.body.errors.map((error) => error.param); expect(paramsErrors).to.include('password'); }); it('Should password and confirm_password be equal.', async () => { - const passwordReset = await create('password_reset'); - const res = await request().post(`/api/reset/${passwordReset.token}`).send({ - password: '123123', + const user = await createUser(tenantWebsite); + const passwordReset = await systemFactory.create('password_reset', { + email: user.email, }); + const res = await request() + .post(`/api/auth/reset/${passwordReset.token}`) + .send({ + password: '123123', + }); + expect(res.status).equals(422); - expect(res.body.code).equals('VALIDATION_ERROR'); + expect(res.body.code).equals('validation_error'); const paramsErrors = res.body.errors.map((error) => error.param); expect(paramsErrors).to.include('password'); }); it('Should response success with correct data form.', async () => { - const passwordReset = await create('password_reset'); - const res = await request().post(`/api/reset/${passwordReset.token}`).send({ - password: '123123', - confirm_password: '123123', + const user = await createUser(tenantWebsite); + const passwordReset = await systemFactory.create('password_reset', { + email: user.email, }); + const res = await request() + .post(`/api/auth/reset/${passwordReset.token}`) + .send({ + password: '123123', + confirm_password: '123123', + }); expect(res.status).equals(200); }); it('Should token be deleted after success response.', async () => { - const passwordReset = await create('password_reset'); - await request().post(`/api/reset/${passwordReset.token}`).send({ - password: '123123', - confirm_password: '123123', + const user = await createUser(tenantWebsite); + const passwordReset = await systemFactory.create('password_reset', { + email: user.email, }); + await request() + .post(`/api/auth/reset/${passwordReset.token}`) + .send({ + password: '123123', + confirm_password: '123123', + }); + + const foundTokens = await PasswordReset.query().where('email', passwordReset.email); - const foundTokens = await knex('password_resets').where('email', passwordReset.email); expect(foundTokens).to.have.lengthOf(0); }); it('Should password be updated after success response.', async () => { - const user = await create('user'); - const passwordReset = await create('password_reset', { user_id: user.id }); + const user = await createUser(tenantWebsite); + const passwordReset = await systemFactory.create('password_reset', { + email: user.email, + }); - await request().post(`/api/reset/${passwordReset.token}`).send({ + const res = await request().post(`/api/auth/reset/${passwordReset.token}`).send({ password: '123123', confirm_password: '123123', }); + const systemUserPasswordUpdated = await SystemUser.query() + .where('id', user.id).first(); - const foundUser = await knex('users').where('id', user.id); - - expect(foundUser.id).equals(user.id); - expect(foundUser.password).not.equals(user.password); + expect(systemUserPasswordUpdated.id).equals(user.id); + expect(systemUserPasswordUpdated.password).not.equals(user.password); }); }); }); diff --git a/server/tests/routes/currencies.test.js b/server/tests/routes/currencies.test.js index 8931cd9b4..2359d79a3 100644 --- a/server/tests/routes/currencies.test.js +++ b/server/tests/routes/currencies.test.js @@ -1,36 +1,31 @@ import { request, - create, expect, - login, } from '~/testInit'; import Currency from '@/models/Currency'; +import { + tenantWebsite, + tenantFactory, + loginRes +} from '~/dbInit'; -let loginRes; describe('route: /currencies/', () => { - beforeEach(async () => { - loginRes = await login(); - }); - afterEach(() => { - loginRes = null; - }); - describe('POST: `/api/currencies`', () => { - it('Should response unauthorized in case user was not logged in.', async () => { const res = await request() .post('/api/currencies') .send(); expect(res.status).equals(401); - expect(res.body.message).equals('unauthorized'); + expect(res.body.message).equals('Unauthorized'); }); it('Should `currency_name` be required.', async () => { const res = await request() .post('/api/currencies') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -44,6 +39,7 @@ describe('route: /currencies/', () => { const res = await request() .post('/api/currencies') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -54,11 +50,12 @@ describe('route: /currencies/', () => { }); it('Should response currency code is duplicated.', async () => { - create('currency', { currency_code: 'USD' }); + tenantFactory.create('currency', { currency_code: 'USD' }); const res = await request() .post('/api/currencies') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ currency_code: 'USD', currency_name: 'Dollar', @@ -74,12 +71,13 @@ describe('route: /currencies/', () => { const res = await request() .post('/api/currencies') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ currency_code: 'USD', currency_name: 'Dollar', }); - const foundCurrency = await Currency.query().where('currency_code', 'USD'); + const foundCurrency = await Currency.tenant().query().where('currency_code', 'USD'); expect(foundCurrency.length).equals(1); expect(foundCurrency[0].currencyCode).equals('USD'); @@ -90,6 +88,7 @@ describe('route: /currencies/', () => { const res = await request() .post('/api/currencies') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ currency_code: 'USD', currency_name: 'Dollar', @@ -100,27 +99,28 @@ describe('route: /currencies/', () => { }); describe('DELETE: `/api/currencies/:currency_code`', () => { - it('Should delete the given currency code from the storage.', async () => { - const currency = await create('currency'); + const currency = await tenantFactory.create('currency'); const res = await request() .delete(`/api/currencies/${currency.currencyCode}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(200); - const foundCurrency = await Currency.query().where('currency_code', 'USD'); + const foundCurrency = await Currency.tenant().query().where('currency_code', 'USD'); expect(foundCurrency.length).equals(0); }); }); describe('POST: `/api/currencies/:id`', () => { it('Should `currency_name` be required.', async () => { - const currency = await create('currency'); + const currency = await tenantFactory.create('currency'); const res = await request() .post(`/api/currencies/${currency.code}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -131,10 +131,11 @@ describe('route: /currencies/', () => { }); it('Should `currency_code` be required.', async () => { - const currency = await create('currency'); + const currency = await tenantFactory.create('currency'); const res = await request() .post(`/api/currencies/${currency.code}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -145,12 +146,13 @@ describe('route: /currencies/', () => { }); it('Should response currency code is duplicated.', async () => { - const currency1 = await create('currency'); - const currency2 = await create('currency'); + const currency1 = await tenantFactory.create('currency'); + const currency2 = await tenantFactory.create('currency'); const res = await request() .post(`/api/currencies/${currency2.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ currency_code: currency1.currencyCode, currency_name: 'Dollar', @@ -163,18 +165,19 @@ describe('route: /currencies/', () => { }); it('Should update currency details of the given currency on the storage.', async () => { - const currency1 = await create('currency'); - const currency2 = await create('currency'); + const currency1 = await tenantFactory.create('currency'); + const currency2 = await tenantFactory.create('currency'); const res = await request() .post(`/api/currencies/${currency2.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ currency_code: 'ABC', currency_name: 'Name', }); - const foundCurrency = await Currency.query().where('currency_code', 'ABC'); + const foundCurrency = await Currency.tenant().query().where('currency_code', 'ABC'); expect(foundCurrency.length).equals(1); expect(foundCurrency[0].currencyCode).equals('ABC'); diff --git a/server/tests/routes/exchange_rates.test.js b/server/tests/routes/exchange_rates.test.js index 043627ce1..c655ab316 100644 --- a/server/tests/routes/exchange_rates.test.js +++ b/server/tests/routes/exchange_rates.test.js @@ -1,21 +1,17 @@ import moment from 'moment'; import { request, - create, expect, - login, } from '~/testInit'; import ExchangeRate from '../../src/models/ExchangeRate'; +import { + tenantWebsite, + tenantFactory, + loginRes +} from '~/dbInit'; -let loginRes; -describe.only('route: /exchange_rates/', () => { - beforeEach(async () => { - loginRes = await login(); - }); - afterEach(() => { - loginRes = null; - }); +describe('route: /exchange_rates/', () => { describe('POST: `/api/exchange_rates`', () => { it('Should response unauthorized in case the user was not logged in.', async () => { const res = await request() @@ -23,13 +19,14 @@ describe.only('route: /exchange_rates/', () => { .send(); expect(res.status).equals(401); - expect(res.body.message).equals('unauthorized'); + expect(res.body.message).equals('Unauthorized'); }); it('Should `currency_code` be required.', async () => { const res = await request() .post('/api/exchange_rates') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -43,6 +40,7 @@ describe.only('route: /exchange_rates/', () => { const res = await request() .post('/api/exchange_rates') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -56,6 +54,7 @@ describe.only('route: /exchange_rates/', () => { const res = await request() .post('/api/exchange_rates') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -66,7 +65,7 @@ describe.only('route: /exchange_rates/', () => { }); it('Should response date and currency code is already exists.', async () => { - await create('exchange_rate', { + await tenantFactory.create('exchange_rate', { date: '2020-02-02', currency_code: 'USD', exchange_rate: 4.4, @@ -74,6 +73,7 @@ describe.only('route: /exchange_rates/', () => { const res = await request() .post('/api/exchange_rates') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ date: '2020-02-02', currency_code: 'USD', @@ -90,6 +90,7 @@ describe.only('route: /exchange_rates/', () => { const res = await request() .post('/api/exchange_rates') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ date: '2020-02-02', currency_code: 'USD', @@ -97,7 +98,7 @@ describe.only('route: /exchange_rates/', () => { }); expect(res.status).equals(200); - const foundExchangeRate = await ExchangeRate.query() + const foundExchangeRate = await ExchangeRate.tenant().query() .where('currency_code', 'USD'); expect(foundExchangeRate.length).equals(1); @@ -111,13 +112,14 @@ describe.only('route: /exchange_rates/', () => { describe('GET: `/api/exchange_rates', () => { it('Should retrieve all exchange rates with pagination meta.', async () => { - await create('exchange_rate'); - await create('exchange_rate'); - await create('exchange_rate'); + await tenantFactory.create('exchange_rate'); + await tenantFactory.create('exchange_rate'); + await tenantFactory.create('exchange_rate'); const res = await request() .get('/api/exchange_rates') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(200); @@ -130,6 +132,7 @@ describe.only('route: /exchange_rates/', () => { const res = await request() .post('/api/exchange_rates/100') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ date: '2020-02-02', currency_code: 'USD', @@ -143,16 +146,17 @@ describe.only('route: /exchange_rates/', () => { }); it('Should update exchange rate of the given id on the storage.', async () => { - const exRate = await create('exchange_rate'); + const exRate = await tenantFactory.create('exchange_rate'); const res = await request() .post(`/api/exchange_rates/${exRate.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ exchange_rate: 4.4, }); expect(res.status).equals(200); - const foundExchangeRate = await ExchangeRate.query() + const foundExchangeRate = await ExchangeRate.tenant().query() .where('id', exRate.id); expect(foundExchangeRate.length).equals(1); @@ -165,7 +169,9 @@ describe.only('route: /exchange_rates/', () => { const res = await request() .delete('/api/exchange_rates/100') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); + expect(res.status).equals(404); expect(res.body.errors).include.something.deep.equals({ type: 'EXCHANGE.RATE.NOT.FOUND', code: 200, @@ -173,14 +179,52 @@ describe.only('route: /exchange_rates/', () => { }); it('Should delete the given exchange rate id from the storage.', async () => { - const exRate = await create('exchange_rate'); + const exRate = await tenantFactory.create('exchange_rate'); const res = await request() .delete(`/api/exchange_rates/${exRate.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); - const foundRates = await ExchangeRate.query(); + const foundRates = await ExchangeRate.tenant().query(); expect(foundRates.length).equals(0); }); }); + + describe('DELETE: `/api/exchange_rates/bulk`', () => { + it('Should response the given exchange rates ids where not found.', async () => { + const res = await request() + .delete('/api/exchange_rates/bulk') + .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) + .query({ + ids: [12332, 32432], + }) + .send(); + + expect(res.status).equals(400); + expect(res.body.errors).include.something.deep.equals({ + type: 'EXCHANGE.RATES.IS.NOT.FOUND', code: 200, ids: [12332, 32432], + }) + }); + + it('Should delete the given excahnge rates ids.', async () => { + const exRate = await tenantFactory.create('exchange_rate'); + const exRate2 = await tenantFactory.create('exchange_rate'); + + const res = await request() + .delete('/api/exchange_rates/bulk') + .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) + .query({ + ids: [exRate.id, exRate2.id], + }) + .send(); + + const foundExchangeRate = await ExchangeRate.tenant().query() + .whereIn('id', [exRate.id, exRate2.id]); + + expect(foundExchangeRate.length).equals(0); + }) + }); }); diff --git a/server/tests/routes/financial_statements.test.js b/server/tests/routes/financial_statements.test.js index c1d5afae2..004d96e90 100644 --- a/server/tests/routes/financial_statements.test.js +++ b/server/tests/routes/financial_statements.test.js @@ -1,57 +1,68 @@ import moment from 'moment'; import { - expect, request, + expect, login, - create, + createTenantFactory, + createTenant, + dropTenant, } from '~/testInit'; +let tenantWebsite; +let tenantFactory; let loginRes; let creditAccount; let debitAccount; describe('routes: `/financial_statements`', () => { beforeEach(async () => { - loginRes = await login(); + tenantWebsite = await createTenant(); + tenantFactory = createTenantFactory(tenantWebsite.tenantDb); + + loginRes = await login(tenantWebsite); // Balance sheet types. - const creditAccType = await create('account_type', { normal: 'credit', balance_sheet: true }); - const debitAccType = await create('account_type', { normal: 'debit', balance_sheet: true }); + const creditAccType = await tenantFactory.create('account_type', { normal: 'credit', balance_sheet: true }); + const debitAccType = await tenantFactory.create('account_type', { normal: 'debit', balance_sheet: true }); // Income statement types. - const incomeType = await create('account_type', { normal: 'credit', income_sheet: true }); - const expenseType = await create('account_type', { normal: 'debit', income_sheet: true }); + const incomeType = await tenantFactory.create('account_type', { normal: 'credit', income_sheet: true }); + const expenseType = await tenantFactory.create('account_type', { normal: 'debit', income_sheet: true }); // Assets & liabilites accounts. - creditAccount = await create('account', { account_type_id: creditAccType.id }); - debitAccount = await create('account', { account_type_id: debitAccType.id }); + creditAccount = await tenantFactory.create('account', { account_type_id: creditAccType.id }); + debitAccount = await tenantFactory.create('account', { account_type_id: debitAccType.id }); // Income && expenses accounts. - const incomeAccount = await create('account', { account_type_id: incomeType.id }); - const expenseAccount = await create('account', { account_type_id: expenseType.id }); - const income2Account = await create('account', { account_type_id: incomeType.id }); + const incomeAccount = await tenantFactory.create('account', { account_type_id: incomeType.id }); + const expenseAccount = await tenantFactory.create('account', { account_type_id: expenseType.id }); + const income2Account = await tenantFactory.create('account', { account_type_id: incomeType.id }); const accountTransactionMixied = { date: '2020-1-10' }; - await create('account_transaction', { + await tenantFactory.create('account_transaction', { credit: 1000, debit: 0, account_id: creditAccount.id, referenceType: 'Expense', ...accountTransactionMixied, }); - await create('account_transaction', { + await tenantFactory.create('account_transaction', { credit: 1000, debit: 0, account_id: creditAccount.id, ...accountTransactionMixied, }); - await create('account_transaction', { + await tenantFactory.create('account_transaction', { debit: 2000, credit: 0, account_id: debitAccount.id, ...accountTransactionMixied, }); - await create('account_transaction', { + await tenantFactory.create('account_transaction', { debit: 2000, credit: 0, account_id: debitAccount.id, ...accountTransactionMixied, }); - await create('account_transaction', { credit: 2000, account_id: incomeAccount.id, ...accountTransactionMixied }); - await create('account_transaction', { debit: 6000, account_id: expenseAccount.id, ...accountTransactionMixied }); + await tenantFactory.create('account_transaction', { credit: 2000, account_id: incomeAccount.id, ...accountTransactionMixied }); + await tenantFactory.create('account_transaction', { debit: 6000, account_id: expenseAccount.id, ...accountTransactionMixied }); }); - afterEach(() => { + afterEach(async () => { + await dropTenant(tenantWebsite); + loginRes = null; + tenantFactory = null; }); + describe('routes: `/financial_statements/journal`', () => { it('Should response unauthorized in case the user was not authorized.', async () => { const res = await request() @@ -65,6 +76,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/journal') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(200); @@ -83,6 +95,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/journal') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_date: '2018-01-01', to_date: '2019-01-01', @@ -96,6 +109,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/journal') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ account_ids: [creditAccount.id], }) @@ -108,6 +122,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/journal') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ transaction_types: ['Expense'], }); @@ -119,6 +134,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/journal') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_range: 2000, to_range: 2000, @@ -140,6 +156,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/journal') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ number_format: { divide_1000: true, @@ -167,6 +184,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/general_ledger') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.body.query.from_date).equals(moment().startOf('year').format('YYYY-MM-DD')); @@ -182,6 +200,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/general_ledger') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.body.accounts).is.an('array'); @@ -201,6 +220,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/general_ledger') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); const targetAccount = res.body.accounts.find((a) => a.id === creditAccount.id); @@ -218,6 +238,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/general_ledger') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_date: '2020-01-20', to_date: '2020-03-30', @@ -240,6 +261,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/general_ledger') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ none_zero: true, }) @@ -262,6 +284,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/general_ledger') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_date: '2020-01-20', to_date: '2020-03-30', @@ -277,6 +300,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/general_ledger') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_date: '2020-01-20', to_date: '2020-03-30', @@ -293,6 +317,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/general_ledger') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_date: '2020-01-01', to_date: '2020-03-30', @@ -311,6 +336,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/general_ledger') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_date: '2020-01-01', to_date: '2020-03-30', @@ -326,13 +352,14 @@ describe('routes: `/financial_statements`', () => { }); it('Should amount transactions rounded with no decimals when `number_format.no_cents` is `true`.', async () => { - await create('account_transaction', { + await tenantFactory.create('account_transaction', { debit: 0.25, credit: 0, account_id: debitAccount.id, date: '2020-1-10', }); const res = await request() .get('/api/financial_statements/general_ledger') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_date: '2020-01-01', to_date: '2020-03-30', @@ -351,6 +378,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/general_ledger') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_date: '2020-01-01', to_date: '2020-03-30', @@ -376,6 +404,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/balance_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ display_columns_by: 'year', from_date: '2020-01-01', @@ -397,6 +426,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/balance_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ display_columns_by: 'year', }) @@ -410,6 +440,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/balance_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ display_columns_type: 'total', from_date: '2012-01-01', @@ -431,6 +462,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/balance_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ display_columns_by: 'year', from_date: '2012-01-01', @@ -484,6 +516,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/balance_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ display_columns_by: 'day', from_date: '2020-01-08', @@ -504,6 +537,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/balance_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ display_columns_by: 'month', from_date: '2019-07-01', @@ -534,6 +568,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/balance_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ display_columns_by: 'quarter', from_date: '2020-01-01', @@ -551,12 +586,13 @@ describe('routes: `/financial_statements`', () => { }); it('Should retrieve the balance sheet amounts without cents.', async () => { - await create('account_transaction', { + await tenantFactory.create('account_transaction', { debit: 0.25, credit: 0, account_id: debitAccount.id, date: '2020-1-10', }); const res = await request() .get('/api/financial_statements/balance_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ display_columns_by: 'quarter', from_date: '2020-01-01', @@ -580,6 +616,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/balance_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ display_columns_by: 'quarter', from_date: '2020', @@ -603,6 +640,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/balance_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ display_columns_by: 'quarter', from_date: '2002', @@ -632,6 +670,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/trial_balance_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); const foundCreditAccount = res.body.items.find((item) => { @@ -646,6 +685,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/trial_balance_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ // There is no transactions between these dates. from_date: '2002-01-01', @@ -661,6 +701,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/trial_balance_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ // There is no transactions between these dates. from_date: '2020-01-05', @@ -680,6 +721,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/trial_balance_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ // There is no transactions between these dates. from_date: '2020-01-05', @@ -702,6 +744,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/trial_balance_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ // There is no transactions between these dates. from_date: '2020-01-05', @@ -732,6 +775,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/profit_loss_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_date: '2020-01-01', to_date: '2020-12-12', @@ -755,6 +799,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/profit_loss_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_date: moment().startOf('year').format('YYYY-MM-DD'), to_date: moment().endOf('year').format('YYYY-MM-DD'), @@ -773,6 +818,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/profit_loss_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_date: moment('2020-01-01').startOf('month').format('YYYY-MM-DD'), to_date: moment('2020-01-01').endOf('month').format('YYYY-MM-DD'), @@ -801,6 +847,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/profit_loss_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_date: moment('2020-01-01').startOf('month').format('YYYY-MM-DD'), to_date: moment('2020-01-01').endOf('month').format('YYYY-MM-DD'), @@ -818,6 +865,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/profit_loss_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_date: moment('2020-01-01').startOf('month').format('YYYY-MM-DD'), to_date: toDate, @@ -838,6 +886,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/profit_loss_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_date: '2020-01-01', to_date: '2021-01-01', @@ -854,6 +903,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/profit_loss_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_date: '2020-01-01', to_date: '2021-01-01', @@ -870,6 +920,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/profit_loss_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_date: '2019-12-01', to_date: '2020-12-01', @@ -887,6 +938,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/profit_loss_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_date: '2019-12-01', to_date: '2020-12-01', @@ -904,6 +956,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/profit_loss_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_date: '2019-12-01', to_date: '2020-12-01', @@ -920,6 +973,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/profit_loss_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_date: '2019-12-01', to_date: '2020-12-01', @@ -942,6 +996,7 @@ describe('routes: `/financial_statements`', () => { const res = await request() .get('/api/financial_statements/profit_loss_sheet') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ from_date: '2020-01-01', to_date: '2021-01-01', diff --git a/server/tests/routes/items.test.js b/server/tests/routes/items.test.js index 1f5aa2936..c2bf550aa 100644 --- a/server/tests/routes/items.test.js +++ b/server/tests/routes/items.test.js @@ -1,21 +1,16 @@ import { request, - create, expect, - login, } from '~/testInit'; -import knex from '@/database/knex'; import Item from '@/models/Item'; +import { + tenantWebsite, + tenantFactory, + loginRes +} from '~/dbInit'; -let loginRes; describe('routes: `/items`', () => { - beforeEach(async () => { - loginRes = await login(); - }); - afterEach(() => { - loginRes = null; - }); describe('POST: `/items`', () => { it('Should not create a new item if the user was not authorized.', async () => { const res = await request() @@ -23,21 +18,14 @@ describe('routes: `/items`', () => { .send(); expect(res.status).equals(401); - expect(res.body.message).equals('unauthorized'); - }); - - it('Should user have create permission to create a new item.', async () => { - const loginRes = await login(); - const res = await request().post('/api/items') - .set('x-access-token', loginRes.body.token).send(); - - expect(res.status).equals(401); + expect(res.body.message).equals('Unauthorized'); }); it('Should `name` be required.', async () => { const res = await request() .post('/api/items') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -51,6 +39,7 @@ describe('routes: `/items`', () => { const res = await request() .post('/api/items') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -64,6 +53,7 @@ describe('routes: `/items`', () => { const res = await request() .post('/api/items') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ type: 'not-defined', }); @@ -80,6 +70,7 @@ describe('routes: `/items`', () => { const res = await request() .post('/api/items') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ cost_price: 'not_numeric', }); @@ -98,6 +89,7 @@ describe('routes: `/items`', () => { const res = await request() .post('/api/items') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ sell_price: 'not_numeric', }); @@ -116,6 +108,7 @@ describe('routes: `/items`', () => { const res = await request() .post('/api/items') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ cost_account_id: 'not_numeric', }); @@ -134,6 +127,7 @@ describe('routes: `/items`', () => { const res = await request() .post('/api/items') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ sell_account_id: 'not_numeric', }); @@ -160,6 +154,7 @@ describe('routes: `/items`', () => { const res = await request() .post('/api/items') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'Item Name', type: 'inventory', @@ -180,6 +175,7 @@ describe('routes: `/items`', () => { const res = await request() .post('/api/items') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'Item Name', type: 'service', @@ -200,6 +196,7 @@ describe('routes: `/items`', () => { const res = await request() .post('/api/items') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'Item Name', type: 'service', @@ -219,6 +216,7 @@ describe('routes: `/items`', () => { const res = await request() .post('/api/items') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'Item Name', type: 'service', @@ -238,6 +236,7 @@ describe('routes: `/items`', () => { const res = await request() .post('/api/items') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'Item Name', type: 'service', @@ -255,13 +254,14 @@ describe('routes: `/items`', () => { }); it('Should response success with correct data format.', async () => { - const account = await create('account'); - const anotherAccount = await create('account'); - const itemCategory = await create('item_category'); + const account = await tenantFactory.create('account'); + const anotherAccount = await tenantFactory.create('account'); + const itemCategory = await tenantFactory.create('item_category'); const res = await request() .post('/api/items') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'Item Name', type: 'service', @@ -274,6 +274,42 @@ describe('routes: `/items`', () => { expect(res.status).equals(200); }); + + it('Should store the given item details to the storage.', async () => { + const account = await tenantFactory.create('account'); + const anotherAccount = await tenantFactory.create('account'); + const itemCategory = await tenantFactory.create('item_category'); + + const res = await request() + .post('/api/items') + .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) + .send({ + name: 'Item Name', + type: 'service', + sku: 'SKU CODE', + sell_price: 10.2, + cost_price: 20.2, + sell_account_id: account.id, + cost_account_id: anotherAccount.id, + category_id: itemCategory.id, + note: 'note about item' + }); + + const storedItem = await Item.tenant().query().where('id', res.body.id).first(); + + expect(storedItem.name).equals('Item Name'); + expect(storedItem.type).equals('service'); + + expect(storedItem.sellPrice).equals(10.2); + expect(storedItem.costPrice).equals(20.2); + expect(storedItem.sellAccountId).equals(account.id); + expect(storedItem.costAccountId).equals(anotherAccount.id); + expect(storedItem.categoryId).equals(itemCategory.id); + expect(storedItem.sku).equals('SKU CODE'); + expect(storedItem.note).equals('note about item'); + expect(storedItem.userId).is.not.null; + }); }); describe('POST: `items/:id`', () => { @@ -281,6 +317,7 @@ describe('routes: `/items`', () => { const res = await request() .post('/api/items/100') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'Item Name', type: 'product', @@ -298,10 +335,11 @@ describe('routes: `/items`', () => { }); it('Should `name` be required.', async () => { - const item = await create('item'); + const item = await tenantFactory.create('item'); const res = await request() .post(`/api/items/${item.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -312,10 +350,11 @@ describe('routes: `/items`', () => { }); it('Should `type` be required.', async () => { - const item = await create('item'); + const item = await tenantFactory.create('item'); const res = await request() .post(`/api/items/${item.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -326,10 +365,11 @@ describe('routes: `/items`', () => { }); it('Should `sell_price` be numeric.', async () => { - const item = await create('item'); + const item = await tenantFactory.create('item'); const res = await request() .post(`/api/items/${item.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ sell_price: 'not_numeric', }); @@ -345,10 +385,11 @@ describe('routes: `/items`', () => { }); it('Should `cost_price` be numeric.', async () => { - const item = await create('item'); + const item = await tenantFactory.create('item'); const res = await request() .post(`/api/items/${item.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ cost_price: 'not_numeric', }); @@ -364,10 +405,11 @@ describe('routes: `/items`', () => { }); it('Should `sell_account_id` be integer.', async () => { - const item = await create('item'); + const item = await tenantFactory.create('item'); const res = await request() .post(`/api/items/${item.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ sell_account_id: 'not_numeric', }); @@ -383,10 +425,11 @@ describe('routes: `/items`', () => { }); it('Should `cost_account_id` be integer.', async () => { - const item = await create('item'); + const item = await tenantFactory.create('item'); const res = await request() .post(`/api/items/${item.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ cost_account_id: 'not_numeric', }); @@ -401,11 +444,12 @@ describe('routes: `/items`', () => { }); }); - it('Should response bad request in case cost account was not exist.', async () => { - const item = await create('item'); + it ('Should response bad request in case cost account was not exist.', async () => { + const item = await tenantFactory.create('item'); const res = await request() .post(`/api/items/${item.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'Item Name', type: 'service', @@ -422,17 +466,18 @@ describe('routes: `/items`', () => { }); it('Should response bad request in case sell account was not exist.', async () => { - const item = await create('item'); + const item = await tenantFactory.create('item'); const res = await request() .post(`/api/items/${item.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'Item Name', type: 'product', sell_price: 10.2, cost_price: 20.2, - sell_account_id: 10, - cost_account_id: 20, + sell_account_id: 1000000, + cost_account_id: 1000000, }); expect(res.status).equals(400); @@ -442,14 +487,15 @@ describe('routes: `/items`', () => { }); it('Should update details of the given item.', async () => { - const account = await create('account'); - const anotherAccount = await create('account'); - const itemCategory = await create('item_category'); + const account = await tenantFactory.create('account'); + const anotherAccount = await tenantFactory.create('account'); + const itemCategory = await tenantFactory.create('item_category'); - const item = await create('item'); + const item = await tenantFactory.create('item'); const res = await request() .post(`/api/items/${item.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'New Item Name', type: 'service', @@ -460,7 +506,7 @@ describe('routes: `/items`', () => { category_id: itemCategory.id, }); - const updatedItem = await Item.query().findById(item.id); + const updatedItem = await Item.tenant().query().findById(item.id); expect(updatedItem.name).equals('New Item Name'); expect(updatedItem.type).equals('service'); @@ -477,29 +523,32 @@ describe('routes: `/items`', () => { const res = await request() .delete('/api/items/10') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(404); }); it('Should response success in case was exist.', async () => { - const item = await create('item'); + const item = await tenantFactory.create('item'); const res = await request() .delete(`/api/items/${item.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(200); }); it('Should delete the given item from the storage.', async () => { - const item = await create('item'); + const item = await tenantFactory.create('item'); await request() .delete(`/api/items/${item.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); - const storedItems = await Item.query().where('id', item.id); + const storedItems = await Item.tenant().query().where('id', item.id); expect(storedItems).to.have.lengthOf(0); }); }); @@ -511,31 +560,17 @@ describe('routes: `/items`', () => { .send(); expect(res.status).equals(401); - expect(res.body.message).equals('unauthorized'); - }); - - it('Should response items resource not found.', async () => { - await create('item'); - - const res = await request() - .get('/api/items') - .set('x-access-token', loginRes.body.token) - .send(); - - expect(res.status).equals(400); - expect(res.body.errors).include.something.that.deep.equal({ - type: 'ITEMS_RESOURCE_NOT_FOUND', - code: 200, - }); + expect(res.body.message).equals('Unauthorized'); }); it('Should retrieve items list with associated accounts.', async () => { - await create('resource', { name: 'items' }); - await create('item'); + await tenantFactory.create('resource', { name: 'items' }); + await tenantFactory.create('item'); const res = await request() .get('/api/items') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(200); @@ -551,12 +586,13 @@ describe('routes: `/items`', () => { }); it('Should retrieve ordered items based on the given `column_sort_order` and `sort_order` query.', async () => { - await create('item', { name: 'ahmed' }); - await create('item', { name: 'mohamed' }); + await tenantFactory.create('item', { name: 'ahmed' }); + await tenantFactory.create('item', { name: 'mohamed' }); const res = await request() .get('/api/items') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ column_sort_order: 'name', sort_order: 'desc', @@ -569,11 +605,12 @@ describe('routes: `/items`', () => { }); it('Should retrieve pagination meta of items list.', async () => { - await create('resource', { name: 'items' }); + await tenantFactory.create('resource', { name: 'items' }); const res = await request() .get('/api/items') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.body.items.results).to.be.a('array'); @@ -584,26 +621,27 @@ describe('routes: `/items`', () => { }); it('Should retrieve filtered items based on custom view conditions.', async () => { - const item1 = await create('item', { type: 'service' }); - const item2 = await create('item', { type: 'service' }); - const item3 = await create('item', { type: 'inventory' }); - const item4 = await create('item', { type: 'inventory' }); + const item1 = await tenantFactory.create('item', { type: 'service' }); + const item2 = await tenantFactory.create('item', { type: 'service' }); + const item3 = await tenantFactory.create('item', { type: 'inventory' }); + const item4 = await tenantFactory.create('item', { type: 'inventory' }); - const view = await create('view', { + const view = await tenantFactory.create('view', { name: 'Items Inventory', resource_id: 2, roles_logic_expression: '1', }); - const viewCondition = await create('view_role', { + const viewCondition = await tenantFactory.create('view_role', { view_id: view.id, index: 1, - field_id: 11, + field_id: 12, value: 'inventory', comparator: 'equals', }); const res = await request() .get('/api/items') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ custom_view_id: view.id, }) @@ -618,14 +656,15 @@ describe('routes: `/items`', () => { }); it('Should retrieve filtered items based on filtering conditions.', async () => { - const item1 = await create('item', { type: 'service' }); - const item2 = await create('item', { type: 'service', name: 'target' }); - const item3 = await create('item', { type: 'inventory' }); - const item4 = await create('item', { type: 'inventory' }); + const item1 = await tenantFactory.create('item', { type: 'service' }); + const item2 = await tenantFactory.create('item', { type: 'service', name: 'target' }); + const item3 = await tenantFactory.create('item', { type: 'inventory' }); + const item4 = await tenantFactory.create('item', { type: 'inventory' }); const res = await request() .get('/api/items') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ stringified_filter_roles: JSON.stringify([ { diff --git a/server/tests/routes/itemsCategories.test.js b/server/tests/routes/itemsCategories.test.js index 8597b7a69..a8d23f6c2 100644 --- a/server/tests/routes/itemsCategories.test.js +++ b/server/tests/routes/itemsCategories.test.js @@ -1,33 +1,28 @@ import { request, expect, - create, - login, } from '~/testInit'; -import knex from '@/database/knex'; - -let loginRes; +import ItemCategory from '@/models/ItemCategory'; +import { + tenantWebsite, + tenantFactory, + loginRes +} from '~/dbInit'; describe('routes: /item_categories/', () => { - beforeEach(async () => { - loginRes = await login(); - }); - afterEach(() => { - loginRes = null; - }); - describe('POST `/items_categories``', async () => { it('Should not create a item category if the user was not authorized.', async () => { const res = await request().post('/api/item_categories').send(); expect(res.status).equals(401); - expect(res.body.message).equals('unauthorized'); + expect(res.body.message).equals('Unauthorized'); }); it('Should `name` be required.', async () => { const res = await request() .post('/api/item_categories') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -38,6 +33,7 @@ describe('routes: /item_categories/', () => { const res = await request() .post('/api/item_categories') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'Clothes', parent_category_id: 10, @@ -53,6 +49,7 @@ describe('routes: /item_categories/', () => { const res = await request() .post('/api/item_categories') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'Clothes', description: 'Here is description', @@ -66,10 +63,11 @@ describe('routes: /item_categories/', () => { }); it('Should item category data be saved to the storage.', async () => { - const category = await create('item_category'); + const category = await tenantFactory.create('item_category'); const res = await request() .post('/api/item_categories') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'Clothes', description: 'Here is description', @@ -78,8 +76,9 @@ describe('routes: /item_categories/', () => { expect(res.status).equals(200); - const storedCategory = await knex('items_categories') - .where('id', res.body.category.id).first(); + const storedCategory = await ItemCategory.tenant().query() + .where('id', res.body.category.id) + .first(); expect(storedCategory.name).equals('Clothes'); expect(storedCategory.description).equals('Here is description'); @@ -90,32 +89,33 @@ describe('routes: /item_categories/', () => { describe('POST `/items_category/{id}`', () => { it('Should not update a item category if the user was not authorized.', async () => { - const category = await create('item_category'); + const category = await tenantFactory.create('item_category'); const res = await request() .post(`/api/item_categories/${category.id}`) .send(); expect(res.status).equals(401); - expect(res.body.message).equals('unauthorized'); + expect(res.body.message).equals('Unauthorized'); }); it('Should `name` be required.', async () => { - const category = await create('item_category'); + const category = await tenantFactory.create('item_category'); const res = await request() .post(`/api/item_categories/${category.id}`) .set('x-access-token', loginRes.body.token) - .send({ - name: '', - }); + .set('organization-id', tenantWebsite.organizationId) + .send(); + expect(res.status).equals(422); expect(res.body.code).equals('validation_error'); }); it('Should `parent_category_id` be exist in the storage.', async () => { - const category = await create('item_category'); + const category = await tenantFactory.create('item_category'); const res = await request() .post(`/api/item_categories/${category.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'Name', parent_category_id: 10, @@ -128,12 +128,13 @@ describe('routes: /item_categories/', () => { }); it('Should response success with correct data format.', async () => { - const category = await create('item_category'); - const anotherCategory = await create('item_category'); + const category = await tenantFactory.create('item_category'); + const anotherCategory = await tenantFactory.create('item_category'); const res = await request() .post(`/api/item_categories/${category.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'Name', parent_category_id: anotherCategory.id, @@ -144,20 +145,22 @@ describe('routes: /item_categories/', () => { }); it('Should item category data be update in the storage.', async () => { - const category = await create('item_category'); - const anotherCategory = await create('item_category'); + const category = await tenantFactory.create('item_category'); + const anotherCategory = await tenantFactory.create('item_category'); const res = await request() .post(`/api/item_categories/${category.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'Name', parent_category_id: anotherCategory.id, description: 'updated description', }); - const storedCategory = await knex('items_categories') - .where('id', res.body.id).first(); + const storedCategory = await ItemCategory.tenant().query() + .where('id', res.body.id) + .first(); expect(storedCategory.name).equals('Name'); expect(storedCategory.description).equals('updated description'); @@ -167,43 +170,48 @@ describe('routes: /item_categories/', () => { describe('DELETE: `/items_categories`', async () => { it('Should not delete the give item category if the user was not authorized.', async () => { - const category = await create('item_category'); + const category = await tenantFactory.create('item_category'); const res = await request() .delete(`/api/item_categories/${category.id}`) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(401); - expect(res.body.message).equals('unauthorized'); + expect(res.body.message).equals('Unauthorized'); }); it('Should not delete if the item category was not found.', async () => { const res = await request() .delete('/api/item_categories/10') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(404); }); it('Should response success after delete the given item category.', async () => { - const category = await create('item_category'); + const category = await tenantFactory.create('item_category'); const res = await request() .delete(`/api/item_categories/${category.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(200); }); it('Should delete the give item category from the storage.', async () => { - const category = await create('item_category'); + const category = await tenantFactory.create('item_category'); const res = await request() .delete(`/api/item_categories/${category.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); - const categories = await knex('items_categories').where('id', category.id); + const categories = await ItemCategory.tenant().query() + .where('id', category.id); expect(categories).to.have.lengthOf(0); }); @@ -212,12 +220,13 @@ describe('routes: /item_categories/', () => { describe('GET: `/item_categories`', () => { it('Should retrieve list of item categories.', async () => { - const category1 = await create('item_category'); - const category2 = await create('item_category', { parent_category_id: category1.id }); + const category1 = await tenantFactory.create('item_category'); + const category2 = await tenantFactory.create('item_category', { parent_category_id: category1.id }); const res = await request() .get('/api/item_categories') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.body.categories).to.be.a('array'); @@ -233,14 +242,15 @@ describe('routes: /item_categories/', () => { it('Should retrieve of related items.', async () => { - const category1 = await create('item_category'); - const category2 = await create('item_category', { parent_category_id: category1.id }); + const category1 = await tenantFactory.create('item_category'); + const category2 = await tenantFactory.create('item_category', { parent_category_id: category1.id }); - await create('item', { category_id: category1.id }); + await tenantFactory.create('item', { category_id: category1.id }); const res = await request() .get('/api/item_categories') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.body.categories[0].count).to.be.a('number'); @@ -261,4 +271,41 @@ describe('routes: /item_categories/', () => { }); }); + + describe('DELETE: `/items_cateogires`', () => { + it('Should response bad request in case one of item categories id not exists in the storage.', async () => { + const res = await request() + .delete('/api/item_categories/bulk') + .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) + .query({ + ids: [1020, 2020], + }) + .send(); + + expect(res.status).equals(400); + expect(res.body.errors).include.something.deep.equals({ + type: 'ITEM.CATEGORIES.IDS.NOT.FOUND', code: 200 + }); + }); + + it('Should delete the given item categories.', async () => { + const itemCategory = await tenantFactory.create('item_category'); + const itemCategory2 = await tenantFactory.create('item_category'); + + const res = await request() + .delete('/api/item_categories/bulk') + .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) + .query({ + ids: [itemCategory.id, itemCategory2.id], + }) + .send(); + + const deleteItemCategories = await ItemCategory.tenant().query() + .whereIn('id', [itemCategory.id, itemCategory2.id]); + + expect(deleteItemCategories.length).equals(0); + }); + }); }); diff --git a/server/tests/routes/options.test.js b/server/tests/routes/options.test.js index 93f1791e3..c8a40298f 100644 --- a/server/tests/routes/options.test.js +++ b/server/tests/routes/options.test.js @@ -1,23 +1,16 @@ -import knex from '@/database/knex'; import { request, expect, - create, - make, - login, } from '~/testInit'; import Option from '@/models/Option'; +import { + tenantWebsite, + tenantFactory, + loginRes +} from '~/dbInit'; -let loginRes; describe('routes: `/options`', () => { - beforeEach(async () => { - loginRes = await login(); - }); - afterEach(() => { - loginRes = null; - }); - describe('POST: `/options/`', () => { it('Should response unauthorized if the user was not logged in.', async () => { const res = await request() @@ -25,13 +18,14 @@ describe('routes: `/options`', () => { .send(); expect(res.status).equals(401); - expect(res.body.message).equals('unauthorized'); + expect(res.body.message).equals('Unauthorized'); }); it('Should response the options key and group is not defined.', async () => { const res = await request() .post('/api/options') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ options: [ { @@ -56,6 +50,7 @@ describe('routes: `/options`', () => { const res = await request() .post('/api/options') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ options: [{ key: 'name', @@ -65,7 +60,7 @@ describe('routes: `/options`', () => { }); expect(res.status).equals(200); - const storedOptions = await Option.query() + const storedOptions = await Option.tenant().query() .where('group', 'organization') .where('key', 'name'); @@ -83,16 +78,17 @@ describe('routes: `/options`', () => { .send(); expect(res.status).equals(401); - expect(res.body.message).equals('unauthorized'); + expect(res.body.message).equals('Unauthorized'); }); it('Should retrieve options the associated to the given group.', async () => { - await create('option', { group: 'organization', key: 'name' }); - await create('option', { group: 'organization', key: 'base_currency' }); + await tenantFactory.create('option', { group: 'organization', key: 'name' }); + await tenantFactory.create('option', { group: 'organization', key: 'base_currency' }); const res = await request() .get('/api/options') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ group: 'organization', }) @@ -104,12 +100,13 @@ describe('routes: `/options`', () => { }); it('Should retrieve options that associated to the given key.', async () => { - await create('option', { group: 'organization', key: 'base_currency' }); - await create('option', { group: 'organization', key: 'name' }); + await tenantFactory.create('option', { group: 'organization', key: 'base_currency' }); + await tenantFactory.create('option', { group: 'organization', key: 'name' }); const res = await request() .get('/api/options') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ key: 'name', }) diff --git a/server/tests/routes/users.test.js b/server/tests/routes/users.test.js index f5497d7ac..9c42d4c69 100644 --- a/server/tests/routes/users.test.js +++ b/server/tests/routes/users.test.js @@ -2,35 +2,30 @@ import knex from '@/database/knex'; import { request, expect, - create, - make, - login, } from '~/testInit'; +import { + tenantWebsite, + tenantFactory, + loginRes +} from '~/dbInit'; -let loginRes; describe('routes: `/routes`', () => { - beforeEach(async () => { - loginRes = await login(); - }); - afterEach(() => { - loginRes = null; - }); - describe('GET: `/users`', () => { it('Should response unauthorized if the user was not authorized.', async () => { const res = await request().get('/api/users'); expect(res.status).equals(401); - expect(res.body.message).equals('unauthorized'); + expect(res.body.message).equals('Unauthorized'); }); it('Should retrieve the stored users with pagination meta.', async () => { - await create('user'); + await tenantFactory.create('user'); const res = await request() .get('/api/users') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.body.users.results.length).equals(2); @@ -38,205 +33,22 @@ describe('routes: `/routes`', () => { }); }); - describe('POST: `/users`', () => { - it('Should create a new user if the user was not authorized.', async () => { - const res = await request().post('/api/users'); - - expect(res.status).equals(401); - expect(res.body.message).equals('unauthorized'); - }); - - it('Should `first_name` be required.', async () => { - const res = await request() - .post('/api/users') - .set('x-access-token', loginRes.body.token) - .send(); - - expect(res.status).equals(422); - - const foundFirstNameParam = res.body.errors.find((error) => error.param === 'first_name'); - expect(!!foundFirstNameParam).equals(true); - }); - - it('Should `last_name` be required.', async () => { - const res = await request() - .post('/api/users') - .set('x-access-token', loginRes.body.token) - .send(); - - expect(res.status).equals(422); - - const foundFirstNameParam = res.body.errors.find((error) => error.param === 'last_name'); - expect(!!foundFirstNameParam).equals(true); - }); - - it('Should `email` be required.', async () => { - const res = await request() - .post('/api/users') - .set('x-access-token', loginRes.body.token) - .send(); - - expect(res.status).equals(422); - - const foundEmailParam = res.body.errors.find((error) => error.param === 'email'); - expect(!!foundEmailParam).equals(true); - }); - - it('Should be `email` be valid format.', async () => { - const user = make('user'); - const res = await request() - .post('/api/users') - .set('x-access-token', loginRes.body.token) - .send({ - first_name: user.first_name, - last_name: user.last_name, - email: 'email', - phone_number: user.phone_number, - status: 1, - }); - - expect(res.status).equals(422); - - const foundEmailParam = res.body.errors.find((error) => error.param === 'email'); - expect(!!foundEmailParam).equals(true); - }); - - it('Should `phone_number` be valid format.', async () => { - const user = make('user'); - const res = await request() - .post('/api/users') - .set('x-access-token', loginRes.body.token) - .send({ - first_name: user.first_name, - last_name: user.last_name, - email: user.email, - phone_number: 'phone_number', - status: 1, - }); - - expect(res.status).equals(422); - - const phoneNumberParam = res.body.errors.find((error) => error.param === 'phone_number'); - expect(!!phoneNumberParam).equals(true); - }); - - it('Should `password` be required.', async () => { - const res = await request() - .post('/api/users') - .set('x-access-token', loginRes.body.token) - .send(); - - expect(res.status).equals(422); - - const passwordParam = res.body.errors.find((error) => error.param === 'password'); - expect(!!passwordParam).equals(true); - }); - - it('Should password be equals confirm_password.', async () => { - const res = await request() - .post('/api/users') - .set('x-access-token', loginRes.body.token) - .send({ - password: '123123', - }); - - expect(res.status).equals(422); - - const passwordParam = res.body.errors.find((error) => error.param === 'password'); - expect(!!passwordParam).equals(true); - }); - - it('Should `status` be boolean', async () => { - const res = await request() - .post('/api/users') - .set('x-access-token', loginRes.body.token) - .send({ - status: 'not_boolean', - }); - - expect(res.status).equals(422); - - const statusParam = res.body.errors.find((error) => error.param === 'status'); - expect(!!statusParam).equals(true); - }); - - it('Should response bad request in case email was already exist.', async () => { - const user = await create('user'); - const res = await request() - .post('/api/users') - .set('x-access-token', loginRes.body.token) - .send({ - first_name: user.firstName, - last_name: user.lastName, - email: user.email, - phone_number: user.phoneNumber, - password: '123123123', - confirm_password: '123123123', - status: 1, - }); - - expect(res.status).equals(400); - expect(res.body.errors).include.something.that.deep.equals({ - type: 'EMAIL_ALREADY_EXIST', code: 100, - }); - }); - - it('Should response bad request in case phone number was already exist.', async () => { - const user = await create('user', { phone_number: '0927918381' }); - - const res = await request() - .post('/api/users') - .set('x-access-token', loginRes.body.token) - .send({ - first_name: user.firstName, - last_name: user.lastName, - email: user.email, - phone_number: '0927918381', - password: user.password, - confirm_password: user.password, - status: 1, - }); - - expect(res.status).equals(400); - expect(res.body.errors).include.something.that.deep.equals({ - type: 'PHONE_NUMBER_ALREADY_EXIST', code: 120, - }); - }); - - it('Should response success with correct data type.', async () => { - const user = await make('user', { phone_number: '0920000000' }); - const res = await request() - .post('/api/users') - .set('x-access-token', loginRes.body.token) - .send({ - first_name: user.firstName, - last_name: user.lastName, - email: user.email, - phone_number: '0920000000', - password: user.password, - confirm_password: user.password, - status: 1, - }); - - expect(res.status).equals(200); - expect(res.body.user.id).equals(2); - }); - }); - describe('POST: `/users/:id`', () => { it('Should create a new user if the user was not authorized.', async () => { - const user = await create('user'); - const res = await request().post(`/api/users/${user.id}`); + const user = await tenantFactory.create('user'); + const res = await request() + .post(`/api/users/${user.id}`); expect(res.status).equals(401); - expect(res.body.message).equals('unauthorized'); + expect(res.body.message).equals('Unauthorized'); }); it('Should `first_name` be required.', async () => { - const user = await create('user'); + const user = await  tenantFactory.create('user'); const res = await request() .post(`/api/users/${user.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -246,10 +58,11 @@ describe('routes: `/routes`', () => { }); it('Should `last_name` be required.', async () => { - const user = await create('user'); + const user = await tenantFactory.create('user'); const res = await request() .post(`/api/users/${user.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -259,10 +72,11 @@ describe('routes: `/routes`', () => { }); it('Should `email` be required.', async () => { - const user = await create('user'); + const user = await tenantFactory.create('user'); const res = await request() .post(`/api/users/${user.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -272,10 +86,11 @@ describe('routes: `/routes`', () => { }); it('Should be `email` be valid format.', async () => { - const user = await create('user'); + const user = await tenantFactory.create('user'); const res = await request() .post(`/api/users/${user.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ first_name: user.first_name, last_name: user.last_name, @@ -291,10 +106,11 @@ describe('routes: `/routes`', () => { }); it('Should `phone_number` be valid format.', async () => { - const user = create('user'); + const user = tenantFactory.create('user'); const res = await request() .post(`/api/users/${user.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ first_name: user.first_name, last_name: user.last_name, @@ -308,70 +124,32 @@ describe('routes: `/routes`', () => { const phoneNumberParam = res.body.errors.find((error) => error.param === 'phone_number'); expect(!!phoneNumberParam).equals(true); }); - - it('Should `password` be required.', async () => { - const user = create('user'); - const res = await request() - .post(`/api/users/${user.id}`) - .set('x-access-token', loginRes.body.token) - .send(); - - expect(res.status).equals(422); - - const passwordParam = res.body.errors.find((error) => error.param === 'password'); - expect(!!passwordParam).equals(true); - }); - - it('Should password be equals confirm_password.', async () => { - const user = create('user'); - const res = await request() - .post(`/api/users/${user.id}`) - .set('x-access-token', loginRes.body.token) - .send({ - password: '123123', - }); - - expect(res.status).equals(422); - - const passwordParam = res.body.errors.find((error) => error.param === 'password'); - expect(!!passwordParam).equals(true); - }); - - it('Should `status` be boolean', async () => { - const user = create('user'); - const res = await request() - .post(`/api/users/${user.id}`) - .set('x-access-token', loginRes.body.token) - .send({ - status: 'not_boolean', - }); - - expect(res.status).equals(422); - - const statusParam = res.body.errors.find((error) => error.param === 'status'); - expect(!!statusParam).equals(true); - }); }); describe('GET: `/users/:id`', () => { - it('Should not success if the user was not authorized.', () => { + it('Should not success if the user was not authorized.', async () => { + const res = await request().get('/api/users/1'); + expect(res.status).equals(401); + expect(res.body.message).equals('Unauthorized'); }); it('Should response not found if the user was not exist.', async () => { const res = await request() .get('/api/users/10') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(404); }); it('Should response success if the user was exist.', async () => { - const user = await create('user'); + const user = await tenantFactory.create('user'); const res = await request() .get(`/api/users/${user.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(200); @@ -379,14 +157,18 @@ describe('routes: `/routes`', () => { }); describe('DELETE: `/users/:id`', () => { - it('Should not success if the user was not authorized.', () => { + it('Should not success if the user was not authorized.', async () => { + const res = await request().delete('/api/users/1'); + expect(res.status).equals(401); + expect(res.body.message).equals('Unauthorized'); }); it('Should response not found if the user was not exist.', async () => { const res = await request() .delete('/api/users/10') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(404); @@ -396,20 +178,22 @@ describe('routes: `/routes`', () => { }); it('Should response success if the user was exist.', async () => { - const user = await create('user'); + const user = await tenantFactory.create('user'); const res = await request() .delete(`/api/users/${user.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(200); }); it('Should delete the give user from the storage.', async () => { - const user = await create('user'); + const user = await tenantFactory.create('user'); await request() .delete(`/api/users/${user.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); const storedUsers = await knex('users').where('id', user.id); diff --git a/server/tests/routes/views.test.js b/server/tests/routes/views.test.js index 233b91542..f42f01784 100644 --- a/server/tests/routes/views.test.js +++ b/server/tests/routes/views.test.js @@ -1,35 +1,30 @@ import { request, expect, - login, - create, } from '~/testInit'; import View from '@/models/View'; import ViewRole from '@/models/ViewRole'; import '@/models/ResourceField'; import ViewColumn from '../../src/models/ViewColumn'; +import { + tenantWebsite, + tenantFactory, + loginRes +} from '~/dbInit'; -let loginRes; describe('routes: `/views`', () => { - beforeEach(async () => { - loginRes = await login(); - }); - afterEach(() => { - loginRes = null; - }); - describe('GET: `/views`', () => { it('Should response unauthorized in case the user was not authorized.', async () => { const res = await request().get('/api/views'); expect(res.status).equals(401); - expect(res.body.message).equals('unauthorized'); + expect(res.body.message).equals('Unauthorized'); }); it('Should retrieve all views of the given resource name.', async () => { - const resource = await create('resource', { name: 'resource_name' }); - const resourceFields = await create('view', { + const resource = await tenantFactory.create('resource', { name: 'resource_name' }); + const resourceFields = await tenantFactory.create('view', { name: 'Resource View', resource_id: resource.id, roles_logic_expression: '', @@ -38,6 +33,7 @@ describe('routes: `/views`', () => { const res = await request() .get('/api/views') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .query({ resource_name: 'resource_name' }) .send(); @@ -48,8 +44,8 @@ describe('routes: `/views`', () => { describe('GET `/views/:id`', () => { it('Should response unauthorized in case the user was not authorized.', async () => { - const resource = await create('resource', { name: 'resource_name' }); - const resourceView = await create('view', { + const resource = await tenantFactory.create('resource', { name: 'resource_name' }); + const resourceView = await tenantFactory.create('view', { name: 'Resource View', resource_id: resource.id, roles_logic_expression: '', @@ -61,12 +57,12 @@ describe('routes: `/views`', () => { .send(); expect(res.status).equals(401); - expect(res.body.message).equals('unauthorized'); + expect(res.body.message).equals('Unauthorized'); }); it('Should response not found in case the given view was not found.', async () => { - const resource = await create('resource', { name: 'resource_name' }); - const resourceView = await create('view', { + const resource = await tenantFactory.create('resource', { name: 'resource_name' }); + const resourceView = await tenantFactory.create('view', { name: 'Resource View', resource_id: resource.id, roles_logic_expression: '', @@ -75,6 +71,7 @@ describe('routes: `/views`', () => { const res = await request() .get('/api/views/123') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(404); @@ -84,13 +81,13 @@ describe('routes: `/views`', () => { }); it('Should retrieve details of the given view with associated graphs.', async () => { - const resource = await create('resource', { name: 'resource_name' }); - const resourceView = await create('view', { + const resource = await tenantFactory.create('resource', { name: 'resource_name' }); + const resourceView = await tenantFactory.create('view', { name: 'Resource View', resource_id: resource.id, roles_logic_expression: '1 AND 2', }); - const resourceField = await create('resource_field', { + const resourceField = await tenantFactory.create('resource_field', { label_name: 'Expense Account', key: 'expense_account', data_type: 'integer', @@ -99,7 +96,7 @@ describe('routes: `/views`', () => { predefined: true, builtin: true, }); - const viewRole = await create('view_role', { + const viewRole = await tenantFactory.create('view_role', { view_id: resourceView.id, index: 1, field_id: resourceField.id, @@ -110,6 +107,7 @@ describe('routes: `/views`', () => { const res = await request() .get(`/api/views/${resourceView.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(200); @@ -127,14 +125,15 @@ describe('routes: `/views`', () => { const res = await request().post('/api/views'); expect(res.status).equals(401); - expect(res.body.message).equals('unauthorized'); + expect(res.body.message).equals('Unauthorized'); }); it('Should `name` be required.', async () => { - await create('resource'); + await tenantFactory.create('resource'); const res = await request() .post('/api/views') - .set('x-access-token', loginRes.body.token); + .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId); expect(res.status).equals(422); expect(res.body.code).equals('validation_error'); @@ -144,10 +143,11 @@ describe('routes: `/views`', () => { }); it('Should `resource_name` be required.', async () => { - await create('resource'); + await tenantFactory.create('resource'); const res = await request() .post('/api/views') - .set('x-access-token', loginRes.body.token); + .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId); expect(res.status).equals(422); expect(res.body.code).equals('validation_error'); @@ -157,13 +157,14 @@ describe('routes: `/views`', () => { }); it('Should `columns` be minimum limited', async () => { - await create('resource'); + await tenantFactory.create('resource'); const res = await request() .post('/api/views', { label: 'View Label', columns: [], }) - .set('x-access-token', loginRes.body.token); + .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId); expect(res.status).equals(422); expect(res.body.code).equals('validation_error'); @@ -173,13 +174,14 @@ describe('routes: `/views`', () => { }); it('Should `columns` be array.', async () => { - await create('resource'); + await tenantFactory.create('resource'); const res = await request() .post('/api/views', { label: 'View Label', columns: 'not_array', }) - .set('x-access-token', loginRes.body.token); + .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId); expect(res.status).equals(422); expect(res.body.code).equals('validation_error'); @@ -189,10 +191,11 @@ describe('routes: `/views`', () => { }); it('Should `roles.*.field_key` be required.', async () => { - const resource = await create('resource'); + const resource = await tenantFactory.create('resource'); const res = await request() .post('/api/views') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ resource_name: resource.name, label: 'View Label', @@ -207,10 +210,11 @@ describe('routes: `/views`', () => { }); it('Should `roles.*.comparator` be valid.', async () => { - const resource = await create('resource'); + const resource = await tenantFactory.create('resource'); const res = await request() .post('/api/views') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ resource_name: resource.name, label: 'View Label', @@ -225,7 +229,7 @@ describe('routes: `/views`', () => { }); it('Should `roles.*.index` be number as integer.', async () => { - const resource = await create('resource'); + const resource = await tenantFactory.create('resource'); const res = await request() .post('/api/views') .send({ @@ -235,7 +239,8 @@ describe('routes: `/views`', () => { { index: 'not_numeric' }, ], }) - .set('x-access-token', loginRes.body.token); + .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId); expect(res.status).equals(422); expect(res.body.code).equals('validation_error'); @@ -251,6 +256,7 @@ describe('routes: `/views`', () => { const res = await request() .post('/api/views') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ resource_name: 'not_found', name: 'View Label', @@ -275,8 +281,8 @@ describe('routes: `/views`', () => { }); it('Should response invalid logic expression.', async () =>{ - const resource = await create('resource'); - await create('resource_field', { + const resource = await tenantFactory.create('resource'); + await tenantFactory.create('resource_field', { resource_id: resource.id, label_name: 'Amount', key: 'amount', @@ -284,6 +290,7 @@ describe('routes: `/views`', () => { const res = await request() .post('/api/views') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ resource_name: resource.name, logic_expression: '100 && 100', @@ -305,12 +312,13 @@ describe('routes: `/views`', () => { }); it('Should response the roles fields not exist in case role field was not exist.', async () => { - const resource = await create('resource'); - await create('resource_field', { resource_id: resource.id, label_name: 'Amount' }); + const resource = await tenantFactory.create('resource'); + await tenantFactory.create('resource_field', { resource_id: resource.id, label_name: 'Amount' }); const res = await request() .post('/api/views') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ resource_name: resource.name, name: 'View Label', @@ -334,8 +342,8 @@ describe('routes: `/views`', () => { }); it('Should response the columns that not exists in case column was not exist.', async () => { - const resource = await create('resource'); - const resourceField = await create('resource_field', { + const resource = await tenantFactory.create('resource'); + const resourceField = await tenantFactory.create('resource_field', { resource_id: resource.id, label_name: 'Amount', key: 'amount', @@ -343,6 +351,7 @@ describe('routes: `/views`', () => { const res = await request() .post('/api/views') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ resource_name: resource.name, name: 'View Label', @@ -366,8 +375,8 @@ describe('routes: `/views`', () => { }); it('Should save the given details of the view.', async () => { - const resource = await create('resource'); - await create('resource_field', { + const resource = await tenantFactory.create('resource'); + await tenantFactory.create('resource_field', { resource_id: resource.id, label_name: 'Amount', key: 'amount', @@ -375,6 +384,7 @@ describe('routes: `/views`', () => { const res = await request() .post('/api/views') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ resource_name: resource.name, name: 'View Label', @@ -390,7 +400,7 @@ describe('routes: `/views`', () => { }], }); - const storedView = await View.query().where('name', 'View Label').first(); + const storedView = await View.tenant().query().where('name', 'View Label').first(); expect(storedView.name).equals('View Label'); expect(storedView.predefined).equals(0); @@ -398,8 +408,8 @@ describe('routes: `/views`', () => { }); it('Should save the given details of view fields that associated to the given view id.', async () => { - const resource = await create('resource'); - const resourceField = await create('resource_field', { + const resource = await tenantFactory.create('resource'); + const resourceField = await tenantFactory.create('resource_field', { resource_id: resource.id, label_name: 'Amount', key: 'amount', @@ -408,6 +418,7 @@ describe('routes: `/views`', () => { const res = await request() .post('/api/views') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ resource_name: resource.name, name: 'View Label', @@ -421,7 +432,7 @@ describe('routes: `/views`', () => { }], }); - const viewRoles = await ViewRole.query().where('view_id', res.body.id); + const viewRoles = await ViewRole.tenant().query().where('view_id', res.body.id); expect(viewRoles.length).equals(1); expect(viewRoles[0].index).equals(1); @@ -431,8 +442,8 @@ describe('routes: `/views`', () => { }); it('Should save columns that associated to the given view.', async () => { - const resource = await create('resource'); - const resourceField = await create('resource_field', { + const resource = await tenantFactory.create('resource'); + const resourceField = await tenantFactory.create('resource_field', { resource_id: resource.id, label_name: 'Amount', key: 'amount', @@ -441,6 +452,7 @@ describe('routes: `/views`', () => { const res = await request() .post('/api/views') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ resource_name: resource.name, name: 'View Label', @@ -456,7 +468,7 @@ describe('routes: `/views`', () => { }], }); - const viewColumns = await ViewColumn.query().where('view_id', res.body.id); + const viewColumns = await ViewColumn.tenant().query().where('view_id', res.body.id); expect(viewColumns.length).equals(1); }); @@ -465,10 +477,11 @@ describe('routes: `/views`', () => { describe('POST: `/views/:view_id`', () => { it('Should `name` be required.', async () => { - const view = await create('view'); + const view = await tenantFactory.create('view'); const res = await request() .post(`/api/views/${view.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -479,13 +492,14 @@ describe('routes: `/views`', () => { }); it('Should columns be minimum limited', async () => { - const view = await create('view'); + const view = await tenantFactory.create('view'); const res = await request() .post(`/api/views/${view.id}`, { label: 'View Label', columns: [], }) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(422); @@ -496,27 +510,31 @@ describe('routes: `/views`', () => { }); it('Should columns be array.', async () => { - const view = await create('view'); + const view = await tenantFactory.create('view'); const res = await request() .post(`/api/views/${view.id}`, { label: 'View Label', columns: 'not_array', }) .set('x-access-token', loginRes.body.token) - .send(); + .set('organization-id', tenantWebsite.organizationId) + .send({ + columns: 'columns' + }); expect(res.status).equals(422); expect(res.body.code).equals('validation_error'); expect(res.body.errors).include.something.deep.equals({ - msg: 'Invalid value', param: 'code', location: 'body', + msg: 'Invalid value', param: 'columns', location: 'body', value: 'columns', }); }); it('Should `roles.*.field_key` be required.', async () => { - const view = await create('view'); + const view = await tenantFactory.create('view'); const res = await request() .post(`/api/views/${view.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ label: 'View Label', roles: [{}], @@ -525,15 +543,16 @@ describe('routes: `/views`', () => { expect(res.status).equals(422); expect(res.body.code).equals('validation_error'); expect(res.body.errors).include.something.deep.equals({ - msg: 'Invalid value', param: 'roles', location: 'body', + msg: 'Invalid value', param: 'roles[0].field_key', location: 'body', }); }); it('Should `roles.*.comparator` be required.', async () => { - const view = await create('view'); + const view = await tenantFactory.create('view'); const res = await request() .post(`/api/views/${view.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ label: 'View Label', roles: [{}], @@ -547,10 +566,11 @@ describe('routes: `/views`', () => { }); it('Should `roles.*.index` be number as integer.', async () => { - const view = await create('view'); + const view = await tenantFactory.create('view'); const res = await request() .post(`/api/views/${view.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ label: 'View Label', roles: [{ index: 'not_numeric' }], @@ -567,14 +587,15 @@ describe('routes: `/views`', () => { }); it('Should response the roles fields not exist in case role field was not exist.', async () => { - const view = await create('view'); - await create('resource_field', { + const view = await tenantFactory.create('view'); + await tenantFactory.create('resource_field', { resource_id: view.resource_id, label_name: 'Amount', }); const res = await request() .post(`/api/views/${view.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'View Label', logic_expression: '1', @@ -601,14 +622,15 @@ describe('routes: `/views`', () => { }); it('Should response the resource columns not exists in case the column keys was not exist.', async () => { - const view = await create('view'); - await create('resource_field', { + const view = await tenantFactory.create('view'); + await tenantFactory.create('resource_field', { resource_id: view.resource_id, label_name: 'Amount', }); const res = await request() .post(`/api/views/${view.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'View Label', logic_expression: '1', @@ -641,21 +663,22 @@ describe('routes: `/views`', () => { }); it('Should delete the view roles that not presented the post data.', async () => { - const resource = await create('resource'); - const resourceField = await create('resource_field', { + const resource = await tenantFactory.create('resource'); + const resourceField = await tenantFactory.create('resource_field', { resource_id: resource.id, label_name: 'Amount', key: 'amount', }); - const view = await create('view', { resource_id: resource.id }); - const viewRole = await create('view_role', { + const view = await tenantFactory.create('view', { resource_id: resource.id }); + const viewRole = await tenantFactory.create('view_role', { view_id: view.id, field_id: resourceField.id, }); const res = await request() .post(`/api/views/${view.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'View Label', logic_expression: '1', @@ -671,26 +694,27 @@ describe('routes: `/views`', () => { }], }); - const foundViewRole = await ViewRole.query().where('id', viewRole.id); + const foundViewRole = await ViewRole.tenant().query().where('id', viewRole.id); expect(foundViewRole.length).equals(0); }); it('Should update the view roles that presented in the given data.', async () => { - const resource = await create('resource'); - const resourceField = await create('resource_field', { + const resource = await tenantFactory.create('resource'); + const resourceField = await tenantFactory.create('resource_field', { resource_id: resource.id, label_name: 'Amount', key: 'amount', }); - const view = await create('view', { resource_id: resource.id }); - const viewRole = await create('view_role', { + const view = await tenantFactory.create('view', { resource_id: resource.id }); + const viewRole = await tenantFactory.create('view_role', { view_id: view.id, field_id: resourceField.id, }); const res = await request() .post(`/api/views/${view.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'View Label', logic_expression: '1', @@ -707,27 +731,28 @@ describe('routes: `/views`', () => { }], }); - const foundViewRole = await ViewRole.query().where('id', viewRole.id); + const foundViewRole = await ViewRole.tenant().query().where('id', viewRole.id); expect(foundViewRole.length).equals(1); - expect(foundViewRole[0].id).equals(1); + expect(foundViewRole[0].id).equals(viewRole.id); expect(foundViewRole[0].index).equals(1); expect(foundViewRole[0].value).equals('100'); expect(foundViewRole[0].comparator).equals('equals'); }); it('Should response not found roles ids in case not exists in the storage.', async () => { - const resource = await create('resource'); - const resourceField = await create('resource_field', { + const resource = await tenantFactory.create('resource'); + const resourceField = await tenantFactory.create('resource_field', { resource_id: resource.id, label_name: 'Amount', key: 'amount', }); - const view = await create('view', { resource_id: resource.id }); + const view = await tenantFactory.create('view', { resource_id: resource.id }); const res = await request() .post(`/api/views/${view.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'View Label', logic_expression: '1', @@ -750,18 +775,19 @@ describe('routes: `/views`', () => { }); it('Should delete columns from storage in case view columns ids not presented.', async () => { - const resource = await create('resource'); - const resourceField = await create('resource_field', { + const resource = await tenantFactory.create('resource'); + const resourceField = await tenantFactory.create('resource_field', { resource_id: resource.id, label_name: 'Amount', key: 'amount', }); - const view = await create('view', { resource_id: resource.id }); - const viewColumn = await create('view_column', { view_id: view.id }); + const view = await tenantFactory.create('view', { resource_id: resource.id }); + const viewColumn = await tenantFactory.create('view_column', { view_id: view.id }); const res = await request() .post(`/api/views/${view.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'View Label', logic_expression: '1', @@ -778,22 +804,23 @@ describe('routes: `/views`', () => { }); // console.log(res.status, res.body); - const foundViewColumns = await ViewColumn.query().where('id', viewColumn.id); + const foundViewColumns = await ViewColumn.tenant().query().where('id', viewColumn.id); expect(foundViewColumns.length).equals(0); }); it('Should insert columns to the storage if where new columns', async () => { - const resource = await create('resource'); - const resourceField = await create('resource_field', { + const resource = await tenantFactory.create('resource'); + const resourceField = await tenantFactory.create('resource_field', { resource_id: resource.id, label_name: 'Amount', key: 'amount', }); - const view = await create('view', { resource_id: resource.id }); + const view = await tenantFactory.create('view', { resource_id: resource.id }); const res = await request() .post(`/api/views/${view.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'View Label', logic_expression: '1', @@ -809,7 +836,7 @@ describe('routes: `/views`', () => { }], }); - const foundViewColumns = await ViewColumn.query().where('view_id', view.id); + const foundViewColumns = await ViewColumn.tenant().query().where('view_id', view.id); expect(foundViewColumns.length).equals(1); expect(foundViewColumns[0].viewId).equals(view.id); @@ -819,18 +846,19 @@ describe('routes: `/views`', () => { it('Should update columns on the storage.', async () => { - const resource = await create('resource'); - const resourceField = await create('resource_field', { + const resource = await tenantFactory.create('resource'); + const resourceField = await tenantFactory.create('resource_field', { resource_id: resource.id, label_name: 'Amount', key: 'amount', }); - const view = await create('view', { resource_id: resource.id }); - const viewColumn = await create('view_column', { view_id: view.id }); + const view = await tenantFactory.create('view', { resource_id: resource.id }); + const viewColumn = await tenantFactory.create('view_column', { view_id: view.id }); const res = await request() .post(`/api/views/${view.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send({ name: 'View Label', logic_expression: '1', @@ -849,7 +877,7 @@ describe('routes: `/views`', () => { console.log(res.body) - const foundViewColumns = await ViewColumn.query().where('id', viewColumn.id); + const foundViewColumns = await ViewColumn.tenant().query().where('id', viewColumn.id); expect(foundViewColumns.length).equals(1); expect(foundViewColumns[0].id).equals(viewColumn.id); @@ -861,10 +889,11 @@ describe('routes: `/views`', () => { describe('DELETE: `/views/:resource_id`', () => { it('Should not delete predefined view.', async () => { - const view = await create('view', { predefined: true }); + const view = await tenantFactory.create('view', { predefined: true }); const res = await request() .delete(`/api/views/${view.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(400); @@ -877,6 +906,7 @@ describe('routes: `/views`', () => { const res = await request() .delete('/api/views/100') .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.status).equals(404); @@ -886,19 +916,20 @@ describe('routes: `/views`', () => { }); it('Should delete the given view and associated view columns and roles.', async () => { - const view = await create('view', { predefined: false }); - await create('view_role', { view_id: view.id }); - await create('view_column', { view_id: view.id }); + const view = await tenantFactory.create('view', { predefined: false }); + await tenantFactory.create('view_role', { view_id: view.id }); + await tenantFactory.create('view_column', { view_id: view.id }); const res = await request() .delete(`/api/views/${view.id}`) .set('x-access-token', loginRes.body.token) + .set('organization-id', tenantWebsite.organizationId) .send(); expect(res.body.id).equals(view.id); - const foundViews = await View.query().where('id', view.id); - const foundViewRoles = await ViewRole.query().where('view_id', view.id); + const foundViews = await View.tenant().query().where('id', view.id); + const foundViewRoles = await ViewRole.tenant().query().where('view_id', view.id); expect(foundViews).to.have.lengthOf(0); expect(foundViewRoles).to.have.lengthOf(0); diff --git a/server/tests/testInit.js b/server/tests/testInit.js index a34a0e557..10870abeb 100644 --- a/server/tests/testInit.js +++ b/server/tests/testInit.js @@ -1,54 +1,88 @@ import chai from 'chai'; import chaiHttp from 'chai-http'; import chaiThings from 'chai-things'; - -import knex from '@/database/knex'; -import '@/models'; +import systemDb from '@/database/knex'; import app from '@/app'; -import factory from '@/database/factories'; -import dbManager from '@/database/manager'; -// import { hashPassword } from '@/utils'; +import createTenantFactory from '@/database/factories'; +import TenantsManager from '@/system/TenantsManager'; +import faker from 'faker'; +import { hashPassword } from '@/utils'; +import TenantModel from '@/models/TenantModel'; +import createSystemFactory from '@/database/factories/system'; + -const request = () => chai.request(app); const { expect } = chai; - -const login = async (givenUser) => { - const user = !givenUser ? await factory.create('user') : givenUser; - - const response = request() - .post('/api/auth/login') - .send({ - crediential: user.email, - password: 'admin', - }); - return response; -}; - -before(async () => { - await dbManager.closeKnex(); - await dbManager.close(); - // await dbManager.dropDb(); - // await dbManager.createDb(); -}); +const request = () => chai.request(app); beforeEach(async () => { - await knex.migrate.rollback(); - await knex.migrate.latest(); + // Rollback/migrate the system database. + await systemDb.migrate.rollback(); + await systemDb.migrate.latest(); }); -after(async () => { +afterEach(async () => { }); chai.use(chaiHttp); chai.use(chaiThings); -const create = async (name, data) => factory.create(name, data); -const make = async (name, data) => factory.build(name, data); +// Create tenant database. +const createTenant = () => { + return TenantsManager.createTenant(); +}; + +// Drops tenant database. +const dropTenant = async (tenantWebsite) => { + return TenantsManager.dropTenant(tenantWebsite); +}; + +// Create a new user that associate to the given tenant Db. +const createUser = async (tenantWebsite, givenUser) => { + const userPassword = (givenUser && givenUser.password) ? givenUser.password : 'admin' + const hashedPassword = await hashPassword(userPassword); + + const userInfo = { + first_name: faker.lorem.word(), + last_name: faker.lorem.word(), + email: faker.internet.email(), + active: 1, + phone_number: faker.phone.phoneNumberFormat().replace('-', ''), + password: hashedPassword, + ...givenUser, + }; + const user = await TenantsManager.createTenantUser(tenantWebsite, userInfo); + return user; +}; + +const login = async (tenantWebsite, givenUser) => { + let user = givenUser; + + if (!givenUser && tenantWebsite) { + const createdUser = await createUser(tenantWebsite, givenUser); + user = createdUser; + } + return request() + .post('/api/auth/login') + .send({ + crediential: user.email, + password: 'admin', + }); +}; + +const bindTenantModel = (tenantDb) => { + TenantModel.knexBinded = tenantDb; +}; + +const systemFactory = createSystemFactory(); export { login, - create, - make, + systemFactory, + createTenantFactory, + createTenant, + createUser, + dropTenant, expect, request, + bindTenantModel, };