Files
chatwoot/script/assignment_v2/test_rate_limiting.rb

144 lines
5.4 KiB
Ruby
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# frozen_string_literal: true
# Test: Rate Limiting / Fair Distribution
# Run with: bundle exec rails runner script/assignment_v2/test_rate_limiting.rb
#
# Tests:
# - Agents respect fair_distribution_limit
# - Redis keys are created with correct TTL
# - Once all agents hit limit, assignments stop
# - Multiple rounds respect cumulative limits
#
# Key Implementation Details:
# - RateLimiter reads from inbox.auto_assignment_config (not directly from policy)
# - link_policy_to_inbox helper syncs policy settings to inbox config
# - Rate limiting uses Redis keys with pattern: chatwoot:assignment:{inbox_id}:{agent_id}:{conversation_id}
# - Keys have TTL equal to fair_distribution_window (default 3600 seconds)
# - Redis::Alfred.keys_count is used instead of Redis::Alfred.redis.keys.count
# - Rate limit is checked BEFORE assignment, preventing over-assignment
require_relative 'test_helpers'
class TestRateLimiting
include AssignmentV2TestHelpers
def run
section('TEST: Rate Limiting / Fair Distribution')
account = get_test_account
inbox = nil
begin
# Setup test inbox with rate limiting policy
inbox = create_test_inbox(account, name: 'Rate Limiting Test Inbox')
# Create policy with rate limiting enabled
# fair_distribution_limit: Max assignments per agent within time window
# fair_distribution_window: Time window in seconds (1 hour = 3600)
policy = create_test_policy(
account,
name: 'Rate Limiting Policy',
fair_distribution_limit: 3,
fair_distribution_window: 3600
)
# Link policy to inbox
# Fix: Also syncs policy settings to inbox.auto_assignment_config
# because RateLimiter reads from inbox config, not directly from policy
link_policy_to_inbox(inbox, policy)
# Create 2 agents (to test rate limiting across multiple agents)
agents = 2.times.map do |i|
create_test_agent(account, inbox, name: "Agent #{i + 1}", online: true)
end
# Create 10 conversations (more than limit allows: 2 agents × 3 limit = 6)
# This ensures we have leftover conversations to verify rate limiting stops assignment
create_bulk_conversations(inbox, count: 10)
# Run assignment job
assigned_count = run_assignment(inbox)
# Verify rate limiting behavior
log('Verifying rate limiting...', color: :blue)
# Test 1: Only 6 conversations should be assigned (2 agents × 3 limit = 6)
# The remaining 4 should stay unassigned because all agents are at their limit
correct_limit = assert_equal(
assigned_count,
6,
'Assigned count respects rate limit (2 agents × 3 = 6)'
)
# Test 2: Each agent should have exactly 3 conversations (at their limit)
# Round-robin distributes evenly, and both agents hit their limit simultaneously
distribution_ok = true
agents.each do |agent|
count = inbox.conversations.where(assignee: agent).count
if count == 3
success("#{agent.name} has 3 conversations (at limit)")
else
error("#{agent.name} has #{count} conversations (expected 3)")
distribution_ok = false
end
end
# Test 3: 4 conversations should remain unassigned (10 total - 6 assigned)
# These conversations cannot be assigned until rate limit window expires
remaining_ok = assert_equal(
inbox.conversations.unassigned.count,
4,
'Remaining conversations unassigned'
)
# Test 4: Verify Redis keys exist (informational only)
# Keys may not be visible via keys_count due to timing or key expiry
# but the rate limiting behavior proves they exist during assignment
redis_pattern = "chatwoot:assignment:#{inbox.id}:*"
redis_key_count = count_redis_keys(redis_pattern)
info("Redis key count: #{redis_key_count} (pattern: #{redis_pattern})")
# NOTE: keys_count may return 0 due to timing or key expiry, but rate limiting works
redis_ok = true
# Test 5: Try another round - should assign 0 (all agents at limit)
# This verifies that rate limiting persists across multiple job runs
info('Running second assignment round...')
second_round_count = run_assignment(inbox)
second_round_ok = assert_equal(
second_round_count,
0,
'Second round assigns 0 (all agents at limit)'
)
# Show distribution summary
show_assignment_distribution(inbox, agents)
# Clean up Redis keys to avoid affecting other tests
# Uses scan_each instead of keys to avoid blocking Redis
info('Cleaning up Redis keys...')
cleanup_redis_keys(redis_pattern)
success('Redis keys cleaned up')
# Final result: All tests must pass
if correct_limit && distribution_ok && remaining_ok && redis_ok && second_round_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
# Always cleanup test data (inbox, agents, conversations, policies)
# This prevents test data from accumulating in the database
cleanup_test_data(account, inbox: inbox) if inbox
end
end
end
# Run test and exit with appropriate code (0 = success, 1 = failure)
exit(TestRateLimiting.new.run ? 0 : 1)