prop/test/microwaveprop/pskr/aggregator_test.exs
Graham McIntire 316fb2fbc7
Fix low-severity bugs and re-enable Credo checks
- Bug #12: Lower rate limit to 15/min
- Bug #13: Wrap model loading in Task.start
- Bug #14: Fix classify_time_period guard gap at -3.0
- Bug #15: Not applicable (Elixir has no ?? operator)
- Bug #16: Remove fallback Repo.get for station preload
- Bug #17: Extract cache_key() in ContactMapController
- Bug #18: Add has_many :contacts and :beacons to User schema
- A&D #4: Move serve_markdown_if_requested after secure headers
- Config #1: Move signing_salt to runtime.exs env var
- Config #2: Use --check-unused instead of --unused
- Config #3: Re-enable UnsafeToAtom Credo check
- Config #5: Re-enable LeakyEnvironment Credo check
- Add @spec annotations to fix re-enabled Specs violations
- Replace String.to_atom with to_existing_atom where guarded
2026-05-29 17:29:22 -05:00

97 lines
2.8 KiB
Elixir

defmodule Microwaveprop.Pskr.AggregatorTest do
use Microwaveprop.DataCase, async: true
alias Ecto.Adapters.SQL.Sandbox
alias Microwaveprop.Pskr.Aggregator
alias Microwaveprop.Pskr.SpotHourly
alias Microwaveprop.Repo
@sample %{
"f" => 144_174_000,
"md" => "FT8",
"rp" => -5,
"t_tx" => 1_662_407_697,
"sc" => "K5ABC",
"sl" => "EM12kl",
"rc" => "K7XYZ",
"rl" => "DM43st",
"sa" => 291,
"ra" => 291,
"b" => "2m"
}
setup do
# credo:disable-for-this-line Credo.Check.Warning.UnsafeToAtom
pid = start_supervised!({Aggregator, name: :"agg_#{:erlang.unique_integer([:positive])}", flush_ms: 0})
Sandbox.allow(Repo, self(), pid)
%{agg: pid}
end
test "ingest collapses repeats on the same path-hour", %{agg: agg} do
payloads = [
Jason.encode!(@sample),
Jason.encode!(%{@sample | "rp" => -3}),
Jason.encode!(%{@sample | "rp" => -12, "md" => "FT4"})
]
Enum.each(payloads, &Aggregator.ingest(agg, &1))
assert Aggregator.flush(agg) == 1
[row] = Repo.all(SpotHourly)
assert row.spot_count == 3
assert row.max_snr_db == -3
assert row.min_snr_db == -12
assert Enum.sort(row.modes) == ["FT4", "FT8"]
assert row.band == "2m"
assert row.sender_grid == "EM12KL"
assert row.receiver_grid == "DM43ST"
assert row.distance_km > 0
end
test "two paths in the same hour produce two rows", %{agg: agg} do
Aggregator.ingest(agg, Jason.encode!(@sample))
Aggregator.ingest(agg, Jason.encode!(%{@sample | "sl" => "FN31pr"}))
assert Aggregator.flush(agg) == 2
assert Repo.aggregate(SpotHourly, :count) == 2
end
test "second flush merges into existing row via upsert", %{agg: agg} do
Aggregator.ingest(agg, Jason.encode!(@sample))
assert Aggregator.flush(agg) == 1
Aggregator.ingest(agg, Jason.encode!(%{@sample | "rp" => 5}))
assert Aggregator.flush(agg) == 1
[row] = Repo.all(SpotHourly)
assert row.spot_count == 2
assert row.max_snr_db == 5
assert row.min_snr_db == -5
end
test "malformed payloads are dropped silently", %{agg: agg} do
Aggregator.ingest(agg, "not json")
Aggregator.ingest(agg, Jason.encode!(%{"oops" => true}))
Aggregator.ingest(agg, Jason.encode!(@sample))
assert Aggregator.flush(agg) == 1
end
test "pending_count returns the number of unflushed rows", %{agg: agg} do
assert Aggregator.pending_count(agg) == 0
Aggregator.ingest(agg, Jason.encode!(@sample))
assert Aggregator.pending_count(agg) == 1
Aggregator.ingest(agg, Jason.encode!(%{@sample | "sl" => "FN31pr"}))
assert Aggregator.pending_count(agg) == 2
Aggregator.flush(agg)
assert Aggregator.pending_count(agg) == 0
end
test "flush with empty rows returns 0", %{agg: agg} do
assert Aggregator.flush(agg) == 0
end
end