towerops/test/towerops/monitoring/equipment_monitor_test.exs
Graham McIntire a3cd43d2c2
Fix credo warnings and software design suggestions
Replace expensive length/1 comparisons with direct list comparisons or Enum.empty?/1 checks (13 instances in source code and tests). Add module aliases for Phoenix.HTML.Form and Towerops.Accounts.Scope to reduce nested module references.

Changes:
- Replace length(list) > 0 with list != [] or refute Enum.empty?(list)
- Add Phoenix.HTML.Form alias in CoreComponents
- Add Towerops.Accounts.Scope alias in ConnCase test helper

This eliminates all credo warnings and software design suggestions.
2026-01-06 12:55:22 -06:00

208 lines
6.5 KiB
Elixir

defmodule Towerops.Monitoring.EquipmentMonitorTest do
use Towerops.DataCase, async: false
import Mox
import Towerops.AccountsFixtures
alias Towerops.Alerts
alias Towerops.Equipment
alias Towerops.Monitoring
alias Towerops.Monitoring.EquipmentMonitor
alias Towerops.Monitoring.PingMock
# Set up Mox to verify expectations on exit
setup :verify_on_exit!
setup :set_mox_from_context
setup do
# Stub default ping behavior using the PingStub module
Mox.stub_with(PingMock, Towerops.Monitoring.PingStub)
user = user_fixture()
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
{:ok, site} =
Towerops.Sites.create_site(%{
name: "Test Site",
organization_id: organization.id
})
{:ok, equipment} =
Equipment.create_equipment(%{
name: "Router 1",
ip_address: "127.0.0.1",
site_id: site.id,
monitoring_enabled: true,
check_interval_seconds: 1
})
%{equipment: equipment}
end
describe "init/1" do
test "initializes with equipment_id and schedules check if monitoring enabled", %{
equipment: equipment
} do
{:ok, state} = EquipmentMonitor.init(equipment.id)
assert state.equipment_id == equipment.id
end
test "initializes but doesn't schedule if monitoring disabled", %{equipment: equipment} do
{:ok, updated} = Equipment.update_equipment(equipment, %{monitoring_enabled: false})
{:ok, state} = EquipmentMonitor.init(updated.id)
assert state.equipment_id == updated.id
end
end
describe "trigger_check/1" do
test "can trigger an immediate check", %{equipment: equipment} do
# Start the monitor
start_supervised!({EquipmentMonitor, equipment.id})
# Trigger a check
EquipmentMonitor.trigger_check(equipment.id)
# Give it time to process (GenServer needs time to handle the message)
Process.sleep(200)
# Should have created a monitoring check
checks = Monitoring.list_equipment_checks(equipment.id)
refute Enum.empty?(checks)
end
end
describe "handle_info :check_equipment" do
test "performs check and creates monitoring record", %{equipment: equipment} do
{:ok, state} = EquipmentMonitor.init(equipment.id)
# Simulate the periodic check message
{:noreply, _new_state} = EquipmentMonitor.handle_info(:check_equipment, state)
# Should have created a check
checks = Monitoring.list_equipment_checks(equipment.id)
assert length(checks) == 1
end
end
describe "SNMP monitoring configuration" do
test "equipment can be configured with SNMP settings", %{equipment: equipment} do
{:ok, updated_equipment} =
Equipment.update_equipment(equipment, %{
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public",
snmp_port: 161
})
assert updated_equipment.snmp_enabled == true
assert updated_equipment.snmp_version == "2c"
assert updated_equipment.snmp_community == "public"
assert updated_equipment.snmp_port == 161
end
test "SNMP version defaults to 2c", %{equipment: equipment} do
{:ok, updated_equipment} =
Equipment.update_equipment(equipment, %{
snmp_enabled: true,
snmp_community: "public"
})
assert updated_equipment.snmp_version == "2c"
end
end
describe "alert creation" do
setup %{equipment: equipment} do
# Set equipment to up status first
{:ok, equipment} = Equipment.update_equipment_status(equipment, :up)
%{equipment: equipment}
end
test "creates equipment_down alert when equipment goes down", %{equipment: equipment} do
# Mock failed ping
expect(PingMock, :ping, fn _ip ->
{:error, :timeout_or_unreachable}
end)
{:ok, state} = EquipmentMonitor.init(equipment.id)
# Trigger check
{:noreply, _state} = EquipmentMonitor.handle_info(:check_equipment, state)
# Should create an alert (no need to sleep since test env doesn't send emails)
alerts = Alerts.list_equipment_alerts(equipment.id)
refute Enum.empty?(alerts)
alert = hd(alerts)
assert alert.alert_type == :equipment_down
end
test "creates equipment_up alert when equipment recovers", %{equipment: equipment} do
# First set to down
{:ok, equipment} = Equipment.update_equipment_status(equipment, :down)
# Create a down alert
{:ok, _} =
Alerts.create_alert(%{
equipment_id: equipment.id,
alert_type: :equipment_down,
triggered_at: DateTime.truncate(DateTime.utc_now(), :second),
message: "Equipment is down"
})
{:ok, state} = EquipmentMonitor.init(equipment.id)
# Trigger check
{:noreply, _state} = EquipmentMonitor.handle_info(:check_equipment, state)
# Should create recovery alert
alerts = Alerts.list_equipment_alerts(equipment.id, 10)
up_alerts = Enum.filter(alerts, &(&1.alert_type == :equipment_up))
refute Enum.empty?(up_alerts)
end
test "does not create duplicate down alerts", %{equipment: equipment} do
# Mock failed ping for both calls
expect(PingMock, :ping, 2, fn _ip ->
{:error, :timeout_or_unreachable}
end)
{:ok, state} = EquipmentMonitor.init(equipment.id)
# Trigger check twice
{:noreply, _} = EquipmentMonitor.handle_info(:check_equipment, state)
{:noreply, _} = EquipmentMonitor.handle_info(:check_equipment, state)
# Should only have one alert
alerts =
equipment.id
|> Alerts.list_equipment_alerts()
|> Enum.filter(&(&1.alert_type == :equipment_down && is_nil(&1.resolved_at)))
assert length(alerts) == 1
end
test "resolves down alert when equipment comes back up", %{equipment: equipment} do
# Set to down first
{:ok, equipment} = Equipment.update_equipment_status(equipment, :down)
# Create down alert
{:ok, down_alert} =
Alerts.create_alert(%{
equipment_id: equipment.id,
alert_type: :equipment_down,
triggered_at: DateTime.truncate(DateTime.utc_now(), :second),
message: "Down"
})
{:ok, state} = EquipmentMonitor.init(equipment.id)
# Check
{:noreply, _} = EquipmentMonitor.handle_info(:check_equipment, state)
# Down alert should be resolved
updated_alert = Alerts.get_alert!(down_alert.id)
assert updated_alert.resolved_at
end
end
end