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