towerops/test/towerops/preseem/baseline_test.exs

204 lines
5.6 KiB
Elixir

defmodule Towerops.Preseem.BaselineTest do
use Towerops.DataCase, async: true
import Towerops.AccountsFixtures
import Towerops.DevicesFixtures
import Towerops.OrganizationsFixtures
alias Towerops.Preseem.AccessPoint
alias Towerops.Preseem.Baseline
alias Towerops.Preseem.DeviceBaseline
alias Towerops.Preseem.SubscriberMetric
setup do
user = user_fixture()
org = organization_fixture(user.id)
%{org: org}
end
describe "compute_baselines/1" do
test "computes baselines for matched APs with sufficient data", %{org: org} do
ap = insert_matched_ap(org)
for i <- 1..5 do
insert_subscriber_metric(ap.id, %{
avg_latency: 10.0 + i,
p95_latency: 20.0 + i,
subscriber_count: 40 + i,
recorded_at: hours_ago(i)
})
end
assert {:ok, 1} = Baseline.compute_baselines(org.id)
baselines = Repo.all(DeviceBaseline)
refute baselines == []
latency_baseline = Enum.find(baselines, &(&1.metric_name == "avg_latency"))
assert latency_baseline
assert latency_baseline.sample_count == 5
assert latency_baseline.period == "all"
assert latency_baseline.mean
assert latency_baseline.stddev
assert latency_baseline.p5
assert latency_baseline.p95
end
test "skips APs with fewer than 3 data points", %{org: org} do
ap = insert_matched_ap(org)
for i <- 1..2 do
insert_subscriber_metric(ap.id, %{
avg_latency: 10.0 + i,
recorded_at: hours_ago(i)
})
end
assert {:ok, 0} = Baseline.compute_baselines(org.id)
assert Repo.all(DeviceBaseline) == []
end
test "skips unmatched APs", %{org: org} do
ap = insert_unmatched_ap(org.id)
for i <- 1..5 do
insert_subscriber_metric(ap.id, %{
avg_latency: 10.0 + i,
recorded_at: hours_ago(i)
})
end
assert {:ok, 0} = Baseline.compute_baselines(org.id)
end
test "handles empty organization", %{org: org} do
assert {:ok, 0} = Baseline.compute_baselines(org.id)
end
test "only includes metrics within the 14-day window", %{org: org} do
ap = insert_matched_ap(org)
# 3 recent metrics (within window)
for i <- 1..3 do
insert_subscriber_metric(ap.id, %{
avg_latency: 10.0 + i,
recorded_at: hours_ago(i)
})
end
# 2 old metrics (outside 14-day window)
for i <- 1..2 do
insert_subscriber_metric(ap.id, %{
avg_latency: 100.0 + i,
recorded_at: days_ago(15 + i)
})
end
assert {:ok, 1} = Baseline.compute_baselines(org.id)
latency_baseline =
DeviceBaseline
|> Repo.all()
|> Enum.find(&(&1.metric_name == "avg_latency"))
# Should only have 3 samples from the recent window
assert latency_baseline.sample_count == 3
end
test "upserts baselines on recomputation", %{org: org} do
ap = insert_matched_ap(org)
for i <- 1..5 do
insert_subscriber_metric(ap.id, %{
avg_latency: 10.0 + i,
recorded_at: hours_ago(i)
})
end
assert {:ok, 1} = Baseline.compute_baselines(org.id)
initial_count = length(Repo.all(DeviceBaseline))
# Recompute - should update existing baselines, not create duplicates
assert {:ok, 1} = Baseline.compute_baselines(org.id)
assert length(Repo.all(DeviceBaseline)) == initial_count
end
test "computes baselines for multiple matched APs", %{org: org} do
for _idx <- 1..3 do
ap = insert_matched_ap(org)
for i <- 1..4 do
insert_subscriber_metric(ap.id, %{
avg_latency: 10.0 + i,
recorded_at: hours_ago(i)
})
end
end
assert {:ok, 3} = Baseline.compute_baselines(org.id)
end
test "skips metrics with nil values for a given metric name", %{org: org} do
ap = insert_matched_ap(org)
# Insert metrics where avg_jitter is nil but avg_latency has values
for i <- 1..5 do
insert_subscriber_metric(ap.id, %{
avg_latency: 10.0 + i,
avg_jitter: nil,
recorded_at: hours_ago(i)
})
end
assert {:ok, 1} = Baseline.compute_baselines(org.id)
baselines = Repo.all(DeviceBaseline)
latency_baseline = Enum.find(baselines, &(&1.metric_name == "avg_latency"))
jitter_baseline = Enum.find(baselines, &(&1.metric_name == "avg_jitter"))
assert latency_baseline
# avg_jitter should not have a baseline since all values are nil (< 3 non-nil)
assert jitter_baseline == nil
end
end
# Helpers
defp insert_matched_ap(org) do
device = device_fixture(%{organization_id: org.id})
%AccessPoint{}
|> AccessPoint.changeset(%{
organization_id: org.id,
preseem_id: "ap-#{System.unique_integer([:positive])}",
name: "Test AP",
device_id: device.id,
match_confidence: "auto_ip"
})
|> Repo.insert!()
end
defp insert_unmatched_ap(org_id) do
%AccessPoint{}
|> AccessPoint.changeset(%{
organization_id: org_id,
preseem_id: "ap-unmatched-#{System.unique_integer([:positive])}",
name: "Unmatched AP"
})
|> Repo.insert!()
end
defp insert_subscriber_metric(ap_id, attrs) do
%SubscriberMetric{}
|> SubscriberMetric.changeset(Map.put(attrs, :preseem_access_point_id, ap_id))
|> Repo.insert!()
end
defp hours_ago(n) do
DateTime.utc_now() |> DateTime.add(-n * 3600, :second) |> DateTime.truncate(:second)
end
defp days_ago(n) do
DateTime.utc_now() |> DateTime.add(-n * 86_400, :second) |> DateTime.truncate(:second)
end
end