From b015c61d7122ed1a128a96c8d49feefdfafd9c87 Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma Date: Thu, 4 Sep 2025 19:11:18 +0700 Subject: [PATCH] fix specs --- .../periodic_assignment_job_spec.rb | 42 +++++++++-- .../assignment_service_spec.rb | 73 ++++++++++++------- .../round_robin_selector_spec.rb | 49 +++++++------ 3 files changed, 109 insertions(+), 55 deletions(-) diff --git a/spec/jobs/auto_assignment/periodic_assignment_job_spec.rb b/spec/jobs/auto_assignment/periodic_assignment_job_spec.rb index 6ef28c832..4b06decd0 100644 --- a/spec/jobs/auto_assignment/periodic_assignment_job_spec.rb +++ b/spec/jobs/auto_assignment/periodic_assignment_job_spec.rb @@ -4,7 +4,7 @@ RSpec.describe AutoAssignment::PeriodicAssignmentJob, type: :job do let(:account) { create(:account) } let(:inbox) { create(:inbox, account: account, enable_auto_assignment: true) } let(:assignment_policy) { create(:assignment_policy, account: account) } - let!(:inbox_assignment_policy) { create(:inbox_assignment_policy, inbox: inbox, assignment_policy: assignment_policy) } + let(:inbox_assignment_policy) { create(:inbox_assignment_policy, inbox: inbox, assignment_policy: assignment_policy) } let(:agent) { create(:user, account: account, role: :agent) } before do @@ -14,26 +14,43 @@ RSpec.describe AutoAssignment::PeriodicAssignmentJob, type: :job do describe '#perform' do context 'when account has assignment_v2 feature enabled' do before do - allow_any_instance_of(Account).to receive(:feature_enabled?).with('assignment_v2').and_return(true) + allow(account).to receive(:feature_enabled?).with('assignment_v2').and_return(true) + allow(Account).to receive(:find_in_batches).and_yield([account]) end context 'when inbox has auto_assignment_v2 enabled' do before do - allow_any_instance_of(Inbox).to receive(:auto_assignment_v2_enabled?).and_return(true) + allow(inbox).to receive(:auto_assignment_v2_enabled?).and_return(true) + inbox_relation = instance_double(ActiveRecord::Relation) + allow(account).to receive(:inboxes).and_return(inbox_relation) + allow(inbox_relation).to receive(:joins).with(:assignment_policy).and_return(inbox_relation) + allow(inbox_relation).to receive(:find_in_batches).and_yield([inbox]) end it 'queues assignment job for eligible inboxes' do + inbox_assignment_policy # ensure it exists expect(AutoAssignment::AssignmentJob).to receive(:perform_later).with(inbox_id: inbox.id) described_class.new.perform end it 'processes multiple accounts' do + inbox_assignment_policy # ensure it exists account2 = create(:account) inbox2 = create(:inbox, account: account2, enable_auto_assignment: true) policy2 = create(:assignment_policy, account: account2) create(:inbox_assignment_policy, inbox: inbox2, assignment_policy: policy2) + allow(account2).to receive(:feature_enabled?).with('assignment_v2').and_return(true) + allow(inbox2).to receive(:auto_assignment_v2_enabled?).and_return(true) + + inbox_relation2 = instance_double(ActiveRecord::Relation) + allow(account2).to receive(:inboxes).and_return(inbox_relation2) + allow(inbox_relation2).to receive(:joins).with(:assignment_policy).and_return(inbox_relation2) + allow(inbox_relation2).to receive(:find_in_batches).and_yield([inbox2]) + + allow(Account).to receive(:find_in_batches).and_yield([account]).and_yield([account2]) + expect(AutoAssignment::AssignmentJob).to receive(:perform_later).with(inbox_id: inbox.id) expect(AutoAssignment::AssignmentJob).to receive(:perform_later).with(inbox_id: inbox2.id) @@ -43,7 +60,7 @@ RSpec.describe AutoAssignment::PeriodicAssignmentJob, type: :job do context 'when inbox does not have auto_assignment_v2 enabled' do before do - allow_any_instance_of(Inbox).to receive(:auto_assignment_v2_enabled?).and_return(false) + allow(inbox).to receive(:auto_assignment_v2_enabled?).and_return(false) end it 'does not queue assignment job' do @@ -56,7 +73,8 @@ RSpec.describe AutoAssignment::PeriodicAssignmentJob, type: :job do context 'when account does not have assignment_v2 feature enabled' do before do - allow_any_instance_of(Account).to receive(:feature_enabled?).with('assignment_v2').and_return(false) + allow(account).to receive(:feature_enabled?).with('assignment_v2').and_return(false) + allow(Account).to receive(:find_in_batches).and_yield([account]) end it 'does not process the account' do @@ -68,12 +86,26 @@ RSpec.describe AutoAssignment::PeriodicAssignmentJob, type: :job do context 'with batch processing' do it 'processes accounts in batches' do + accounts = [] # Create multiple accounts 5.times do |_i| acc = create(:account) inb = create(:inbox, account: acc, enable_auto_assignment: true) policy = create(:assignment_policy, account: acc) create(:inbox_assignment_policy, inbox: inb, assignment_policy: policy) + allow(acc).to receive(:feature_enabled?).with('assignment_v2').and_return(true) + allow(inb).to receive(:auto_assignment_v2_enabled?).and_return(true) + + inbox_relation = instance_double(ActiveRecord::Relation) + allow(acc).to receive(:inboxes).and_return(inbox_relation) + allow(inbox_relation).to receive(:joins).with(:assignment_policy).and_return(inbox_relation) + allow(inbox_relation).to receive(:find_in_batches).and_yield([inb]) + + accounts << acc + end + + allow(Account).to receive(:find_in_batches) do |&block| + accounts.each { |acc| block.call([acc]) } end expect(Account).to receive(:find_in_batches).and_call_original diff --git a/spec/services/auto_assignment/assignment_service_spec.rb b/spec/services/auto_assignment/assignment_service_spec.rb index 7413b6626..36f7d029e 100644 --- a/spec/services/auto_assignment/assignment_service_spec.rb +++ b/spec/services/auto_assignment/assignment_service_spec.rb @@ -14,24 +14,27 @@ RSpec.describe AutoAssignment::AssignmentService do describe '#perform_bulk_assignment' do context 'when auto assignment is enabled' do + let(:rate_limiter) { instance_double(AutoAssignment::RateLimiter) } + before do allow(OnlineStatusTracker).to receive(:get_available_users).and_return({ agent.id.to_s => 'online' }) - + # Mock RoundRobinSelector to return the agent round_robin_selector = instance_double(AutoAssignment::RoundRobinSelector) allow(AutoAssignment::RoundRobinSelector).to receive(:new).and_return(round_robin_selector) allow(round_robin_selector).to receive(:select_agent).and_return(agent) - + # Mock RateLimiter to allow all assignments by default - allow_any_instance_of(AutoAssignment::RateLimiter).to receive(:within_limit?).and_return(true) - allow_any_instance_of(AutoAssignment::RateLimiter).to receive(:track_assignment) + allow(AutoAssignment::RateLimiter).to receive(:new).and_return(rate_limiter) + allow(rate_limiter).to receive(:within_limit?).and_return(true) + allow(rate_limiter).to receive(:track_assignment) end it 'assigns conversations to available agents' do # Create conversation and ensure it's unassigned conv = create(:conversation, inbox: inbox, status: 'open') conv.update!(assignee_id: nil) - + assigned_count = service.perform_bulk_assignment(limit: 1) expect(assigned_count).to eq(1) @@ -88,7 +91,7 @@ RSpec.describe AutoAssignment::AssignmentService do it 'dispatches assignee changed event' do conversation # ensure it exists conversation.update!(assignee_id: nil) - + # The conversation model also dispatches a conversation.updated event allow(Rails.configuration.dispatcher).to receive(:dispatch) expect(Rails.configuration.dispatcher).to receive(:dispatch).with( @@ -113,17 +116,20 @@ RSpec.describe AutoAssignment::AssignmentService do end context 'with conversation priority' do + let(:rate_limiter) { instance_double(AutoAssignment::RateLimiter) } + before do allow(OnlineStatusTracker).to receive(:get_available_users).and_return({ agent.id.to_s => 'online' }) - + # Mock RoundRobinSelector to return the agent round_robin_selector = instance_double(AutoAssignment::RoundRobinSelector) allow(AutoAssignment::RoundRobinSelector).to receive(:new).and_return(round_robin_selector) allow(round_robin_selector).to receive(:select_agent).and_return(agent) - + # Mock RateLimiter to allow all assignments by default - allow_any_instance_of(AutoAssignment::RateLimiter).to receive(:within_limit?).and_return(true) - allow_any_instance_of(AutoAssignment::RateLimiter).to receive(:track_assignment) + allow(AutoAssignment::RateLimiter).to receive(:new).and_return(rate_limiter) + allow(rate_limiter).to receive(:within_limit?).and_return(true) + allow(rate_limiter).to receive(:track_assignment) end context 'when priority is longest_waiting' do @@ -189,12 +195,17 @@ RSpec.describe AutoAssignment::AssignmentService do round_robin_selector = instance_double(AutoAssignment::RoundRobinSelector) allow(AutoAssignment::RoundRobinSelector).to receive(:new).and_return(round_robin_selector) allow(round_robin_selector).to receive(:select_agent).and_return(agent2) - + # Mock agent1 at limit, agent2 not at limit - allow_any_instance_of(AutoAssignment::RateLimiter).to receive(:within_limit?) do |limiter| - limiter.instance_variable_get(:@agent) != agent - end - allow_any_instance_of(AutoAssignment::RateLimiter).to receive(:track_assignment) + agent1_limiter = instance_double(AutoAssignment::RateLimiter) + agent2_limiter = instance_double(AutoAssignment::RateLimiter) + + allow(AutoAssignment::RateLimiter).to receive(:new).with(inbox: inbox, agent: agent).and_return(agent1_limiter) + allow(AutoAssignment::RateLimiter).to receive(:new).with(inbox: inbox, agent: agent2).and_return(agent2_limiter) + + allow(agent1_limiter).to receive(:within_limit?).and_return(false) + allow(agent2_limiter).to receive(:within_limit?).and_return(true) + allow(agent2_limiter).to receive(:track_assignment) unassigned_conversation = create(:conversation, inbox: inbox, status: 'open') unassigned_conversation.update!(assignee_id: nil) @@ -207,14 +218,16 @@ RSpec.describe AutoAssignment::AssignmentService do it 'tracks assignments in Redis' do conversation # ensure it exists conversation.update!(assignee_id: nil) - + # Mock RoundRobinSelector round_robin_selector = instance_double(AutoAssignment::RoundRobinSelector) allow(AutoAssignment::RoundRobinSelector).to receive(:new).and_return(round_robin_selector) allow(round_robin_selector).to receive(:select_agent).and_return(agent) - - allow_any_instance_of(AutoAssignment::RateLimiter).to receive(:within_limit?).and_return(true) - expect_any_instance_of(AutoAssignment::RateLimiter).to receive(:track_assignment) + + limiter = instance_double(AutoAssignment::RateLimiter) + allow(AutoAssignment::RateLimiter).to receive(:new).and_return(limiter) + allow(limiter).to receive(:within_limit?).and_return(true) + expect(limiter).to receive(:track_assignment) service.perform_bulk_assignment(limit: 1) end @@ -224,13 +237,15 @@ RSpec.describe AutoAssignment::AssignmentService do round_robin_selector = instance_double(AutoAssignment::RoundRobinSelector) allow(AutoAssignment::RoundRobinSelector).to receive(:new).and_return(round_robin_selector) allow(round_robin_selector).to receive(:select_agent).and_return(agent, agent2) - + # Mock RateLimiter to allow all - allow_any_instance_of(AutoAssignment::RateLimiter).to receive(:within_limit?).and_return(true) - allow_any_instance_of(AutoAssignment::RateLimiter).to receive(:track_assignment) - + limiter = instance_double(AutoAssignment::RateLimiter) + allow(AutoAssignment::RateLimiter).to receive(:new).and_return(limiter) + allow(limiter).to receive(:within_limit?).and_return(true) + allow(limiter).to receive(:track_assignment) + # Simulate time passing for rate limit window - travel_to(Time.current) do + freeze_time do 2.times do conversation_new = create(:conversation, inbox: inbox, status: 'open') conversation_new.update!(assignee_id: nil) @@ -255,15 +270,17 @@ RSpec.describe AutoAssignment::AssignmentService do conv = create(:conversation, inbox: inbox, status: 'open') conv.update!(assignee_id: nil) end - + # Mock RoundRobinSelector round_robin_selector = instance_double(AutoAssignment::RoundRobinSelector) allow(AutoAssignment::RoundRobinSelector).to receive(:new).and_return(round_robin_selector) allow(round_robin_selector).to receive(:select_agent).and_return(agent) - + # Mock RateLimiter to allow all - allow_any_instance_of(AutoAssignment::RateLimiter).to receive(:within_limit?).and_return(true) - allow_any_instance_of(AutoAssignment::RateLimiter).to receive(:track_assignment) + limiter = instance_double(AutoAssignment::RateLimiter) + allow(AutoAssignment::RateLimiter).to receive(:new).and_return(limiter) + allow(limiter).to receive(:within_limit?).and_return(true) + allow(limiter).to receive(:track_assignment) assigned_count = service.perform_bulk_assignment(limit: 5) expect(assigned_count).to eq(5) diff --git a/spec/services/auto_assignment/round_robin_selector_spec.rb b/spec/services/auto_assignment/round_robin_selector_spec.rb index 0b82ad137..05c1dcd73 100644 --- a/spec/services/auto_assignment/round_robin_selector_spec.rb +++ b/spec/services/auto_assignment/round_robin_selector_spec.rb @@ -7,16 +7,33 @@ RSpec.describe AutoAssignment::RoundRobinSelector do let(:agent1) { create(:user, account: account, role: :agent, availability: :online) } let(:agent2) { create(:user, account: account, role: :agent, availability: :online) } let(:agent3) { create(:user, account: account, role: :agent, availability: :online) } - let(:member1) { create(:inbox_member, inbox: inbox, user: agent1) } - let(:member2) { create(:inbox_member, inbox: inbox, user: agent2) } - let(:member3) { create(:inbox_member, inbox: inbox, user: agent3) } - + + let(:round_robin_service) { instance_double(AutoAssignment::InboxRoundRobinService) } + + let(:member1) do + allow(AutoAssignment::InboxRoundRobinService).to receive(:new).and_return(round_robin_service) + allow(round_robin_service).to receive(:add_agent_to_queue) + create(:inbox_member, inbox: inbox, user: agent1) + end + + let(:member2) do + allow(AutoAssignment::InboxRoundRobinService).to receive(:new).and_return(round_robin_service) + allow(round_robin_service).to receive(:add_agent_to_queue) + create(:inbox_member, inbox: inbox, user: agent2) + end + + let(:member3) do + allow(AutoAssignment::InboxRoundRobinService).to receive(:new).and_return(round_robin_service) + allow(round_robin_service).to receive(:add_agent_to_queue) + create(:inbox_member, inbox: inbox, user: agent3) + end + before do # Mock the round robin service to avoid Redis calls - allow_any_instance_of(AutoAssignment::InboxRoundRobinService).to receive(:add_agent_to_queue) - allow_any_instance_of(AutoAssignment::InboxRoundRobinService).to receive(:reset_queue) - allow_any_instance_of(AutoAssignment::InboxRoundRobinService).to receive(:validate_queue?).and_return(true) - allow_any_instance_of(AutoAssignment::InboxRoundRobinService).to receive(:available_agent).and_return(nil) + allow(AutoAssignment::InboxRoundRobinService).to receive(:new).and_return(round_robin_service) + allow(round_robin_service).to receive(:add_agent_to_queue) + allow(round_robin_service).to receive(:reset_queue) + allow(round_robin_service).to receive(:validate_queue?).and_return(true) end describe '#select_agent' do @@ -24,12 +41,8 @@ RSpec.describe AutoAssignment::RoundRobinSelector do let(:available_agents) { [member1, member2, member3] } it 'returns an agent from the available list' do - # Mock the round robin service to return an agent - round_robin_service = instance_double(AutoAssignment::InboxRoundRobinService) - allow(round_robin_service).to receive(:add_agent_to_queue) - allow(AutoAssignment::InboxRoundRobinService).to receive(:new).and_return(round_robin_service) allow(round_robin_service).to receive(:available_agent).and_return(agent1) - + selected_agent = selector.select_agent(available_agents) expect(selected_agent).not_to be_nil @@ -37,10 +50,6 @@ RSpec.describe AutoAssignment::RoundRobinSelector do end it 'uses round robin service for selection' do - round_robin_service = instance_double(AutoAssignment::InboxRoundRobinService) - allow(round_robin_service).to receive(:add_agent_to_queue) - allow(AutoAssignment::InboxRoundRobinService).to receive(:new).and_return(round_robin_service) - expect(round_robin_service).to receive(:available_agent).with( allowed_agent_ids: [agent1.id.to_s, agent2.id.to_s, agent3.id.to_s] ).and_return(agent1) @@ -59,12 +68,8 @@ RSpec.describe AutoAssignment::RoundRobinSelector do context 'when one agent is available' do it 'returns that agent' do - # Mock the round robin service to return the agent - round_robin_service = instance_double(AutoAssignment::InboxRoundRobinService) - allow(round_robin_service).to receive(:add_agent_to_queue) - allow(AutoAssignment::InboxRoundRobinService).to receive(:new).and_return(round_robin_service) allow(round_robin_service).to receive(:available_agent).and_return(agent1) - + selected_agent = selector.select_agent([member1]) expect(selected_agent).to eq(agent1) end