Files
chatwoot/script/assignment_v2/test_exclusion_rules.rb
T

308 lines
9.8 KiB
Ruby

# frozen_string_literal: true
# Test: Conversation Exclusion Rules (Enterprise)
# Run with: bundle exec rails runner script/assignment_v2/test_exclusion_rules.rb
#
# Tests:
# - Conversations with excluded labels are not assigned
# - Conversations older than threshold are not assigned
# - Multiple exclusion rules work together
# - Conversations without exclusions are assigned normally
#
# Key Implementation Details:
# - Exclusion rules are stored in AgentCapacityPolicy#exclusion_rules (JSONB)
# - exclusion_rules format: { excluded_labels: ['vip', 'escalated'], exclude_older_than_hours: 24 }
# - Enterprise::AutoAssignment::AssignmentService#apply_exclusion_rules filters conversations
# - Label exclusions use: scope.tagged_with(labels, exclude: true, on: :labels)
# - Age exclusions use: scope.where('created_at >= ?', hours.hours.ago)
# - Exclusions apply BEFORE conversations are fetched for assignment
require_relative 'test_helpers'
class TestExclusionRules
include AssignmentV2TestHelpers
def run
skip_if_not_enterprise
section('TEST: Conversation Exclusion Rules (Enterprise)')
account = get_test_account
inbox = nil
begin
# Setup test inbox and policy
inbox = create_test_inbox(account, name: 'Exclusion Test Inbox')
policy = create_test_policy(account, name: 'Exclusion Test Policy')
link_policy_to_inbox(inbox, policy)
# Test 1: Label exclusions
section('Test 1: Exclude Conversations by Label')
# Create agent
create_test_agent(account, inbox, name: 'Agent Exclusion Test', online: true)
# Create capacity policy with label exclusions
capacity_policy = account.agent_capacity_policies.create!(
name: "Exclusion Policy #{SecureRandom.hex(4)}",
exclusion_rules: {
'excluded_labels' => %w[vip escalated]
}
)
# Link capacity policy to inbox
capacity_policy.inbox_capacity_limits.create!(
inbox: inbox,
conversation_limit: 100 # High limit to ensure exclusions are the only filter
)
info('Created exclusion rule: exclude conversations with labels [vip, escalated]')
# Create conversations: 2 with excluded labels, 2 without
conv_vip = create_test_conversation(inbox, contact_name: 'VIP Customer')
conv_vip.label_list.add('vip')
conv_vip.save!
conv_escalated = create_test_conversation(inbox, contact_name: 'Escalated Customer')
conv_escalated.label_list.add('escalated')
conv_escalated.save!
conv_normal_1 = create_test_conversation(inbox, contact_name: 'Normal Customer 1')
conv_normal_2 = create_test_conversation(inbox, contact_name: 'Normal Customer 2')
info('Created 4 conversations: 2 with excluded labels, 2 without')
# Run assignment
assigned_count = run_assignment(inbox)
# Verify only non-excluded conversations assigned
log('Verifying label exclusions...', color: :blue)
# Should assign only 2 conversations (the ones without excluded labels)
correct_count = assert_equal(
assigned_count,
2,
'Only 2 conversations assigned (excluded label conversations skipped)'
)
# VIP conversation should remain unassigned
conv_vip.reload
vip_excluded = assert(
conv_vip.assignee_id.nil?,
'VIP conversation remains unassigned',
'VIP conversation was assigned (should be excluded)'
)
# Escalated conversation should remain unassigned
conv_escalated.reload
escalated_excluded = assert(
conv_escalated.assignee_id.nil?,
'Escalated conversation remains unassigned',
'Escalated conversation was assigned (should be excluded)'
)
# Normal conversations should be assigned
conv_normal_1.reload
conv_normal_2.reload
normals_assigned = assert(
conv_normal_1.assignee_id.present? && conv_normal_2.assignee_id.present?,
'Normal conversations assigned',
'Normal conversations not assigned'
)
test1_ok = correct_count && vip_excluded && escalated_excluded && normals_assigned
# Test 2: Age exclusions
section('Test 2: Exclude Conversations by Age')
# Clear previous test data
inbox.conversations.destroy_all
# Update exclusion rules to exclude conversations older than 1 hour
capacity_policy.update!(
exclusion_rules: {
'exclude_older_than_hours' => 1
}
)
info('Updated exclusion rule: exclude conversations older than 1 hour')
# Create conversations: 2 old (>1 hour), 2 recent (<1 hour)
conv_old_1 = create_test_conversation(
inbox,
contact_name: 'Old Customer 1',
created_at: 2.hours.ago,
last_activity_at: 2.hours.ago
)
conv_old_2 = create_test_conversation(
inbox,
contact_name: 'Old Customer 2',
created_at: 90.minutes.ago,
last_activity_at: 90.minutes.ago
)
conv_recent_1 = create_test_conversation(
inbox,
contact_name: 'Recent Customer 1',
created_at: 30.minutes.ago,
last_activity_at: 30.minutes.ago
)
conv_recent_2 = create_test_conversation(
inbox,
contact_name: 'Recent Customer 2',
created_at: 10.minutes.ago,
last_activity_at: 10.minutes.ago
)
info('Created 4 conversations: 2 older than 1 hour, 2 recent')
# Run assignment
assigned_count_2 = run_assignment(inbox)
# Verify only recent conversations assigned
log('Verifying age exclusions...', color: :blue)
# Should assign only 2 conversations (the recent ones)
correct_count_2 = assert_equal(
assigned_count_2,
2,
'Only 2 conversations assigned (old conversations excluded)'
)
# Old conversations should remain unassigned
conv_old_1.reload
conv_old_2.reload
old_excluded = assert(
conv_old_1.assignee_id.nil? && conv_old_2.assignee_id.nil?,
'Old conversations remain unassigned',
'Old conversations were assigned (should be excluded)'
)
# Recent conversations should be assigned
conv_recent_1.reload
conv_recent_2.reload
recent_assigned = assert(
conv_recent_1.assignee_id.present? && conv_recent_2.assignee_id.present?,
'Recent conversations assigned',
'Recent conversations not assigned'
)
test2_ok = correct_count_2 && old_excluded && recent_assigned
# Test 3: Combined exclusions (both label and age)
section('Test 3: Combined Label and Age Exclusions')
# Clear previous test data
inbox.conversations.destroy_all
# Update exclusion rules to have both label and age exclusions
capacity_policy.update!(
exclusion_rules: {
'excluded_labels' => ['urgent'],
'exclude_older_than_hours' => 1
}
)
info('Updated exclusion rules: exclude [urgent] labels AND conversations older than 1 hour')
# Create 5 conversations with different combinations
# 1. Recent + no label = should be assigned
conv_good = create_test_conversation(
inbox,
contact_name: 'Good Customer',
created_at: 30.minutes.ago
)
# 2. Old + no label = excluded by age
conv_old = create_test_conversation(
inbox,
contact_name: 'Old Customer',
created_at: 2.hours.ago
)
# 3. Recent + urgent label = excluded by label
conv_urgent = create_test_conversation(
inbox,
contact_name: 'Urgent Customer',
created_at: 30.minutes.ago
)
conv_urgent.label_list.add('urgent')
conv_urgent.save!
# 4. Old + urgent label = excluded by both (double excluded)
conv_double_excluded = create_test_conversation(
inbox,
contact_name: 'Old Urgent Customer',
created_at: 2.hours.ago
)
conv_double_excluded.label_list.add('urgent')
conv_double_excluded.save!
# 5. Recent + normal label = should be assigned
conv_good_2 = create_test_conversation(
inbox,
contact_name: 'Good Customer 2',
created_at: 20.minutes.ago
)
conv_good_2.label_list.add('normal')
conv_good_2.save!
info('Created 5 conversations: 1 old, 1 urgent, 1 old+urgent, 2 assignable')
# Run assignment
assigned_count_3 = run_assignment(inbox)
# Verify combined exclusions
log('Verifying combined exclusions...', color: :blue)
# Should assign only 2 conversations (the ones passing both filters)
correct_count_3 = assert_equal(
assigned_count_3,
2,
'Only 2 conversations assigned (combined exclusions work)'
)
# Excluded conversations should remain unassigned
conv_old.reload
conv_urgent.reload
conv_double_excluded.reload
all_excluded = assert(
conv_old.assignee_id.nil? &&
conv_urgent.assignee_id.nil? &&
conv_double_excluded.assignee_id.nil?,
'All excluded conversations remain unassigned',
'Some excluded conversations were assigned'
)
# Good conversations should be assigned
conv_good.reload
conv_good_2.reload
good_assigned = assert(
conv_good.assignee_id.present? && conv_good_2.assignee_id.present?,
'Valid conversations assigned',
'Valid conversations not assigned'
)
test3_ok = correct_count_3 && all_excluded && good_assigned
# Final result
if test1_ok && test2_ok && test3_ok
section('✓ ALL TESTS PASSED')
true
else
section('✗ SOME TESTS FAILED')
false
end
rescue StandardError => e
error("Test failed with error: #{e.message}")
puts e.backtrace.first(5).join("\n")
false
ensure
cleanup_test_data(account, inbox: inbox) if inbox
end
end
end
# Run test and exit with appropriate code
exit(TestExclusionRules.new.run ? 0 : 1)