Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions spec/factories/roles.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
FactoryBot.define do
factory :role do
sequence(:key) { |n| "role_#{n}" }
sequence(:name) { |n| "Role #{n}" }

trait :administrator do
key { 'administrator' }
name { 'Administrator' }
end

trait :super_admin do
key { 'super_admin' }
name { 'Super Admin' }
end

trait :account_owner do
key { 'account_owner' }
name { 'Account Owner' }
end

trait :agent do
key { 'agent' }
name { 'Agent' }
end
end

factory :user_role do
association :user
association :role

trait :granted_by do
association :granted_by, factory: :user
end
end
end
69 changes: 69 additions & 0 deletions spec/mailers/administrator_notifications/base_mailer_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
RSpec.describe AdministratorNotifications::BaseMailer, type: :mailer do
describe '#admin_emails' do
let!(:admin_user) { create(:user, email: 'admin@example.com') }
let!(:super_admin_user) { create(:user, email: 'super_admin@example.com') }
let!(:agent_user) { create(:user, email: 'agent@example.com') }
let!(:admin_role) { create(:role, key: 'administrator') }
let!(:super_admin_role) { create(:role, key: 'super_admin') }
let!(:agent_role) { create(:role, key: 'agent') }

before do
admin_user.roles << admin_role
super_admin_user.roles << super_admin_role
agent_user.roles << agent_role
end

it 'returns emails of admin users' do
mailer_class = described_class.new
admin_emails = mailer_class.send(:admin_emails)

expect(admin_emails).to match_array(['admin@example.com', 'super_admin@example.com'])
end

it 'does not include agent users' do
mailer_class = described_class.new
admin_emails = mailer_class.send(:admin_emails)

expect(admin_emails).not_to include('agent@example.com')
end
end

describe '#send_notification' do
let!(:admin_user) { create(:user, email: 'admin@example.com') }
let!(:admin_role) { create(:role, key: 'administrator') }

before do
admin_user.roles << admin_role
allow(ENV).to receive(:fetch).with('FRONTEND_URL', nil).and_return('http://example.com')
end

it 'sends notification to admin emails' do
mail = described_class.send_notification(
'Test Subject',
action_url: 'http://example.com/action',
meta: { key: 'value' }
)

expect(mail.to).to include('admin@example.com')
end

it 'includes action URL in action_url variable' do
mail = described_class.send_notification(
'Test Subject',
action_url: 'http://example.com/action'
)

expect(mail.body.encoded).to include('http://example.com/action')
end

it 'includes meta data' do
mail = described_class.send_notification(
'Test Subject',
action_url: 'http://example.com/action',
meta: { key: 'value' }
)

expect(mail.body.encoded).to include('value')
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
require 'rails_helper'
require_relative '../../db/migrate/20241020000100_optimize_contacts_performance'

RSpec.describe OptimizeContactsPerformance, type: :migration do
let(:migration) { described_class.new }

describe '#up' do
it 'creates idx_contact_inboxes_contact_id partial index' do
migration.up

expect(ActiveRecord::Base.connection.index_exists?(
:contact_inboxes,
:contact_id,
name: 'idx_contact_inboxes_contact_id',
where: 'contact_id IS NOT NULL'
)).to be true
end

it 'creates idx_contacts_with_identity partial index' do
migration.up

expect(ActiveRecord::Base.connection.index_exists?(
:contacts,
:id,
name: 'idx_contacts_with_identity',
where: "(email <> '' OR phone_number <> '' OR identifier <> '')"
)).to be true
end

context 'when contacts.type column does not exist' do
before do
# Ensure type column doesn't exist
ActiveRecord::Base.connection.remove_column(:contacts, :type) if ActiveRecord::Base.connection.column_exists?(:contacts, :type)
end

it 'skips creating idx_contacts_name_type_resolved without error' do
expect { migration.up }.not_to raise_error
end
end

context 'when contacts.type column exists' do
before do
# Add type column if it doesn't exist
unless ActiveRecord::Base.connection.column_exists?(:contacts, :type)
ActiveRecord::Base.connection.add_column(:contacts, :type, :string)
end
end

after do
ActiveRecord::Base.connection.remove_column(:contacts, :type) if ActiveRecord::Base.connection.column_exists?(:contacts, :type)
end

it 'creates idx_contacts_name_type_resolved composite index' do
migration.up

expect(ActiveRecord::Base.connection.index_exists?(
:contacts,
%i[name type id],
name: 'idx_contacts_name_type_resolved',
where: "(email <> '' OR phone_number <> '' OR identifier <> '')"
)).to be true
end
end
end

describe '#down' do
before do
migration.up
end

it 'removes all created indexes' do
migration.down

expect(ActiveRecord::Base.connection.index_exists?(
:contact_inboxes,
:contact_id,
name: 'idx_contact_inboxes_contact_id'
)).to be false

expect(ActiveRecord::Base.connection.index_exists?(
:contacts,
:id,
name: 'idx_contacts_with_identity'
)).to be false
end
end
end
65 changes: 65 additions & 0 deletions spec/migrations/20251117132621_add_type_to_contacts_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
require 'rails_helper'
require_relative '../../db/migrate/20251117132621_add_type_to_contacts'

RSpec.describe AddTypeToContacts, type: :migration do
let(:migration) { described_class.new }

describe '#up' do
it 'adds type column to contacts' do
migration.up

expect(ActiveRecord::Base.connection.column_exists?(:contacts, :type)).to be true
end

it 'creates contact_type_enum type' do
migration.up

types = ActiveRecord::Base.connection.query('SELECT unnest(enum_range(NULL::contact_type_enum))')
expect(types.flatten).to match_array(['person', 'company'])
end

it 'sets default value to person' do
migration.up

column = ActiveRecord::Base.connection.columns(:contacts).find { |c| c.name == 'type' }
expect(column.default).to eq('person')
end

it 'creates index on type column' do
migration.up

expect(ActiveRecord::Base.connection.index_exists?(:contacts, :type)).to be true
end

it 'backfills idx_contacts_name_type_resolved index' do
migration.up

expect(ActiveRecord::Base.connection.index_exists?(
:contacts,
%i[name type id],
name: 'idx_contacts_name_type_resolved',
where: "(email <> '' OR phone_number <> '' OR identifier <> '')"
)).to be true
end
end

describe '#down' do
before do
migration.up
end

it 'removes type column' do
migration.down

expect(ActiveRecord::Base.connection.column_exists?(:contacts, :type)).to be false
end

it 'drops contact_type_enum type' do
migration.down

expect {
ActiveRecord::Base.connection.query('SELECT unnest(enum_range(NULL::contact_type_enum))')
}.to raise_error(ActiveRecord::StatementInvalid, /type "contact_type_enum" does not exist/)
end
end
end
80 changes: 80 additions & 0 deletions spec/models/role_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
RSpec.describe Role, type: :model do
describe 'associations' do
it { should have_many(:user_roles).dependent(:destroy_async) }
it { should have_many(:users).through(:user_roles) }
end

describe 'validations' do
it { should validate_presence_of(:key) }
it { should validate_presence_of(:name) }
it { should validate_uniqueness_of(:key) }
end

describe '.ADMIN_ROLE_KEYS' do
it 'includes super_admin, account_owner, administrator, admin' do
expect(Role::ADMIN_ROLE_KEYS).to match_array(%w[super_admin account_owner administrator admin])
end
end

describe '#administrator?' do
context 'when role key is in ADMIN_ROLE_KEYS' do
it 'returns true for super_admin' do
role = build(:role, key: 'super_admin')
expect(role.administrator?).to be true
end

it 'returns true for administrator' do
role = build(:role, key: 'administrator')
expect(role.administrator?).to be true
end

it 'returns true for account_owner' do
role = build(:role, key: 'account_owner')
expect(role.administrator?).to be true
end

it 'returns true for admin' do
role = build(:role, key: 'admin')
expect(role.administrator?).to be true
end
end

context 'when role key is not in ADMIN_ROLE_KEYS' do
it 'returns false for agent role' do
role = build(:role, key: 'agent')
expect(role.administrator?).to be false
end
end
end

describe '.administrator_role' do
let!(:admin_role) { create(:role, key: 'administrator') }
let!(:agent_role) { create(:role, key: 'agent') }

it 'returns first matching admin role' do
expect(Role.administrator_role).to eq(admin_role)
end
end

describe '.administrator_users' do
let!(:admin_role) { create(:role, key: 'administrator') }
let!(:super_admin_role) { create(:role, key: 'super_admin') }
let!(:admin_user) { create(:user) }
let!(:super_admin_user) { create(:user) }
let!(:agent_user) { create(:user) }

before do
admin_user.roles << admin_role
super_admin_user.roles << super_admin_role
agent_user.roles << create(:role, key: 'agent')
end

it 'returns all users with admin roles' do
expect(Role.administrator_users).to match_array([admin_user, super_admin_user])
end

it 'does not include agent users' do
expect(Role.administrator_users).not_to include(agent_user)
end
end
end
14 changes: 14 additions & 0 deletions spec/models/user_role_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
RSpec.describe UserRole, type: :model do
describe 'associations' do
it { should belong_to(:user) }
it { should belong_to(:role) }
it { should belong_to(:granted_by).class_name('User').optional }
end

describe 'validations' do
subject { build(:user_role) }

it { should validate_presence_of(:user) }
it { should validate_presence_of(:role) }
end
end