prop/test/microwaveprop/radio_test.exs
Graham McIntire 6f16395f44
Add QSO import, solar indices, Oban workers, LiveView UI, and parallel weather import
- Radio context with QSO schema and CSV import script (58K contacts)
- Solar index schema, GFZ client, and daily Oban cron worker
- LiveView dashboard with QSO table and weather correlation views
- Parallelize weather import script using Task.async_stream (10 concurrent workers)
- Replace sequential rate_limit sleeps with concurrency-based backpressure
- Atomic progress counter for interleave-safe reporting
2026-03-29 13:04:55 -05:00

118 lines
3.2 KiB
Elixir

defmodule Microwaveprop.RadioTest do
use Microwaveprop.DataCase, async: true
alias Microwaveprop.Radio
alias Microwaveprop.Radio.Qso
defp create_qso(attrs \\ %{}) do
default = %{
station1: "W5XD",
station2: "K5TR",
qso_timestamp: ~U[2026-03-28 18:00:00Z],
mode: "CW",
band: Decimal.new("1296"),
grid1: "EM12",
grid2: "EM00",
pos1: %{"lat" => 32.9, "lon" => -97.0},
pos2: %{"lat" => 30.3, "lon" => -97.7},
distance_km: Decimal.new("295")
}
{:ok, qso} =
%Qso{}
|> Qso.changeset(Map.merge(default, attrs))
|> Repo.insert()
qso
end
describe "list_qsos/1" do
test "returns empty page when no QSOs exist" do
result = Radio.list_qsos()
assert result.entries == []
assert result.page == 1
assert result.total_pages == 1
assert result.total_entries == 0
end
test "paginates 20 per page, ordered by qso_timestamp desc" do
for i <- 1..25 do
ts = DateTime.add(~U[2026-01-01 00:00:00Z], i * 3600, :second)
create_qso(%{qso_timestamp: ts})
end
result = Radio.list_qsos()
assert length(result.entries) == 20
assert result.page == 1
assert result.total_pages == 2
assert result.total_entries == 25
# Most recent first
first = hd(result.entries)
assert first.qso_timestamp == DateTime.add(~U[2026-01-01 00:00:00Z], 25 * 3600, :second)
end
test "returns second page" do
for i <- 1..25 do
ts = DateTime.add(~U[2026-01-01 00:00:00Z], i * 3600, :second)
create_qso(%{qso_timestamp: ts})
end
result = Radio.list_qsos(page: 2)
assert length(result.entries) == 5
assert result.page == 2
assert result.total_pages == 2
end
end
describe "list_qsos/1 sorting" do
test "sorts by station1 ascending" do
create_qso(%{station1: "ZZ9ZZ"})
create_qso(%{station1: "AA1AA"})
result = Radio.list_qsos(sort_by: :station1, sort_order: :asc)
stations = Enum.map(result.entries, & &1.station1)
assert stations == ["AA1AA", "ZZ9ZZ"]
end
test "sorts by distance_km descending" do
create_qso(%{distance_km: Decimal.new("100"), station1: "A1A"})
create_qso(%{distance_km: Decimal.new("500"), station1: "B2B"})
result = Radio.list_qsos(sort_by: :distance_km, sort_order: :desc)
distances = Enum.map(result.entries, & &1.distance_km)
assert distances == [Decimal.new("500"), Decimal.new("100")]
end
test "invalid sort_by falls back to qso_timestamp desc" do
early = create_qso(%{qso_timestamp: ~U[2026-01-01 00:00:00Z]})
late = create_qso(%{qso_timestamp: ~U[2026-03-01 00:00:00Z]})
result = Radio.list_qsos(sort_by: :bogus)
ids = Enum.map(result.entries, & &1.id)
assert ids == [late.id, early.id]
end
end
describe "get_qso!/1" do
test "returns a QSO by ID" do
qso = create_qso()
found = Radio.get_qso!(qso.id)
assert found.id == qso.id
assert found.station1 == "W5XD"
end
test "raises Ecto.NoResultsError on bad ID" do
assert_raise Ecto.NoResultsError, fn ->
Radio.get_qso!(Ecto.UUID.generate())
end
end
end
end