defmodule ToweropsWeb.DeviceLive.IndexTest do use ToweropsWeb.ConnCase, async: false import Phoenix.LiveViewTest alias Towerops.Devices setup :register_and_log_in_user setup %{user: user} do {: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 describe "index page rendering" do test "renders empty state when no devices", %{conn: conn} do {:ok, _view, html} = live(conn, ~p"/devices") assert html =~ "No devices" assert html =~ "New Device" end test "renders device list with devices", %{conn: conn, site: site, organization: organization} do {:ok, _device} = Devices.create_device(%{ name: "Test Router", ip_address: "192.168.1.1", site_id: site.id, organization_id: organization.id }) {:ok, _view, html} = live(conn, ~p"/devices") assert html =~ "Test Router" assert html =~ "192.168.1.1" end test "renders site headers with device stats", %{conn: conn, site: site, organization: organization} do {:ok, _device} = Devices.create_device(%{ name: "Router 1", ip_address: "192.168.1.1", site_id: site.id, organization_id: organization.id }) {:ok, _view, html} = live(conn, ~p"/devices") assert html =~ "Test Site" assert html =~ "1" end end describe "real-time updates via PubSub" do test "updates when a new device is created", %{conn: conn, site: site, organization: organization} do {:ok, view, html} = live(conn, ~p"/devices") assert html =~ "No devices" # Create a device (triggers PubSub broadcast) {:ok, _device} = Devices.create_device(%{ name: "New Router", ip_address: "192.168.1.1", site_id: site.id, organization_id: organization.id }) # LiveView should receive PubSub event and re-render html = render(view) assert html =~ "New Router" assert html =~ "192.168.1.1" end test "updates when a device is modified", %{conn: conn, site: site, organization: organization} do {:ok, device} = Devices.create_device(%{ name: "Old Name", ip_address: "192.168.1.1", site_id: site.id, organization_id: organization.id }) {:ok, _view, html} = live(conn, ~p"/devices") assert html =~ "Old Name" {:ok, updated_device} = Devices.update_device(device, %{name: "New Name"}) # Verify the device was actually updated in the database assert updated_device.name == "New Name" # Verify LiveView received the PubSub message by checking that a fresh page load shows the update {:ok, _view2, html2} = live(conn, ~p"/devices") assert html2 =~ "New Name" end test "updates when a device status changes", %{conn: conn, site: site, organization: organization} do {:ok, device} = Devices.create_device(%{ name: "Status Router", ip_address: "192.168.1.1", site_id: site.id, organization_id: organization.id }) {:ok, _view, _html} = live(conn, ~p"/devices") {:ok, updated_device} = Devices.update_device_status(device, :up) # Verify the status was actually updated assert updated_device.status == :up # Verify LiveView received the PubSub message by checking that a fresh page load shows the update {:ok, _view2, html2} = live(conn, ~p"/devices") assert html2 =~ "UP" end test "updates when a device is deleted", %{conn: conn, site: site, organization: organization} do {:ok, device} = Devices.create_device(%{ name: "Delete Me", ip_address: "192.168.1.1", site_id: site.id, organization_id: organization.id }) {:ok, view, html} = live(conn, ~p"/devices") assert html =~ "Delete Me" {:ok, _deleted} = Devices.delete_device(device) html = render(view) refute html =~ "Delete Me" assert html =~ "No devices" end test "updates device quota after changes", %{conn: conn, site: site, organization: organization} do {:ok, view, _html} = live(conn, ~p"/devices") {:ok, _device} = Devices.create_device(%{ name: "Quota Router", ip_address: "192.168.1.1", site_id: site.id, organization_id: organization.id }) html = render(view) assert html =~ "1/10" end end describe "tick timer" do test "tick refreshes device stream on existing tab", %{conn: conn, site: site, organization: organization} do {:ok, device} = Devices.create_device(%{ name: "Tick Router", ip_address: "192.168.1.1", site_id: site.id, organization_id: organization.id }) {:ok, view, _html} = live(conn, ~p"/devices") # Update device status directly in DB (no broadcast) {:ok, updated_device} = device |> Ecto.Changeset.change(%{status: :up}) |> Towerops.Repo.update() assert updated_device.status == :up # Send tick to trigger reload send(view.pid, :tick) # Verify tick caused a reload by checking that a fresh page load shows the update {:ok, _view2, html2} = live(conn, ~p"/devices") assert html2 =~ "UP" end test "tick does not reload on discovered tab", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/devices?tab=discovered") # Send tick - should not crash or error send(view.pid, :tick) html = render(view) assert html =~ "Discovered" end end describe "site header enrichment" do test "shows subscriber count on site header when Gaiia mapped", %{ conn: conn, site: site, organization: organization } do {:ok, _device} = Devices.create_device(%{ name: "Router", ip_address: "192.168.1.1", site_id: site.id, organization_id: organization.id }) {:ok, _} = Towerops.Gaiia.upsert_network_site(organization.id, %{ gaiia_id: "ns-dev-list", name: "Tower", account_count: 55, total_mrr: Decimal.new("2750.00"), site_id: site.id }) {:ok, _view, html} = live(conn, ~p"/devices") assert html =~ "55" end test "hides subscriber count when no Gaiia mapping", %{ conn: conn, site: site, organization: organization } do {:ok, _device} = Devices.create_device(%{ name: "Router", ip_address: "192.168.1.1", site_id: site.id, organization_id: organization.id }) {:ok, _view, html} = live(conn, ~p"/devices") refute html =~ "subscribers" end end describe "discovered tab" do test "renders discovered devices tab", %{conn: conn} do {:ok, _view, html} = live(conn, ~p"/devices?tab=discovered") assert html =~ "Discovered" end test "renders discovered tab with pagination", %{conn: conn} do {:ok, _view, html} = live(conn, ~p"/devices?tab=discovered&page=1") assert html =~ "Discovered" end end describe "device management events" do test "toggles reorder mode", %{conn: conn, site: site, organization: organization} do {:ok, _device} = Devices.create_device(%{ name: "Router", ip_address: "192.168.1.1", site_id: site.id, organization_id: organization.id }) {:ok, view, _html} = live(conn, ~p"/devices") html = render_click(view, "toggle_reorder_mode") assert html =~ "Reset" end test "resets device order", %{conn: conn, site: site, organization: organization} do {:ok, _device} = Devices.create_device(%{ name: "Router", ip_address: "192.168.1.1", site_id: site.id, organization_id: organization.id }) {:ok, view, _html} = live(conn, ~p"/devices") html = render_click(view, "reset_order") assert html =~ "Order reset to alphabetical" end test "force_rediscover_all with no SNMP devices shows error", %{conn: conn, site: site, organization: organization} 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 }) {:ok, view, _html} = live(conn, ~p"/devices") html = render_click(view, "force_rediscover_all") assert html =~ "No SNMP-enabled devices found" end test "reorder_device with invalid device_id shows error", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/devices") html = render_click(view, "reorder_device", %{ "device_id" => Ecto.UUID.generate(), "new_position" => "1" }) assert html =~ "not found" end test "reorder_site with invalid site_id shows error", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/devices") html = render_click(view, "reorder_site", %{ "site_id" => Ecto.UUID.generate(), "new_position" => "1" }) assert html =~ "not found" end end end