204 lines
5.6 KiB
Elixir
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
|