towerops/test/towerops_web/live/dashboard_live_test.exs
Graham McIntie c790794191 Fix tests, credo issues, and Gaiia sync bugs
- Fix CLOAK_KEY placeholder in nix shell (invalid base64)
- Fix error HTML test assertion to match actual template
- Fix Gaiia sync: read 'subnet' field instead of 'block' for IP blocks
- Fix Gaiia sync: extract nested status name from subscription objects
- Fix security headers tests for environment detection
- Fix mobile auth tests to use raw_token
- Fix LiveView test assertions to match actual template content
- Fix SNMP config test expectations for test environment
- Refactor: resolve all Credo complexity/nesting issues
  - Extract helper functions in NetBox sync, VISP sync, Sonar sync
  - Flatten nested conditionals in settings, integrations, alerts
  - Use with/validate_present pattern for connection testing
2026-02-15 11:55:49 -06:00

405 lines
11 KiB
Elixir

defmodule ToweropsWeb.DashboardLiveTest do
use ToweropsWeb.ConnCase
import Phoenix.LiveViewTest
alias Towerops.Preseem.Insights
setup :register_and_log_in_user
setup %{conn: conn, user: user} do
{:ok, organization} =
Towerops.Organizations.create_organization(%{name: "Test Org", use_sites: true}, user.id)
{:ok, site} =
Towerops.Sites.create_site(%{
name: "Test Site",
organization_id: organization.id
})
conn = Plug.Conn.put_session(conn, :current_organization_id, organization.id)
%{conn: conn, organization: organization, site: site}
end
describe "empty state" do
test "shows onboarding when no devices exist", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/dashboard")
assert html =~ "Let's get started!"
assert html =~ "Create Your First Site"
end
test "requires authentication" do
conn = build_conn()
{:error, redirect} = live(conn, ~p"/dashboard")
assert {:redirect, %{to: path}} = redirect
assert path == ~p"/users/log-in"
end
end
describe "health overview" do
test "displays health score", %{conn: conn, organization: organization, site: site} do
{:ok, device} = create_device(organization, site)
Towerops.Devices.update_device_status(device, :up)
{:ok, _view, html} = live(conn, ~p"/dashboard")
assert html =~ "Health"
assert html =~ "100"
end
test "displays device counts with up/down/unknown", %{
conn: conn,
organization: organization,
site: site
} do
{:ok, d1} = create_device(organization, site, "Router 1", "192.168.1.1")
{:ok, d2} = create_device(organization, site, "Router 2", "192.168.1.2")
Towerops.Devices.update_device_status(d1, :up)
Towerops.Devices.update_device_status(d2, :down)
{:ok, _view, html} = live(conn, ~p"/dashboard")
assert html =~ "Devices"
assert html =~ "Up"
assert html =~ "Down"
end
test "displays active alert count", %{conn: conn, organization: organization, site: site} do
{:ok, device} = create_device(organization, site)
{:ok, _alert} =
Towerops.Alerts.create_alert(%{
device_id: device.id,
alert_type: :device_down,
triggered_at: DateTime.utc_now(),
message: "Device is down"
})
{:ok, _view, html} = live(conn, ~p"/dashboard")
assert html =~ "Active Issues"
assert html =~ "1"
end
test "displays insight counts by urgency", %{
conn: conn,
organization: organization,
site: site
} do
{:ok, device} = create_device(organization, site)
{:ok, _} =
Insights.insert_insight_if_new(%{
organization_id: organization.id,
device_id: device.id,
type: "device_poll_gap",
source: "snmp",
urgency: "warning",
channel: "proactive",
title: "Device not polled"
})
{:ok, _view, html} = live(conn, ~p"/dashboard")
assert html =~ "Insights"
end
test "hides subscriber section when no Gaiia integration", %{
conn: conn,
organization: organization,
site: site
} do
{:ok, _device} = create_device(organization, site)
{:ok, _view, html} = live(conn, ~p"/dashboard")
refute html =~ "Subscribers"
refute html =~ "MRR"
end
test "shows subscriber section when Gaiia data exists", %{
conn: conn,
organization: organization,
site: site
} do
{:ok, _device} = create_device(organization, site)
{:ok, _} =
Towerops.Gaiia.upsert_network_site(organization.id, %{
gaiia_id: "ns-dash",
name: "Tower",
account_count: 100,
total_mrr: Decimal.new("5000.00")
})
{:ok, _view, html} = live(conn, ~p"/dashboard")
assert html =~ "Subs"
assert html =~ "100"
assert html =~ "5,000"
end
end
describe "alert feed" do
test "shows alerts with device links", %{conn: conn, organization: organization, site: site} do
{:ok, device} = create_device(organization, site, "Main Router", "10.0.0.1")
Towerops.Devices.update_device_status(device, :down)
{:ok, _alert} =
Towerops.Alerts.create_alert(%{
device_id: device.id,
alert_type: :device_down,
triggered_at: DateTime.utc_now(),
message: "Device is unreachable"
})
{:ok, _view, html} = live(conn, ~p"/dashboard")
assert html =~ "Main Router"
assert html =~ "Device is unreachable"
assert html =~ ~p"/devices/#{device.id}"
end
test "shows View All link when alerts exist", %{
conn: conn,
organization: organization,
site: site
} do
{:ok, device} = create_device(organization, site)
{:ok, _alert} =
Towerops.Alerts.create_alert(%{
device_id: device.id,
alert_type: :device_down,
triggered_at: DateTime.utc_now(),
message: "Device is down"
})
{:ok, _view, html} = live(conn, ~p"/dashboard")
# Alert feed appears when alerts exist
assert html =~ "Active Issues"
end
test "shows acknowledge button for unacknowledged alerts", %{
conn: conn,
organization: organization,
site: site
} do
{:ok, device} = create_device(organization, site)
{:ok, _alert} =
Towerops.Alerts.create_alert(%{
device_id: device.id,
alert_type: :device_down,
triggered_at: DateTime.utc_now(),
message: "Device is down"
})
{:ok, _view, html} = live(conn, ~p"/dashboard")
assert html =~ "ACK"
end
end
describe "insight feed" do
test "shows insights from multiple sources", %{
conn: conn,
organization: organization,
site: site
} do
{:ok, device} = create_device(organization, site)
{:ok, _} =
Insights.insert_insight_if_new(%{
organization_id: organization.id,
device_id: device.id,
type: "device_poll_gap",
source: "snmp",
urgency: "warning",
channel: "proactive",
title: "Device not polled in 24h"
})
{:ok, _view, html} = live(conn, ~p"/dashboard")
assert html =~ "Device not polled in 24h"
assert html =~ "snmp"
end
test "dismiss insight removes it from feed", %{
conn: conn,
organization: organization,
site: site
} do
{:ok, device} = create_device(organization, site)
{:ok, insight} =
Insights.insert_insight_if_new(%{
organization_id: organization.id,
device_id: device.id,
type: "device_poll_gap",
source: "snmp",
urgency: "warning",
channel: "proactive",
title: "Poll gap detected"
})
{:ok, view, html} = live(conn, ~p"/dashboard")
assert html =~ "Poll gap detected"
view |> element("button[phx-value-id='#{insight.id}']") |> render_click()
html = render(view)
refute html =~ "Poll gap detected"
end
test "source filter filters insights via URL", %{
conn: conn,
organization: organization,
site: site
} do
{:ok, device} = create_device(organization, site)
{:ok, _} =
Insights.insert_insight_if_new(%{
organization_id: organization.id,
device_id: device.id,
type: "device_poll_gap",
source: "snmp",
urgency: "warning",
channel: "proactive",
title: "SNMP insight"
})
{:ok, _} =
Insights.insert_insight_if_new(%{
organization_id: organization.id,
device_id: device.id,
type: "reconciliation_finding",
source: "gaiia",
urgency: "info",
channel: "proactive",
title: "Gaiia insight",
dedup_key: "test_dedup",
metadata: %{"dedup_key" => "test_dedup"}
})
{:ok, view, _html} = live(conn, ~p"/dashboard?insight_source=snmp")
html = render(view)
assert html =~ "SNMP insight"
refute html =~ "Gaiia insight"
end
end
describe "site health grid" do
test "shows site cards with device counts", %{
conn: conn,
organization: organization,
site: site
} do
{:ok, device} = create_device(organization, site)
Towerops.Devices.update_device_status(device, :up)
{:ok, _view, html} = live(conn, ~p"/dashboard")
assert html =~ "Test Site"
assert html =~ ~p"/sites/#{site.id}"
end
test "shows subscriber data on site cards when Gaiia mapped", %{
conn: conn,
organization: organization,
site: site
} do
{:ok, device} = create_device(organization, site)
Towerops.Devices.update_device_status(device, :up)
{:ok, _} =
Towerops.Gaiia.upsert_network_site(organization.id, %{
gaiia_id: "ns-site",
name: "Tower",
account_count: 42,
total_mrr: Decimal.new("2100.00"),
site_id: site.id
})
{:ok, _view, html} = live(conn, ~p"/dashboard")
assert html =~ "42"
end
end
describe "real-time updates" do
test "PubSub new alert event updates dashboard", %{
conn: conn,
organization: organization,
site: site
} do
{:ok, device} = create_device(organization, site)
{:ok, view, _html} = live(conn, ~p"/dashboard")
# Create alert and broadcast
{:ok, _alert} =
Towerops.Alerts.create_alert(%{
device_id: device.id,
alert_type: :device_down,
triggered_at: DateTime.utc_now(),
message: "Device went down"
})
Phoenix.PubSub.broadcast(
Towerops.PubSub,
"alerts:org:#{organization.id}:new",
{:new_alert, device.id, :device_down}
)
html = render(view)
assert html =~ "Device went down"
end
test "PubSub alert resolved event updates dashboard", %{
conn: conn,
organization: organization,
site: site
} do
{:ok, device} = create_device(organization, site)
{:ok, alert} =
Towerops.Alerts.create_alert(%{
device_id: device.id,
alert_type: :device_down,
triggered_at: DateTime.utc_now(),
message: "Device went down"
})
{:ok, view, _html} = live(conn, ~p"/dashboard")
# Resolve the alert
Towerops.Alerts.resolve_alert(alert)
Phoenix.PubSub.broadcast(
Towerops.PubSub,
"alerts:org:#{organization.id}:resolved",
{:alert_resolved, device.id, :device_down}
)
html = render(view)
refute html =~ "Device went down"
end
end
defp create_device(organization, site, name \\ "Test Device", ip \\ "192.168.1.1") do
Towerops.Devices.create_device(%{
name: name,
ip_address: ip,
site_id: site.id,
organization_id: organization.id
})
end
end