From f693cb5b5605c6178d01bf630d539825a85ae6db Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Thu, 16 Apr 2026 14:08:10 -0500 Subject: [PATCH] test: override stale distance_km when filling a missing pos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Makes the 'always recompute' intent explicit by removing the dead-code short-circuit in add_distance_change — the candidate query already requires at least one nil pos, so any previously stored distance is tied to a stale grid/pos pair and must be overwritten. --- .../contact_position_backfill_worker.ex | 27 ++++++++--------- .../contact_position_backfill_worker_test.exs | 29 +++++++++++++++++++ 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/lib/microwaveprop/workers/contact_position_backfill_worker.ex b/lib/microwaveprop/workers/contact_position_backfill_worker.ex index 81ea667c..89220dc4 100644 --- a/lib/microwaveprop/workers/contact_position_backfill_worker.ex +++ b/lib/microwaveprop/workers/contact_position_backfill_worker.ex @@ -78,23 +78,20 @@ defmodule Microwaveprop.Workers.ContactPositionBackfillWorker do defp add_pos_change(updates, _field, _existing, nil), do: updates defp add_pos_change(updates, field, _nil, new), do: [{field, new} | updates] - defp add_distance_change(updates, contact, pos1, pos2) do - cond do - pos1 == nil or pos2 == nil -> - updates + # Always overwrite distance_km when both positions resolve — the candidate + # query only selects contacts with at least one nil pos, so any previously + # stored distance is tied to a stale grid/pos pair. + defp add_distance_change(updates, _contact, nil, _pos2), do: updates + defp add_distance_change(updates, _contact, _pos1, nil), do: updates - not is_nil(contact.distance_km) and contact.pos1 != nil and contact.pos2 != nil -> - updates + defp add_distance_change(updates, _contact, pos1, pos2) do + distance = + pos1["lat"] + |> Radio.haversine_km(pos1["lon"], pos2["lat"], pos2["lon"]) + |> round() + |> Decimal.new() - true -> - distance = - pos1["lat"] - |> Radio.haversine_km(pos1["lon"], pos2["lat"], pos2["lon"]) - |> round() - |> Decimal.new() - - [{:distance_km, distance} | updates] - end + [{:distance_km, distance} | updates] end defp maybe_touch_updated_at([]), do: [] diff --git a/test/microwaveprop/workers/contact_position_backfill_worker_test.exs b/test/microwaveprop/workers/contact_position_backfill_worker_test.exs index febfa36c..7df3b290 100644 --- a/test/microwaveprop/workers/contact_position_backfill_worker_test.exs +++ b/test/microwaveprop/workers/contact_position_backfill_worker_test.exs @@ -136,5 +136,34 @@ defmodule Microwaveprop.Workers.ContactPositionBackfillWorkerTest do distance = Decimal.to_integer(reloaded.distance_km) assert distance > 0 and distance < 1000 end + + # Scenario: user updates grid1 to higher precision via direct DB write and + # nulls pos1 but leaves distance_km at the old (less-precise) value. Worker + # must re-resolve pos1 AND overwrite the stale distance_km. + test "overrides stale distance_km when filling a missing pos" do + pos2 = %{"lat" => 30.3, "lon" => -97.7} + stale_distance = Decimal.new(9999) + + contact = + insert_raw_contact!(%{ + grid1: "EM12", + grid2: "EM00", + pos1: nil, + pos2: pos2, + distance_km: stale_distance + }) + + assert {:ok, %{updated: 1}} = ContactPositionBackfillWorker.perform(%Oban.Job{args: %{}}) + + reloaded = Repo.reload!(contact) + assert %{"lat" => _, "lon" => _} = reloaded.pos1 + + refute Decimal.equal?(reloaded.distance_km, stale_distance), + "expected distance_km to be recomputed from refined grid, got #{reloaded.distance_km}" + + # EM12 <-> EM00 is ~333 km; sanity check the new value is in that ballpark. + new_distance = Decimal.to_integer(reloaded.distance_km) + assert new_distance > 0 and new_distance < 1000 + end end end