towerops/test/towerops/snmp_test.exs

751 lines
21 KiB
Elixir

defmodule Towerops.SnmpTest do
use Towerops.DataCase
import Towerops.AccountsFixtures
alias Towerops.Snmp
alias Towerops.Snmp.Device
alias Towerops.Snmp.Interface
alias Towerops.Snmp.InterfaceStat
alias Towerops.Snmp.Sensor
alias Towerops.Snmp.SensorReading
alias Towerops.Snmp.SnmpMock
setup do
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, device} =
Towerops.Devices.create_device(%{
name: "Test Router",
ip_address: "192.168.1.1",
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public",
snmp_port: 161,
site_id: site.id
})
snmp_device =
%Device{}
|> Device.changeset(%{
device_id: device.id,
sys_name: "test-router",
sys_descr: "Test Device"
})
|> Repo.insert!()
%{device: device, snmp_device: snmp_device, organization: organization}
end
describe "test_connection/4" do
import Mox
setup :verify_on_exit!
test "delegates to Client.test_connection" do
expect(SnmpMock, :get, fn _, _, _ ->
{:ok, 12_345}
end)
result = Snmp.test_connection("192.168.1.1", "public", "2c")
case result do
{:ok, _} -> assert true
{:error, _} -> assert true
end
end
test "accepts custom port" do
expect(SnmpMock, :get, fn _, _, _ ->
{:ok, 12_345}
end)
result = Snmp.test_connection("192.168.1.1", "public", "2c", 1161)
case result do
{:ok, _} -> assert true
{:error, _} -> assert true
end
end
end
describe "get_device/1" do
test "returns device for device_id", %{device: device, snmp_device: snmp_device} do
found_device = Snmp.get_device(device.id)
assert found_device.id == snmp_device.id
assert found_device.sys_name == "test-router"
end
test "returns nil for non-existent device_id" do
assert Snmp.get_device(Ecto.UUID.generate()) == nil
end
end
describe "get_device_with_associations/1" do
test "returns device with preloaded associations", %{device: device, snmp_device: snmp_device} do
found_device = Snmp.get_device_with_associations(device.id)
assert found_device.id == snmp_device.id
assert Ecto.assoc_loaded?(found_device.interfaces)
assert Ecto.assoc_loaded?(found_device.sensors)
end
test "returns nil for non-existent device_id" do
assert Snmp.get_device_with_associations(Ecto.UUID.generate()) == nil
end
end
describe "list_interfaces/1" do
test "returns all interfaces for a device ordered by if_index", %{device: device, snmp_device: snmp_device} do
interface1 =
%Interface{}
|> Interface.changeset(%{
snmp_device_id: snmp_device.id,
if_index: 2,
if_name: "eth1"
})
|> Repo.insert!()
interface2 =
%Interface{}
|> Interface.changeset(%{
snmp_device_id: snmp_device.id,
if_index: 1,
if_name: "eth0"
})
|> Repo.insert!()
interfaces = Snmp.list_interfaces(snmp_device.id)
assert length(interfaces) == 2
assert hd(interfaces).id == interface2.id
assert List.last(interfaces).id == interface1.id
end
test "returns empty list for device with no interfaces", %{device: device, snmp_device: snmp_device} do
assert Snmp.list_interfaces(snmp_device.id) == []
end
end
describe "list_monitored_interfaces/1" do
test "returns only monitored interfaces", %{device: device, snmp_device: snmp_device} do
monitored =
%Interface{}
|> Interface.changeset(%{
snmp_device_id: snmp_device.id,
if_index: 1,
if_name: "eth0",
monitored: true
})
|> Repo.insert!()
_unmonitored =
%Interface{}
|> Interface.changeset(%{
snmp_device_id: snmp_device.id,
if_index: 2,
if_name: "eth1",
monitored: false
})
|> Repo.insert!()
interfaces = Snmp.list_monitored_interfaces(snmp_device.id)
assert length(interfaces) == 1
assert hd(interfaces).id == monitored.id
end
end
describe "get_interface/1" do
test "returns interface by id", %{device: device, snmp_device: snmp_device} do
interface =
%Interface{}
|> Interface.changeset(%{
snmp_device_id: snmp_device.id,
if_index: 1,
if_name: "eth0"
})
|> Repo.insert!()
found = Snmp.get_interface(interface.id)
assert found.id == interface.id
assert found.if_name == "eth0"
end
test "returns nil for non-existent id" do
assert Snmp.get_interface(Ecto.UUID.generate()) == nil
end
end
describe "update_interface/2" do
test "updates interface attributes", %{device: device, snmp_device: snmp_device} do
interface =
%Interface{}
|> Interface.changeset(%{
snmp_device_id: snmp_device.id,
if_index: 1,
if_name: "eth0",
monitored: false
})
|> Repo.insert!()
{:ok, updated} = Snmp.update_interface(interface, %{monitored: true})
assert updated.monitored == true
end
test "returns error for invalid attributes", %{device: device, snmp_device: snmp_device} do
interface =
%Interface{}
|> Interface.changeset(%{
snmp_device_id: snmp_device.id,
if_index: 1,
if_name: "eth0"
})
|> Repo.insert!()
{:error, changeset} = Snmp.update_interface(interface, %{if_index: nil})
assert changeset.errors[:if_index]
end
end
describe "list_sensors/1" do
test "returns all sensors for a device ordered by type and index", %{device: device, snmp_device: snmp_device} do
sensor1 =
%Sensor{}
|> Sensor.changeset(%{
snmp_device_id: snmp_device.id,
sensor_type: "temperature",
sensor_index: "1",
sensor_oid: "1.2.3.4"
})
|> Repo.insert!()
sensor2 =
%Sensor{}
|> Sensor.changeset(%{
snmp_device_id: snmp_device.id,
sensor_type: "power",
sensor_index: "2",
sensor_oid: "1.2.3.5"
})
|> Repo.insert!()
sensors = Snmp.list_sensors(snmp_device.id)
assert length(sensors) == 2
assert sensor1.id in Enum.map(sensors, & &1.id)
assert sensor2.id in Enum.map(sensors, & &1.id)
end
test "returns empty list for device with no sensors", %{device: device, snmp_device: snmp_device} do
assert Snmp.list_sensors(snmp_device.id) == []
end
end
describe "list_monitored_sensors/1" do
test "returns only monitored sensors", %{device: device, snmp_device: snmp_device} do
monitored =
%Sensor{}
|> Sensor.changeset(%{
snmp_device_id: snmp_device.id,
sensor_type: "temperature",
sensor_index: "1",
sensor_oid: "1.2.3.4",
monitored: true
})
|> Repo.insert!()
_unmonitored =
%Sensor{}
|> Sensor.changeset(%{
snmp_device_id: snmp_device.id,
sensor_type: "power",
sensor_index: "2",
sensor_oid: "1.2.3.5",
monitored: false
})
|> Repo.insert!()
sensors = Snmp.list_monitored_sensors(snmp_device.id)
assert length(sensors) == 1
assert hd(sensors).id == monitored.id
end
end
describe "list_sensors_by_type/1" do
test "groups sensors by type", %{device: device, snmp_device: snmp_device} do
_temp1 =
%Sensor{}
|> Sensor.changeset(%{
snmp_device_id: snmp_device.id,
sensor_type: "temperature",
sensor_index: "1",
sensor_oid: "1.2.3.4"
})
|> Repo.insert!()
_temp2 =
%Sensor{}
|> Sensor.changeset(%{
snmp_device_id: snmp_device.id,
sensor_type: "temperature",
sensor_index: "2",
sensor_oid: "1.2.3.5"
})
|> Repo.insert!()
_power =
%Sensor{}
|> Sensor.changeset(%{
snmp_device_id: snmp_device.id,
sensor_type: "power",
sensor_index: "3",
sensor_oid: "1.2.3.6"
})
|> Repo.insert!()
grouped = Snmp.list_sensors_by_type(snmp_device.id)
assert Map.has_key?(grouped, "temperature")
assert Map.has_key?(grouped, "power")
assert length(grouped["temperature"]) == 2
assert length(grouped["power"]) == 1
end
test "returns empty map for device with no sensors", %{device: device, snmp_device: snmp_device} do
assert Snmp.list_sensors_by_type(snmp_device.id) == %{}
end
end
describe "get_sensor/1" do
test "returns sensor by id", %{device: device, snmp_device: snmp_device} do
sensor =
%Sensor{}
|> Sensor.changeset(%{
snmp_device_id: snmp_device.id,
sensor_type: "temperature",
sensor_index: "1",
sensor_oid: "1.2.3.4"
})
|> Repo.insert!()
found = Snmp.get_sensor(sensor.id)
assert found.id == sensor.id
assert found.sensor_type == "temperature"
end
test "returns nil for non-existent id" do
assert Snmp.get_sensor(Ecto.UUID.generate()) == nil
end
end
describe "update_sensor/2" do
test "updates sensor attributes", %{device: device, snmp_device: snmp_device} do
sensor =
%Sensor{}
|> Sensor.changeset(%{
snmp_device_id: snmp_device.id,
sensor_type: "temperature",
sensor_index: "1",
sensor_oid: "1.2.3.4",
monitored: false
})
|> Repo.insert!()
{:ok, updated} = Snmp.update_sensor(sensor, %{monitored: true})
assert updated.monitored == true
end
test "returns error for invalid attributes", %{device: device, snmp_device: snmp_device} do
sensor =
%Sensor{}
|> Sensor.changeset(%{
snmp_device_id: snmp_device.id,
sensor_type: "temperature",
sensor_index: "1",
sensor_oid: "1.2.3.4"
})
|> Repo.insert!()
{:error, changeset} = Snmp.update_sensor(sensor, %{sensor_oid: nil})
assert changeset.errors[:sensor_oid]
end
end
describe "get_sensor_readings/2" do
test "returns recent readings for a sensor", %{device: device, snmp_device: snmp_device} do
sensor =
%Sensor{}
|> Sensor.changeset(%{
snmp_device_id: snmp_device.id,
sensor_type: "temperature",
sensor_index: "1",
sensor_oid: "1.2.3.4"
})
|> Repo.insert!()
for i <- 1..5 do
%SensorReading{}
|> SensorReading.changeset(%{
sensor_id: sensor.id,
value: 20.0 + i,
status: "ok",
checked_at: DateTime.utc_now()
})
|> Repo.insert!()
end
readings = Snmp.get_sensor_readings(sensor.id)
assert length(readings) == 5
end
test "respects limit option", %{device: device, snmp_device: snmp_device} do
sensor =
%Sensor{}
|> Sensor.changeset(%{
snmp_device_id: snmp_device.id,
sensor_type: "temperature",
sensor_index: "1",
sensor_oid: "1.2.3.4"
})
|> Repo.insert!()
for i <- 1..10 do
%SensorReading{}
|> SensorReading.changeset(%{
sensor_id: sensor.id,
value: 20.0 + i,
status: "ok",
checked_at: DateTime.utc_now()
})
|> Repo.insert!()
end
readings = Snmp.get_sensor_readings(sensor.id, limit: 3)
assert length(readings) == 3
end
test "respects since option", %{device: device, snmp_device: snmp_device} do
sensor =
%Sensor{}
|> Sensor.changeset(%{
snmp_device_id: snmp_device.id,
sensor_type: "temperature",
sensor_index: "1",
sensor_oid: "1.2.3.4"
})
|> Repo.insert!()
past = DateTime.add(DateTime.utc_now(), -3600, :second)
recent = DateTime.add(DateTime.utc_now(), -60, :second)
%SensorReading{}
|> SensorReading.changeset(%{
sensor_id: sensor.id,
value: 20.0,
status: "ok",
checked_at: past
})
|> Repo.insert!()
%SensorReading{}
|> SensorReading.changeset(%{
sensor_id: sensor.id,
value: 25.0,
status: "ok",
checked_at: recent
})
|> Repo.insert!()
since_time = DateTime.add(DateTime.utc_now(), -120, :second)
readings = Snmp.get_sensor_readings(sensor.id, since: since_time)
assert length(readings) == 1
assert hd(readings).value == 25.0
end
test "returns empty list for sensor with no readings", %{device: device, snmp_device: snmp_device} do
sensor =
%Sensor{}
|> Sensor.changeset(%{
snmp_device_id: snmp_device.id,
sensor_type: "temperature",
sensor_index: "1",
sensor_oid: "1.2.3.4"
})
|> Repo.insert!()
assert Snmp.get_sensor_readings(sensor.id) == []
end
end
describe "get_latest_sensor_reading/1" do
test "returns most recent reading", %{device: device, snmp_device: snmp_device} do
sensor =
%Sensor{}
|> Sensor.changeset(%{
snmp_device_id: snmp_device.id,
sensor_type: "temperature",
sensor_index: "1",
sensor_oid: "1.2.3.4"
})
|> Repo.insert!()
old_reading =
%SensorReading{}
|> SensorReading.changeset(%{
sensor_id: sensor.id,
value: 20.0,
status: "ok",
checked_at: DateTime.add(DateTime.utc_now(), -3600, :second)
})
|> Repo.insert!()
new_reading =
%SensorReading{}
|> SensorReading.changeset(%{
sensor_id: sensor.id,
value: 25.0,
status: "ok",
checked_at: DateTime.utc_now()
})
|> Repo.insert!()
latest = Snmp.get_latest_sensor_reading(sensor.id)
assert latest.id == new_reading.id
refute latest.id == old_reading.id
end
test "returns nil for sensor with no readings", %{device: device, snmp_device: snmp_device} do
sensor =
%Sensor{}
|> Sensor.changeset(%{
snmp_device_id: snmp_device.id,
sensor_type: "temperature",
sensor_index: "1",
sensor_oid: "1.2.3.4"
})
|> Repo.insert!()
assert Snmp.get_latest_sensor_reading(sensor.id) == nil
end
end
describe "get_interface_stats/2" do
test "returns recent stats for an interface", %{device: device, snmp_device: snmp_device} do
interface =
%Interface{}
|> Interface.changeset(%{
snmp_device_id: snmp_device.id,
if_index: 1,
if_name: "eth0"
})
|> Repo.insert!()
for i <- 1..5 do
%InterfaceStat{}
|> InterfaceStat.changeset(%{
interface_id: interface.id,
if_in_octets: i * 1000,
if_out_octets: i * 2000,
checked_at: DateTime.utc_now()
})
|> Repo.insert!()
end
stats = Snmp.get_interface_stats(interface.id)
assert length(stats) == 5
end
test "respects limit option", %{device: device, snmp_device: snmp_device} do
interface =
%Interface{}
|> Interface.changeset(%{
snmp_device_id: snmp_device.id,
if_index: 1,
if_name: "eth0"
})
|> Repo.insert!()
for i <- 1..10 do
%InterfaceStat{}
|> InterfaceStat.changeset(%{
interface_id: interface.id,
if_in_octets: i * 1000,
if_out_octets: i * 2000,
checked_at: DateTime.utc_now()
})
|> Repo.insert!()
end
stats = Snmp.get_interface_stats(interface.id, limit: 3)
assert length(stats) == 3
end
test "respects since option", %{device: device, snmp_device: snmp_device} do
interface =
%Interface{}
|> Interface.changeset(%{
snmp_device_id: snmp_device.id,
if_index: 1,
if_name: "eth0"
})
|> Repo.insert!()
past = DateTime.add(DateTime.utc_now(), -3600, :second)
recent = DateTime.add(DateTime.utc_now(), -60, :second)
%InterfaceStat{}
|> InterfaceStat.changeset(%{
interface_id: interface.id,
if_in_octets: 1000,
if_out_octets: 2000,
checked_at: past
})
|> Repo.insert!()
%InterfaceStat{}
|> InterfaceStat.changeset(%{
interface_id: interface.id,
if_in_octets: 5000,
if_out_octets: 6000,
checked_at: recent
})
|> Repo.insert!()
since_time = DateTime.add(DateTime.utc_now(), -120, :second)
stats = Snmp.get_interface_stats(interface.id, since: since_time)
assert length(stats) == 1
assert hd(stats).if_in_octets == 5000
end
test "returns empty list for interface with no stats", %{device: device, snmp_device: snmp_device} do
interface =
%Interface{}
|> Interface.changeset(%{
snmp_device_id: snmp_device.id,
if_index: 1,
if_name: "eth0"
})
|> Repo.insert!()
assert Snmp.get_interface_stats(interface.id) == []
end
end
describe "get_latest_interface_stat/1" do
test "returns most recent stat", %{device: device, snmp_device: snmp_device} do
interface =
%Interface{}
|> Interface.changeset(%{
snmp_device_id: snmp_device.id,
if_index: 1,
if_name: "eth0"
})
|> Repo.insert!()
old_stat =
%InterfaceStat{}
|> InterfaceStat.changeset(%{
interface_id: interface.id,
if_in_octets: 1000,
if_out_octets: 2000,
checked_at: DateTime.add(DateTime.utc_now(), -3600, :second)
})
|> Repo.insert!()
new_stat =
%InterfaceStat{}
|> InterfaceStat.changeset(%{
interface_id: interface.id,
if_in_octets: 5000,
if_out_octets: 6000,
checked_at: DateTime.utc_now()
})
|> Repo.insert!()
latest = Snmp.get_latest_interface_stat(interface.id)
assert latest.id == new_stat.id
refute latest.id == old_stat.id
end
test "returns nil for interface with no stats", %{device: device, snmp_device: snmp_device} do
interface =
%Interface{}
|> Interface.changeset(%{
snmp_device_id: snmp_device.id,
if_index: 1,
if_name: "eth0"
})
|> Repo.insert!()
assert Snmp.get_latest_interface_stat(interface.id) == nil
end
end
describe "create_sensor_reading/1" do
test "creates a sensor reading with valid attributes", %{device: device, snmp_device: snmp_device} do
sensor =
%Sensor{}
|> Sensor.changeset(%{
snmp_device_id: snmp_device.id,
sensor_type: "temperature",
sensor_index: "1",
sensor_oid: "1.2.3.4"
})
|> Repo.insert!()
{:ok, reading} =
Snmp.create_sensor_reading(%{
sensor_id: sensor.id,
value: 25.5,
status: "ok",
checked_at: DateTime.utc_now()
})
assert reading.sensor_id == sensor.id
assert reading.value == 25.5
assert reading.status == "ok"
end
test "returns error with invalid attributes" do
{:error, changeset} = Snmp.create_sensor_reading(%{value: "not a number"})
assert changeset.errors[:sensor_id]
end
end
describe "create_interface_stat/1" do
test "creates an interface stat with valid attributes", %{device: device, snmp_device: snmp_device} do
interface =
%Interface{}
|> Interface.changeset(%{
snmp_device_id: snmp_device.id,
if_index: 1,
if_name: "eth0"
})
|> Repo.insert!()
{:ok, stat} =
Snmp.create_interface_stat(%{
interface_id: interface.id,
if_in_octets: 1000,
if_out_octets: 2000,
if_in_errors: 0,
if_out_errors: 0,
if_in_discards: 0,
if_out_discards: 0,
checked_at: DateTime.utc_now()
})
assert stat.interface_id == interface.id
assert stat.if_in_octets == 1000
assert stat.if_out_octets == 2000
end
test "returns error with invalid attributes" do
{:error, changeset} = Snmp.create_interface_stat(%{if_in_octets: "not a number"})
assert changeset.errors[:interface_id]
end
end
end