Files
chatwoot/spec/enterprise/models/account_spec.rb
Aakash BakhleandGitHub 568aae875b feat: wire up auto-sync job backend [AI-150] (#14117)
# Pull Request Template

## Description

- Wires up Controllers to auto-sync job
- adds plan based sync schedule
- a scheduler that runs every hour to check syncable documents
- guards the whole feature behind feature flag by reclaiming
`twilio_content_templates`
- Adds a global and account level cap on how many documents to enqueue
to prevent sudden burst at first run
- some refactor to simplify code
- specs

Fixes # (issue)

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.

specs and locally

## 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
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
2026-04-29 14:47:14 +05:30

328 lines
12 KiB
Ruby

# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Account, type: :model do
include ActiveJob::TestHelper
describe 'associations' do
it { is_expected.to have_many(:sla_policies).dependent(:destroy_async) }
it { is_expected.to have_many(:applied_slas).dependent(:destroy_async) }
it { is_expected.to have_many(:custom_roles).dependent(:destroy_async) }
end
describe 'sla_policies' do
let!(:account) { create(:account) }
let!(:sla_policy) { create(:sla_policy, account: account) }
it 'returns associated sla policies' do
expect(account.sla_policies).to eq([sla_policy])
end
it 'deletes associated sla policies' do
perform_enqueued_jobs do
account.destroy!
end
expect { sla_policy.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
context 'with usage_limits' do
let(:captain_limits) do
{
:startups => { :documents => 100, :responses => 100 },
:business => { :documents => 200, :responses => 300 },
:enterprise => { :documents => 300, :responses => 500 }
}.with_indifferent_access
end
let(:account) { create(:account, { custom_attributes: { plan_name: 'startups' } }) }
let(:assistant) { create(:captain_assistant, account: account) }
before do
create(:installation_config, name: 'ACCOUNT_AGENTS_LIMIT', value: 20)
end
describe 'when captain limits are configured' do
before do
create_list(:captain_document, 3, account: account, assistant: assistant, status: :available)
create(:installation_config, name: 'CAPTAIN_CLOUD_PLAN_LIMITS', value: captain_limits.to_json)
end
## Document
it 'updates document count accurately' do
account.update_document_usage
expect(account.custom_attributes['captain_documents_usage']).to eq(3)
end
it 'handles zero documents' do
account.captain_documents.destroy_all
account.update_document_usage
expect(account.custom_attributes['captain_documents_usage']).to eq(0)
end
it 'reflects document limits' do
document_limits = account.usage_limits[:captain][:documents]
expect(document_limits[:consumed]).to eq 3
expect(document_limits[:current_available]).to eq captain_limits[:startups][:documents] - 3
end
## Responses
it 'incrementing responses updates usage_limits' do
account.increment_response_usage
responses_limits = account.usage_limits[:captain][:responses]
expect(account.custom_attributes['captain_responses_usage']).to eq 1
expect(responses_limits[:consumed]).to eq 1
expect(responses_limits[:current_available]).to eq captain_limits[:startups][:responses] - 1
end
it 'reseting responses limits updates usage_limits' do
account.custom_attributes['captain_responses_usage'] = 30
account.save!
responses_limits = account.usage_limits[:captain][:responses]
expect(responses_limits[:consumed]).to eq 30
expect(responses_limits[:current_available]).to eq captain_limits[:startups][:responses] - 30
account.reset_response_usage
responses_limits = account.usage_limits[:captain][:responses]
expect(account.custom_attributes['captain_responses_usage']).to eq 0
expect(responses_limits[:consumed]).to eq 0
expect(responses_limits[:current_available]).to eq captain_limits[:startups][:responses]
end
it 'returns monthly limit accurately' do
%w[startups business enterprise].each do |plan|
account.custom_attributes = { 'plan_name': plan }
account.save!
expect(account.captain_monthly_limit).to eq captain_limits[plan]
end
end
it 'current_available is never out of bounds' do
account.custom_attributes['captain_responses_usage'] = 3000
account.save!
responses_limits = account.usage_limits[:captain][:responses]
expect(responses_limits[:consumed]).to eq 3000
expect(responses_limits[:current_available]).to eq 0
account.custom_attributes['captain_responses_usage'] = -100
account.save!
responses_limits = account.usage_limits[:captain][:responses]
expect(responses_limits[:consumed]).to eq 0
expect(responses_limits[:current_available]).to eq captain_limits[:startups][:responses]
end
end
describe 'when captain limits are not configured' do
it 'returns default values' do
account.custom_attributes = { 'plan_name': 'unknown' }
expect(account.captain_monthly_limit).to eq(
{ documents: ChatwootApp.max_limit, responses: ChatwootApp.max_limit }.with_indifferent_access
)
end
end
describe 'when limits are configured for an account' do
before do
create(:installation_config, name: 'CAPTAIN_CLOUD_PLAN_LIMITS', value: captain_limits.to_json)
account.update(limits: { captain_documents: 5555, captain_responses: 9999 })
end
it 'returns limits based on custom attributes' do
usage_limits = account.usage_limits
expect(usage_limits[:captain][:documents][:total_count]).to eq(5555)
expect(usage_limits[:captain][:responses][:total_count]).to eq(9999)
end
end
describe 'audit logs' do
it 'returns audit logs' do
# checking whether associated_audits method is present
expect(account.associated_audits.present?).to be false
end
it 'creates audit logs when account is updated' do
account.update(name: 'New Name')
expect(Audited::Audit.where(auditable_type: 'Account', action: 'update').count).to eq 1
end
end
it 'returns max limits from global config when enterprise version' do
expect(account.usage_limits[:agents]).to eq(20)
end
it 'returns max limits from account when enterprise version' do
account.update(limits: { agents: 10 })
expect(account.usage_limits[:agents]).to eq(10)
end
it 'returns limits based on subscription' do
account.update(limits: { agents: 10 }, custom_attributes: { subscribed_quantity: 5 })
expect(account.usage_limits[:agents]).to eq(5)
end
it 'returns max limits from global config if account limit is absent' do
account.update(limits: { agents: '' })
expect(account.usage_limits[:agents]).to eq(20)
end
it 'returns max limits from app limit if account limit and installation config is absent' do
account.update(limits: { agents: '' })
InstallationConfig.where(name: 'ACCOUNT_AGENTS_LIMIT').update(value: '')
expect(account.usage_limits[:agents]).to eq(ChatwootApp.max_limit)
end
end
describe 'subscribed_features' do
let(:account) { create(:account) }
let(:plan_features) do
{
'hacker' => %w[feature1 feature2],
'startups' => %w[feature1 feature2 feature3 feature4]
}
end
before do
InstallationConfig.where(name: 'CHATWOOT_CLOUD_PLAN_FEATURES').first_or_create(value: plan_features)
end
context 'when plan_name is hacker' do
it 'returns the features for the hacker plan' do
account.custom_attributes = { 'plan_name': 'hacker' }
account.save!
expect(account.subscribed_features).to eq(%w[feature1 feature2])
end
end
context 'when plan_name is startups' do
it 'returns the features for the startups plan' do
account.custom_attributes = { 'plan_name': 'startups' }
account.save!
expect(account.subscribed_features).to eq(%w[feature1 feature2 feature3 feature4])
end
end
context 'when plan_features is blank' do
it 'returns an empty array' do
account.custom_attributes = {}
account.save!
expect(account.subscribed_features).to be_nil
end
end
end
describe 'captain document sync cadence' do
let(:account) { create(:account) }
it 'has no cadence on the hacker plan' do
account.update!(custom_attributes: { plan_name: 'hacker' })
expect(account.captain_document_sync_interval).to be_nil
end
it 'syncs weekly on the startups plan' do
account.update!(custom_attributes: { plan_name: 'startups' })
expect(account.captain_document_sync_interval).to eq(7.days)
end
it 'syncs daily on the business plan' do
account.update!(custom_attributes: { plan_name: 'business' })
expect(account.captain_document_sync_interval).to eq(1.day)
end
it 'syncs every six hours on the enterprise plan' do
account.update!(custom_attributes: { plan_name: 'enterprise' })
expect(account.captain_document_sync_interval).to eq(6.hours)
end
it 'has no cadence when plan is missing' do
account.update!(custom_attributes: {})
expect(account.captain_document_sync_interval).to be_nil
end
it 'has no cadence for unknown plans' do
account.update!(custom_attributes: { plan_name: 'mystery' })
expect(account.captain_document_sync_interval).to be_nil
end
it 'normalizes plan name casing' do
account.update!(custom_attributes: { plan_name: 'Business' })
expect(account.captain_document_sync_interval).to eq(1.day)
end
it 'syncs every six hours on self-hosted enterprise installs without a plan_name' do
allow(ChatwootApp).to receive(:self_hosted_enterprise?).and_return(true)
account.update!(custom_attributes: {})
expect(account.captain_document_sync_interval).to eq(6.hours)
end
end
describe 'account deletion' do
let(:account) { create(:account) }
let(:admin) { create(:user, account: account, role: :administrator) }
describe '#mark_for_deletion' do
it 'sets the marked_for_deletion_at and marked_for_deletion_reason attributes' do
expect do
account.mark_for_deletion('inactivity')
end.to change { account.reload.custom_attributes['marked_for_deletion_at'] }.from(nil).to(be_present)
.and change { account.reload.custom_attributes['marked_for_deletion_reason'] }.from(nil).to('inactivity')
end
it 'sends a user-initiated deletion email when reason is manual_deletion' do
mailer = double
expect(AdministratorNotifications::AccountNotificationMailer).to receive(:with).with(account: account).and_return(mailer)
expect(mailer).to receive(:account_deletion_user_initiated).with(account, 'manual_deletion').and_return(mailer)
expect(mailer).to receive(:deliver_later)
account.mark_for_deletion('manual_deletion')
end
it 'sends a system-initiated deletion email when reason is not manual_deletion' do
mailer = double
expect(AdministratorNotifications::AccountNotificationMailer).to receive(:with).with(account: account).and_return(mailer)
expect(mailer).to receive(:account_deletion_for_inactivity).with(account, 'inactivity').and_return(mailer)
expect(mailer).to receive(:deliver_later)
account.mark_for_deletion('inactivity')
end
it 'returns true when successful' do
expect(account.mark_for_deletion).to be_truthy
end
end
describe '#unmark_for_deletion' do
before do
account.update!(
custom_attributes: {
'marked_for_deletion_at' => 7.days.from_now.iso8601,
'marked_for_deletion_reason' => 'test_reason'
}
)
end
it 'removes the marked_for_deletion_at and marked_for_deletion_reason attributes' do
expect do
account.unmark_for_deletion
end.to change { account.reload.custom_attributes['marked_for_deletion_at'] }.from(be_present).to(nil)
.and change { account.reload.custom_attributes['marked_for_deletion_reason'] }.from('test_reason').to(nil)
end
it 'returns true when successful' do
expect(account.unmark_for_deletion).to be_truthy
end
end
end
end