prop/test/microwaveprop/radio_refinement_test.exs
Graham McIntire bbeb95ff5d
CSV/ADIF import: upsert existing contacts on grid/mode refinement
Widen dedup match to 4-char grid prefix so an upload with a longer grid
(e.g. FN42aa25) finds the existing FN42 contact. Within a match, classify
as a refinement when the upload strictly extends the existing grid or
fills a missing mode; as a contradiction (still skipped) when grids or
modes genuinely disagree. Refinements flow through a new preview bucket,
update the existing row in place, and re-enqueue enrichment when the
position changed.
2026-04-16 10:57:37 -05:00

152 lines
5.4 KiB
Elixir

defmodule Microwaveprop.RadioRefinementTest do
use Microwaveprop.DataCase, async: true
alias Microwaveprop.Radio
alias Microwaveprop.Radio.Contact
alias Microwaveprop.Terrain.ElevationClient
alias Microwaveprop.Weather.HrrrClient
alias Microwaveprop.Weather.IemClient
setup do
Req.Test.stub(IemClient, fn conn ->
case conn.request_path do
"/cgi-bin/request/asos.py" -> Req.Test.text(conn, "#DEBUG,\nstation,valid,tmpf\n")
_ -> Req.Test.json(conn, %{"profiles" => []})
end
end)
Req.Test.stub(HrrrClient, fn conn ->
Plug.Conn.send_resp(conn, 404, "not found")
end)
Req.Test.stub(ElevationClient, fn conn ->
params = Plug.Conn.fetch_query_params(conn).query_params
lat_count = params["latitude"] |> String.split(",") |> length()
Req.Test.json(conn, %{"elevation" => List.duplicate(200.0, lat_count)})
end)
:ok
end
defp insert_contact(attrs \\ %{}) do
base = %{
station1: "W5XD",
station2: "K5TR",
grid1: "EM12",
grid2: "EM00",
band: "10000",
mode: "CW",
qso_timestamp: ~U[2026-03-28 18:00:00Z],
submitter_email: "test@example.com"
}
{:ok, contact} = Radio.create_contact(Map.merge(base, attrs))
contact
end
defp mark_all_enrichment_complete(%Contact{} = contact) do
contact
|> Ecto.Changeset.change(%{
hrrr_status: :complete,
weather_status: :complete,
terrain_status: :complete,
iemre_status: :complete
})
|> Repo.update!()
end
describe "apply_contact_refinement/2" do
test "refining grid1 updates grid1, pos1, distance, and resets all enrichment statuses" do
contact = mark_all_enrichment_complete(insert_contact())
original_grid2 = contact.grid2
original_pos2 = contact.pos2
assert {:ok, refined} = Radio.apply_contact_refinement(contact, %{grid1: "EM12KP"})
assert refined.grid1 == "EM12KP"
assert refined.grid2 == original_grid2
assert refined.pos2 == original_pos2
refute refined.pos1 == contact.pos1
assert refined.pos1["lat"] != contact.pos1["lat"] or refined.pos1["lon"] != contact.pos1["lon"]
assert refined.distance_km
assert refined.hrrr_status == :pending
assert refined.weather_status == :pending
assert refined.terrain_status == :pending
assert refined.iemre_status == :pending
end
test "refining grid2 updates grid2, pos2, distance, and resets all enrichment statuses" do
contact = mark_all_enrichment_complete(insert_contact())
original_grid1 = contact.grid1
original_pos1 = contact.pos1
assert {:ok, refined} = Radio.apply_contact_refinement(contact, %{grid2: "EM00CD"})
assert refined.grid1 == original_grid1
assert refined.pos1 == original_pos1
assert refined.grid2 == "EM00CD"
refute refined.pos2 == contact.pos2
assert refined.hrrr_status == :pending
assert refined.weather_status == :pending
assert refined.terrain_status == :pending
assert refined.iemre_status == :pending
end
test "refining both grids updates both + recomputes distance" do
contact = mark_all_enrichment_complete(insert_contact())
original_distance = contact.distance_km
assert {:ok, refined} =
Radio.apply_contact_refinement(contact, %{grid1: "EM12KP", grid2: "EM00CD"})
assert refined.grid1 == "EM12KP"
assert refined.grid2 == "EM00CD"
assert refined.distance_km
# Distances remain similar (grids refined within same squares) but pos values change.
assert refined.pos1["lat"] != contact.pos1["lat"] or refined.pos1["lon"] != contact.pos1["lon"]
assert refined.pos2["lat"] != contact.pos2["lat"] or refined.pos2["lon"] != contact.pos2["lon"]
assert refined.distance_km != original_distance or refined.distance_km == original_distance
end
test "refining only :mode updates mode without touching pos/distance or statuses" do
contact = %{mode: nil} |> insert_contact() |> mark_all_enrichment_complete()
original_pos1 = contact.pos1
original_pos2 = contact.pos2
original_distance = contact.distance_km
assert {:ok, refined} = Radio.apply_contact_refinement(contact, %{mode: "CW"})
assert refined.mode == "CW"
assert refined.pos1 == original_pos1
assert refined.pos2 == original_pos2
assert refined.distance_km == original_distance
assert refined.hrrr_status == :complete
assert refined.weather_status == :complete
assert refined.terrain_status == :complete
assert refined.iemre_status == :complete
end
test "empty changes map returns the contact unchanged" do
contact = mark_all_enrichment_complete(insert_contact())
assert {:ok, unchanged} = Radio.apply_contact_refinement(contact, %{})
assert unchanged.id == contact.id
assert unchanged.grid1 == contact.grid1
assert unchanged.grid2 == contact.grid2
assert unchanged.mode == contact.mode
assert unchanged.hrrr_status == :complete
end
test "invalid Maidenhead grid returns error and does not mutate the DB row" do
contact = mark_all_enrichment_complete(insert_contact())
assert {:error, %Ecto.Changeset{} = changeset} =
Radio.apply_contact_refinement(contact, %{grid1: "ZZZZ"})
refute changeset.valid?
reloaded = Repo.reload!(contact)
assert reloaded.grid1 == contact.grid1
assert reloaded.hrrr_status == :complete
end
end
end