fix rubcop validations

This commit is contained in:
Tanmay Deep Sharma
2025-07-31 17:11:14 +05:30
parent f1832148fd
commit 9a09df4563
3 changed files with 0 additions and 866 deletions
@@ -1,235 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe AssignmentV2::ContinuousAssignmentJob, type: :job do
let(:account) { create(:account) }
let(:inbox) { create(:inbox, account: account) }
let(:assignment_policy) { create(:assignment_policy, account: account) }
before do
create(:inbox_assignment_policy, inbox: inbox, assignment_policy: assignment_policy)
allow(inbox).to receive(:assignment_v2_enabled?).and_return(true)
end
describe '#perform' do
context 'when inbox exists and assignment should run' do
before do
create(:conversation, inbox: inbox, assignee: nil, status: :open)
create(:conversation, inbox: inbox, assignee: nil, status: :open)
end
it 'runs assignment orchestrator' do
orchestrator_double = instance_double(AssignmentV2::AssignmentOrchestrator)
expect(AssignmentV2::AssignmentOrchestrator).to receive(:new).with(inbox).and_return(orchestrator_double)
expect(orchestrator_double).to receive(:assign_conversations).with(limit: 50).and_return(2)
described_class.new.perform(inbox.id)
end
it 'logs assignment start and completion' do
orchestrator = instance_double(AssignmentV2::AssignmentOrchestrator)
allow(AssignmentV2::AssignmentOrchestrator).to receive(:new).with(inbox).and_return(orchestrator)
allow(orchestrator).to receive(:assign_conversations).and_return(2)
expect(Rails.logger).to receive(:info).with("Assignment V2: Running continuous assignment for inbox #{inbox.id}")
expect(Rails.logger).to receive(:info).with("Assignment V2: Completed continuous assignment for inbox #{inbox.id}, made 2 assignments")
described_class.new.perform(inbox.id)
end
context 'when more conversations need processing' do
it 'schedules next run when batch size equals assignments made' do
orchestrator = instance_double(AssignmentV2::AssignmentOrchestrator)
allow(AssignmentV2::AssignmentOrchestrator).to receive(:new).with(inbox).and_return(orchestrator)
allow(orchestrator).to receive(:assign_conversations).and_return(50)
allow(inbox.conversations.unassigned.open).to receive(:exists?).and_return(true)
expect(described_class).to receive(:set).with(wait: anything).and_return(described_class)
expect(described_class).to receive(:perform_later).with(inbox.id)
described_class.new.perform(inbox.id, batch_size: 50)
end
it 'does not schedule next run when fewer assignments made than batch size' do
orchestrator = instance_double(AssignmentV2::AssignmentOrchestrator)
allow(AssignmentV2::AssignmentOrchestrator).to receive(:new).with(inbox).and_return(orchestrator)
allow(orchestrator).to receive(:assign_conversations).and_return(25)
expect(described_class).not_to receive(:set)
described_class.new.perform(inbox.id, batch_size: 50)
end
it 'does not schedule next run when no more unassigned conversations' do
orchestrator = instance_double(AssignmentV2::AssignmentOrchestrator)
allow(AssignmentV2::AssignmentOrchestrator).to receive(:new).with(inbox).and_return(orchestrator)
allow(orchestrator).to receive(:assign_conversations).and_return(50)
allow(inbox.conversations.unassigned.open).to receive(:exists?).and_return(false)
expect(described_class).not_to receive(:set)
described_class.new.perform(inbox.id, batch_size: 50)
end
end
end
context 'when inbox does not exist' do
it 'logs error and does not raise exception' do
expect(Rails.logger).to receive(:error).with('Assignment V2: Inbox 999 not found')
expect { described_class.new.perform(999) }.not_to raise_error
end
end
context 'when assignment should not run' do
before do
allow(inbox).to receive(:assignment_v2_enabled?).and_return(false)
end
it 'returns early without running assignment' do
expect(AssignmentV2::AssignmentOrchestrator).not_to receive(:new)
described_class.new.perform(inbox.id)
end
end
context 'when assignment policy is disabled' do
before do
assignment_policy.update!(enabled: false)
end
it 'returns early without running assignment' do
expect(AssignmentV2::AssignmentOrchestrator).not_to receive(:new)
described_class.new.perform(inbox.id)
end
end
context 'when no unassigned conversations exist' do
it 'returns early without running assignment' do
expect(AssignmentV2::AssignmentOrchestrator).not_to receive(:new)
described_class.new.perform(inbox.id)
end
end
context 'when assignment fails with error' do
before do
create(:conversation, inbox: inbox, assignee: nil, status: :open)
orchestrator = instance_double(AssignmentV2::AssignmentOrchestrator)
allow(AssignmentV2::AssignmentOrchestrator).to receive(:new).with(inbox).and_return(orchestrator)
allow(orchestrator).to receive(:assign_conversations).and_raise(StandardError, 'Assignment failed')
end
it 'logs error and re-raises exception' do
expect(Rails.logger).to receive(:error).with("Assignment V2: Continuous assignment failed for inbox #{inbox.id}: Assignment failed")
expect { described_class.new.perform(inbox.id) }.to raise_error(StandardError, 'Assignment failed')
end
end
end
describe '.trigger' do
let(:conversation) { create(:conversation, inbox: inbox) }
context 'when inbox has assignment v2 enabled' do
before do
allow(inbox).to receive(:assignment_v2_enabled?).and_return(true)
end
it 'enqueues job for conversation inbox' do
expect(described_class).to receive(:perform_later).with(inbox.id, batch_size: 10)
described_class.trigger(conversation)
end
end
context 'when inbox does not have assignment v2 enabled' do
before do
allow(inbox).to receive(:assignment_v2_enabled?).and_return(false)
end
it 'does not enqueue job' do
expect(described_class).not_to receive(:perform_later)
described_class.trigger(conversation)
end
end
end
describe '.process_all_inboxes' do
let(:account2) { create(:account) }
let(:inbox2) { create(:inbox, account: account2) }
let(:policy2) { create(:assignment_policy, account: account2) }
before do
create(:inbox_assignment_policy, inbox: inbox2, assignment_policy: policy2)
allow(inbox).to receive(:assignment_v2_enabled?).and_return(true)
allow(inbox2).to receive(:assignment_v2_enabled?).and_return(true)
create(:conversation, inbox: inbox, assignee: nil, status: :open)
create(:conversation, inbox: inbox2, assignee: nil, status: :open)
end
it 'processes all inboxes with enabled assignment policies' do
expect(Rails.logger).to receive(:info).with(/Scheduling bulk assignment for inbox #{inbox.id}/)
expect(Rails.logger).to receive(:info).with(/Scheduling bulk assignment for inbox #{inbox2.id}/)
expect(described_class).to receive(:perform_later).with(inbox.id)
expect(described_class).to receive(:perform_later).with(inbox2.id)
described_class.process_all_inboxes
end
it 'skips inboxes without unassigned conversations' do
# Remove unassigned conversations
Conversation.find_each { |c| c.update!(status: :resolved) }
expect(described_class).not_to receive(:perform_later)
described_class.process_all_inboxes
end
it 'skips inboxes without assignment v2 enabled' do
allow(inbox).to receive(:assignment_v2_enabled?).and_return(false)
expect(described_class).to receive(:perform_later).with(inbox2.id)
expect(described_class).not_to receive(:perform_later).with(inbox.id)
described_class.process_all_inboxes
end
end
describe 'delay calculation' do
let(:job_instance) { described_class.new }
it 'calculates delay with base time and jitter' do
delay = job_instance.send(:calculate_delay, inbox)
expect(delay).to be >= 30.seconds
expect(delay).to be <= 40.seconds
end
it 'adds randomization to prevent thundering herd' do
delays = []
10.times do
delays << job_instance.send(:calculate_delay, inbox)
end
# All delays should be different due to randomization
expect(delays.uniq.size).to be > 1
end
end
describe 'retry configuration' do
it 'has proper retry configuration' do
expect(described_class.get_sidekiq_options['retry']).to eq(3)
end
it 'uses exponentially longer wait times' do
# This tests the retry_on configuration
expect(described_class.instance_variable_get(:@retry_callbacks)).to be_present
end
end
end
@@ -1,369 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe AssignmentV2::AssignmentOrchestrator, type: :integration do
let(:account) { create(:account) }
let(:inbox) { create(:inbox, account: account) }
# Create agents with different availability
let!(:agent1) { create(:user, account: account, name: 'Agent 1', role: :agent, availability: :online) }
let!(:agent2) { create(:user, account: account, name: 'Agent 2', role: :agent, availability: :online) }
let!(:agent3) { create(:user, account: account, name: 'Agent 3', role: :agent, availability: :busy) }
let!(:agent4) { create(:user, account: account, name: 'Agent 4', role: :agent, availability: :offline) }
before do
# Make agents members of inbox
[agent1, agent2, agent3, agent4].each do |agent|
create(:inbox_member, inbox: inbox, user: agent)
end
# Clear Redis to ensure clean state
Redis::Alfred.flushdb
end
describe 'Round Robin Assignment' do
let(:assignment_policy) do
create(:assignment_policy,
account: account,
name: 'Round Robin Policy',
assignment_order: :round_robin,
conversation_priority: :earliest_created,
enabled: true)
end
let(:inbox_assignment_policy) do
create(:inbox_assignment_policy, inbox: inbox, assignment_policy: assignment_policy)
end
it 'assigns conversations in round-robin fashion to online agents only' do
# Create unassigned conversations
conversations = create_list(:conversation, 6, inbox: inbox, assignee: nil, status: :open)
# Process assignments
service = AssignmentV2::AssignmentService.new(inbox)
assigned_count = service.assign_conversations
expect(assigned_count).to eq(6)
# Verify all conversations are assigned
conversations.each(&:reload)
expect(conversations.filter_map(&:assignee).count).to eq(6)
# Verify only online agents received assignments
assigned_agents = conversations.map(&:assignee).uniq
expect(assigned_agents).to contain_exactly(agent1, agent2)
# Verify round-robin distribution
agent1_count = conversations.count { |c| c.assignee == agent1 }
agent2_count = conversations.count { |c| c.assignee == agent2 }
expect([agent1_count, agent2_count]).to contain_exactly(3, 3)
end
it 'respects conversation priority order' do
# Create conversations with different creation times
old_conv = create(:conversation, inbox: inbox, assignee: nil, created_at: 2.hours.ago)
mid_conv = create(:conversation, inbox: inbox, assignee: nil, created_at: 1.hour.ago)
new_conv = create(:conversation, inbox: inbox, assignee: nil, created_at: 5.minutes.ago)
# Assign only 2 conversations
service = AssignmentV2::AssignmentService.new(inbox)
service.assign_conversations(limit: 2)
# Oldest conversations should be assigned first
expect(old_conv.reload.assignee).not_to be_nil
expect(mid_conv.reload.assignee).not_to be_nil
expect(new_conv.reload.assignee).to be_nil
end
it 'handles agent availability changes mid-assignment' do
conversations = create_list(:conversation, 4, inbox: inbox, assignee: nil)
# Assign first batch
service = AssignmentV2::AssignmentService.new(inbox)
service.assign_conversations(limit: 2)
# Make agent1 offline
agent1.update!(availability: :offline)
# Assign remaining conversations
service.assign_conversations(limit: 2)
# All remaining should go to agent2
remaining_assignments = conversations.reload.last(2).map(&:assignee)
expect(remaining_assignments).to all(eq(agent2))
end
end
describe 'Balanced Assignment' do
let(:assignment_policy) do
create(:assignment_policy,
account: account,
name: 'Balanced Policy',
assignment_order: :balanced,
enabled: true)
end
let(:inbox_assignment_policy) do
create(:inbox_assignment_policy, inbox: inbox, assignment_policy: assignment_policy)
end
before do
# Mock enterprise features
allow(account).to receive(:feature_enabled?).with(:enterprise_agent_capacity).and_return(true)
end
it 'assigns to agent with least conversations' do
# Create existing load imbalance
create_list(:conversation, 5, inbox: inbox, assignee: agent1, status: :open)
create_list(:conversation, 2, inbox: inbox, assignee: agent2, status: :open)
# Create new conversations
new_conversations = create_list(:conversation, 3, inbox: inbox, assignee: nil)
# Process assignments
service = AssignmentV2::AssignmentService.new(inbox)
service.assign_conversations
# All should go to agent2 (less loaded)
new_conversations.each(&:reload)
expect(new_conversations.map(&:assignee)).to all(eq(agent2))
# Final count should be more balanced
expect(agent1.assigned_conversations.open.where(inbox: inbox).count).to eq(5)
expect(agent2.assigned_conversations.open.where(inbox: inbox).count).to eq(5)
end
it 'only counts open conversations for balancing' do
# Agent1 has many resolved conversations (shouldn't count)
create_list(:conversation, 10, inbox: inbox, assignee: agent1, status: :resolved)
# Agent1 has 1 open conversation
create(:conversation, inbox: inbox, assignee: agent1, status: :open)
# Agent2 has 3 open conversations
create_list(:conversation, 3, inbox: inbox, assignee: agent2, status: :open)
# New conversation should go to agent1
new_conversation = create(:conversation, inbox: inbox, assignee: nil)
service = AssignmentV2::AssignmentService.new(inbox)
service.assign_conversation(new_conversation)
expect(new_conversation.reload.assignee).to eq(agent1)
end
end
describe 'Enterprise Capacity Management' do
let(:assignment_policy) do
create(:assignment_policy,
account: account,
assignment_order: :balanced,
enabled: true)
end
let(:inbox_assignment_policy) do
create(:inbox_assignment_policy, inbox: inbox, assignment_policy: assignment_policy)
end
let(:capacity_policy) do
create(:enterprise_agent_capacity_policy, account: account, name: 'Limited Capacity')
end
before do
# Mock enterprise features
stub_const('Enterprise', Module.new)
stub_const('Enterprise::AgentCapacityPolicy', Class.new(ApplicationRecord))
stub_const('Enterprise::InboxCapacityLimit', Class.new(ApplicationRecord))
allow(account).to receive(:feature_enabled?).with(:enterprise_agent_capacity).and_return(true)
# Set up capacity limits
agent1.account_users.first.update!(agent_capacity_policy: capacity_policy)
agent2.account_users.first.update!(agent_capacity_policy: capacity_policy)
create(:enterprise_inbox_capacity_limit,
agent_capacity_policy: capacity_policy,
inbox: inbox,
conversation_limit: 3)
end
it 'respects agent capacity limits' do
# Fill agent1 to capacity
create_list(:conversation, 3, inbox: inbox, assignee: agent1, status: :open)
# Create new conversations
new_conversations = create_list(:conversation, 4, inbox: inbox, assignee: nil)
# Mock capacity manager
capacity_manager = instance_double(Enterprise::AssignmentV2::CapacityManager)
allow(Enterprise::AssignmentV2::CapacityManager).to receive(:new).and_return(capacity_manager)
# Agent1 at capacity, agent2 has room
allow(capacity_manager).to receive(:get_agent_capacity).with(agent1, inbox).and_return(
{ available_capacity: 0, current_assignments: 3, total_capacity: 3 }
)
allow(capacity_manager).to receive(:get_agent_capacity).with(agent2, inbox).and_return(
{ available_capacity: 3, current_assignments: 0, total_capacity: 3 }
)
# Process assignments
service = AssignmentV2::AssignmentService.new(inbox)
assigned_count = service.assign_conversations
# Only 3 should be assigned (agent2's capacity)
expect(assigned_count).to eq(3)
# All should go to agent2
assigned_conversations = new_conversations.select { |c| c.reload.assignee.present? }
expect(assigned_conversations.map(&:assignee)).to all(eq(agent2))
end
it 'handles capacity policy with exclusion rules' do
# Update capacity policy with exclusion rules
capacity_policy.update!(
exclusion_rules: {
'labels' => ['urgent'],
'hours_threshold' => 24
}
)
# Create urgent label
urgent_label = create(:label, account: account, title: 'urgent')
# Create mixed conversations for agent1
create(:conversation, inbox: inbox, assignee: agent1, status: :open)
urgent_conv = create(:conversation, inbox: inbox, assignee: agent1, status: :open)
create(:conversation_label, conversation: urgent_conv, label: urgent_label)
create(:conversation, inbox: inbox, assignee: agent1, status: :open, created_at: 2.days.ago)
# Mock capacity calculation with exclusions
capacity_manager = instance_double(Enterprise::AssignmentV2::CapacityManager)
allow(Enterprise::AssignmentV2::CapacityManager).to receive(:new).and_return(capacity_manager)
# Only regular conversation counts toward capacity
allow(capacity_manager).to receive(:get_agent_capacity).with(agent1, inbox).and_return(
{ available_capacity: 2, current_assignments: 1, total_capacity: 3 }
)
# New conversation should still be assignable
new_conversation = create(:conversation, inbox: inbox, assignee: nil)
service = AssignmentV2::AssignmentService.new(inbox)
expect(service.assign_conversation(new_conversation)).to be true
end
end
describe 'Team-based Assignment' do
let(:team) { create(:team, account: account) }
let(:assignment_policy) { create(:assignment_policy, account: account, enabled: true) }
before do
create(:inbox_assignment_policy, inbox: inbox, assignment_policy: assignment_policy)
create(:team_member, team: team, user: agent1)
create(:team_member, team: team, user: agent2)
end
it 'assigns only to team members when conversation has team' do
# Create conversation with team
conversation = create(:conversation, inbox: inbox, assignee: nil, team: team)
# Mock team filtering in service
service = AssignmentV2::AssignmentService.new(inbox)
# Should only consider team members
100.times do
conversation.update!(assignee: nil)
service.assign_conversation(conversation)
expect(conversation.reload.assignee).to be_in([agent1, agent2])
end
end
end
describe 'Feature Flag Control' do
let(:assignment_policy) { create(:assignment_policy, account: account, enabled: true) }
before do
create(:inbox_assignment_policy, inbox: inbox, assignment_policy: assignment_policy)
allow(inbox).to receive(:assignment_v2_enabled?).and_return(false)
end
it 'falls back to legacy assignment when V2 is disabled' do
conversation = create(:conversation, inbox: inbox, assignee: nil)
# Enable auto assignment
inbox.update!(enable_auto_assignment: true)
# Should use legacy service
expect(AutoAssignment::AgentAssignmentService).to receive(:new).and_call_original
# Trigger assignment through model callback
conversation.update!(status: :open)
end
end
describe 'Concurrent Assignment Handling' do
let(:assignment_policy) { create(:assignment_policy, account: account, enabled: true) }
before { create(:inbox_assignment_policy, inbox: inbox, assignment_policy: assignment_policy) }
it 'handles multiple simultaneous assignment jobs' do
conversations = create_list(:conversation, 10, inbox: inbox, assignee: nil)
# Simulate concurrent job execution
threads = []
3.times do
threads << Thread.new do
AssignmentV2::AssignmentJob.new.perform(inbox_id: inbox.id)
end
end
threads.each(&:join)
# All conversations should be assigned without duplicates
conversations.each(&:reload)
assigned_count = conversations.count { |c| c.assignee.present? }
expect(assigned_count).to eq(10)
# No conversation should have been assigned multiple times
assignment_counts = conversations.group_by(&:assignee).transform_values(&:count)
expect(assignment_counts.values.sum).to eq(10)
end
end
describe 'Error Recovery' do
let(:assignment_policy) { create(:assignment_policy, account: account, enabled: true) }
before { create(:inbox_assignment_policy, inbox: inbox, assignment_policy: assignment_policy) }
it 'continues assignment after individual conversation failure' do
conversations = create_list(:conversation, 5, inbox: inbox, assignee: nil)
# Make one conversation invalid
conversations[2].update!(status: 'resolved')
service = AssignmentV2::AssignmentService.new(inbox)
assigned_count = service.assign_conversations
# Should assign 4 out of 5
expect(assigned_count).to eq(4)
# Invalid conversation remains unassigned
expect(conversations[2].reload.assignee).to be_nil
end
it 'recovers from Redis failures' do
# Simulate Redis connection failure
allow(Redis::Alfred).to receive(:lpop).and_raise(Redis::CannotConnectError)
conversation = create(:conversation, inbox: inbox, assignee: nil)
service = AssignmentV2::AssignmentService.new(inbox)
# Should fall back to database-based assignment
expect(service.assign_conversation(conversation)).to be true
expect(conversation.reload.assignee).not_to be_nil
end
end
end
@@ -1,262 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Enterprise::AssignmentV2::CapacityManager do
let(:manager) { described_class.new }
let(:account) { create(:account) }
let(:inbox) { create(:inbox, account: account) }
let(:agent) { create(:user, account: account, role: :agent) }
let(:capacity_policy) { create(:enterprise_agent_capacity_policy, account: account) }
let!(:account_user) { create(:account_user, account: account, user: agent, agent_capacity_policy: capacity_policy) }
before do
# Clear any existing cache
Rails.cache.clear
end
describe '#get_agent_capacity' do
context 'with capacity policy and limits' do
before { create(:enterprise_inbox_capacity_limit, agent_capacity_policy: capacity_policy, inbox: inbox, conversation_limit: 10) }
it 'returns correct capacity data' do
capacity = manager.get_agent_capacity(agent, inbox)
expect(capacity).to include(
total_capacity: 10,
current_assignments: 0,
available_capacity: 10,
policy_id: capacity_policy.id,
has_policy: true
)
end
it 'counts only open conversations' do
# Create conversations with different statuses
create_list(:conversation, 3, inbox: inbox, assignee: agent, status: :open)
create_list(:conversation, 2, inbox: inbox, assignee: agent, status: :resolved)
create(:conversation, inbox: inbox, assignee: agent, status: :snoozed)
capacity = manager.get_agent_capacity(agent, inbox)
expect(capacity[:current_assignments]).to eq(3)
expect(capacity[:available_capacity]).to eq(7)
end
it 'caches capacity data' do
# First call
manager.get_agent_capacity(agent, inbox)
# Second call should use cache
expect(Rails.cache).to receive(:fetch).and_call_original
manager.get_agent_capacity(agent, inbox)
end
it 'respects cache TTL' do
cache_key = "assignment_v2:capacity:#{agent.accounts.first.id}:#{agent.id}:#{inbox.id}"
manager.get_agent_capacity(agent, inbox)
# Check cache exists with TTL
expect(Rails.cache.exist?(cache_key)).to be true
end
end
context 'without capacity policy' do
before { account_user.update!(agent_capacity_policy: nil) }
it 'returns unlimited capacity' do
capacity = manager.get_agent_capacity(agent, inbox)
expect(capacity).to include(
total_capacity: 999_999,
current_assignments: 0,
available_capacity: 999_999,
policy_id: nil,
has_policy: false
)
end
end
context 'without inbox limit' do
it 'returns unlimited capacity' do
capacity = manager.get_agent_capacity(agent, inbox)
expect(capacity[:has_policy]).to be false
expect(capacity[:available_capacity]).to eq(999_999)
end
end
context 'with exclusion rules' do
let(:excluded_label) { create(:label, account: account, title: 'vip') }
let(:capacity_policy) do
create(:enterprise_agent_capacity_policy,
account: account,
exclusion_rules: {
'labels' => ['vip'],
'hours_threshold' => 24
})
end
before { create(:enterprise_inbox_capacity_limit, agent_capacity_policy: capacity_policy, inbox: inbox, conversation_limit: 10) }
it 'excludes conversations with specified labels' do
# Create regular conversations
create_list(:conversation, 3, inbox: inbox, assignee: agent, status: :open)
# Create VIP conversations (should be excluded)
vip_conversations = create_list(:conversation, 2, inbox: inbox, assignee: agent, status: :open)
vip_conversations.each { |conv| create(:conversation_label, conversation: conv, label: excluded_label) }
capacity = manager.get_agent_capacity(agent, inbox)
expect(capacity[:current_assignments]).to eq(3) # Only non-VIP conversations
end
it 'excludes old conversations based on hours threshold' do
# Create recent conversations
create_list(:conversation, 2, inbox: inbox, assignee: agent, status: :open, created_at: 1.hour.ago)
# Create old conversations (should be excluded)
create_list(:conversation, 3, inbox: inbox, assignee: agent, status: :open, created_at: 2.days.ago)
capacity = manager.get_agent_capacity(agent, inbox)
expect(capacity[:current_assignments]).to eq(2) # Only recent conversations
end
end
context 'when error occurs' do
it 'returns unlimited capacity on error' do
allow(Rails.cache).to receive(:fetch).and_raise(StandardError, 'Cache error')
expect(Rails.logger).to receive(:error).with(/Capacity manager failed/)
capacity = manager.get_agent_capacity(agent, inbox)
expect(capacity[:has_policy]).to be false
expect(capacity[:available_capacity]).to eq(999_999)
end
end
end
describe '#get_agents_capacity_status' do
let(:agent2) { create(:user, account: account, role: :agent) }
before { create(:account_user, account: account, user: agent2) }
it 'returns capacity status for multiple agents' do
agents = [agent, agent2]
statuses = manager.get_agents_capacity_status(agents, inbox)
expect(statuses.length).to eq(2)
expect(statuses[0][:agent]).to eq(agent)
expect(statuses[1][:agent]).to eq(agent2)
end
end
describe '#agent_has_capacity?' do
before { create(:enterprise_inbox_capacity_limit, agent_capacity_policy: capacity_policy, inbox: inbox, conversation_limit: 2) }
context 'when agent has capacity' do
it 'returns true' do
create(:conversation, inbox: inbox, assignee: agent, status: :open)
expect(manager.agent_has_capacity?(agent, inbox)).to be true
end
end
context 'when agent is at capacity' do
it 'returns false' do
create_list(:conversation, 2, inbox: inbox, assignee: agent, status: :open)
expect(manager.agent_has_capacity?(agent, inbox)).to be false
end
end
end
describe '#get_remaining_capacity' do
before { create(:enterprise_inbox_capacity_limit, agent_capacity_policy: capacity_policy, inbox: inbox, conversation_limit: 5) }
it 'returns correct remaining capacity' do
create_list(:conversation, 3, inbox: inbox, assignee: agent, status: :open)
expect(manager.get_remaining_capacity(agent, inbox)).to eq(2)
end
it 'returns 0 when over capacity' do
create_list(:conversation, 6, inbox: inbox, assignee: agent, status: :open)
expect(manager.get_remaining_capacity(agent, inbox)).to eq(0)
end
end
describe '#invalidate_agent_capacity_cache' do
it 'clears specific inbox cache when inbox provided' do
cache_key = "assignment_v2:capacity:#{agent.accounts.first.id}:#{agent.id}:#{inbox.id}"
Rails.cache.write(cache_key, 'test_data')
manager.invalidate_agent_capacity_cache(agent, inbox)
expect(Rails.cache.read(cache_key)).to be_nil
end
it 'clears all agent caches when inbox not provided' do
cache_key1 = "assignment_v2:capacity:#{agent.id}:inbox1"
cache_key2 = "assignment_v2:capacity:#{agent.id}:inbox2"
Rails.cache.write(cache_key1, 'test_data')
Rails.cache.write(cache_key2, 'test_data')
expect(Rails.cache).to receive(:delete_matched).with("assignment_v2:capacity:#{agent.id}:*")
manager.invalidate_agent_capacity_cache(agent)
end
end
describe '#invalidate_inbox_capacity_cache' do
it 'clears all capacity caches for inbox' do
expect(Rails.cache).to receive(:delete_matched).with("assignment_v2:capacity:*:#{inbox.id}")
manager.invalidate_inbox_capacity_cache(inbox)
end
end
describe 'edge cases' do
it 'handles agent without account correctly' do
agent_without_account = create(:user, role: :agent)
capacity = manager.get_agent_capacity(agent_without_account, inbox)
expect(capacity[:has_policy]).to be false
end
it 'handles multiple capacity policies gracefully' do
# Create another policy and try to assign it
another_policy = create(:enterprise_agent_capacity_policy, account: account)
# Update account user
account_user.update!(agent_capacity_policy: another_policy)
# Should use the new policy
capacity = manager.get_agent_capacity(agent, inbox)
expect(capacity[:policy_id]).to eq(another_policy.id)
end
it 'handles conversations from different inboxes' do
other_inbox = create(:inbox, account: account)
create(:enterprise_inbox_capacity_limit,
agent_capacity_policy: capacity_policy,
inbox: inbox,
conversation_limit: 5)
# Create conversations in different inboxes
create_list(:conversation, 3, inbox: inbox, assignee: agent, status: :open)
create_list(:conversation, 10, inbox: other_inbox, assignee: agent, status: :open)
# Should only count conversations from specified inbox
capacity = manager.get_agent_capacity(agent, inbox)
expect(capacity[:current_assignments]).to eq(3)
end
end
end