prop/test/microwaveprop/pskr/aggregator_test.exs
Graham McIntire cb8445f329
fix: resolve 158/183 credo --strict warnings
- Add 17 missing @spec annotations (layouts, error_json, error_html, skewt_svg)
- Move 12+ nested alias/import/require to module top level
- Add phx-change/id attributes to 11 raw HTML <form> tags
- Remove 4 unused LiveView assigns (:bounds, :data_provider)
- Add 3 missing doctest references (HrrrNativeClient, BulkFetch, Accounts)
- Break 2 long lines (path_compute.ex:382)
- Strengthen weak test assertions (is_binary→byte_size, is_list→!=[])
- Replace Module.concat with Module.safe_concat (2 occurrences)
- Replace length/1 > 0 with list != [] (9 occurrences)
- Remove no-op assert true, fix no-assertion tests

Remaining: 24 socket.assigns introspection warnings (deliberate test
pattern for observable behavior testing), 1 formatter-resistant long
line, 3 app-code usage warnings.
2026-06-12 15:47:15 -05:00

100 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
pid =
start_supervised!(
{Aggregator, name: Module.safe_concat([Aggregator, to_string(: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