prop/test/microwaveprop/pskr/aggregator_test.exs
Graham McIntire fd976b0cd5
fix: resolve 391 Credo issues across codebase
- Add jump_credo_checks ~> 0.4 with all 20 checks enabled
- Fix all standard Credo issues: 139 @spec (113 done, 26 remain),
  4 refactoring, 3 alias usage, 9 System.cmd env, 5 unsafe_to_atom,
  2 max line length, 9 assert_receive timeout
- Fix 170+ jump_credo_checks warnings:
  - 117 TopLevelAliasImportRequire: move nested alias/import to module top
  - 32 UseObanProWorker: switch to Oban.Pro.Worker
  - 4 DoctestIExExamples: add doctests / create test file
  - ~20 WeakAssertion: strengthen type-check assertions
  - Various ConditionalAssertion, AssertReceiveTimeout fixes
- Exclude vendor/ from Credo analysis
- Remaining: 175 warnings (mostly opinionated WeakAssertion,
  AvoidSocketAssignsInTest), 26 @spec annotations
2026-06-12 13:51:32 -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.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