towerops/test/towerops_web/live/alert_live_test.exs
Graham McIntire 20f5f48372
feat: command center dashboard with unified insights and contextual enrichment
Transform the dashboard into a single-pane-of-glass command center that
blends operational metrics with business context from Gaiia subscriber data.

- Extend insight schema with multi-source support (snmp, gaiia, system)
- Add Oban workers for automated insight generation (device health, system, gaiia)
- New Dashboard context with health score computation and site summaries
- Rewrite dashboard LiveView with health overview, alert/insight feeds, site grid
- Add source filter pills for insights with URL state management
- Add metrics bar to site detail pages (device count, alerts, subscribers, MRR)
- Add subscriber/MRR context to alert list site links
- Add subscriber badges to device list site headers
- Real-time PubSub updates for alerts on dashboard
2026-02-13 14:52:49 -06:00

225 lines
6.1 KiB
Elixir

defmodule ToweropsWeb.AlertLive.IndexTest do
use ToweropsWeb.ConnCase
import Phoenix.LiveViewTest
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
})
{:ok, device} =
Towerops.Devices.create_device(%{
name: "Test Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id
})
%{organization: organization, site: site, device: device}
end
describe "Index" do
test "displays alerts page", %{conn: conn, organization: _organization} do
{:ok, _view, html} = live(conn, ~p"/alerts")
assert html =~ "Alerts"
assert html =~ "Monitor and acknowledge system alerts"
end
test "displays empty state when no alerts", %{conn: conn, organization: _organization} do
{:ok, _view, html} = live(conn, ~p"/alerts")
assert html =~ "No alerts"
end
test "lists all alerts", %{conn: conn, organization: _organization, device: device} do
{: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"/alerts")
assert html =~ "Test Router"
assert html =~ "Device is down"
assert html =~ "Device Down"
end
test "filters active alerts", %{conn: conn, organization: _organization, device: device} do
# Create an active equipment_down alert
{:ok, _active_alert} =
Towerops.Alerts.create_alert(%{
device_id: device.id,
alert_type: :device_down,
triggered_at: DateTime.utc_now(),
message: "Device is down"
})
# Create a recovery alert (device_up)
{:ok, _recovery_alert} =
Towerops.Alerts.create_alert(%{
device_id: device.id,
alert_type: :device_up,
triggered_at: DateTime.utc_now(),
message: "Device recovered"
})
{:ok, _view, html} =
live(conn, ~p"/alerts?filter=active")
# Should show equipment_down alert
assert html =~ "Device is down"
# Should NOT show equipment_up alert in active filter
refute html =~ "Device recovered"
end
test "acknowledges an alert", %{conn: conn, organization: _organization, device: device} do
{: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"/alerts")
html =
view
|> element("button", "Acknowledge")
|> render_click(%{"id" => alert.id})
assert html =~ "Alert acknowledged"
end
test "does not show acknowledge button for device_up alerts", %{
conn: conn,
organization: _organization,
device: device
} do
{:ok, _alert} =
Towerops.Alerts.create_alert(%{
device_id: device.id,
alert_type: :device_up,
triggered_at: DateTime.utc_now(),
message: "Device recovered"
})
{:ok, _view, html} = live(conn, ~p"/alerts")
# Should not show acknowledge button for device_up alerts
refute html =~ "Acknowledge"
end
test "updates in real-time when new alert is created", %{
conn: conn,
organization: organization,
device: device
} do
{:ok, view, _html} = live(conn, ~p"/alerts")
# Broadcast new alert event on org-scoped topic
Phoenix.PubSub.broadcast(
Towerops.PubSub,
"alerts:org:#{organization.id}:new",
{:new_alert, device.id, :device_down}
)
# View should update
assert render(view)
end
test "updates in real-time when alert is resolved", %{
conn: conn,
organization: organization,
device: device
} do
{: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"/alerts")
# Resolve the alert and broadcast
Towerops.Alerts.resolve_alert(alert)
Phoenix.PubSub.broadcast(
Towerops.PubSub,
"alerts:org:#{organization.id}:resolved",
{:alert_resolved, device.id, :device_down}
)
# View should update
assert render(view)
end
test "requires authentication", %{organization: _organization} do
conn = build_conn()
{:error, redirect} = live(conn, ~p"/alerts")
assert {:redirect, %{to: path}} = redirect
assert path == ~p"/users/log-in"
end
test "shows site subscriber context when Gaiia mapped", %{
conn: conn,
organization: organization,
site: site,
device: device
} do
{:ok, _alert} =
Towerops.Alerts.create_alert(%{
device_id: device.id,
alert_type: :device_down,
triggered_at: DateTime.utc_now(),
message: "Device is down"
})
{:ok, _} =
Towerops.Gaiia.upsert_network_site(organization.id, %{
gaiia_id: "ns-alert-test",
name: "Tower",
account_count: 75,
total_mrr: Decimal.new("3750.00"),
site_id: site.id
})
{:ok, _view, html} = live(conn, ~p"/alerts")
assert html =~ "75 subscribers"
assert html =~ "3,750"
end
test "hides site subscriber context when no Gaiia mapping", %{
conn: conn,
device: device
} do
{: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"/alerts")
refute html =~ "subscribers"
refute html =~ "MRR"
end
end
end