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.
208 lines
6.5 KiB
Elixir
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
|