From 448d3636a173423a7f23231a1bfc890bab38e1ba Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Sat, 25 Apr 2026 16:15:36 -0500 Subject: [PATCH] feat(rover): Oban worker enriches station elevation post-insert --- .../workers/station_elevation_worker.ex | 43 +++++++++++++++++ .../workers/station_elevation_worker_test.exs | 47 +++++++++++++++++++ .../microwaveprop_web/live/skewt_svg_test.exs | 2 +- 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 lib/microwaveprop/workers/station_elevation_worker.ex create mode 100644 test/microwaveprop/workers/station_elevation_worker_test.exs diff --git a/lib/microwaveprop/workers/station_elevation_worker.ex b/lib/microwaveprop/workers/station_elevation_worker.ex new file mode 100644 index 00000000..cd8f4c88 --- /dev/null +++ b/lib/microwaveprop/workers/station_elevation_worker.ex @@ -0,0 +1,43 @@ +defmodule Microwaveprop.Workers.StationElevationWorker do + @moduledoc """ + Oban worker that enriches a `FixedStation` row with its SRTM + elevation. Enqueued from `Microwaveprop.Rover.create_station/2` + whenever the user-supplied attrs leave `elevation_m` nil. + + A missing tile or a void value is non-fatal — the row simply stays + with `elevation_m = nil`. The worker also no-ops if the station has + been deleted between enqueue and execution. + """ + use Oban.Worker, queue: :terrain, max_attempts: 3 + + alias Microwaveprop.Repo + alias Microwaveprop.Rover.FixedStation + alias Microwaveprop.Terrain.Srtm + + @impl Oban.Worker + def perform(%Oban.Job{args: %{"id" => id}}) do + case Repo.get(FixedStation, id) do + nil -> + :ok + + station -> + update_elevation(station) + end + end + + defp update_elevation(station) do + tiles_dir = Application.get_env(:microwaveprop, :srtm_tiles_dir, "/data/srtm") + + case Srtm.lookup(station.lat, station.lon, tiles_dir) do + {:ok, elev} -> + station + |> Ecto.Changeset.change(elevation_m: elev) + |> Repo.update() + + :ok + + {:error, _} -> + :ok + end + end +end diff --git a/test/microwaveprop/workers/station_elevation_worker_test.exs b/test/microwaveprop/workers/station_elevation_worker_test.exs new file mode 100644 index 00000000..63ec0a4c --- /dev/null +++ b/test/microwaveprop/workers/station_elevation_worker_test.exs @@ -0,0 +1,47 @@ +defmodule Microwaveprop.Workers.StationElevationWorkerTest do + use Microwaveprop.DataCase, async: false + + import Microwaveprop.AccountsFixtures + + alias Microwaveprop.Rover + alias Microwaveprop.Rover.FixedStation + alias Microwaveprop.Workers.StationElevationWorker + + setup do + tmp = Path.join(System.tmp_dir!(), "station_elev_#{System.unique_integer([:positive])}") + File.mkdir_p!(tmp) + prev = Application.get_env(:microwaveprop, :srtm_tiles_dir) + Application.put_env(:microwaveprop, :srtm_tiles_dir, tmp) + + on_exit(fn -> + File.rm_rf!(tmp) + + if prev do + Application.put_env(:microwaveprop, :srtm_tiles_dir, prev) + else + Application.delete_env(:microwaveprop, :srtm_tiles_dir) + end + end) + + :ok + end + + describe "perform/1" do + test "returns :ok and leaves elevation nil when SRTM tile is missing" do + user = user_fixture() + {:ok, station} = Rover.create_station(user, %{callsign: "W5LUA", lat: 33.0, lon: -96.0}) + + assert :ok = StationElevationWorker.perform(%Oban.Job{args: %{"id" => station.id}}) + + reloaded = Repo.get!(FixedStation, station.id) + assert reloaded.elevation_m == nil + end + + test "returns :ok when station id no longer exists" do + assert :ok = + StationElevationWorker.perform(%Oban.Job{ + args: %{"id" => Ecto.UUID.generate()} + }) + end + end +end diff --git a/test/microwaveprop_web/live/skewt_svg_test.exs b/test/microwaveprop_web/live/skewt_svg_test.exs index 01ab3303..dcc6bfe4 100644 --- a/test/microwaveprop_web/live/skewt_svg_test.exs +++ b/test/microwaveprop_web/live/skewt_svg_test.exs @@ -16,7 +16,7 @@ defmodule MicrowavepropWeb.SkewtSvgTest do test "produces a valid root with the expected viewBox" do svg = @profile |> SkewtSvg.render() |> IO.iodata_to_binary() - assert svg =~ ~r|]*viewBox="0 0 720 640"| + assert svg =~ ~r|]*viewBox="0 0 820 640"| assert String.ends_with?(svg, "") end