1009 lines
31 KiB
Elixir
1009 lines
31 KiB
Elixir
defmodule ToweropsWeb.DeviceLive.ShowTest do
|
|
use ToweropsWeb.ConnCase, async: true
|
|
|
|
import Phoenix.LiveViewTest
|
|
|
|
alias Towerops.Snmp.Device
|
|
alias Towerops.Snmp.Interface
|
|
alias Towerops.Snmp.InterfaceStat
|
|
|
|
setup do
|
|
user = Towerops.AccountsFixtures.user_fixture(enable_totp: true)
|
|
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
|
|
|
|
device =
|
|
Towerops.DevicesFixtures.device_fixture(%{
|
|
organization_id: organization.id,
|
|
name: "Test Router",
|
|
ip_address: "192.168.1.1"
|
|
})
|
|
|
|
%{user: user, organization: organization, device: device}
|
|
end
|
|
|
|
describe "authenticated access" do
|
|
test "redirects to login when not authenticated", %{conn: conn, device: device} do
|
|
assert {:error, redirect} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
assert {:redirect, %{to: to}} = redirect
|
|
assert to =~ "/users/log-in"
|
|
end
|
|
end
|
|
|
|
describe "mount and display" do
|
|
test "displays device name and IP address", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
assert html =~ "Test Router"
|
|
assert html =~ "192.168.1.1"
|
|
end
|
|
|
|
test "redirects to add tab=overview when no tab param", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
|
|
# Without a tab param, the LiveView does push_patch to add ?tab=overview
|
|
assert {:error, {:live_redirect, %{to: to}}} = live(conn, ~p"/devices/#{device.id}")
|
|
assert to =~ "tab=overview"
|
|
end
|
|
|
|
test "sets page title to device name", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
assert page_title(view) =~ "Test Router"
|
|
end
|
|
end
|
|
|
|
describe "checks tab" do
|
|
test "displays checks tab link", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
assert html =~ "Checks"
|
|
end
|
|
|
|
test "loads checks tab showing auto-created ping check", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=checks")
|
|
|
|
assert html =~ "ICMP Ping"
|
|
assert html =~ "Add Check"
|
|
end
|
|
end
|
|
|
|
describe "tab navigation" do
|
|
test "renders overview tab with device information", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
assert html =~ "Device Information"
|
|
end
|
|
|
|
test "switches to neighbors tab via patch", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
# Clicking the Neighbors tab link triggers a patch (not a redirect)
|
|
html = view |> element("a", "Neighbors") |> render_click()
|
|
|
|
assert html =~ "Neighbors"
|
|
end
|
|
|
|
test "renders logs tab directly", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=logs")
|
|
|
|
assert html =~ "Test Router"
|
|
end
|
|
end
|
|
|
|
describe "organization access control" do
|
|
test "denies access to device from another organization", %{conn: conn, device: device} do
|
|
other_user = Towerops.AccountsFixtures.user_fixture(enable_totp: true)
|
|
|
|
{:ok, _other_org} =
|
|
Towerops.Organizations.create_organization(%{name: "Other Org"}, other_user.id)
|
|
|
|
conn = log_in_user(conn, other_user)
|
|
|
|
assert {:error, {:live_redirect, %{to: "/devices", flash: flash}}} =
|
|
live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
assert flash["error"] =~ "You don't have access to this device"
|
|
end
|
|
|
|
test "redirects with not found for nonexistent device", %{conn: conn, user: user} do
|
|
conn = log_in_user(conn, user)
|
|
fake_id = Ecto.UUID.generate()
|
|
|
|
assert {:error, {:live_redirect, %{to: "/devices", flash: flash}}} =
|
|
live(conn, ~p"/devices/#{fake_id}?tab=overview")
|
|
|
|
assert flash["error"] =~ "Device not found"
|
|
end
|
|
end
|
|
|
|
describe "tab rendering" do
|
|
test "renders interfaces tab", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=interfaces")
|
|
|
|
assert html =~ "Test Router"
|
|
end
|
|
|
|
test "renders sensors tab", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=sensors")
|
|
|
|
assert html =~ "Test Router"
|
|
end
|
|
|
|
test "renders events tab", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=events")
|
|
|
|
assert html =~ "Test Router"
|
|
end
|
|
end
|
|
|
|
describe "handle_info events" do
|
|
test "handles device_status_changed", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
send(view.pid, {:device_status_changed, device.id, :up, 10})
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles discovery_completed", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
send(view.pid, {:discovery_completed, device.id})
|
|
html = render(view)
|
|
assert html =~ "Discovery completed"
|
|
end
|
|
|
|
test "handles sensors_updated", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
send(view.pid, {:sensors_updated, device.id})
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles interfaces_updated", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
send(view.pid, {:interfaces_updated, device.id})
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles neighbors_updated", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
send(view.pid, {:neighbors_updated, device.id})
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles arp_updated", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
send(view.pid, {:arp_updated, device.id})
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles mac_updated", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
send(view.pid, {:mac_updated, device.id})
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles state_sensors_updated", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
send(view.pid, {:state_sensors_updated, device.id})
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles processors_updated", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
send(view.pid, {:processors_updated, device.id})
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles storage_updated", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
send(view.pid, {:storage_updated, device.id})
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles monitoring_check_updated", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
send(view.pid, {:monitoring_check_updated, device.id})
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles device_event", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
send(view.pid, {:device_event, %{device_id: device.id, type: "test"}})
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles refresh_data", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
send(view.pid, :refresh_data)
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles agents_stale with unrelated agent", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
send(view.pid, {:agents_stale, []})
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles agent_connected with unrelated agent", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
send(view.pid, {:agent_connected, Ecto.UUID.generate(), Ecto.UUID.generate()})
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles agent_disconnected with unrelated agent", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
send(view.pid, {:agent_disconnected, Ecto.UUID.generate(), Ecto.UUID.generate()})
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles agent_heartbeat with unrelated agent", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
send(view.pid, {:agent_heartbeat, Ecto.UUID.generate(), Ecto.UUID.generate()})
|
|
assert render(view)
|
|
end
|
|
end
|
|
|
|
describe "wireless tab" do
|
|
setup %{device: device} do
|
|
alias Device, as: SnmpDevice
|
|
|
|
snmp_device =
|
|
%SnmpDevice{}
|
|
|> SnmpDevice.changeset(%{device_id: device.id, sys_name: "ap", sys_descr: "Access Point"})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
# Create wireless clients with varying signal quality
|
|
client1 =
|
|
Towerops.SnmpFixtures.wireless_client_fixture(%{
|
|
snmp_device_id: snmp_device.id,
|
|
device_id: device.id,
|
|
mac_address: "AA:BB:CC:DD:EE:01",
|
|
ip_address: "192.168.1.101",
|
|
signal_strength: -65,
|
|
snr: 25,
|
|
distance: 100,
|
|
tx_rate: 54_000,
|
|
rx_rate: 48_000,
|
|
uptime_seconds: 3661
|
|
})
|
|
|
|
client2 =
|
|
Towerops.SnmpFixtures.wireless_client_fixture(%{
|
|
snmp_device_id: snmp_device.id,
|
|
device_id: device.id,
|
|
mac_address: "AA:BB:CC:DD:EE:02",
|
|
ip_address: "192.168.1.102",
|
|
signal_strength: -85,
|
|
snr: 10,
|
|
distance: 250
|
|
})
|
|
|
|
%{snmp_device: snmp_device, client1: client1, client2: client2}
|
|
end
|
|
|
|
test "displays wireless tab link with client count badge", %{
|
|
conn: conn,
|
|
user: user,
|
|
device: device
|
|
} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
assert html =~ "Wireless"
|
|
assert html =~ "2"
|
|
end
|
|
|
|
test "renders wireless tab with client table", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=wireless")
|
|
|
|
# Check for table headers
|
|
assert html =~ "MAC Address"
|
|
assert html =~ "IP Address"
|
|
assert html =~ "Signal"
|
|
assert html =~ "SNR"
|
|
|
|
# Check for client data
|
|
assert html =~ "AA:BB:CC:DD:EE:01"
|
|
assert html =~ "192.168.1.101"
|
|
assert html =~ "AA:BB:CC:DD:EE:02"
|
|
assert html =~ "192.168.1.102"
|
|
end
|
|
|
|
test "displays signal strength badges with correct values", %{
|
|
conn: conn,
|
|
user: user,
|
|
device: device
|
|
} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=wireless")
|
|
|
|
assert html =~ "-65 dBm"
|
|
assert html =~ "-85 dBm"
|
|
end
|
|
|
|
test "displays SNR badges with correct values", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=wireless")
|
|
|
|
assert html =~ "25 dB"
|
|
assert html =~ "10 dB"
|
|
end
|
|
|
|
test "displays formatted uptime", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=wireless")
|
|
|
|
# 3661 seconds = 1 hour, 1 minute, 1 second
|
|
assert html =~ "1 hour"
|
|
end
|
|
|
|
test "displays formatted TX/RX rates", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=wireless")
|
|
|
|
# 54000 Kbps = 54 Mbps
|
|
assert html =~ "54"
|
|
# 48000 Kbps = 48 Mbps
|
|
assert html =~ "48"
|
|
end
|
|
|
|
test "handles wireless_clients_updated PubSub message", %{
|
|
conn: conn,
|
|
user: user,
|
|
device: device
|
|
} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=wireless")
|
|
|
|
# Send PubSub update
|
|
send(view.pid, {:wireless_clients_updated, device.id, 2})
|
|
|
|
# Re-render and verify still works
|
|
html = render(view)
|
|
assert html =~ "AA:BB:CC:DD:EE:01"
|
|
end
|
|
|
|
test "shows empty state when no wireless clients", %{conn: conn, user: user, organization: organization} do
|
|
alias Device, as: SnmpDevice
|
|
# Create a new device without wireless clients
|
|
device_without_clients =
|
|
Towerops.DevicesFixtures.device_fixture(%{
|
|
organization_id: organization.id,
|
|
name: "AP Without Clients",
|
|
ip_address: "192.168.1.2"
|
|
})
|
|
|
|
# Create snmp_device so the wireless tab appears
|
|
%SnmpDevice{}
|
|
|> SnmpDevice.changeset(%{
|
|
device_id: device_without_clients.id,
|
|
sys_name: "ap",
|
|
sys_descr: "AP"
|
|
})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device_without_clients.id}?tab=wireless")
|
|
|
|
assert html =~ "No wireless clients"
|
|
end
|
|
|
|
test "switches to wireless tab via patch", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
html = view |> element("a", "Wireless") |> render_click()
|
|
|
|
assert html =~ "MAC Address"
|
|
assert html =~ "AA:BB:CC:DD:EE:01"
|
|
end
|
|
end
|
|
|
|
describe "ports tab capacity" do
|
|
setup %{device: device} do
|
|
alias Device, as: SnmpDevice
|
|
|
|
snmp_device =
|
|
%SnmpDevice{}
|
|
|> SnmpDevice.changeset(%{device_id: device.id, sys_name: "radio", sys_descr: "PTP"})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
interface =
|
|
%Interface{}
|
|
|> Interface.changeset(%{
|
|
snmp_device_id: snmp_device.id,
|
|
if_index: 1,
|
|
if_name: "eth0",
|
|
if_speed: 1_000_000_000,
|
|
if_type: 6,
|
|
configured_capacity_bps: 300_000_000,
|
|
capacity_source: "manual"
|
|
})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
# Insert stats for utilization calculation
|
|
now = DateTime.utc_now()
|
|
|
|
%InterfaceStat{}
|
|
|> InterfaceStat.changeset(%{
|
|
interface_id: interface.id,
|
|
if_in_octets: 0,
|
|
if_out_octets: 0,
|
|
checked_at: DateTime.add(now, -120, :second)
|
|
})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
# ~150 Mbps = 50% of 300 Mbps capacity
|
|
%InterfaceStat{}
|
|
|> InterfaceStat.changeset(%{
|
|
interface_id: interface.id,
|
|
if_in_octets: 0,
|
|
if_out_octets: 1_125_000_000,
|
|
checked_at: DateTime.add(now, -60, :second)
|
|
})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
%{snmp_device: snmp_device, interface: interface}
|
|
end
|
|
|
|
test "displays capacity column on ports tab", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=ports")
|
|
|
|
assert html =~ "Capacity"
|
|
assert html =~ "300.0 Mbps"
|
|
end
|
|
|
|
test "displays utilization bar on ports tab", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=ports")
|
|
|
|
assert html =~ "Utilization"
|
|
end
|
|
|
|
test "set capacity via event handler", %{
|
|
conn: conn,
|
|
user: user,
|
|
device: device,
|
|
interface: interface
|
|
} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=ports")
|
|
|
|
html =
|
|
render_click(view, "set_capacity", %{
|
|
"interface_id" => interface.id,
|
|
"capacity_mbps" => "500"
|
|
})
|
|
|
|
assert html =~ "500.0 Mbps"
|
|
end
|
|
|
|
test "clear capacity via event handler", %{
|
|
conn: conn,
|
|
user: user,
|
|
device: device,
|
|
interface: interface
|
|
} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=ports")
|
|
|
|
html = render_click(view, "clear_capacity", %{"interface_id" => interface.id})
|
|
|
|
# After clearing, the capacity badge should not show 300 Mbps anymore
|
|
refute html =~ "300.0 Mbps"
|
|
end
|
|
end
|
|
|
|
describe "overall traffic chart" do
|
|
setup %{device: device} do
|
|
snmp_device =
|
|
%Device{}
|
|
|> Device.changeset(%{device_id: device.id, sys_name: "radio", sys_descr: "PTP"})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
device =
|
|
device
|
|
|> Ecto.Changeset.change(device_role: "backhaul")
|
|
|> Towerops.Repo.update!()
|
|
|
|
interface =
|
|
%Interface{}
|
|
|> Interface.changeset(%{
|
|
snmp_device_id: snmp_device.id,
|
|
if_index: 1,
|
|
if_name: "eth0",
|
|
if_descr: "eth0",
|
|
if_speed: 1_000_000_000,
|
|
if_type: 6,
|
|
configured_capacity_bps: 500_000_000,
|
|
capacity_source: "manual"
|
|
})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
now = DateTime.utc_now()
|
|
|
|
%InterfaceStat{}
|
|
|> InterfaceStat.changeset(%{
|
|
interface_id: interface.id,
|
|
if_in_octets: 1_000,
|
|
if_out_octets: 2_000,
|
|
checked_at: DateTime.add(now, -60, :second)
|
|
})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
%InterfaceStat{}
|
|
|> InterfaceStat.changeset(%{
|
|
interface_id: interface.id,
|
|
if_in_octets: 10_000,
|
|
if_out_octets: 14_000,
|
|
checked_at: now
|
|
})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
%{device: device}
|
|
end
|
|
|
|
test "renders overall backhaul traffic and capacity with consistent polarity", %{
|
|
conn: conn,
|
|
user: user,
|
|
device: device
|
|
} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
datasets = chart_datasets_from_html(html, "traffic-chart")
|
|
|
|
assert dataset_y_values(datasets, "Outbound") == [-1600.0]
|
|
assert dataset_y_values(datasets, "Inbound") == [1200.0]
|
|
assert dataset_y_values(datasets, "Capacity (In)") == [500_000_000.0]
|
|
assert dataset_y_values(datasets, "Capacity (Out)") == [-500_000_000.0]
|
|
assert dataset_fill(datasets, "Capacity (In)") == false
|
|
assert dataset_fill(datasets, "Capacity (Out)") == false
|
|
end
|
|
end
|
|
|
|
defp chart_datasets_from_html(html, chart_id) do
|
|
pattern = ~r/id="#{chart_id}"[^>]*data-chart="([^"]+)"/
|
|
[_, encoded_json] = Regex.run(pattern, html)
|
|
|
|
encoded_json
|
|
|> String.replace(""", "\"")
|
|
|> Jason.decode!()
|
|
|> Map.fetch!("datasets")
|
|
end
|
|
|
|
defp dataset_y_values(datasets, label) do
|
|
datasets
|
|
|> Enum.find(&(Map.get(&1, "label") == label))
|
|
|> Map.fetch!("data")
|
|
|> Enum.map(&Map.fetch!(&1, "y"))
|
|
end
|
|
|
|
defp dataset_fill(datasets, label) do
|
|
datasets
|
|
|> Enum.find(&(Map.get(&1, "label") == label))
|
|
|> Map.get("fill")
|
|
end
|
|
|
|
describe "transceivers tab" do
|
|
setup %{device: device} do
|
|
alias Device, as: SnmpDevice
|
|
alias Towerops.Snmp.Transceiver
|
|
|
|
snmp_device =
|
|
%SnmpDevice{}
|
|
|> SnmpDevice.changeset(%{device_id: device.id, sys_name: "switch", sys_descr: "Switch"})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
transceiver1 =
|
|
%Transceiver{}
|
|
|> Transceiver.changeset(%{
|
|
snmp_device_id: snmp_device.id,
|
|
port_index: "1001",
|
|
transceiver_type: "SFP+",
|
|
vendor_name: "Finisar",
|
|
vendor_part_number: "FTLX8571D3BCL",
|
|
vendor_serial_number: "ABC123456",
|
|
wavelength_nm: 850,
|
|
supports_dom: true
|
|
})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
transceiver2 =
|
|
%Transceiver{}
|
|
|> Transceiver.changeset(%{
|
|
snmp_device_id: snmp_device.id,
|
|
port_index: "1002",
|
|
transceiver_type: "QSFP28",
|
|
vendor_name: "Intel",
|
|
vendor_part_number: "E810-XXVDA4",
|
|
supports_dom: false
|
|
})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
%{snmp_device: snmp_device, transceiver1: transceiver1, transceiver2: transceiver2}
|
|
end
|
|
|
|
test "displays transceivers tab link", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
assert html =~ "Transceivers"
|
|
end
|
|
|
|
test "renders transceivers tab with table", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=transceivers")
|
|
|
|
# Check for table headers
|
|
assert html =~ "Port"
|
|
assert html =~ "Type"
|
|
assert html =~ "Vendor"
|
|
assert html =~ "Part Number"
|
|
assert html =~ "Serial Number"
|
|
|
|
# Check for transceiver data
|
|
assert html =~ "SFP+"
|
|
assert html =~ "QSFP28"
|
|
assert html =~ "Finisar"
|
|
assert html =~ "Intel"
|
|
assert html =~ "FTLX8571D3BCL"
|
|
assert html =~ "ABC123456"
|
|
end
|
|
|
|
test "displays DOM badge for transceivers with supports_dom=true", %{
|
|
conn: conn,
|
|
user: user,
|
|
device: device
|
|
} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=transceivers")
|
|
|
|
assert html =~ "DOM"
|
|
end
|
|
|
|
test "shows empty state when no transceivers", %{conn: conn, user: user, organization: organization} do
|
|
alias Device, as: SnmpDevice
|
|
|
|
device_without_transceivers =
|
|
Towerops.DevicesFixtures.device_fixture(%{
|
|
organization_id: organization.id,
|
|
name: "Switch Without Transceivers",
|
|
ip_address: "192.168.1.3"
|
|
})
|
|
|
|
%SnmpDevice{}
|
|
|> SnmpDevice.changeset(%{
|
|
device_id: device_without_transceivers.id,
|
|
sys_name: "switch",
|
|
sys_descr: "Switch"
|
|
})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device_without_transceivers.id}?tab=transceivers")
|
|
|
|
assert html =~ "No transceivers found"
|
|
end
|
|
|
|
test "switches to transceivers tab via patch", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
html = view |> element("a", "Transceivers") |> render_click()
|
|
|
|
assert html =~ "Port"
|
|
assert html =~ "SFP+"
|
|
end
|
|
end
|
|
|
|
describe "printer supplies tab" do
|
|
setup %{device: device} do
|
|
alias Device, as: SnmpDevice
|
|
alias Towerops.Snmp.PrinterSupply
|
|
|
|
snmp_device =
|
|
%SnmpDevice{}
|
|
|> SnmpDevice.changeset(%{device_id: device.id, sys_name: "printer", sys_descr: "Printer"})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
supply1 =
|
|
%PrinterSupply{}
|
|
|> PrinterSupply.changeset(%{
|
|
snmp_device_id: snmp_device.id,
|
|
supply_index: "1",
|
|
supply_type: "toner",
|
|
supply_description: "Black Toner Cartridge",
|
|
supply_unit: "percent",
|
|
max_capacity: 100,
|
|
current_level: 45,
|
|
color_name: "black",
|
|
part_number: "TN-450"
|
|
})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
supply2 =
|
|
%PrinterSupply{}
|
|
|> PrinterSupply.changeset(%{
|
|
snmp_device_id: snmp_device.id,
|
|
supply_index: "2",
|
|
supply_type: "drum",
|
|
supply_description: "Drum Unit",
|
|
supply_unit: "percent",
|
|
max_capacity: 100,
|
|
current_level: 15,
|
|
part_number: "DR-420"
|
|
})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
%{snmp_device: snmp_device, supply1: supply1, supply2: supply2}
|
|
end
|
|
|
|
test "displays printer supplies tab link", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
assert html =~ "Printer Supplies"
|
|
end
|
|
|
|
test "renders printer supplies tab with table", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=printer_supplies")
|
|
|
|
# Check for table headers
|
|
assert html =~ "Type"
|
|
assert html =~ "Description"
|
|
assert html =~ "Level"
|
|
assert html =~ "Part Number"
|
|
|
|
# Check for supply data
|
|
assert html =~ "toner"
|
|
assert html =~ "drum"
|
|
assert html =~ "Black Toner Cartridge"
|
|
assert html =~ "Drum Unit"
|
|
assert html =~ "TN-450"
|
|
assert html =~ "DR-420"
|
|
end
|
|
|
|
test "displays supply level percentage bars", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=printer_supplies")
|
|
|
|
assert html =~ "45%"
|
|
assert html =~ "15%"
|
|
end
|
|
|
|
test "shows low supply warning for supplies below 20%", %{
|
|
conn: conn,
|
|
user: user,
|
|
device: device
|
|
} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=printer_supplies")
|
|
|
|
# Supply at 15% should have warning styling
|
|
assert html =~ "15%"
|
|
end
|
|
|
|
test "shows empty state when no printer supplies", %{
|
|
conn: conn,
|
|
user: user,
|
|
organization: organization
|
|
} do
|
|
alias Device, as: SnmpDevice
|
|
|
|
device_without_supplies =
|
|
Towerops.DevicesFixtures.device_fixture(%{
|
|
organization_id: organization.id,
|
|
name: "Device Without Supplies",
|
|
ip_address: "192.168.1.4"
|
|
})
|
|
|
|
%SnmpDevice{}
|
|
|> SnmpDevice.changeset(%{
|
|
device_id: device_without_supplies.id,
|
|
sys_name: "device",
|
|
sys_descr: "Device"
|
|
})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device_without_supplies.id}?tab=printer_supplies")
|
|
|
|
assert html =~ "No printer supplies found"
|
|
end
|
|
|
|
test "switches to printer supplies tab via patch", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
html = view |> element("a", "Printer Supplies") |> render_click()
|
|
|
|
assert html =~ "Black Toner Cartridge"
|
|
assert html =~ "45%"
|
|
end
|
|
end
|
|
|
|
describe "hardware inventory tab" do
|
|
setup %{device: device} do
|
|
alias Device, as: SnmpDevice
|
|
alias Towerops.Snmp.EntityPhysical
|
|
|
|
snmp_device =
|
|
%SnmpDevice{}
|
|
|> SnmpDevice.changeset(%{device_id: device.id, sys_name: "switch", sys_descr: "Switch"})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
# Create chassis (root level)
|
|
chassis =
|
|
%EntityPhysical{}
|
|
|> EntityPhysical.changeset(%{
|
|
snmp_device_id: snmp_device.id,
|
|
entity_index: "1",
|
|
entity_class: "chassis",
|
|
name: "Chassis 1",
|
|
description: "Main Chassis",
|
|
serial_number: "SN123456",
|
|
model: "CCR1009",
|
|
manufacturer: "MikroTik"
|
|
})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
# Create power supply (child of chassis)
|
|
power_supply =
|
|
%EntityPhysical{}
|
|
|> EntityPhysical.changeset(%{
|
|
snmp_device_id: snmp_device.id,
|
|
entity_index: "2",
|
|
parent_index: "1",
|
|
parent_id: chassis.id,
|
|
entity_class: "powerSupply",
|
|
name: "Power Supply 1",
|
|
description: "Main Power Supply",
|
|
serial_number: "PS789012"
|
|
})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
# Create module (child of chassis)
|
|
module =
|
|
%EntityPhysical{}
|
|
|> EntityPhysical.changeset(%{
|
|
snmp_device_id: snmp_device.id,
|
|
entity_index: "3",
|
|
parent_index: "1",
|
|
parent_id: chassis.id,
|
|
entity_class: "module",
|
|
name: "SFP Module 1",
|
|
description: "10G SFP+ Module"
|
|
})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
%{snmp_device: snmp_device, chassis: chassis, power_supply: power_supply, module: module}
|
|
end
|
|
|
|
test "displays hardware inventory tab link", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
assert html =~ "Hardware"
|
|
end
|
|
|
|
test "renders hardware inventory tab with table", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=hardware")
|
|
|
|
# Check for table headers
|
|
assert html =~ "Class"
|
|
assert html =~ "Name"
|
|
assert html =~ "Description"
|
|
assert html =~ "Serial Number"
|
|
assert html =~ "Model"
|
|
|
|
# Check for entity data
|
|
assert html =~ "chassis"
|
|
assert html =~ "powerSupply"
|
|
assert html =~ "module"
|
|
assert html =~ "Chassis 1"
|
|
assert html =~ "Power Supply 1"
|
|
assert html =~ "SN123456"
|
|
assert html =~ "CCR1009"
|
|
assert html =~ "MikroTik"
|
|
end
|
|
|
|
test "displays hierarchical structure with indentation", %{
|
|
conn: conn,
|
|
user: user,
|
|
device: device
|
|
} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}?tab=hardware")
|
|
|
|
# Power supply and module should appear after chassis
|
|
assert html =~ "Main Chassis"
|
|
assert html =~ "Main Power Supply"
|
|
assert html =~ "SFP Module 1"
|
|
end
|
|
|
|
test "shows empty state when no hardware inventory", %{
|
|
conn: conn,
|
|
user: user,
|
|
organization: organization
|
|
} do
|
|
alias Device, as: SnmpDevice
|
|
|
|
device_without_hardware =
|
|
Towerops.DevicesFixtures.device_fixture(%{
|
|
organization_id: organization.id,
|
|
name: "Device Without Hardware",
|
|
ip_address: "192.168.1.5"
|
|
})
|
|
|
|
%SnmpDevice{}
|
|
|> SnmpDevice.changeset(%{
|
|
device_id: device_without_hardware.id,
|
|
sys_name: "device",
|
|
sys_descr: "Device"
|
|
})
|
|
|> Towerops.Repo.insert!()
|
|
|
|
conn = log_in_user(conn, user)
|
|
{:ok, _view, html} = live(conn, ~p"/devices/#{device_without_hardware.id}?tab=hardware")
|
|
|
|
assert html =~ "No hardware inventory found"
|
|
end
|
|
|
|
test "switches to hardware tab via patch", %{conn: conn, user: user, device: device} do
|
|
conn = log_in_user(conn, user)
|
|
{:ok, view, _html} = live(conn, ~p"/devices/#{device.id}?tab=overview")
|
|
|
|
html = view |> element("a", "Hardware") |> render_click()
|
|
|
|
assert html =~ "Class"
|
|
assert html =~ "Chassis 1"
|
|
end
|
|
end
|
|
end
|