prop/test/microwaveprop/pskr/aggregator_test.exs
Graham McIntire b764034e46
test: push coverage to 78.6% — MapLayers 100%, LinkMargin 100%, Elevation 94%
- Weather.MapLayers: unit tests for all/0, default_id/0, valid_id?/1 (25→100%)
- Rover.LinkMargin: link_margin_db ScoresFile round-trip tests (57→100%)
- Rover.Elevation: SRTM tile file read path + void sentinel (44→94%)
- Pskr.Aggregator: pending_count, empty flush tests (65→74%)
- Buildings.MsFootprints: quadkey/bbox pure function tests (51→51%)
- Weather.Grib2.Wgrib2: property tests for parse_lon_val_segment (23→23%)
- test/support/data_case: File.rm_rf -> rm_rf to fix concurrent race
2026-05-07 13:17:40 -05:00

96 lines
2.7 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
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