defmodule ToweropsWeb.GraphLive.ShowTest do use ToweropsWeb.ConnCase import Phoenix.LiveViewTest alias Towerops.Monitoring alias Towerops.Organizations alias Towerops.Repo alias Towerops.Sites alias Towerops.Snmp alias Towerops.Snmp.Device alias Towerops.Snmp.Interface alias Towerops.Snmp.Sensor setup :register_and_log_in_user setup %{user: user} do {:ok, organization} = Organizations.create_organization(%{name: "Test Org"}, user.id) {:ok, site} = Sites.create_site(%{ name: "Test Site", organization_id: organization.id }) {:ok, device} = Towerops.Devices.create_device(%{ name: "Test Router", ip_address: "192.168.1.1", site_id: site.id, organization_id: organization.id, snmp_enabled: true }) # Create SNMP device for sensor tests snmp_device = %Device{} |> Device.changeset(%{ device_id: device.id, sys_name: "test-router", sys_descr: "Test Router", sys_object_id: "1.3.6.1.4.1.9" }) |> Repo.insert!() %{organization: organization, site: site, device: device, snmp_device: snmp_device} end describe "Latency graph" do test "renders latency graph page with correct content", %{ conn: conn, device: device, organization: _org } do # Create monitoring checks Monitoring.create_check(%{ device_id: device.id, status: :success, response_time_ms: 10.5, checked_at: DateTime.utc_now() }) {:ok, _view, html} = live(conn, ~p"/devices/#{device.id}/graph/latency") # Verify page renders with correct content assert html =~ "ICMP Latency" assert html =~ device.name assert html =~ "24 Hours" end test "uses current_organization in back link", %{ conn: conn, device: device, organization: _org } do {:ok, _view, html} = live(conn, ~p"/devices/#{device.id}/graph/latency") # Verify back link uses organization slug (this was the bug) assert html =~ ~s[/devices/#{device.id}] end test "displays empty state when no latency data", %{ conn: conn, device: device, organization: _org } do {:ok, _view, html} = live(conn, ~p"/devices/#{device.id}/graph/latency") assert html =~ "No sensor data available" end end describe "Processor graph" do test "renders processor graph page", %{ conn: conn, device: device, organization: _org, snmp_device: snmp_device } do # Create a CPU sensor sensor = %Sensor{} |> Sensor.changeset(%{ snmp_device_id: snmp_device.id, sensor_type: "cpu_load", sensor_index: "1", sensor_oid: "1.3.6.1.4.1.9.9.109.1.1.1.1.8.1", sensor_descr: "CPU 1", sensor_unit: "%" }) |> Repo.insert!() # Create sensor reading Snmp.create_sensor_reading(%{ sensor_id: sensor.id, value: 45.2, checked_at: DateTime.utc_now() }) {:ok, _view, html} = live(conn, ~p"/devices/#{device.id}/graph/processors") assert html =~ "Processor Usage" assert html =~ device.name end end describe "Memory graph" do test "renders memory graph page", %{ conn: conn, device: device, organization: _org, snmp_device: snmp_device } do # Create a memory sensor sensor = %Sensor{} |> Sensor.changeset(%{ snmp_device_id: snmp_device.id, sensor_type: "memory_usage", sensor_index: "1", sensor_oid: "1.3.6.1.4.1.9.9.109.1.1.1.1.12.1", sensor_descr: "Memory", sensor_unit: "%" }) |> Repo.insert!() # Create sensor reading Snmp.create_sensor_reading(%{ sensor_id: sensor.id, value: 75.5, checked_at: DateTime.utc_now() }) {:ok, _view, html} = live(conn, ~p"/devices/#{device.id}/graph/memory") assert html =~ "Memory Usage" assert html =~ device.name end end describe "Traffic graph" do test "renders overall traffic graph page", %{ conn: conn, device: device, organization: _org, snmp_device: snmp_device } do # Create an interface interface = %Interface{} |> Interface.changeset(%{ snmp_device_id: snmp_device.id, if_index: 1, if_descr: "eth0", if_name: "eth0", if_oper_status: "up", if_speed: 1_000_000_000 }) |> Repo.insert!() # Create interface stats Snmp.create_interface_stat(%{ interface_id: interface.id, if_in_octets: 1000, if_out_octets: 2000, checked_at: DateTime.utc_now() }) {:ok, _view, html} = live(conn, ~p"/devices/#{device.id}/graph/traffic") assert html =~ "Overall Traffic" assert html =~ device.name end test "renders specific interface traffic graph", %{ conn: conn, device: device, organization: _org, snmp_device: snmp_device } do # Create an interface interface = %Interface{} |> Interface.changeset(%{ snmp_device_id: snmp_device.id, if_index: 1, if_descr: "eth0", if_name: "eth0", if_oper_status: "up", if_speed: 1_000_000_000 }) |> Repo.insert!() # Create interface stats Snmp.create_interface_stat(%{ interface_id: interface.id, if_in_octets: 1000, if_out_octets: 2000, checked_at: DateTime.utc_now() }) {:ok, _view, html} = live( conn, ~p"/devices/#{device.id}/graph/traffic?interface_id=#{interface.id}" ) assert html =~ "Overall Traffic" assert html =~ device.name end test "handles nil if_in_octets gracefully", %{ conn: conn, device: device, organization: _org, snmp_device: snmp_device } do # Create an interface interface = %Interface{} |> Interface.changeset(%{ snmp_device_id: snmp_device.id, if_index: 1, if_descr: "eth0", if_name: "eth0", if_oper_status: "up", if_speed: 1_000_000_000 }) |> Repo.insert!() # Create interface stats with nil if_in_octets base_time = DateTime.utc_now() Snmp.create_interface_stat(%{ interface_id: interface.id, if_in_octets: nil, if_out_octets: 1000, checked_at: DateTime.add(base_time, -60, :second) }) Snmp.create_interface_stat(%{ interface_id: interface.id, if_in_octets: nil, if_out_octets: 2000, checked_at: base_time }) # Should not crash {:ok, _view, html} = live(conn, ~p"/devices/#{device.id}/graph/traffic") assert html =~ "Overall Traffic" assert html =~ device.name end test "handles nil if_out_octets gracefully", %{ conn: conn, device: device, organization: _org, snmp_device: snmp_device } do # Create an interface interface = %Interface{} |> Interface.changeset(%{ snmp_device_id: snmp_device.id, if_index: 1, if_descr: "eth0", if_name: "eth0", if_oper_status: "up", if_speed: 1_000_000_000 }) |> Repo.insert!() # Create interface stats with nil if_out_octets base_time = DateTime.utc_now() Snmp.create_interface_stat(%{ interface_id: interface.id, if_in_octets: 1000, if_out_octets: nil, checked_at: DateTime.add(base_time, -60, :second) }) Snmp.create_interface_stat(%{ interface_id: interface.id, if_in_octets: 2000, if_out_octets: nil, checked_at: base_time }) # Should not crash {:ok, _view, html} = live(conn, ~p"/devices/#{device.id}/graph/traffic") assert html =~ "Overall Traffic" assert html =~ device.name end test "handles mixed nil and valid octets values", %{ conn: conn, device: device, organization: _org, snmp_device: snmp_device } do # Create an interface interface = %Interface{} |> Interface.changeset(%{ snmp_device_id: snmp_device.id, if_index: 1, if_descr: "eth0", if_name: "eth0", if_oper_status: "up", if_speed: 1_000_000_000 }) |> Repo.insert!() # Create interface stats with mixed nil and valid values base_time = DateTime.utc_now() # First stat: nil values Snmp.create_interface_stat(%{ interface_id: interface.id, if_in_octets: nil, if_out_octets: nil, checked_at: DateTime.add(base_time, -120, :second) }) # Second stat: valid values Snmp.create_interface_stat(%{ interface_id: interface.id, if_in_octets: 1000, if_out_octets: 2000, checked_at: DateTime.add(base_time, -60, :second) }) # Third stat: valid values Snmp.create_interface_stat(%{ interface_id: interface.id, if_in_octets: 2000, if_out_octets: 4000, checked_at: base_time }) # Should not crash and should render valid data points {:ok, _view, html} = live(conn, ~p"/devices/#{device.id}/graph/traffic") assert html =~ "Overall Traffic" assert html =~ device.name end end describe "Time range selection" do test "defaults to 24h range", %{conn: conn, device: device, organization: _org} do {:ok, _view, html} = live(conn, ~p"/devices/#{device.id}/graph/latency") assert html =~ "24 Hours" end test "accepts custom range parameter", %{conn: conn, device: device, organization: _org} do {:ok, _view, html} = live(conn, ~p"/devices/#{device.id}/graph/latency?range=1h") assert html =~ "1 Hour" end test "changes range when user clicks range button", %{ conn: conn, device: device, organization: _org } do {:ok, view, html} = live(conn, ~p"/devices/#{device.id}/graph/latency") # Initially 24h assert html =~ "24 Hours" # Change to 1h html = view |> element("button", "1 Hour") |> render_click() assert html =~ "1 Hour" end test "preserves interface_id when changing range", %{ conn: conn, device: device, organization: _org, snmp_device: snmp_device } do # Create an interface interface = %Interface{} |> Interface.changeset(%{ snmp_device_id: snmp_device.id, if_index: 1, if_descr: "eth0", if_name: "eth0", if_oper_status: "up", if_speed: 1_000_000_000 }) |> Repo.insert!() {:ok, view, _html} = live( conn, ~p"/devices/#{device.id}/graph/traffic?interface_id=#{interface.id}" ) # Change range and verify page still works html = view |> element("button", "1 Hour") |> render_click() assert html =~ "1 Hour" assert html =~ "Overall Traffic" end end describe "Page rendering" do test "renders latency page with organization context", %{ conn: conn, device: device, organization: _org } do {:ok, _view, html} = live(conn, ~p"/devices/#{device.id}/graph/latency") # Verify page renders with expected content assert html =~ "ICMP Latency" assert html =~ device.name end test "renders with all required page elements", %{ conn: conn, device: device, organization: _org } do {:ok, _view, html} = live(conn, ~p"/devices/#{device.id}/graph/latency") # Verify key elements are present assert html =~ "ICMP Latency" assert html =~ device.name assert html =~ "24 Hours" end end end