chore: add scripts to test

This commit is contained in:
Shivam Mishra
2025-11-14 15:18:30 +05:30
parent e84efdccbd
commit 1e1f79025e
11 changed files with 2987 additions and 0 deletions
+306
View File
@@ -0,0 +1,306 @@
# frozen_string_literal: true
# Shared test helpers for Assignment v2 feature testing
#
# Key Design Decisions & Fixes:
#
# 1. Password Requirements:
# - Users require complex passwords: 'Password123!' (uppercase + special char)
#
# 2. Unique Naming:
# - All entities (inboxes, policies, agents) get unique names using SecureRandom.hex(4)
# - Prevents validation errors across multiple test runs
#
# 3. Conversation Creation:
# - Uses FactoryBot to handle complex associations automatically
# - ContactInbox requires source_id (cannot be blank)
# - FactoryBot factory handles: contact → contact_inbox → conversation chain
#
# 4. Policy Linking:
# - RateLimiter reads from inbox.auto_assignment_config (not policy directly)
# - link_policy_to_inbox syncs policy settings to inbox config
# - Must call inbox.reload after linking to pick up association
#
# 5. Redis Access:
# - Use Redis::Alfred.keys_count() instead of Redis::Alfred.redis.keys.count
# - Use Redis::Alfred.scan_each() instead of Redis::Alfred.redis.keys for cleanup
#
# 6. Assignment Job:
# - Must use keyword argument: perform_now(inbox_id: id)
# - Not positional: perform_now(id) will fail
#
module AssignmentV2TestHelpers
# Default test account ID
DEFAULT_ACCOUNT_ID = 2
# Color codes for terminal output
COLORS = {
green: "\e[32m",
red: "\e[31m",
yellow: "\e[33m",
blue: "\e[34m",
reset: "\e[0m"
}.freeze
def log(msg, prefix: '==>', color: nil)
colored_prefix = color ? "#{COLORS[color]}#{prefix}#{COLORS[:reset]}" : prefix
puts "\n#{colored_prefix} #{msg}"
end
def success(msg)
log(msg, prefix: '✓', color: :green)
end
def error(msg)
log(msg, prefix: '✗', color: :red)
end
def warning(msg)
log(msg, prefix: '⚠', color: :yellow)
end
def info(msg)
log(msg, prefix: '', color: :blue)
end
def section(title)
puts "\n#{'=' * 60}"
puts " #{title}"
puts '=' * 60
end
def assert(condition, success_msg, error_msg)
if condition
success(success_msg)
true
else
error(error_msg)
false
end
end
def assert_equal(actual, expected, description)
if actual == expected
success("#{description}: #{actual}")
true
else
error("#{description}: Expected #{expected}, got #{actual}")
false
end
end
def get_test_account
account = Account.find_by(id: DEFAULT_ACCOUNT_ID)
raise "Account #{DEFAULT_ACCOUNT_ID} not found. Please create it first." unless account
# Enable assignment_v2 feature
account.enable_features('assignment_v2')
account
end
def create_test_inbox(account, name: 'Test Inbox')
unique_name = "#{name} #{SecureRandom.hex(4)}"
channel = Channel::WebWidget.create!(
account: account,
website_url: 'https://test.com',
widget_color: '#0000FF'
)
inbox = account.inboxes.create!(
name: unique_name,
channel: channel,
enable_auto_assignment: true
)
info("Created inbox: #{inbox.name} (ID: #{inbox.id})")
inbox
end
def create_test_policy(account, **options)
defaults = {
name: 'Test Policy',
conversation_priority: 'earliest_created',
enabled: true
}
merged_options = defaults.merge(options)
# Always make name unique
merged_options[:name] = "#{merged_options[:name]} #{SecureRandom.hex(4)}"
policy = account.assignment_policies.create!(merged_options)
info("Created policy: #{policy.name} (ID: #{policy.id})")
policy
end
def link_policy_to_inbox(inbox, policy)
InboxAssignmentPolicy.find_or_create_by!(
inbox: inbox,
assignment_policy: policy
)
# Also set inbox config for rate limiter (RateLimiter reads from inbox.auto_assignment_config)
inbox.update!(
auto_assignment_config: {
'fair_distribution_limit' => policy.fair_distribution_limit,
'fair_distribution_window' => policy.fair_distribution_window,
'conversation_priority' => policy.conversation_priority
}.compact
)
inbox.reload # Reload to pick up the association
info("Linked policy #{policy.id} to inbox #{inbox.id}")
inbox
end
def create_test_agent(account, inbox, name:, email: nil, online: true)
unique_id = SecureRandom.hex(4)
email ||= "#{name.downcase.tr(' ', '_')}_#{unique_id}@test.com"
unique_name = "#{name} #{unique_id}"
user = account.users.create!(
email: email,
password: 'Password123!',
password_confirmation: 'Password123!',
name: unique_name,
confirmed_at: Time.zone.now
)
# Add to inbox
inbox.inbox_members.create!(user: user)
# Set online status
# Note: inbox.available_agents filters by status == 'online', so we set both presence and status
if online
OnlineStatusTracker.update_presence(account.id, 'User', user.id)
OnlineStatusTracker.set_status(account.id, user.id, 'online')
else
# For offline agents, set status to 'offline' (they won't be in available_agents)
OnlineStatusTracker.set_status(account.id, user.id, 'offline')
end
info("Created agent: #{user.name} (#{user.email}, online: #{online})")
user
end
def create_test_conversation(inbox, **options)
contact_name = options.delete(:contact_name) || "Customer #{SecureRandom.hex(4)}"
# Use FactoryBot if available
if defined?(FactoryBot)
FactoryBot.create(
:conversation,
options.merge(
inbox: inbox,
account: inbox.account,
contact: FactoryBot.create(:contact, account: inbox.account, name: contact_name)
)
)
else
# Fallback to manual creation
defaults = {
status: 'open',
assignee_id: nil,
created_at: Time.zone.now,
last_activity_at: Time.zone.now
}
contact = inbox.contacts.create!(
account: inbox.account,
name: contact_name
)
contact_inbox = ContactInbox.create!(
contact: contact,
inbox: inbox,
source_id: "test_#{SecureRandom.uuid}"
)
inbox.conversations.create!(
defaults.merge(options).merge(
account: inbox.account,
contact: contact,
contact_inbox: contact_inbox
)
)
end
end
def create_bulk_conversations(inbox, count:, **options)
conversations = count.times.map do |i|
create_test_conversation(
inbox,
**options, contact_name: "Customer #{i + 1}",
created_at: (count - i).minutes.ago,
last_activity_at: options[:last_activity_at] || (count - i).minutes.ago
)
end
info("Created #{count} test conversations")
conversations
end
def run_assignment(inbox)
info("Running assignment job for inbox #{inbox.id}...")
before_count = inbox.conversations.unassigned.count
AutoAssignment::AssignmentJob.perform_now(inbox_id: inbox.id)
after_count = inbox.conversations.unassigned.count
assigned_count = before_count - after_count
info("Assigned: #{assigned_count} conversations (#{before_count}#{after_count} unassigned)")
assigned_count
end
def show_assignment_distribution(inbox, agents)
log('Assignment Distribution:', color: :blue)
agents.each do |agent|
count = inbox.conversations.where(assignee: agent, status: 'open').count
puts " #{agent.name}: #{count} conversations"
end
unassigned = inbox.conversations.unassigned.count
puts " Unassigned: #{unassigned} conversations"
end
def count_redis_keys(pattern)
Redis::Alfred.keys_count(pattern)
end
def cleanup_redis_keys(pattern)
# Use scan to avoid blocking Redis with KEYS command
Redis::Alfred.scan_each(match: pattern) do |key|
Redis::Alfred.del(key)
end
end
def cleanup_test_data(account, inbox: nil)
if inbox
info("Cleaning up inbox #{inbox.id}...")
inbox.conversations.destroy_all
inbox.inbox_members.destroy_all
InboxAssignmentPolicy.where(inbox: inbox).destroy_all
inbox.destroy
else
info("Cleaning up account #{account.id} test data...")
account.conversations.where('created_at > ?', 1.hour.ago).destroy_all
account.inbox_members.joins(:user).where(users: { email: [/.+@test\.com/] }).destroy_all
account.inboxes.where('name LIKE ?', '%Test%').destroy_all
account.assignment_policies.where('name LIKE ?', '%Test%').destroy_all
account.users.where('email LIKE ?', '%@test.com%').destroy_all
end
success('Cleanup complete')
end
def enterprise_available?
defined?(Enterprise)
end
def skip_if_not_enterprise
return if enterprise_available?
warning('Enterprise features not available, skipping test')
exit(0)
end
end