2426 lines
75 KiB
Elixir
2426 lines
75 KiB
Elixir
defmodule Towerops.EquipmentTest do
|
|
use Towerops.DataCase
|
|
use ExUnitProperties
|
|
|
|
alias Towerops.Devices
|
|
alias Towerops.Devices.Device
|
|
|
|
describe "device" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
alias Device, as: DeviceSchema
|
|
|
|
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
|
|
})
|
|
|
|
%{organization: organization, site: site, user: user}
|
|
end
|
|
|
|
@valid_attrs %{
|
|
name: "Router 1",
|
|
ip_address: "192.168.1.1",
|
|
description: "Main router",
|
|
monitoring_enabled: true,
|
|
check_interval_seconds: 300
|
|
}
|
|
@update_attrs %{
|
|
name: "Updated Router",
|
|
ip_address: "192.168.1.2",
|
|
description: "Updated",
|
|
monitoring_enabled: false
|
|
}
|
|
@invalid_attrs %{name: nil, ip_address: nil}
|
|
|
|
test "list_site_devices/1 returns all devices for a site", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
|
|
|
|
assert Devices.list_site_devices(site.id) == [device]
|
|
end
|
|
|
|
test "list_organization_devices/1 returns all devices for an organization", %{
|
|
organization: organization,
|
|
site: site
|
|
} do
|
|
{:ok, device} =
|
|
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
|
|
|
|
result = Devices.list_organization_devices(organization.id)
|
|
assert length(result) == 1
|
|
assert hd(result).id == device.id
|
|
end
|
|
|
|
test "list_monitored_devices/0 returns only device with monitoring enabled", %{
|
|
organization: organization,
|
|
site: site
|
|
} do
|
|
{:ok, monitored} =
|
|
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
|
|
|
|
{:ok, _not_monitored} =
|
|
Devices.create_device(%{
|
|
name: "Not Monitored",
|
|
ip_address: "192.168.1.3",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
monitoring_enabled: false
|
|
})
|
|
|
|
result = Devices.list_monitored_devices()
|
|
assert length(result) == 1
|
|
assert hd(result).id == monitored.id
|
|
end
|
|
|
|
test "get_device!/1 returns the device with given id", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
|
|
|
|
assert Devices.get_device!(device.id).id == device.id
|
|
end
|
|
|
|
test "get_site_equipment!/2 returns device for specific site", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
|
|
|
|
assert Devices.get_site_device!(site.id, device.id).id == device.id
|
|
end
|
|
|
|
test "create_device/1 with valid data creates device", %{organization: organization, site: site} do
|
|
attrs = Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id})
|
|
assert {:ok, %DeviceSchema{} = device} = Devices.create_device(attrs)
|
|
assert device.name == "Router 1"
|
|
assert device.ip_address == "192.168.1.1"
|
|
assert device.status == :unknown
|
|
assert device.monitoring_enabled == true
|
|
assert device.check_interval_seconds == 300
|
|
end
|
|
|
|
test "create_device/1 with valid IPv6 address", %{organization: organization, site: site} do
|
|
attrs = Map.put(@valid_attrs, :ip_address, "2001:0db8:85a3::8a2e:0370:7334")
|
|
attrs = Map.merge(attrs, %{site_id: site.id, organization_id: organization.id})
|
|
assert {:ok, %DeviceSchema{}} = Devices.create_device(attrs)
|
|
end
|
|
|
|
test "create_device/1 with invalid IP address returns error", %{organization: organization, site: site} do
|
|
attrs = Map.put(@valid_attrs, :ip_address, "invalid-ip")
|
|
attrs = Map.merge(attrs, %{site_id: site.id, organization_id: organization.id})
|
|
assert {:error, changeset} = Devices.create_device(attrs)
|
|
assert "must be a valid IPv4 or IPv6 address" in errors_on(changeset).ip_address
|
|
end
|
|
|
|
test "create_device/1 with invalid data returns error changeset" do
|
|
assert {:error, %Ecto.Changeset{}} = Devices.create_device(@invalid_attrs)
|
|
end
|
|
|
|
test "update_device/2 with valid data updates the device", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
|
|
|
|
assert {:ok, %DeviceSchema{} = device} =
|
|
Devices.update_device(device, @update_attrs)
|
|
|
|
assert device.name == "Updated Router"
|
|
assert device.ip_address == "192.168.1.2"
|
|
assert device.monitoring_enabled == false
|
|
end
|
|
|
|
test "update_device/2 with invalid data returns error changeset", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
|
|
|
|
assert {:error, %Ecto.Changeset{}} =
|
|
Devices.update_device(device, @invalid_attrs)
|
|
|
|
assert Devices.get_device!(device.id).name == device.name
|
|
end
|
|
|
|
test "change_device/1 returns an device changeset", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
|
|
|
|
assert %Ecto.Changeset{} = Devices.change_device(device)
|
|
end
|
|
|
|
test "update_device_status/2 updates status and timestamps", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
|
|
|
|
assert {:ok, updated} = Devices.update_device_status(device, :up)
|
|
assert updated.status == :up
|
|
assert updated.last_checked_at
|
|
assert updated.last_status_change_at
|
|
end
|
|
|
|
test "update_device_status/2 only updates last_checked_at if status unchanged", %{
|
|
organization: organization,
|
|
site: site
|
|
} do
|
|
{:ok, device} =
|
|
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
|
|
|
|
{:ok, device} = Devices.update_device_status(device, :up)
|
|
|
|
# Manually set timestamps to 1 hour ago to avoid sleeping
|
|
# Must truncate to :second to match DB column type
|
|
past_time =
|
|
DateTime.utc_now()
|
|
|> DateTime.add(-3600, :second)
|
|
|> DateTime.truncate(:second)
|
|
|
|
{:ok, device} =
|
|
device
|
|
|> Ecto.Changeset.change(%{last_checked_at: past_time, last_status_change_at: past_time})
|
|
|> Towerops.Repo.update()
|
|
|
|
first_change_at = device.last_status_change_at
|
|
first_checked_at = device.last_checked_at
|
|
|
|
{:ok, updated} = Devices.update_device_status(device, :up)
|
|
# Status change timestamp should not change
|
|
assert updated.last_status_change_at == first_change_at
|
|
# But checked_at should be updated (to now, which is after 1 hour ago)
|
|
assert DateTime.after?(updated.last_checked_at, first_checked_at)
|
|
end
|
|
|
|
test "list_snmp_enabled_devices/0 returns only device with SNMP enabled", %{organization: organization, site: site} do
|
|
{:ok, snmp_enabled} =
|
|
Devices.create_device(%{
|
|
name: "SNMP Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_enabled: true,
|
|
snmp_community: "public"
|
|
})
|
|
|
|
{:ok, _snmp_disabled} =
|
|
Devices.create_device(%{
|
|
name: "Non-SNMP Router",
|
|
ip_address: "192.168.1.2",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_enabled: false
|
|
})
|
|
|
|
result = Devices.list_snmp_enabled_devices()
|
|
assert length(result) == 1
|
|
assert hd(result).id == snmp_enabled.id
|
|
end
|
|
|
|
test "update_snmp_poll_time/1 updates last_snmp_poll_at timestamp", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
|
|
|
|
assert device.last_snmp_poll_at == nil
|
|
|
|
assert {:ok, updated} = Devices.update_snmp_poll_time(device)
|
|
assert updated.last_snmp_poll_at
|
|
assert DateTime.before?(updated.last_snmp_poll_at, DateTime.utc_now())
|
|
end
|
|
|
|
test "create_device/1 with SNMP enabled and nil community string succeeds", %{organization: organization, site: site} do
|
|
attrs = %{
|
|
name: "SNMP Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_enabled: true,
|
|
snmp_version: "2c",
|
|
snmp_community: nil
|
|
}
|
|
|
|
assert {:ok, %DeviceSchema{} = device} = Devices.create_device(attrs)
|
|
assert device.snmp_enabled == true
|
|
assert device.snmp_community == nil
|
|
end
|
|
|
|
test "create_device/1 with SNMP enabled and empty community string succeeds", %{
|
|
organization: organization,
|
|
site: site
|
|
} do
|
|
attrs = %{
|
|
name: "SNMP Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_enabled: true,
|
|
snmp_version: "2c",
|
|
snmp_community: ""
|
|
}
|
|
|
|
# Empty string should be converted to nil by the database
|
|
assert {:ok, %DeviceSchema{} = device} = Devices.create_device(attrs)
|
|
assert device.snmp_enabled == true
|
|
# Empty strings are stored as nil in the database
|
|
assert device.snmp_community == nil || device.snmp_community == ""
|
|
end
|
|
|
|
test "reorder_device/2 reorders device to first position", %{organization: organization, site: site} do
|
|
{:ok, device1} =
|
|
Devices.create_device(%{
|
|
name: "Device 1",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
{:ok, device2} =
|
|
Devices.create_device(%{
|
|
name: "Device 2",
|
|
ip_address: "192.168.1.2",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
{:ok, device3} =
|
|
Devices.create_device(%{
|
|
name: "Device 3",
|
|
ip_address: "192.168.1.3",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
# Move device3 to first position
|
|
{:ok, _updated} = Devices.reorder_device(device3.id, 1)
|
|
|
|
# Verify order
|
|
devices = Devices.list_site_devices(site.id)
|
|
assert Enum.at(devices, 0).id == device3.id
|
|
assert Enum.at(devices, 0).display_order == 1
|
|
|
|
assert Enum.at(devices, 1).id == device1.id
|
|
assert Enum.at(devices, 1).display_order == 2
|
|
|
|
assert Enum.at(devices, 2).id == device2.id
|
|
assert Enum.at(devices, 2).display_order == 3
|
|
end
|
|
|
|
test "reorder_device/2 reorders device to last position", %{organization: organization, site: site} do
|
|
{:ok, device1} =
|
|
Devices.create_device(%{
|
|
name: "Device 1",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
{:ok, device2} =
|
|
Devices.create_device(%{
|
|
name: "Device 2",
|
|
ip_address: "192.168.1.2",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
{:ok, device3} =
|
|
Devices.create_device(%{
|
|
name: "Device 3",
|
|
ip_address: "192.168.1.3",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
# Move device1 to last position
|
|
{:ok, _updated} = Devices.reorder_device(device1.id, 3)
|
|
|
|
# Verify order
|
|
devices = Devices.list_site_devices(site.id)
|
|
assert Enum.at(devices, 0).id == device2.id
|
|
assert Enum.at(devices, 0).display_order == 1
|
|
|
|
assert Enum.at(devices, 1).id == device3.id
|
|
assert Enum.at(devices, 1).display_order == 2
|
|
|
|
assert Enum.at(devices, 2).id == device1.id
|
|
assert Enum.at(devices, 2).display_order == 3
|
|
end
|
|
|
|
test "reorder_device/2 reorders device to middle position", %{organization: organization, site: site} do
|
|
{:ok, device1} =
|
|
Devices.create_device(%{
|
|
name: "Device 1",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
{:ok, device2} =
|
|
Devices.create_device(%{
|
|
name: "Device 2",
|
|
ip_address: "192.168.1.2",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
{:ok, device3} =
|
|
Devices.create_device(%{
|
|
name: "Device 3",
|
|
ip_address: "192.168.1.3",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
# Move device3 to middle position
|
|
{:ok, _updated} = Devices.reorder_device(device3.id, 2)
|
|
|
|
# Verify order
|
|
devices = Devices.list_site_devices(site.id)
|
|
assert Enum.at(devices, 0).id == device1.id
|
|
assert Enum.at(devices, 0).display_order == 1
|
|
|
|
assert Enum.at(devices, 1).id == device3.id
|
|
assert Enum.at(devices, 1).display_order == 2
|
|
|
|
assert Enum.at(devices, 2).id == device2.id
|
|
assert Enum.at(devices, 2).display_order == 3
|
|
end
|
|
|
|
test "reorder_device/2 maintains continuous numbering", %{organization: organization, site: site} do
|
|
{:ok, _device1} =
|
|
Devices.create_device(%{
|
|
name: "Device 1",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
{:ok, device2} =
|
|
Devices.create_device(%{
|
|
name: "Device 2",
|
|
ip_address: "192.168.1.2",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
{:ok, _device3} =
|
|
Devices.create_device(%{
|
|
name: "Device 3",
|
|
ip_address: "192.168.1.3",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
{:ok, device4} =
|
|
Devices.create_device(%{
|
|
name: "Device 4",
|
|
ip_address: "192.168.1.4",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
# Reorder multiple times
|
|
{:ok, _} = Devices.reorder_device(device2.id, 1)
|
|
{:ok, _} = Devices.reorder_device(device4.id, 2)
|
|
|
|
# Verify all have continuous display_order values
|
|
devices = Devices.list_site_devices(site.id)
|
|
display_orders = Enum.map(devices, & &1.display_order)
|
|
assert display_orders == [1, 2, 3, 4]
|
|
end
|
|
|
|
test "reorder_device/2 only affects devices in same site", %{organization: organization, site: site} do
|
|
# Create devices in first site
|
|
{:ok, device1} =
|
|
Devices.create_device(%{
|
|
name: "Device 1",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
{:ok, device2} =
|
|
Devices.create_device(%{
|
|
name: "Device 2",
|
|
ip_address: "192.168.1.2",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
# Create second site and device
|
|
{:ok, site2} = Towerops.Sites.create_site(%{name: "Site 2", organization_id: organization.id})
|
|
|
|
{:ok, device3} =
|
|
Devices.create_device(%{
|
|
name: "Device 3",
|
|
ip_address: "192.168.2.1",
|
|
site_id: site2.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
# Reorder device in first site
|
|
{:ok, _} = Devices.reorder_device(device1.id, 2)
|
|
|
|
# Verify first site is reordered
|
|
site1_devices = Devices.list_site_devices(site.id)
|
|
assert Enum.at(site1_devices, 0).id == device2.id
|
|
assert Enum.at(site1_devices, 1).id == device1.id
|
|
|
|
# Verify second site is unchanged (no display_order set)
|
|
site2_devices = Devices.list_site_devices(site2.id)
|
|
assert length(site2_devices) == 1
|
|
assert hd(site2_devices).id == device3.id
|
|
assert is_nil(hd(site2_devices).display_order)
|
|
end
|
|
|
|
test "reset_organization_device_order/1 clears all display_order values", %{
|
|
organization: organization,
|
|
site: site
|
|
} do
|
|
{:ok, device1} =
|
|
Devices.create_device(%{
|
|
name: "Zebra Device",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
{:ok, device2} =
|
|
Devices.create_device(%{
|
|
name: "Alpha Device",
|
|
ip_address: "192.168.1.2",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
# Set custom order
|
|
{:ok, _} = Devices.reorder_device(device1.id, 1)
|
|
{:ok, _} = Devices.reorder_device(device2.id, 2)
|
|
|
|
# Verify custom order is set
|
|
devices_before = Devices.list_site_devices(site.id)
|
|
assert List.first(devices_before).id == device1.id
|
|
assert List.first(devices_before).display_order == 1
|
|
|
|
# Reset order
|
|
{count, _} = Devices.reset_organization_device_order(organization.id)
|
|
assert count == 2
|
|
|
|
# Verify display_order is cleared and falls back to alphabetical
|
|
devices_after = Devices.list_site_devices(site.id)
|
|
assert List.first(devices_after).name == "Alpha Device"
|
|
assert List.first(devices_after).display_order == nil
|
|
assert List.last(devices_after).name == "Zebra Device"
|
|
assert List.last(devices_after).display_order == nil
|
|
end
|
|
end
|
|
|
|
describe "SNMP configuration inheritance" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
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
|
|
})
|
|
|
|
%{organization: organization, site: site, user: user}
|
|
end
|
|
|
|
test "get_snmp_config/1 returns device-level community string when set", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_enabled: true,
|
|
snmp_community: "device-community"
|
|
})
|
|
|
|
config = Devices.get_snmp_config(device)
|
|
assert config.community == "device-community"
|
|
assert config.source == :device
|
|
end
|
|
|
|
test "get_snmp_config/1 returns site-level community when device has nil", %{
|
|
organization: organization,
|
|
site: site
|
|
} do
|
|
# Update site with SNMP community
|
|
{:ok, site} =
|
|
Towerops.Sites.update_site(site, %{
|
|
snmp_community: "site-community",
|
|
snmp_version: "2c"
|
|
})
|
|
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_enabled: true,
|
|
snmp_community: nil
|
|
})
|
|
|
|
device = Towerops.Repo.preload(device, site: :organization)
|
|
config = Devices.get_snmp_config(device)
|
|
assert config.community == "site-community"
|
|
assert config.source == :site
|
|
end
|
|
|
|
test "get_snmp_config/1 returns org-level community when device and site have nil", %{
|
|
organization: organization,
|
|
site: site
|
|
} do
|
|
# Update organization with SNMP community
|
|
{:ok, _organization} =
|
|
Towerops.Organizations.update_organization(organization, %{
|
|
snmp_community: "org-community",
|
|
snmp_version: "2c"
|
|
})
|
|
|
|
# Reload site with updated organization
|
|
site = Towerops.Repo.preload(site, :organization, force: true)
|
|
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_enabled: true,
|
|
snmp_community: nil
|
|
})
|
|
|
|
device = Towerops.Repo.preload(device, site: :organization)
|
|
config = Devices.get_snmp_config(device)
|
|
assert config.community == "org-community"
|
|
assert config.source == :organization
|
|
end
|
|
|
|
test "get_snmp_config/1 returns nil when all levels have nil", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_enabled: true,
|
|
snmp_community: nil
|
|
})
|
|
|
|
device = Towerops.Repo.preload(device, site: :organization)
|
|
config = Devices.get_snmp_config(device)
|
|
assert config.community == nil
|
|
assert config.source == :organization
|
|
end
|
|
|
|
test "get_snmp_config/1 prioritizes device over site over org", %{
|
|
organization: organization,
|
|
site: site
|
|
} do
|
|
# Set community at all levels
|
|
{:ok, _organization} =
|
|
Towerops.Organizations.update_organization(organization, %{
|
|
snmp_community: "org-community",
|
|
snmp_version: "2c"
|
|
})
|
|
|
|
{:ok, site} =
|
|
Towerops.Sites.update_site(site, %{
|
|
snmp_community: "site-community",
|
|
snmp_version: "2c"
|
|
})
|
|
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_enabled: true,
|
|
snmp_community: "device-community"
|
|
})
|
|
|
|
device = Towerops.Repo.preload(device, site: :organization)
|
|
config = Devices.get_snmp_config(device)
|
|
# Should use device-level community
|
|
assert config.community == "device-community"
|
|
assert config.source == :device
|
|
end
|
|
end
|
|
|
|
describe "events" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
alias Towerops.Devices.Event
|
|
|
|
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} =
|
|
Devices.create_device(%{
|
|
name: "Router 1",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
%{device: device}
|
|
end
|
|
|
|
test "create_event/1 creates an device event", %{device: device} do
|
|
attrs = %{
|
|
device_id: device.id,
|
|
event_type: "interface_up",
|
|
severity: "info",
|
|
message: "Interface came online",
|
|
metadata: %{"interface" => "eth0"},
|
|
occurred_at: Towerops.Time.now()
|
|
}
|
|
|
|
assert {:ok, %Event{} = event} = Devices.create_event(attrs)
|
|
assert event.device_id == device.id
|
|
assert event.event_type == "interface_up"
|
|
assert event.severity == "info"
|
|
assert event.message == "Interface came online"
|
|
assert event.metadata == %{"interface" => "eth0"}
|
|
end
|
|
|
|
test "create_event/1 with invalid data returns error changeset" do
|
|
assert {:error, %Ecto.Changeset{}} = Devices.create_event(%{})
|
|
end
|
|
|
|
test "list_devices_events/2 returns events for device ordered by most recent", %{
|
|
device: device
|
|
} do
|
|
now = DateTime.utc_now()
|
|
|
|
{:ok, event1} =
|
|
Devices.create_event(%{
|
|
device_id: device.id,
|
|
event_type: "interface_up",
|
|
severity: "info",
|
|
message: "Interface came up",
|
|
occurred_at: DateTime.add(now, -3600, :second)
|
|
})
|
|
|
|
{:ok, event2} =
|
|
Devices.create_event(%{
|
|
device_id: device.id,
|
|
event_type: "interface_down",
|
|
severity: "warning",
|
|
message: "Interface went down",
|
|
occurred_at: DateTime.add(now, -1800, :second)
|
|
})
|
|
|
|
events = Devices.list_devices_events(device.id)
|
|
assert length(events) == 2
|
|
# Should be ordered by most recent first
|
|
assert hd(events).id == event2.id
|
|
assert List.last(events).id == event1.id
|
|
end
|
|
|
|
test "list_devices_events/2 limits results", %{device: device} do
|
|
now = DateTime.utc_now()
|
|
|
|
# Create 10 events
|
|
for i <- 1..10 do
|
|
Devices.create_event(%{
|
|
device_id: device.id,
|
|
event_type: "interface_down",
|
|
severity: "warning",
|
|
message: "Interface down event #{i}",
|
|
occurred_at: DateTime.add(now, -i * 60, :second)
|
|
})
|
|
end
|
|
|
|
events = Devices.list_devices_events(device.id, 5)
|
|
assert length(events) == 5
|
|
end
|
|
end
|
|
|
|
describe "additional coverage tests" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
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
|
|
})
|
|
|
|
%{organization: organization, site: site, user: user}
|
|
end
|
|
|
|
test "get_device/1 returns device when exists", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
found = Devices.get_device(device.id)
|
|
assert found
|
|
assert found.id == device.id
|
|
end
|
|
|
|
test "get_device/1 returns nil when not exists" do
|
|
assert Devices.get_device(Ecto.UUID.generate()) == nil
|
|
end
|
|
|
|
test "get_device_with_details/1 preloads associations", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
result = Devices.get_device_with_details(device.id)
|
|
assert result.id == device.id
|
|
assert Ecto.assoc_loaded?(result.site)
|
|
end
|
|
|
|
@tag :skip
|
|
test "delete_device/1 deletes the device", %{organization: organization, site: site} do
|
|
# Skipped: Requires Monitoring.Supervisor to be running
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
assert {:ok, _deleted} = Devices.delete_device(device)
|
|
assert Devices.get_device(device.id) == nil
|
|
end
|
|
|
|
test "count_organization_devices/1 returns correct count", %{organization: organization, site: site} do
|
|
{:ok, _device1} =
|
|
Devices.create_device(%{
|
|
name: "Router 1",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
{:ok, _device2} =
|
|
Devices.create_device(%{
|
|
name: "Router 2",
|
|
ip_address: "192.168.1.2",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
assert Devices.count_organization_devices(organization.id) == 2
|
|
end
|
|
|
|
test "count_site_devices/1 returns correct count", %{organization: organization, site: site} do
|
|
{:ok, _device1} =
|
|
Devices.create_device(%{
|
|
name: "Router 1",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
{:ok, _device2} =
|
|
Devices.create_device(%{
|
|
name: "Router 2",
|
|
ip_address: "192.168.1.2",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
assert Devices.count_site_devices(site.id) == 2
|
|
end
|
|
|
|
test "count_site_devices_down/1 returns correct count", %{organization: organization, site: site} do
|
|
{:ok, device1} =
|
|
Devices.create_device(%{
|
|
name: "Router 1",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
{:ok, _device2} =
|
|
Devices.create_device(%{
|
|
name: "Router 2",
|
|
ip_address: "192.168.1.2",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
# Mark one as down
|
|
{:ok, _} = Devices.update_device_status(device1, :down)
|
|
|
|
assert Devices.count_site_devices_down(site.id) == 1
|
|
end
|
|
|
|
test "list_organization_devices/2 filters by site_id", %{organization: organization, site: site} do
|
|
{:ok, site2} =
|
|
Towerops.Sites.create_site(%{
|
|
name: "Site 2",
|
|
organization_id: organization.id
|
|
})
|
|
|
|
{:ok, device1} =
|
|
Devices.create_device(%{
|
|
name: "Router 1",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
{:ok, _device2} =
|
|
Devices.create_device(%{
|
|
name: "Router 2",
|
|
ip_address: "192.168.1.2",
|
|
site_id: site2.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
result = Devices.list_organization_devices(organization.id, %{"site_id" => site.id})
|
|
assert length(result) == 1
|
|
assert hd(result).id == device1.id
|
|
end
|
|
|
|
test "list_organization_devices/2 filters by status", %{organization: organization, site: site} do
|
|
{:ok, device1} =
|
|
Devices.create_device(%{
|
|
name: "Router 1",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
{:ok, _device2} =
|
|
Devices.create_device(%{
|
|
name: "Router 2",
|
|
ip_address: "192.168.1.2",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
# Mark one as up
|
|
Devices.update_device_status(device1, :up)
|
|
|
|
result = Devices.list_organization_devices(organization.id, %{"status" => "up"})
|
|
assert length(result) == 1
|
|
assert hd(result).id == device1.id
|
|
end
|
|
|
|
test "get_snmp_config/1 with device ID string", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_enabled: true,
|
|
snmp_community: "test-community"
|
|
})
|
|
|
|
config = Devices.get_snmp_config(device.id)
|
|
assert config.community == "test-community"
|
|
assert config.source == :device
|
|
end
|
|
end
|
|
|
|
describe "automatic rediscovery on device updates" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
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
|
|
})
|
|
|
|
%{organization: organization, site: site, user: user}
|
|
end
|
|
|
|
test "triggers discovery when snmp_enabled changes from false to true", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_enabled: false
|
|
})
|
|
|
|
# Clear any existing jobs
|
|
Towerops.Repo.delete_all(Oban.Job)
|
|
|
|
# Update to enable SNMP
|
|
{:ok, _updated} = Devices.update_device(device, %{snmp_enabled: true})
|
|
|
|
# Check that a discovery job was enqueued
|
|
jobs =
|
|
Oban.Job
|
|
|> Towerops.Repo.all()
|
|
|> Enum.filter(&(&1.worker == "Towerops.Workers.DiscoveryWorker"))
|
|
|
|
assert [job] = jobs
|
|
assert job.args["device_id"] == device.id
|
|
end
|
|
|
|
test "triggers discovery when snmp_version changes while enabled", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_enabled: true,
|
|
snmp_version: "2c"
|
|
})
|
|
|
|
# Clear any existing jobs
|
|
Towerops.Repo.delete_all(Oban.Job)
|
|
|
|
# Update SNMP version
|
|
{:ok, _updated} = Devices.update_device(device, %{snmp_version: "1"})
|
|
|
|
# Check that a discovery job was enqueued
|
|
jobs =
|
|
Oban.Job
|
|
|> Towerops.Repo.all()
|
|
|> Enum.filter(&(&1.worker == "Towerops.Workers.DiscoveryWorker"))
|
|
|
|
assert [job] = jobs
|
|
assert job.args["device_id"] == device.id
|
|
end
|
|
|
|
test "triggers discovery when snmp_port changes while enabled", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_enabled: true,
|
|
snmp_port: 161
|
|
})
|
|
|
|
# Clear any existing jobs
|
|
Towerops.Repo.delete_all(Oban.Job)
|
|
|
|
# Update SNMP port
|
|
{:ok, _updated} = Devices.update_device(device, %{snmp_port: 1161})
|
|
|
|
# Check that a discovery job was enqueued
|
|
jobs =
|
|
Oban.Job
|
|
|> Towerops.Repo.all()
|
|
|> Enum.filter(&(&1.worker == "Towerops.Workers.DiscoveryWorker"))
|
|
|
|
assert [job] = jobs
|
|
assert job.args["device_id"] == device.id
|
|
end
|
|
|
|
test "triggers discovery when monitoring_enabled changes to true with SNMP enabled", %{
|
|
organization: organization,
|
|
site: site
|
|
} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_enabled: true,
|
|
monitoring_enabled: false
|
|
})
|
|
|
|
# Clear any existing jobs
|
|
Towerops.Repo.delete_all(Oban.Job)
|
|
|
|
# Enable monitoring
|
|
{:ok, _updated} = Devices.update_device(device, %{monitoring_enabled: true})
|
|
|
|
# Check that a discovery job was enqueued
|
|
jobs =
|
|
Oban.Job
|
|
|> Towerops.Repo.all()
|
|
|> Enum.filter(&(&1.worker == "Towerops.Workers.DiscoveryWorker"))
|
|
|
|
assert [job] = jobs
|
|
assert job.args["device_id"] == device.id
|
|
end
|
|
|
|
test "does not trigger discovery when monitoring_enabled changes but SNMP disabled", %{
|
|
organization: organization,
|
|
site: site
|
|
} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_enabled: false,
|
|
monitoring_enabled: false
|
|
})
|
|
|
|
# Clear any existing jobs
|
|
Towerops.Repo.delete_all(Oban.Job)
|
|
|
|
# Enable monitoring (but SNMP is disabled)
|
|
{:ok, _updated} = Devices.update_device(device, %{monitoring_enabled: true})
|
|
|
|
# Check that NO discovery job was enqueued
|
|
jobs =
|
|
Oban.Job
|
|
|> Towerops.Repo.all()
|
|
|> Enum.filter(&(&1.worker == "Towerops.Workers.DiscoveryWorker"))
|
|
|
|
assert [] = jobs
|
|
end
|
|
|
|
test "does not trigger discovery when SNMP is disabled", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_enabled: true,
|
|
snmp_version: "2c"
|
|
})
|
|
|
|
# Clear any existing jobs
|
|
Towerops.Repo.delete_all(Oban.Job)
|
|
|
|
# Disable SNMP
|
|
{:ok, _updated} = Devices.update_device(device, %{snmp_enabled: false})
|
|
|
|
# Check that NO discovery job was enqueued (SNMP is disabled)
|
|
jobs =
|
|
Oban.Job
|
|
|> Towerops.Repo.all()
|
|
|> Enum.filter(&(&1.worker == "Towerops.Workers.DiscoveryWorker"))
|
|
|
|
assert [] = jobs
|
|
end
|
|
|
|
test "does not trigger discovery for unrelated field changes", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_enabled: true
|
|
})
|
|
|
|
# Clear any existing jobs
|
|
Towerops.Repo.delete_all(Oban.Job)
|
|
|
|
# Update unrelated fields (name, description)
|
|
{:ok, _updated} = Devices.update_device(device, %{name: "Updated Router"})
|
|
|
|
# Check that NO discovery job was enqueued
|
|
jobs =
|
|
Oban.Job
|
|
|> Towerops.Repo.all()
|
|
|> Enum.filter(&(&1.worker == "Towerops.Workers.DiscoveryWorker"))
|
|
|
|
assert [] = jobs
|
|
end
|
|
|
|
test "does not trigger discovery when snmp_version changes while disabled", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_enabled: false,
|
|
snmp_version: "2c"
|
|
})
|
|
|
|
# Clear any existing jobs
|
|
Towerops.Repo.delete_all(Oban.Job)
|
|
|
|
# Update SNMP version (but SNMP is disabled)
|
|
{:ok, _updated} = Devices.update_device(device, %{snmp_version: "1"})
|
|
|
|
# Check that NO discovery job was enqueued (SNMP is disabled)
|
|
jobs =
|
|
Oban.Job
|
|
|> Towerops.Repo.all()
|
|
|> Enum.filter(&(&1.worker == "Towerops.Workers.DiscoveryWorker"))
|
|
|
|
assert [] = jobs
|
|
end
|
|
end
|
|
|
|
describe "property-based tests" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
property "creating devices with valid IPs always succeeds" do
|
|
# Create fixtures once outside the property check
|
|
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
|
|
})
|
|
|
|
check all(
|
|
name <- string(:alphanumeric, min_length: 2, max_length: 20),
|
|
octet1 <- integer(0..255),
|
|
octet2 <- integer(0..255),
|
|
octet3 <- integer(0..255),
|
|
octet4 <- integer(1..254),
|
|
max_runs: 20
|
|
) do
|
|
ip_address = "#{octet1}.#{octet2}.#{octet3}.#{octet4}"
|
|
|
|
attrs = %{
|
|
name: name,
|
|
ip_address: ip_address,
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
}
|
|
|
|
# Use bypass_limits since property tests may create > 10 devices
|
|
assert {:ok, %Device{} = device} = Devices.create_device(attrs, bypass_limits: true)
|
|
assert device.ip_address == ip_address
|
|
assert device.status == :unknown
|
|
end
|
|
end
|
|
|
|
property "device status changes are idempotent" do
|
|
check all(status <- member_of([:up, :down, :unknown]), max_runs: 10) 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} =
|
|
Devices.create_device(%{
|
|
name: "Test Device",
|
|
ip_address: "192.168.1.100",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
# Update status twice
|
|
{:ok, updated1} = Devices.update_device_status(device, status)
|
|
{:ok, updated2} = Devices.update_device_status(updated1, status)
|
|
|
|
assert updated2.status == status
|
|
# Second update should not change last_status_change_at
|
|
assert updated2.last_status_change_at == updated1.last_status_change_at
|
|
end
|
|
end
|
|
|
|
property "device counters are always non-negative" do
|
|
check all(device_count <- integer(0..5), max_runs: 10) 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
|
|
})
|
|
|
|
# Create specified number of devices
|
|
if device_count > 0 do
|
|
for i <- 1..device_count do
|
|
Devices.create_device(%{
|
|
name: "Device #{i}",
|
|
ip_address: "192.168.1.#{i}",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
end
|
|
end
|
|
|
|
org_count = Devices.count_organization_devices(organization.id)
|
|
site_count = Devices.count_site_devices(site.id)
|
|
down_count = Devices.count_site_devices_down(site.id)
|
|
|
|
assert org_count >= 0
|
|
assert site_count >= 0
|
|
assert down_count >= 0
|
|
assert down_count <= site_count
|
|
assert org_count == site_count
|
|
end
|
|
end
|
|
|
|
property "SNMP config prioritization is consistent" do
|
|
check all(
|
|
device_community <- one_of([constant(nil), string(:alphanumeric, min_length: 1, max_length: 20)]),
|
|
site_community <- one_of([constant(nil), string(:alphanumeric, min_length: 1, max_length: 20)]),
|
|
max_runs: 10
|
|
) 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
|
|
})
|
|
|
|
# Update site with community if provided
|
|
site =
|
|
if site_community do
|
|
{:ok, updated_site} = Towerops.Sites.update_site(site, %{snmp_community: site_community})
|
|
updated_site
|
|
else
|
|
site
|
|
end
|
|
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_enabled: true,
|
|
snmp_community: device_community
|
|
})
|
|
|
|
device = Towerops.Repo.preload(device, site: :organization)
|
|
config = Devices.get_snmp_config(device)
|
|
|
|
# Device-level always takes precedence if set
|
|
if device_community do
|
|
assert config.community == device_community
|
|
assert config.source == :device
|
|
else
|
|
if site_community do
|
|
assert config.community == site_community
|
|
assert config.source == :site
|
|
else
|
|
assert config.source in [:organization, :default]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "device creation with subscription limits" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
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
|
|
})
|
|
|
|
%{organization: organization, site: site, user: user}
|
|
end
|
|
|
|
test "blocks device creation when at 10-device limit", %{organization: organization, site: site} do
|
|
# Create 10 devices (the limit)
|
|
for i <- 1..10 do
|
|
{:ok, _device} =
|
|
Devices.create_device(%{
|
|
name: "Device #{i}",
|
|
ip_address: "192.168.1.#{i}",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
monitoring_enabled: false,
|
|
snmp_enabled: false
|
|
})
|
|
end
|
|
|
|
# 11th device should be blocked
|
|
assert {:error, changeset} =
|
|
Devices.create_device(%{
|
|
name: "Device 11",
|
|
ip_address: "192.168.1.11",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
monitoring_enabled: false,
|
|
snmp_enabled: false
|
|
})
|
|
|
|
assert %{base: ["You've reached your plan limit of 10 devices. Upgrade to add more."]} =
|
|
errors_on(changeset)
|
|
end
|
|
|
|
test "allows device creation when under limit", %{organization: organization, site: site} do
|
|
# Create 9 devices
|
|
for i <- 1..9 do
|
|
{:ok, _device} =
|
|
Devices.create_device(%{
|
|
name: "Device #{i}",
|
|
ip_address: "192.168.1.#{i}",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
monitoring_enabled: false,
|
|
snmp_enabled: false
|
|
})
|
|
end
|
|
|
|
# 10th device should succeed
|
|
assert {:ok, _device} =
|
|
Devices.create_device(%{
|
|
name: "Device 10",
|
|
ip_address: "192.168.1.10",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
monitoring_enabled: false,
|
|
snmp_enabled: false
|
|
})
|
|
end
|
|
|
|
test "bypasses limit with bypass_limits option", %{organization: organization, site: site} do
|
|
# Create 10 devices (at limit)
|
|
for i <- 1..10 do
|
|
{:ok, _device} =
|
|
Devices.create_device(%{
|
|
name: "Device #{i}",
|
|
ip_address: "192.168.1.#{i}",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
monitoring_enabled: false,
|
|
snmp_enabled: false
|
|
})
|
|
end
|
|
|
|
# 11th device succeeds with bypass
|
|
assert {:ok, _device} =
|
|
Devices.create_device(
|
|
%{
|
|
name: "Device 11",
|
|
ip_address: "192.168.1.11",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
monitoring_enabled: false,
|
|
snmp_enabled: false
|
|
},
|
|
bypass_limits: true
|
|
)
|
|
end
|
|
|
|
test "counts devices across multiple sites in same organization", %{
|
|
organization: organization,
|
|
site: site1
|
|
} do
|
|
{:ok, site2} =
|
|
Towerops.Sites.create_site(%{
|
|
name: "Site 2",
|
|
organization_id: organization.id
|
|
})
|
|
|
|
# Create 6 devices in site1
|
|
for i <- 1..6 do
|
|
{:ok, _device} =
|
|
Devices.create_device(%{
|
|
name: "Site1 Device #{i}",
|
|
ip_address: "192.168.1.#{i}",
|
|
site_id: site1.id,
|
|
organization_id: organization.id,
|
|
monitoring_enabled: false,
|
|
snmp_enabled: false
|
|
})
|
|
end
|
|
|
|
# Create 4 devices in site2 (total = 10)
|
|
for i <- 1..4 do
|
|
{:ok, _device} =
|
|
Devices.create_device(%{
|
|
name: "Site2 Device #{i}",
|
|
ip_address: "192.168.2.#{i}",
|
|
site_id: site2.id,
|
|
organization_id: organization.id,
|
|
monitoring_enabled: false,
|
|
snmp_enabled: false
|
|
})
|
|
end
|
|
|
|
# 11th device in either site should be blocked
|
|
assert {:error, changeset} =
|
|
Devices.create_device(%{
|
|
name: "Site1 Device 7",
|
|
ip_address: "192.168.1.7",
|
|
site_id: site1.id,
|
|
organization_id: organization.id,
|
|
monitoring_enabled: false,
|
|
snmp_enabled: false
|
|
})
|
|
|
|
assert %{base: ["You've reached your plan limit of 10 devices. Upgrade to add more."]} =
|
|
errors_on(changeset)
|
|
end
|
|
end
|
|
|
|
describe "GDPR data access - list_devices_for_organizations/1" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
setup do
|
|
user = user_fixture()
|
|
{:ok, org1} = Towerops.Organizations.create_organization(%{name: "Org 1"}, user.id)
|
|
|
|
# Create org2 with a different owner to avoid subscription limits
|
|
other_user = user_fixture()
|
|
{:ok, org2} = Towerops.Organizations.create_organization(%{name: "Org 2"}, other_user.id)
|
|
|
|
{:ok, site1} =
|
|
Towerops.Sites.create_site(%{
|
|
name: "Org1 Site",
|
|
organization_id: org1.id
|
|
})
|
|
|
|
{:ok, site2} =
|
|
Towerops.Sites.create_site(%{
|
|
name: "Org2 Site",
|
|
organization_id: org2.id
|
|
})
|
|
|
|
%{org1: org1, org2: org2, site1: site1, site2: site2}
|
|
end
|
|
|
|
test "returns all devices for given organizations", %{
|
|
org1: org1,
|
|
org2: org2,
|
|
site1: site1,
|
|
site2: site2
|
|
} do
|
|
{:ok, device1} =
|
|
Devices.create_device(%{
|
|
name: "Device 1",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site1.id,
|
|
organization_id: org1.id
|
|
})
|
|
|
|
{:ok, device2} =
|
|
Devices.create_device(%{
|
|
name: "Device 2",
|
|
ip_address: "192.168.1.2",
|
|
site_id: site2.id,
|
|
organization_id: org2.id
|
|
})
|
|
|
|
result = Devices.list_devices_for_organizations([org1.id, org2.id])
|
|
assert length(result) == 2
|
|
|
|
device_ids = Enum.map(result, & &1.id)
|
|
assert device1.id in device_ids
|
|
assert device2.id in device_ids
|
|
end
|
|
|
|
test "returns only devices from specified organizations", %{
|
|
org1: org1,
|
|
org2: org2,
|
|
site1: site1,
|
|
site2: site2
|
|
} do
|
|
{:ok, device1} =
|
|
Devices.create_device(%{
|
|
name: "Device 1",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site1.id,
|
|
organization_id: org1.id
|
|
})
|
|
|
|
{:ok, _device2} =
|
|
Devices.create_device(%{
|
|
name: "Device 2",
|
|
ip_address: "192.168.1.2",
|
|
site_id: site2.id,
|
|
organization_id: org2.id
|
|
})
|
|
|
|
# Only request devices from org1
|
|
result = Devices.list_devices_for_organizations([org1.id])
|
|
assert length(result) == 1
|
|
assert hd(result).id == device1.id
|
|
end
|
|
|
|
test "returns empty list when organizations have no devices", %{org1: org1} do
|
|
result = Devices.list_devices_for_organizations([org1.id])
|
|
assert result == []
|
|
end
|
|
|
|
test "returns empty list for empty organization list" do
|
|
result = Devices.list_devices_for_organizations([])
|
|
assert result == []
|
|
end
|
|
|
|
test "preloads site association", %{org1: org1, site1: site1} do
|
|
{:ok, _device} =
|
|
Devices.create_device(%{
|
|
name: "Device 1",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site1.id,
|
|
organization_id: org1.id
|
|
})
|
|
|
|
result = Devices.list_devices_for_organizations([org1.id])
|
|
assert length(result) == 1
|
|
loaded_device = hd(result)
|
|
assert Ecto.assoc_loaded?(loaded_device.site)
|
|
assert loaded_device.site.id == site1.id
|
|
end
|
|
|
|
test "orders devices by organization and name", %{org1: org1, org2: org2, site1: site1, site2: site2} do
|
|
{:ok, _zebra_dev} =
|
|
Devices.create_device(%{
|
|
name: "Zebra Device",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site1.id,
|
|
organization_id: org1.id
|
|
})
|
|
|
|
{:ok, _alpha_dev} =
|
|
Devices.create_device(%{
|
|
name: "Alpha Device",
|
|
ip_address: "192.168.1.2",
|
|
site_id: site1.id,
|
|
organization_id: org1.id
|
|
})
|
|
|
|
{:ok, _beta_dev} =
|
|
Devices.create_device(%{
|
|
name: "Beta Device",
|
|
ip_address: "192.168.2.1",
|
|
site_id: site2.id,
|
|
organization_id: org2.id
|
|
})
|
|
|
|
result = Devices.list_devices_for_organizations([org1.id, org2.id])
|
|
assert length(result) == 3
|
|
|
|
# Devices should be ordered by org ID, then by name alphabetically
|
|
# Group by org to verify ordering within each org
|
|
org1_devices = Enum.filter(result, &(&1.site.organization_id == org1.id))
|
|
org2_devices = Enum.filter(result, &(&1.site.organization_id == org2.id))
|
|
|
|
# Within org1, should be alphabetical by name
|
|
assert length(org1_devices) == 2
|
|
org1_names = Enum.map(org1_devices, & &1.name)
|
|
assert org1_names == ["Alpha Device", "Zebra Device"]
|
|
|
|
# Org2 has one device
|
|
assert length(org2_devices) == 1
|
|
assert hd(org2_devices).name == "Beta Device"
|
|
|
|
# Overall list should be ordered by org_id first
|
|
org_ids = Enum.map(result, & &1.site.organization_id)
|
|
assert org_ids == Enum.sort(org_ids)
|
|
end
|
|
end
|
|
|
|
describe "PubSub broadcasts" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
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
|
|
})
|
|
|
|
%{organization: organization, site: site, user: user}
|
|
end
|
|
|
|
test "create_device/1 broadcasts :device_created", %{organization: organization, site: site} do
|
|
Phoenix.PubSub.subscribe(Towerops.PubSub, "devices:org:#{organization.id}")
|
|
|
|
{:ok, _device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
assert_receive {:device_created, org_id}
|
|
assert org_id == organization.id
|
|
end
|
|
|
|
test "update_device/2 broadcasts :device_updated", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
Phoenix.PubSub.subscribe(Towerops.PubSub, "devices:org:#{organization.id}")
|
|
|
|
{:ok, _updated} = Devices.update_device(device, %{name: "Updated Router"})
|
|
|
|
assert_receive {:device_updated, org_id}
|
|
assert org_id == organization.id
|
|
end
|
|
|
|
test "delete_device/1 broadcasts :device_deleted", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
Phoenix.PubSub.subscribe(Towerops.PubSub, "devices:org:#{organization.id}")
|
|
|
|
{:ok, _deleted} = Devices.delete_device(device)
|
|
|
|
assert_receive {:device_deleted, org_id}
|
|
assert org_id == organization.id
|
|
end
|
|
|
|
test "update_device_status/2 broadcasts :device_status_changed on actual status change", %{
|
|
organization: organization,
|
|
site: site
|
|
} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
Phoenix.PubSub.subscribe(Towerops.PubSub, "devices:org:#{organization.id}")
|
|
|
|
# Status changes from :unknown to :up — should broadcast
|
|
{:ok, _updated} = Devices.update_device_status(device, :up)
|
|
|
|
assert_receive {:device_status_changed, org_id}
|
|
assert org_id == organization.id
|
|
end
|
|
|
|
test "update_device_status/2 broadcasts :device_updated when status unchanged", %{
|
|
organization: organization,
|
|
site: site
|
|
} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
# Set initial status to :up
|
|
{:ok, device} = Devices.update_device_status(device, :up)
|
|
|
|
Phoenix.PubSub.subscribe(Towerops.PubSub, "devices:org:#{organization.id}")
|
|
|
|
# Same status — should broadcast :device_updated (not :device_status_changed)
|
|
{:ok, _updated} = Devices.update_device_status(device, :up)
|
|
|
|
refute_receive {:device_status_changed, _}
|
|
assert_receive {:device_updated, org_id}
|
|
assert org_id == organization.id
|
|
end
|
|
end
|
|
|
|
describe "get_device_by_ip/3" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
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
|
|
})
|
|
|
|
%{organization: organization, site: site}
|
|
end
|
|
|
|
test "returns device matching IP and site", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
found = Devices.get_device_by_ip("192.168.1.1", site.id)
|
|
assert found.id == device.id
|
|
end
|
|
|
|
test "returns nil when IP not found in site", %{site: site} do
|
|
assert Devices.get_device_by_ip("10.0.0.1", site.id) == nil
|
|
end
|
|
|
|
test "returns nil when site_id is nil" do
|
|
assert Devices.get_device_by_ip("192.168.1.1", nil) == nil
|
|
end
|
|
|
|
test "excludes device with given exclude_id", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
assert Devices.get_device_by_ip("192.168.1.1", site.id, device.id) == nil
|
|
end
|
|
end
|
|
|
|
describe "get_mikrotik_config/1" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
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
|
|
})
|
|
|
|
%{organization: organization, site: site}
|
|
end
|
|
|
|
test "returns mikrotik config from device", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "MikroTik Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
mikrotik_enabled: true,
|
|
mikrotik_username: "admin",
|
|
mikrotik_port: 8729,
|
|
mikrotik_use_ssl: true
|
|
})
|
|
|
|
config = Devices.get_mikrotik_config(device)
|
|
assert config.username == "admin"
|
|
assert config.port == 8729
|
|
assert config.use_ssl == true
|
|
assert config.enabled == true
|
|
end
|
|
|
|
test "returns defaults when fields are nil", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
config = Devices.get_mikrotik_config(device)
|
|
assert config.port == 8729
|
|
assert config.ssh_port == 22
|
|
assert config.use_ssl == true
|
|
assert config.enabled == false
|
|
end
|
|
|
|
test "accepts device_id string", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
mikrotik_enabled: true,
|
|
mikrotik_username: "admin"
|
|
})
|
|
|
|
config = Devices.get_mikrotik_config(device.id)
|
|
assert config.username == "admin"
|
|
end
|
|
end
|
|
|
|
describe "get_snmpv3_config/1" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
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
|
|
})
|
|
|
|
%{organization: organization, site: site}
|
|
end
|
|
|
|
test "returns nil for non-v3 device", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_version: "2c"
|
|
})
|
|
|
|
assert Devices.get_snmpv3_config(device) == nil
|
|
end
|
|
|
|
test "returns v3 config for v3 device", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_version: "3",
|
|
snmpv3_security_level: "authPriv",
|
|
snmpv3_username: "snmpuser",
|
|
snmpv3_auth_protocol: "SHA-256",
|
|
snmpv3_auth_password: "authpass123",
|
|
snmpv3_priv_protocol: "AES",
|
|
snmpv3_priv_password: "privpass123"
|
|
})
|
|
|
|
config = Devices.get_snmpv3_config(device)
|
|
assert config.security_level == "authPriv"
|
|
assert config.username == "snmpuser"
|
|
assert config.auth_protocol == "SHA-256"
|
|
assert config.priv_protocol == "AES"
|
|
end
|
|
|
|
test "accepts device_id string", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_version: "3",
|
|
snmpv3_username: "snmpuser",
|
|
snmpv3_auth_password: "authpass123",
|
|
snmpv3_priv_password: "privpass123"
|
|
})
|
|
|
|
config = Devices.get_snmpv3_config(device.id)
|
|
assert config.username == "snmpuser"
|
|
end
|
|
end
|
|
|
|
describe "propagate_site_community_change/2" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
setup do
|
|
user = user_fixture()
|
|
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
|
|
|
|
# Set community on site so devices will inherit from it
|
|
{:ok, site} =
|
|
Towerops.Sites.create_site(%{
|
|
name: "Test Site",
|
|
organization_id: organization.id,
|
|
snmp_community: "site-community"
|
|
})
|
|
|
|
%{organization: organization, site: site}
|
|
end
|
|
|
|
test "updates devices inheriting from site", %{organization: organization, site: site} do
|
|
# Device created without community → inherits from site, source = "site"
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_community: nil
|
|
})
|
|
|
|
assert device.snmp_community_source == "site"
|
|
|
|
assert :ok = Devices.propagate_site_community_change(site.id, "new-community")
|
|
|
|
updated = Devices.get_device!(device.id)
|
|
assert updated.snmp_community == "new-community"
|
|
end
|
|
|
|
test "does not update devices with device-level community", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_community: "device-community"
|
|
})
|
|
|
|
assert device.snmp_community_source == "device"
|
|
|
|
assert :ok = Devices.propagate_site_community_change(site.id, "new-community")
|
|
|
|
updated = Devices.get_device!(device.id)
|
|
assert updated.snmp_community == "device-community"
|
|
end
|
|
end
|
|
|
|
describe "propagate_organization_community_change/2" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
setup do
|
|
user = user_fixture()
|
|
|
|
{:ok, organization} =
|
|
Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
|
|
|
|
# Update org with community so devices can inherit from it
|
|
{:ok, organization} =
|
|
Towerops.Organizations.update_organization(organization, %{snmp_community: "org-community"})
|
|
|
|
# Site without community so devices inherit from org
|
|
{:ok, site} =
|
|
Towerops.Sites.create_site(%{
|
|
name: "Test Site",
|
|
organization_id: organization.id
|
|
})
|
|
|
|
%{organization: organization, site: site}
|
|
end
|
|
|
|
test "updates devices inheriting from organization", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_community: nil
|
|
})
|
|
|
|
assert device.snmp_community_source == "organization"
|
|
|
|
assert :ok = Devices.propagate_organization_community_change(organization.id, "new-community")
|
|
|
|
updated = Devices.get_device!(device.id)
|
|
assert updated.snmp_community == "new-community"
|
|
end
|
|
|
|
test "does not update devices with device-level source", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_community: "device-community"
|
|
})
|
|
|
|
assert device.snmp_community_source == "device"
|
|
|
|
assert :ok = Devices.propagate_organization_community_change(organization.id, "new-community")
|
|
|
|
updated = Devices.get_device!(device.id)
|
|
assert updated.snmp_community == "device-community"
|
|
end
|
|
end
|
|
|
|
describe "propagate_site_mikrotik_change/2" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
setup do
|
|
user = user_fixture()
|
|
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
|
|
|
|
# Set mikrotik creds on site so devices inherit
|
|
{:ok, site} =
|
|
Towerops.Sites.create_site(%{
|
|
name: "Test Site",
|
|
organization_id: organization.id,
|
|
mikrotik_username: "site-user"
|
|
})
|
|
|
|
%{organization: organization, site: site}
|
|
end
|
|
|
|
test "updates devices inheriting mikrotik from site", %{organization: organization, site: site} do
|
|
# Device without mikrotik_username → inherits from site
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
assert device.mikrotik_credential_source == "site"
|
|
|
|
assert :ok =
|
|
Devices.propagate_site_mikrotik_change(site.id, %{
|
|
mikrotik_username: "new-user"
|
|
})
|
|
|
|
updated = Devices.get_device!(device.id)
|
|
assert updated.mikrotik_username == "new-user"
|
|
end
|
|
end
|
|
|
|
describe "propagate_organization_mikrotik_change/2" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
setup do
|
|
user = user_fixture()
|
|
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
|
|
|
|
# Set mikrotik creds on org so devices inherit
|
|
{:ok, organization} =
|
|
Towerops.Organizations.update_organization(organization, %{mikrotik_username: "org-user"})
|
|
|
|
# Site without mikrotik creds so devices inherit from org
|
|
{:ok, site} =
|
|
Towerops.Sites.create_site(%{
|
|
name: "Test Site",
|
|
organization_id: organization.id
|
|
})
|
|
|
|
%{organization: organization, site: site}
|
|
end
|
|
|
|
test "updates devices inheriting mikrotik from organization", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
assert device.mikrotik_credential_source == "organization"
|
|
|
|
assert :ok =
|
|
Devices.propagate_organization_mikrotik_change(organization.id, %{
|
|
mikrotik_username: "new-user"
|
|
})
|
|
|
|
updated = Devices.get_device!(device.id)
|
|
assert updated.mikrotik_username == "new-user"
|
|
end
|
|
end
|
|
|
|
describe "propagate_site_snmpv3_change/2" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
setup do
|
|
user = user_fixture()
|
|
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
|
|
|
|
# Set snmpv3 creds on site
|
|
{:ok, site} =
|
|
Towerops.Sites.create_site(%{
|
|
name: "Test Site",
|
|
organization_id: organization.id,
|
|
snmpv3_username: "site-v3-user",
|
|
snmpv3_auth_password: "authpass123",
|
|
snmpv3_priv_password: "privpass123"
|
|
})
|
|
|
|
%{organization: organization, site: site}
|
|
end
|
|
|
|
test "updates devices inheriting snmpv3 from site", %{organization: organization, site: site} do
|
|
# Create device and manually set snmpv3_credential_source to "site"
|
|
# (can't use create_device with v3 + no username due to changeset validation order)
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmpv3_username: "site-v3-user",
|
|
snmpv3_auth_password: "authpass123",
|
|
snmpv3_priv_password: "privpass123",
|
|
snmp_version: "3"
|
|
})
|
|
|
|
# Force source to "site" for propagation test
|
|
device
|
|
|> Ecto.Changeset.change(%{snmpv3_credential_source: "site"})
|
|
|> Towerops.Repo.update!()
|
|
|
|
assert :ok =
|
|
Devices.propagate_site_snmpv3_change(site.id, %{
|
|
snmpv3_username: "new-v3-user"
|
|
})
|
|
|
|
updated = Devices.get_device!(device.id)
|
|
assert updated.snmpv3_username == "new-v3-user"
|
|
end
|
|
end
|
|
|
|
describe "propagate_organization_snmpv3_change/2" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
setup do
|
|
user = user_fixture()
|
|
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
|
|
|
|
# Set snmpv3 creds on org (with passwords required for v3 validation)
|
|
{:ok, organization} =
|
|
Towerops.Organizations.update_organization(organization, %{
|
|
snmpv3_username: "org-v3-user",
|
|
snmpv3_auth_password: "authpass123",
|
|
snmpv3_priv_password: "privpass123"
|
|
})
|
|
|
|
# Site without snmpv3 creds
|
|
{:ok, site} =
|
|
Towerops.Sites.create_site(%{
|
|
name: "Test Site",
|
|
organization_id: organization.id
|
|
})
|
|
|
|
%{organization: organization, site: site}
|
|
end
|
|
|
|
test "updates devices inheriting snmpv3 from organization", %{organization: organization, site: site} do
|
|
# Create device with v3 credentials and force source to "organization"
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmpv3_username: "org-v3-user",
|
|
snmpv3_auth_password: "authpass123",
|
|
snmpv3_priv_password: "privpass123",
|
|
snmp_version: "3"
|
|
})
|
|
|
|
# Force source to "organization" for propagation test
|
|
device
|
|
|> Ecto.Changeset.change(%{snmpv3_credential_source: "organization"})
|
|
|> Towerops.Repo.update!()
|
|
|
|
assert :ok =
|
|
Devices.propagate_organization_snmpv3_change(organization.id, %{
|
|
snmpv3_username: "new-v3-user"
|
|
})
|
|
|
|
updated = Devices.get_device!(device.id)
|
|
assert updated.snmpv3_username == "new-v3-user"
|
|
end
|
|
end
|
|
|
|
describe "resolve_snmp_community/1" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
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
|
|
})
|
|
|
|
%{organization: organization, site: site}
|
|
end
|
|
|
|
test "returns device community when source is device", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_community: "my-community"
|
|
})
|
|
|
|
assert Devices.resolve_snmp_community(device) == "my-community"
|
|
end
|
|
|
|
test "returns site community when device has none", %{organization: organization, site: site} do
|
|
{:ok, site} = Towerops.Sites.update_site(site, %{snmp_community: "site-community"})
|
|
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_community: nil,
|
|
snmp_community_source: "site"
|
|
})
|
|
|
|
assert Devices.resolve_snmp_community(device) == "site-community"
|
|
end
|
|
|
|
test "returns org community when device and site have none", %{organization: organization, site: site} do
|
|
{:ok, _org} =
|
|
Towerops.Organizations.update_organization(organization, %{snmp_community: "org-community"})
|
|
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_community: nil,
|
|
snmp_community_source: "organization"
|
|
})
|
|
|
|
assert Devices.resolve_snmp_community(device) == "org-community"
|
|
end
|
|
end
|
|
|
|
describe "list_mikrotik_devices_with_api/0" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
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
|
|
})
|
|
|
|
%{organization: organization, site: site}
|
|
end
|
|
|
|
test "returns devices with mikrotik enabled and username", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "MikroTik Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
mikrotik_enabled: true,
|
|
mikrotik_username: "admin"
|
|
})
|
|
|
|
result = Devices.list_mikrotik_devices_with_api()
|
|
device_ids = Enum.map(result, & &1.id)
|
|
assert device.id in device_ids
|
|
end
|
|
|
|
test "excludes devices without mikrotik enabled", %{organization: organization, site: site} do
|
|
{:ok, _device} =
|
|
Devices.create_device(%{
|
|
name: "Regular Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
mikrotik_enabled: false
|
|
})
|
|
|
|
result = Devices.list_mikrotik_devices_with_api()
|
|
assert result == []
|
|
end
|
|
end
|
|
|
|
describe "auto ICMP ping check" do
|
|
import Towerops.AccountsFixtures
|
|
|
|
alias Towerops.Monitoring
|
|
|
|
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
|
|
})
|
|
|
|
%{organization: organization, site: site}
|
|
end
|
|
|
|
test "create_device/1 auto-creates an ICMP ping check", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
checks = Monitoring.list_checks(organization.id, device_id: device.id, check_type: "ping")
|
|
assert [ping_check] = checks
|
|
assert ping_check.source_type == "auto_discovery"
|
|
assert ping_check.config["host"] == "192.168.1.1"
|
|
end
|
|
|
|
test "update_device/2 syncs ping check when IP changes", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
{:ok, updated_device} = Devices.update_device(device, %{ip_address: "10.0.0.1"})
|
|
|
|
checks = Monitoring.list_checks(organization.id, device_id: updated_device.id, check_type: "ping")
|
|
assert [ping_check] = checks
|
|
assert ping_check.config["host"] == "10.0.0.1"
|
|
end
|
|
|
|
test "update_device/2 does not sync ping check when IP unchanged", %{organization: organization, site: site} do
|
|
{:ok, device} =
|
|
Devices.create_device(%{
|
|
name: "Router",
|
|
ip_address: "192.168.1.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
|
|
checks_before = Monitoring.list_checks(organization.id, device_id: device.id, check_type: "ping")
|
|
assert [check_before] = checks_before
|
|
|
|
{:ok, _updated} = Devices.update_device(device, %{name: "Updated Router"})
|
|
|
|
checks_after = Monitoring.list_checks(organization.id, device_id: device.id, check_type: "ping")
|
|
assert [check_after] = checks_after
|
|
assert check_after.updated_at == check_before.updated_at
|
|
end
|
|
end
|
|
end
|