towerops/test/towerops/workers/agent_latency_evaluator_test.exs
Graham McIntire fc37680931
perf: optimize slow tests with bulk inserts and fewer iterations
Optimized the slowest tests to improve test suite performance:

1. Property-based IP validation test (6s → ~2.4s)
   - Reduced max_runs from 50 to 20 iterations
   - Still provides good coverage while being much faster

2. Database-heavy tests (~1s each)
   - Replaced individual create_check calls with bulk Repo.insert_all
   - Reduced check counts from 15 to 10 where adequate for testing
   - Fixed timestamp handling (truncate to :second for Ecto)
   - Fixed response_time_ms to use floats (schema requirement)
   - Removed updated_at (disabled in Check schema)
   - Tests affected:
     * get_device_latency_by_agent/2
     * get_uptime_percentage/1
     * get_latency_data/2 respects limit
     * AgentLatencyEvaluator tests

Expected speedup: ~40% reduction in top 10 slowest tests (12.8s → ~7.7s)

These optimizations maintain test coverage while significantly reducing
database transaction overhead.
2026-01-24 15:13:21 -06:00

529 lines
15 KiB
Elixir

defmodule Towerops.Workers.AgentLatencyEvaluatorTest do
use Towerops.DataCase, async: false
import Towerops.AccountsFixtures
import Towerops.OrganizationsFixtures
alias Towerops.Agents
alias Towerops.Monitoring
alias Towerops.Sites
alias Towerops.Workers.AgentLatencyEvaluator
setup do
# Start the worker for testing
{:ok, pid} = start_supervised(AgentLatencyEvaluator)
%{worker_pid: pid}
end
describe "evaluate_latency/0" do
test "reassigns device to faster agent when 20%+ improvement available" do
user = user_fixture()
org = organization_fixture(user.id)
{:ok, slow_agent, _} = Agents.create_agent_token(org.id, "Slow Agent")
{:ok, fast_agent, _} = Agents.create_agent_token(org.id, "Fast Agent")
{:ok, site} =
Sites.create_site(%{
name: "Test Site",
organization_id: org.id,
agent_token_id: slow_agent.id
})
{:ok, device} =
Towerops.Devices.create_device(%{
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
})
# Create checks: slow = 100ms, fast = 50ms (50% improvement)
for _ <- 1..15 do
Monitoring.create_check(%{
device_id: device.id,
agent_token_id: slow_agent.id,
status: :success,
response_time_ms: 100,
checked_at: DateTime.utc_now()
})
Monitoring.create_check(%{
device_id: device.id,
agent_token_id: fast_agent.id,
status: :success,
response_time_ms: 50,
checked_at: DateTime.utc_now()
})
end
# Trigger evaluation
AgentLatencyEvaluator.trigger_evaluation()
# Wait for async processing
Process.sleep(100)
# Verify site was reassigned
updated_site = Sites.get_site!(site.id)
assert updated_site.agent_token_id == fast_agent.id
end
test "does not reassign when improvement is below 20% threshold" do
user = user_fixture()
org = organization_fixture(user.id)
{:ok, agent1, _} = Agents.create_agent_token(org.id, "Agent 1")
{:ok, agent2, _} = Agents.create_agent_token(org.id, "Agent 2")
{:ok, site} =
Sites.create_site(%{
name: "Test Site",
organization_id: org.id,
agent_token_id: agent1.id
})
{:ok, device} =
Towerops.Devices.create_device(%{
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
})
# Create checks: agent1 = 100ms, agent2 = 85ms (15% improvement, below 20%)
for _ <- 1..15 do
Monitoring.create_check(%{
device_id: device.id,
agent_token_id: agent1.id,
status: :success,
response_time_ms: 100,
checked_at: DateTime.utc_now()
})
Monitoring.create_check(%{
device_id: device.id,
agent_token_id: agent2.id,
status: :success,
response_time_ms: 85,
checked_at: DateTime.utc_now()
})
end
# Trigger evaluation
AgentLatencyEvaluator.trigger_evaluation()
# Wait for async processing
Process.sleep(100)
# Verify site was NOT reassigned
updated_site = Sites.get_site!(site.id)
assert updated_site.agent_token_id == agent1.id
end
test "reassigns organization-level assignment" do
user = user_fixture()
{:ok, slow_agent, _} = Agents.create_cloud_poller("Slow Cloud")
{:ok, fast_agent, _} = Agents.create_cloud_poller("Fast Cloud")
org =
organization_fixture(user.id, %{
default_agent_token_id: slow_agent.id
})
{:ok, site} = Sites.create_site(%{name: "Test Site", organization_id: org.id})
{:ok, device} =
Towerops.Devices.create_device(%{
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
})
# Create checks showing fast agent is better
for _ <- 1..15 do
Monitoring.create_check(%{
device_id: device.id,
agent_token_id: slow_agent.id,
status: :success,
response_time_ms: 150,
checked_at: DateTime.utc_now()
})
Monitoring.create_check(%{
device_id: device.id,
agent_token_id: fast_agent.id,
status: :success,
response_time_ms: 50,
checked_at: DateTime.utc_now()
})
end
# Trigger evaluation
AgentLatencyEvaluator.trigger_evaluation()
# Wait for async processing
Process.sleep(100)
# Verify organization was reassigned
updated_org = Towerops.Organizations.get_organization!(org.id)
assert updated_org.default_agent_token_id == fast_agent.id
end
test "creates device assignment for global default assignment" do
user = user_fixture()
org = organization_fixture(user.id)
{:ok, slow_cloud, _} = Agents.create_cloud_poller("Slow Global")
{:ok, fast_cloud, _} = Agents.create_cloud_poller("Fast Global")
# Set slow cloud as global default
{:ok, _} = Towerops.Settings.set_global_default_cloud_poller(slow_cloud.id)
{:ok, site} = Sites.create_site(%{name: "Test Site", organization_id: org.id})
{:ok, device} =
Towerops.Devices.create_device(%{
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
})
# Create checks showing fast cloud is better
for _ <- 1..15 do
Monitoring.create_check(%{
device_id: device.id,
agent_token_id: slow_cloud.id,
status: :success,
response_time_ms: 200,
checked_at: DateTime.utc_now()
})
Monitoring.create_check(%{
device_id: device.id,
agent_token_id: fast_cloud.id,
status: :success,
response_time_ms: 60,
checked_at: DateTime.utc_now()
})
end
# Trigger evaluation
AgentLatencyEvaluator.trigger_evaluation()
# Wait for async processing
Process.sleep(100)
# Verify device assignment was created
assignment = Agents.get_device_assignment(device.id)
assert assignment.agent_token_id == fast_cloud.id
# Cleanup global default
{:ok, _} = Towerops.Settings.set_global_default_cloud_poller(nil)
end
test "creates device assignment for cloud polling (no assignment)" do
user = user_fixture()
org = organization_fixture(user.id)
{:ok, slow_cloud, _} = Agents.create_cloud_poller("Slow Cloud")
{:ok, fast_cloud, _} = Agents.create_cloud_poller("Fast Cloud")
{:ok, site} = Sites.create_site(%{name: "Test Site", organization_id: org.id})
{:ok, device} =
Towerops.Devices.create_device(%{
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
})
# Create checks from both cloud agents (bulk insert for speed)
now = DateTime.truncate(DateTime.utc_now(), :second)
for_result =
for _ <- 1..10 do
[
%{
id: Ecto.UUID.generate(),
device_id: device.id,
agent_token_id: slow_cloud.id,
status: :success,
response_time_ms: 100.0,
checked_at: now,
inserted_at: now
},
%{
id: Ecto.UUID.generate(),
device_id: device.id,
agent_token_id: fast_cloud.id,
status: :success,
response_time_ms: 50.0,
checked_at: now,
inserted_at: now
}
]
end
checks = List.flatten(for_result)
Repo.insert_all(Towerops.Monitoring.Check, checks)
# Trigger evaluation
AgentLatencyEvaluator.trigger_evaluation()
# Wait for async processing
Process.sleep(500)
# Verify device assignment was created to the fast agent
assignment = Agents.get_device_assignment(device.id)
assert assignment != nil, "Expected device assignment to be created"
assert assignment.agent_token_id == fast_cloud.id
end
test "does not reassign direct device assignments" do
user = user_fixture()
org = organization_fixture(user.id)
{:ok, slow_agent, _} = Agents.create_agent_token(org.id, "Slow Agent")
{:ok, fast_agent, _} = Agents.create_agent_token(org.id, "Fast Agent")
{:ok, site} = Sites.create_site(%{name: "Test Site", organization_id: org.id})
{:ok, device} =
Towerops.Devices.create_device(%{
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
})
# Create direct device assignment
{:ok, _} = Agents.assign_device_to_agent(slow_agent.id, device.id)
# Create checks showing fast agent is better
for _ <- 1..15 do
Monitoring.create_check(%{
device_id: device.id,
agent_token_id: slow_agent.id,
status: :success,
response_time_ms: 100,
checked_at: DateTime.utc_now()
})
Monitoring.create_check(%{
device_id: device.id,
agent_token_id: fast_agent.id,
status: :success,
response_time_ms: 50,
checked_at: DateTime.utc_now()
})
end
# Trigger evaluation
AgentLatencyEvaluator.trigger_evaluation()
# Wait for async processing
Process.sleep(100)
# Verify device assignment was NOT changed
assignment = Agents.get_device_assignment(device.id)
assert assignment.agent_token_id == slow_agent.id
end
test "requires minimum check count" do
user = user_fixture()
org = organization_fixture(user.id)
{:ok, slow_agent, _} = Agents.create_agent_token(org.id, "Slow Agent")
{:ok, fast_agent, _} = Agents.create_agent_token(org.id, "Fast Agent")
{:ok, site} =
Sites.create_site(%{
name: "Test Site",
organization_id: org.id,
agent_token_id: slow_agent.id
})
{:ok, device} =
Towerops.Devices.create_device(%{
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
})
# Create only 5 checks (below default minimum of 10)
for _ <- 1..5 do
Monitoring.create_check(%{
device_id: device.id,
agent_token_id: slow_agent.id,
status: :success,
response_time_ms: 100,
checked_at: DateTime.utc_now()
})
Monitoring.create_check(%{
device_id: device.id,
agent_token_id: fast_agent.id,
status: :success,
response_time_ms: 50,
checked_at: DateTime.utc_now()
})
end
# Trigger evaluation
AgentLatencyEvaluator.trigger_evaluation()
# Wait for async processing
Process.sleep(100)
# Verify site was NOT reassigned (insufficient data)
updated_site = Sites.get_site!(site.id)
assert updated_site.agent_token_id == slow_agent.id
end
test "handles multiple devices in single evaluation" do
user = user_fixture()
org = organization_fixture(user.id)
{:ok, slow_agent, _} = Agents.create_agent_token(org.id, "Slow Agent")
{:ok, fast_agent, _} = Agents.create_agent_token(org.id, "Fast Agent")
{:ok, site1} =
Sites.create_site(%{
name: "Site 1",
organization_id: org.id,
agent_token_id: slow_agent.id
})
{:ok, site2} =
Sites.create_site(%{
name: "Site 2",
organization_id: org.id,
agent_token_id: slow_agent.id
})
{:ok, device1} =
Towerops.Devices.create_device(%{
name: "Router 1",
ip_address: "192.168.1.1",
site_id: site1.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
})
{:ok, device2} =
Towerops.Devices.create_device(%{
name: "Router 2",
ip_address: "192.168.2.1",
site_id: site2.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
})
# Create checks for both devices
for device_id <- [device1.id, device2.id] do
for _ <- 1..15 do
Monitoring.create_check(%{
device_id: device_id,
agent_token_id: slow_agent.id,
status: :success,
response_time_ms: 100,
checked_at: DateTime.utc_now()
})
Monitoring.create_check(%{
device_id: device_id,
agent_token_id: fast_agent.id,
status: :success,
response_time_ms: 50,
checked_at: DateTime.utc_now()
})
end
end
# Trigger evaluation
AgentLatencyEvaluator.trigger_evaluation()
# Wait for async processing
Process.sleep(100)
# Verify both sites were reassigned
updated_site1 = Sites.get_site!(site1.id)
updated_site2 = Sites.get_site!(site2.id)
assert updated_site1.agent_token_id == fast_agent.id
assert updated_site2.agent_token_id == fast_agent.id
end
end
describe "handle_info(:evaluate_latency)" do
test "worker processes evaluation on schedule" do
user = user_fixture()
org = organization_fixture(user.id)
{:ok, slow_agent, _} = Agents.create_agent_token(org.id, "Slow Agent")
{:ok, fast_agent, _} = Agents.create_agent_token(org.id, "Fast Agent")
{:ok, site} =
Sites.create_site(%{
name: "Test Site",
organization_id: org.id,
agent_token_id: slow_agent.id
})
{:ok, device} =
Towerops.Devices.create_device(%{
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
})
# Create checks
for _ <- 1..15 do
Monitoring.create_check(%{
device_id: device.id,
agent_token_id: slow_agent.id,
status: :success,
response_time_ms: 100,
checked_at: DateTime.utc_now()
})
Monitoring.create_check(%{
device_id: device.id,
agent_token_id: fast_agent.id,
status: :success,
response_time_ms: 50,
checked_at: DateTime.utc_now()
})
end
# Send the message directly to the worker
send(Process.whereis(AgentLatencyEvaluator), :evaluate_latency)
# Wait for async processing
Process.sleep(100)
# Verify site was reassigned
updated_site = Sites.get_site!(site.id)
assert updated_site.agent_token_id == fast_agent.id
end
end
end