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.
152 lines
5.4 KiB
Elixir
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
|