towerops/test/towerops/preseem/fleet_intelligence_test.exs

187 lines
5.5 KiB
Elixir

defmodule Towerops.Preseem.FleetIntelligenceTest do
use Towerops.DataCase, async: true
import Towerops.AccountsFixtures
import Towerops.DevicesFixtures
import Towerops.OrganizationsFixtures
alias Towerops.Preseem.AccessPoint
alias Towerops.Preseem.FleetIntelligence
alias Towerops.Preseem.FleetProfile
setup do
user = user_fixture()
org = organization_fixture(user.id)
%{org: org}
end
describe "compute_fleet_profiles/1" do
test "computes profiles grouped by model", %{org: org} do
for i <- 1..3 do
insert_matched_ap_with_model(org, "Ubiquiti AF5XHD", %{
subscriber_count: 30 + i * 5,
qoe_score: 80.0 + i,
capacity_score: 70.0 + i
})
end
assert {:ok, 1} = FleetIntelligence.compute_fleet_profiles(org.id)
profiles = Repo.all(FleetProfile)
assert length(profiles) == 1
profile = hd(profiles)
assert profile.model == "Ubiquiti AF5XHD"
assert profile.manufacturer == "Ubiquiti"
assert profile.device_count == 3
assert profile.avg_subscribers
assert profile.avg_qoe_score
assert profile.avg_capacity_score
end
test "creates separate profiles for different models", %{org: org} do
for _i <- 1..2 do
insert_matched_ap_with_model(org, "Ubiquiti AF5XHD", %{
subscriber_count: 30,
qoe_score: 80.0
})
end
for _i <- 1..2 do
insert_matched_ap_with_model(org, "MikroTik RB4011", %{
subscriber_count: 20,
qoe_score: 75.0
})
end
assert {:ok, 2} = FleetIntelligence.compute_fleet_profiles(org.id)
profiles = Repo.all(FleetProfile)
assert length(profiles) == 2
manufacturers = profiles |> Enum.map(& &1.manufacturer) |> Enum.sort()
assert manufacturers == ["MikroTik", "Ubiquiti"]
end
test "handles empty organization", %{org: org} do
assert {:ok, 0} = FleetIntelligence.compute_fleet_profiles(org.id)
end
test "skips APs without model", %{org: org} do
device = device_fixture(%{organization_id: org.id})
%AccessPoint{}
|> AccessPoint.changeset(%{
organization_id: org.id,
preseem_id: "ap-no-model-#{System.unique_integer([:positive])}",
name: "No Model",
device_id: device.id,
match_confidence: "auto_ip"
})
|> Repo.insert!()
assert {:ok, 0} = FleetIntelligence.compute_fleet_profiles(org.id)
end
test "skips unmatched APs", %{org: org} do
%AccessPoint{}
|> AccessPoint.changeset(%{
organization_id: org.id,
preseem_id: "ap-unmatched-#{System.unique_integer([:positive])}",
name: "Unmatched",
model: "Ubiquiti AF5XHD"
})
|> Repo.insert!()
assert {:ok, 0} = FleetIntelligence.compute_fleet_profiles(org.id)
end
test "computes capacity ceiling from APs with good QoE", %{org: org} do
# AP with high subscribers and good QoE - should define ceiling
insert_matched_ap_with_model(org, "Ubiquiti AF5XHD", %{
subscriber_count: 50,
qoe_score: 85.0
})
# AP with even higher subscribers but poor QoE - excluded from ceiling
insert_matched_ap_with_model(org, "Ubiquiti AF5XHD", %{
subscriber_count: 80,
qoe_score: 55.0
})
# AP with low subscribers and good QoE
insert_matched_ap_with_model(org, "Ubiquiti AF5XHD", %{
subscriber_count: 20,
qoe_score: 95.0
})
assert {:ok, 1} = FleetIntelligence.compute_fleet_profiles(org.id)
profile = Repo.one!(FleetProfile)
# Capacity ceiling should be 50 (highest subscriber count with QoE >= 70)
assert profile.capacity_ceiling == 50
end
test "upserts profiles on recomputation", %{org: org} do
for _i <- 1..3 do
insert_matched_ap_with_model(org, "Ubiquiti AF5XHD", %{
subscriber_count: 30,
qoe_score: 80.0
})
end
assert {:ok, 1} = FleetIntelligence.compute_fleet_profiles(org.id)
assert length(Repo.all(FleetProfile)) == 1
# Recompute - should update, not duplicate
assert {:ok, 1} = FleetIntelligence.compute_fleet_profiles(org.id)
assert length(Repo.all(FleetProfile)) == 1
end
test "includes performance_data with subscriber range and firmware distribution", %{org: org} do
insert_matched_ap_with_model(org, "Ubiquiti AF5XHD", %{
subscriber_count: 20,
firmware: "v4.3.1"
})
insert_matched_ap_with_model(org, "Ubiquiti AF5XHD", %{
subscriber_count: 50,
firmware: "v4.3.1"
})
insert_matched_ap_with_model(org, "Ubiquiti AF5XHD", %{
subscriber_count: 35,
firmware: "v4.3.2"
})
assert {:ok, 1} = FleetIntelligence.compute_fleet_profiles(org.id)
profile = Repo.one!(FleetProfile)
assert profile.performance_data["subscriber_range"]["min"] == 20
assert profile.performance_data["subscriber_range"]["max"] == 50
assert profile.performance_data["firmware_distribution"] == %{"v4.3.1" => 2, "v4.3.2" => 1}
end
end
# Helpers
defp insert_matched_ap_with_model(org, model, extra_attrs) do
device = device_fixture(%{organization_id: org.id})
attrs =
Map.merge(
%{
organization_id: org.id,
preseem_id: "ap-#{System.unique_integer([:positive])}",
name: "AP",
model: model,
device_id: device.id,
match_confidence: "auto_ip"
},
extra_attrs
)
%AccessPoint{}
|> AccessPoint.changeset(attrs)
|> Repo.insert!()
end
end