Two critical production bugs fixed: 1. **Alert type enum → string conversion** - Changed Alert.alert_type from Ecto.Enum to :string for flexibility - Updated all queries to use "device_down"/"device_up" strings instead of atoms - Fixed pattern matching in alerts.ex and device_monitor_worker.ex - Updated 44+ test files to use string literals 2. **SQL array indexing syntax error in activity feed** - Fixed PostgreSQL syntax: `array_agg(...)[1]` → `(array_agg(...))[1]` - Prevents "syntax error at or near [" in activity feed queries 3. **Added comprehensive timer cleanup tests** - Tests for mobile_qr_live.ex timer cleanup on terminate - Tests for agent_live index timer cleanup - Verifies memory leak fixes from previous commits Files changed: - lib/towerops/alerts.ex - lib/towerops/alerts/alert.ex - lib/towerops/activity_feed.ex - lib/towerops/workers/device_monitor_worker.ex - test/**/*_test.exs (44+ files with alert_type references) - test/towerops_web/live/mobile_qr_live_test.exs - test/towerops_web/live/agent_live_test.exs - test/towerops/workers/device_poller_worker_test.exs All tests passing except 1 unrelated brute force protection test.
165 lines
5.2 KiB
Elixir
165 lines
5.2 KiB
Elixir
defmodule Towerops.DashboardTest do
|
|
use Towerops.DataCase, async: true
|
|
|
|
import Towerops.AccountsFixtures
|
|
import Towerops.DevicesFixtures
|
|
import Towerops.OrganizationsFixtures
|
|
|
|
alias Towerops.Dashboard
|
|
alias Towerops.Integrations
|
|
|
|
setup do
|
|
user = user_fixture()
|
|
org = organization_fixture(user.id)
|
|
|
|
%{org: org, user: user}
|
|
end
|
|
|
|
describe "get_dashboard_summary/1" do
|
|
test "returns correct structure with empty org", %{org: org} do
|
|
summary = Dashboard.get_dashboard_summary(org.id)
|
|
|
|
assert is_map(summary)
|
|
assert Map.has_key?(summary, :health_score)
|
|
assert Map.has_key?(summary, :devices)
|
|
assert Map.has_key?(summary, :alerts)
|
|
assert Map.has_key?(summary, :subscribers)
|
|
assert Map.has_key?(summary, :insights)
|
|
end
|
|
|
|
test "returns device counts", %{org: org} do
|
|
device = device_fixture(%{organization_id: org.id})
|
|
Towerops.Devices.update_device_status(device, :up)
|
|
|
|
summary = Dashboard.get_dashboard_summary(org.id)
|
|
|
|
assert summary.devices.total == 1
|
|
assert summary.devices.up == 1
|
|
assert summary.devices.down == 0
|
|
end
|
|
|
|
test "returns alert count", %{org: org} do
|
|
device = device_fixture(%{organization_id: org.id})
|
|
|
|
{:ok, _alert} =
|
|
Towerops.Alerts.create_alert(%{
|
|
device_id: device.id,
|
|
alert_type: "device_down",
|
|
triggered_at: DateTime.utc_now(),
|
|
message: "Device is down"
|
|
})
|
|
|
|
summary = Dashboard.get_dashboard_summary(org.id)
|
|
assert summary.alerts.active_count == 1
|
|
end
|
|
|
|
test "returns subscriber totals from billing integrations", %{org: org} do
|
|
{:ok, integration} =
|
|
Integrations.create_integration(org.id, %{provider: "gaiia", enabled: true})
|
|
|
|
{:ok, _} = Integrations.update_billing_totals(integration, 100, Decimal.new("5000.00"))
|
|
|
|
summary = Dashboard.get_dashboard_summary(org.id)
|
|
assert summary.subscribers.total == 100
|
|
assert Decimal.equal?(summary.subscribers.total_mrr, Decimal.new("5000.00"))
|
|
end
|
|
|
|
test "returns zero subscribers when no billing integrations", %{org: org} do
|
|
summary = Dashboard.get_dashboard_summary(org.id)
|
|
assert summary.subscribers.total == 0
|
|
end
|
|
|
|
test "returns insight counts by urgency", %{org: org} do
|
|
summary = Dashboard.get_dashboard_summary(org.id)
|
|
assert summary.insights.critical == 0
|
|
assert summary.insights.warning == 0
|
|
assert summary.insights.info == 0
|
|
end
|
|
end
|
|
|
|
describe "compute_health_score/1" do
|
|
test "returns 100 when all devices up, no alerts", %{org: org} do
|
|
device = device_fixture(%{organization_id: org.id})
|
|
Towerops.Devices.update_device_status(device, :up)
|
|
|
|
score = Dashboard.compute_health_score(org.id)
|
|
assert score == 100
|
|
end
|
|
|
|
test "returns lower score when devices down", %{org: org} do
|
|
d1 = device_fixture(%{organization_id: org.id})
|
|
d2 = device_fixture(%{organization_id: org.id})
|
|
Towerops.Devices.update_device_status(d1, :up)
|
|
Towerops.Devices.update_device_status(d2, :down)
|
|
|
|
score = Dashboard.compute_health_score(org.id)
|
|
assert score < 100
|
|
assert score > 0
|
|
end
|
|
|
|
test "returns 100 when no devices exist", %{org: org} do
|
|
score = Dashboard.compute_health_score(org.id)
|
|
assert score == 100
|
|
end
|
|
|
|
test "penalizes for critical alerts", %{org: org} do
|
|
device = device_fixture(%{organization_id: org.id})
|
|
Towerops.Devices.update_device_status(device, :up)
|
|
|
|
{:ok, _} =
|
|
Towerops.Alerts.create_alert(%{
|
|
device_id: device.id,
|
|
alert_type: "device_down",
|
|
triggered_at: DateTime.utc_now(),
|
|
message: "Device down"
|
|
})
|
|
|
|
score = Dashboard.compute_health_score(org.id)
|
|
assert score < 100
|
|
end
|
|
end
|
|
|
|
describe "get_org_subscriber_summary/1" do
|
|
test "returns totals from billing integrations", %{org: org} do
|
|
{:ok, i1} =
|
|
Integrations.create_integration(org.id, %{provider: "gaiia", enabled: true})
|
|
|
|
{:ok, _} = Integrations.update_billing_totals(i1, 50, Decimal.new("2500.00"))
|
|
|
|
{:ok, i2} =
|
|
Integrations.create_integration(org.id, %{provider: "splynx", enabled: true})
|
|
|
|
{:ok, _} = Integrations.update_billing_totals(i2, 30, Decimal.new("1500.00"))
|
|
|
|
result = Dashboard.get_org_subscriber_summary(org.id)
|
|
assert result.total == 80
|
|
assert Decimal.equal?(result.total_mrr, Decimal.new("4000.00"))
|
|
end
|
|
|
|
test "returns zeros when no billing integrations", %{org: org} do
|
|
result = Dashboard.get_org_subscriber_summary(org.id)
|
|
assert result.total == 0
|
|
assert Decimal.equal?(result.total_mrr, Decimal.new(0))
|
|
end
|
|
end
|
|
|
|
describe "list_site_summaries/1" do
|
|
test "returns enriched site data", %{org: org} do
|
|
{:ok, site} =
|
|
Towerops.Sites.create_site(%{
|
|
name: "Test Tower",
|
|
organization_id: org.id
|
|
})
|
|
|
|
device = device_fixture(%{organization_id: org.id, site_id: site.id})
|
|
Towerops.Devices.update_device_status(device, :up)
|
|
|
|
summaries = Dashboard.list_site_summaries(org.id)
|
|
assert summaries != []
|
|
|
|
summary = Enum.find(summaries, &(&1.site_id == site.id))
|
|
assert summary.name == "Test Tower"
|
|
assert summary.device_count >= 1
|
|
end
|
|
end
|
|
end
|