towerops/test/towerops/llm/network_snapshot_test.exs
2026-06-15 11:15:46 -05:00

137 lines
4 KiB
Elixir

defmodule Towerops.LLM.NetworkSnapshotTest do
use Towerops.DataCase, async: false
import Ecto.Query
import Towerops.AccountsFixtures
import Towerops.DevicesFixtures
import Towerops.OrganizationsFixtures
alias Towerops.LLM.NetworkSnapshot
alias Towerops.Preseem.AccessPoint
alias Towerops.Preseem.Insight
alias Towerops.Repo
setup do
user = user_fixture()
org = organization_fixture(user.id)
%{org: org}
end
describe "build/1 — empty org" do
test "returns a struct with zero counts and empty lists", %{org: org} do
snap = NetworkSnapshot.build(org.id)
assert snap.organization_id == org.id
assert snap.totals.devices >= 0
assert snap.preseem.lowest_qoe_aps == []
assert snap.snmp.hottest_devices == []
assert snap.backhaul.high_utilization == []
assert snap.existing_insights == []
end
end
describe "build/1 — devices and counts" do
test "totals.devices reflects monitored device count", %{org: org} do
device_fixture(%{organization_id: org.id, monitoring_enabled: true})
device_fixture(%{organization_id: org.id, monitoring_enabled: true})
device_fixture(%{organization_id: org.id, monitoring_enabled: false})
snap = NetworkSnapshot.build(org.id)
assert snap.totals.devices == 3
assert snap.totals.monitored_devices == 2
end
test "stale_polled_devices includes devices with old last_snmp_poll_at", %{org: org} do
d1 = device_fixture(%{organization_id: org.id, monitoring_enabled: true})
cutoff = DateTime.add(DateTime.utc_now(), -48, :hour)
Repo.update_all(
from(d in Towerops.Devices.Device, where: d.id == ^d1.id),
set: [last_snmp_poll_at: cutoff]
)
snap = NetworkSnapshot.build(org.id)
assert Enum.any?(snap.snmp.stale_polled_devices, &(&1.device_id == d1.id))
end
end
describe "build/1 — Preseem APs" do
test "lowest_qoe_aps is sorted ascending by qoe_score and capped", %{org: org} do
for i <- 1..15 do
%AccessPoint{}
|> AccessPoint.changeset(%{
organization_id: org.id,
preseem_id: "ap-#{i}",
name: "AP #{i}",
qoe_score: i * 5.0,
subscriber_count: i,
model: "ePMP 3000"
})
|> Repo.insert!()
end
snap = NetworkSnapshot.build(org.id)
qoe_scores = Enum.map(snap.preseem.lowest_qoe_aps, & &1.qoe_score)
assert qoe_scores == Enum.sort(qoe_scores)
assert length(snap.preseem.lowest_qoe_aps) <= 10
assert hd(qoe_scores) == 5.0
end
test "skips APs with nil qoe_score", %{org: org} do
%AccessPoint{}
|> AccessPoint.changeset(%{
organization_id: org.id,
preseem_id: "no-qoe",
name: "Quiet AP",
qoe_score: nil
})
|> Repo.insert!()
snap = NetworkSnapshot.build(org.id)
refute Enum.any?(snap.preseem.lowest_qoe_aps, &(&1.preseem_id == "no-qoe"))
end
end
describe "build/1 — existing insights" do
test "groups active insights by type with counts", %{org: org} do
for type <- ["qoe_degradation", "qoe_degradation", "device_overheating"] do
%Insight{}
|> Insight.changeset(%{
organization_id: org.id,
type: type,
urgency: "warning",
channel: "proactive",
title: "x"
})
|> Repo.insert!()
end
snap = NetworkSnapshot.build(org.id)
counts = Map.new(snap.existing_insights, &{&1.type, &1.count})
assert counts["qoe_degradation"] == 2
assert counts["device_overheating"] == 1
end
test "ignores dismissed and resolved insights", %{org: org} do
%Insight{}
|> Insight.changeset(%{
organization_id: org.id,
type: "agent_offline",
urgency: "critical",
channel: "proactive",
title: "x",
status: "resolved"
})
|> Repo.insert!()
snap = NetworkSnapshot.build(org.id)
refute Enum.any?(snap.existing_insights, &(&1.type == "agent_offline"))
end
end
end