feat: Add company model and API with tests (#12548)
# Pull Request Template ## Description * add Company model with validations for name, domain, description and avatar * Add database migration fo * Implement endpoints for company CRUD operations * Add optional company relationship for contacts * Add test for models, controllers, factories and policies * Add authorization policies restricting delete to admins * support JSON API responses Please include a summary of the change and issue(s) fixed. Also, mention relevant motivation, context, and any dependencies that this change requires. Fixes #(cw-5650) ## Type of change Please delete options that are not relevant. - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? Tests are implemented using `RSpec` ``` $ bundle exec rails db:migrate $ bundle exec rspec spec/models/company_spec.rb spec/controllers/api/v1/accounts/companies_controller_spec.rb ``` ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Companies API', type: :request do
|
||||
let(:account) { create(:account) }
|
||||
|
||||
describe 'GET /api/v1/accounts/{account.id}/companies' do
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v1/accounts/#{account.id}/companies"
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
let(:admin) { create(:user, account: account, role: :administrator) }
|
||||
let!(:company1) { create(:company, name: 'Company 1', account: account) }
|
||||
let!(:company2) { create(:company, account: account) }
|
||||
|
||||
it 'returns all companies' do
|
||||
get "/api/v1/accounts/#{account.id}/companies",
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
expect(response).to have_http_status(:success)
|
||||
response_body = response.parsed_body
|
||||
expect(response_body['payload'].size).to eq(2)
|
||||
expect(response_body['payload'].map { |c| c['name'] }).to contain_exactly(company1.name, company2.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v1/accounts/{account.id}/companies/{id}' do
|
||||
context 'when it is an authenticated user' do
|
||||
let(:admin) { create(:user, account: account, role: :administrator) }
|
||||
let(:company) { create(:company, account: account) }
|
||||
|
||||
it 'returns the company' do
|
||||
get "/api/v1/accounts/#{account.id}/companies/#{company.id}",
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
expect(response).to have_http_status(:success)
|
||||
response_body = response.parsed_body
|
||||
expect(response_body['payload']['name']).to eq(company.name)
|
||||
expect(response_body['payload']['id']).to eq(company.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v1/accounts/{account.id}/companies' do
|
||||
context 'when it is an authenticated user' do
|
||||
let(:admin) { create(:user, account: account, role: :administrator) }
|
||||
let(:valid_params) do
|
||||
{
|
||||
company: {
|
||||
name: 'New Company',
|
||||
domain: 'newcompany.com',
|
||||
description: 'A new company'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates a new company' do
|
||||
expect do
|
||||
post "/api/v1/accounts/#{account.id}/companies",
|
||||
params: valid_params,
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
end.to change(Company, :count).by(1)
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
response_body = response.parsed_body
|
||||
expect(response_body['payload']['name']).to eq('New Company')
|
||||
expect(response_body['payload']['domain']).to eq('newcompany.com')
|
||||
end
|
||||
|
||||
it 'returns error for invalid params' do
|
||||
invalid_params = { company: { name: '' } }
|
||||
|
||||
post "/api/v1/accounts/#{account.id}/companies",
|
||||
params: invalid_params,
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PATCH /api/v1/accounts/{account.id}/companies/{id}' do
|
||||
context 'when it is an authenticated user' do
|
||||
let(:admin) { create(:user, account: account, role: :administrator) }
|
||||
let(:company) { create(:company, account: account) }
|
||||
let(:update_params) do
|
||||
{
|
||||
company: {
|
||||
name: 'Updated Company Name',
|
||||
domain: 'updated.com'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates the company' do
|
||||
patch "/api/v1/accounts/#{account.id}/companies/#{company.id}",
|
||||
params: update_params,
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
expect(response).to have_http_status(:success)
|
||||
response_body = response.parsed_body
|
||||
expect(response_body['payload']['name']).to eq('Updated Company Name')
|
||||
expect(response_body['payload']['domain']).to eq('updated.com')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE /api/v1/accounts/{account.id}/companies/{id}' do
|
||||
context 'when it is an authenticated administrator' do
|
||||
let(:admin) { create(:user, account: account, role: :administrator) }
|
||||
let(:company) { create(:company, account: account) }
|
||||
|
||||
it 'deletes the company' do
|
||||
company
|
||||
expect do
|
||||
delete "/api/v1/accounts/#{account.id}/companies/#{company.id}",
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
end.to change(Company, :count).by(-1)
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is a regular agent' do
|
||||
let(:agent) { create(:user, account: account, role: :agent) }
|
||||
let(:company) { create(:company, account: account) }
|
||||
|
||||
it 'returns unauthorized' do
|
||||
delete "/api/v1/accounts/#{account.id}/companies/#{company.id}",
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
38
spec/enterprise/models/company_spec.rb
Normal file
38
spec/enterprise/models/company_spec.rb
Normal file
@@ -0,0 +1,38 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Company, type: :model do
|
||||
context 'with validations' do
|
||||
it { is_expected.to validate_presence_of(:account_id) }
|
||||
it { is_expected.to validate_presence_of(:name) }
|
||||
it { is_expected.to validate_length_of(:name).is_at_most(100) }
|
||||
it { is_expected.to validate_length_of(:description).is_at_most(1000) }
|
||||
|
||||
describe 'domain validation' do
|
||||
it { is_expected.to allow_value('example.com').for(:domain) }
|
||||
it { is_expected.to allow_value('sub.example.com').for(:domain) }
|
||||
it { is_expected.to allow_value('').for(:domain) }
|
||||
it { is_expected.to allow_value(nil).for(:domain) }
|
||||
it { is_expected.not_to allow_value('invalid-domain').for(:domain) }
|
||||
it { is_expected.not_to allow_value('.example.com').for(:domain) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with associations' do
|
||||
it { is_expected.to belong_to(:account) }
|
||||
it { is_expected.to have_many(:contacts).dependent(:nullify) }
|
||||
end
|
||||
|
||||
describe 'scopes' do
|
||||
let(:account) { create(:account) }
|
||||
let!(:company_b) { create(:company, name: 'B Company', account: account) }
|
||||
let!(:company_a) { create(:company, name: 'A Company', account: account) }
|
||||
let!(:company_c) { create(:company, name: 'C Company', account: account) }
|
||||
|
||||
describe '.ordered_by_name' do
|
||||
it 'orders companies by name alphabetically' do
|
||||
companies = described_class.where(account: account).ordered_by_name
|
||||
expect(companies.map(&:name)).to eq([company_a.name, company_b.name, company_c.name])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
33
spec/enterprise/policies/company_policy_spec.rb
Normal file
33
spec/enterprise/policies/company_policy_spec.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe CompanyPolicy, type: :policy do
|
||||
subject(:company_policy) { described_class }
|
||||
|
||||
let(:account) { create(:account) }
|
||||
let(:administrator) { create(:user, :administrator, account: account) }
|
||||
let(:agent) { create(:user, account: account) }
|
||||
let(:company) { create(:company, account: account) }
|
||||
|
||||
let(:administrator_context) { { user: administrator, account: account, account_user: account.account_users.first } }
|
||||
let(:agent_context) { { user: agent, account: account, account_user: account.account_users.first } }
|
||||
|
||||
permissions :index?, :show?, :create?, :update? do
|
||||
context 'when administrator' do
|
||||
it { expect(company_policy).to permit(administrator_context, company) }
|
||||
end
|
||||
|
||||
context 'when agent' do
|
||||
it { expect(company_policy).to permit(agent_context, company) }
|
||||
end
|
||||
end
|
||||
|
||||
permissions :destroy? do
|
||||
context 'when administrator' do
|
||||
it { expect(company_policy).to permit(administrator_context, company) }
|
||||
end
|
||||
|
||||
context 'when agent' do
|
||||
it { expect(company_policy).not_to permit(agent_context, company) }
|
||||
end
|
||||
end
|
||||
end
|
||||
20
spec/factories/companies.rb
Normal file
20
spec/factories/companies.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
FactoryBot.define do
|
||||
factory :company do
|
||||
sequence(:name) { |n| "Company #{n}" }
|
||||
sequence(:domain) { |n| "company#{n}.com" }
|
||||
description { 'A sample company description' }
|
||||
account
|
||||
|
||||
trait :without_domain do
|
||||
domain { nil }
|
||||
end
|
||||
|
||||
trait :with_avatar do
|
||||
avatar { fixture_file_upload(Rails.root.join('spec/assets/avatar.png'), 'image/png') }
|
||||
end
|
||||
|
||||
trait :with_long_description do
|
||||
description { 'A' * 500 }
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user