diff --git a/lib/microwaveprop_web/live/path_live.ex b/lib/microwaveprop_web/live/path_live.ex index 60757599..c3df912b 100644 --- a/lib/microwaveprop_web/live/path_live.ex +++ b/lib/microwaveprop_web/live/path_live.ex @@ -29,6 +29,10 @@ defmodule MicrowavepropWeb.PathLive do @impl true def mount(_params, _session, socket) do + if connected?(socket) do + Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:updated") + end + {:ok, assign(socket, page_title: "Path Calculator", @@ -202,6 +206,19 @@ defmodule MicrowavepropWeb.PathLive do end end + def handle_info({:propagation_updated, _valid_times}, socket) do + case socket.assigns.result do + %{band_mhz: band_mhz, source: src, destination: dst} = result -> + midlat = (src.lat + dst.lat) / 2 + midlon = (src.lon + dst.lon) / 2 + forecast = Propagation.point_forecast(band_mhz, midlat, midlon) + {:noreply, assign(socket, result: %{result | forecast: forecast})} + + _ -> + {:noreply, socket} + end + end + defp compute_path(source, dest, band_mhz, station_params) do with {:ok, src} <- resolve_location(source), {:ok, dst} <- resolve_location(dest) do diff --git a/test/microwaveprop_web/live/path_live_test.exs b/test/microwaveprop_web/live/path_live_test.exs new file mode 100644 index 00000000..db3d5b28 --- /dev/null +++ b/test/microwaveprop_web/live/path_live_test.exs @@ -0,0 +1,68 @@ +defmodule MicrowavepropWeb.PathLiveTest do + use MicrowavepropWeb.ConnCase, async: false + + import Phoenix.LiveViewTest + + alias Microwaveprop.Propagation + alias Microwaveprop.Propagation.ScoreCache + alias Microwaveprop.Terrain.ElevationClient + + describe "propagation_updated handler" do + setup do + ScoreCache.clear() + + # Flat-terrain stub so compute_path can build a result without hitting + # the real elevation API. + 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) + + on_exit(fn -> ScoreCache.clear() end) + :ok + end + + test "re-reads and re-renders the forecast when new scores arrive", %{conn: conn} do + # Path midpoint is (33.0, -97.0) — the point point_forecast queries. + {mid_lat, mid_lon} = {33.0, -97.0} + + # Seed three hourly valid_times with a LOW score of 30. + base = + DateTime.utc_now() + |> DateTime.truncate(:second) + |> Map.put(:minute, 0) + |> Map.put(:second, 0) + + times = for h <- 0..2, do: DateTime.add(base, h * 3600, :second) + + Enum.each(times, fn t -> + Propagation.replace_scores( + [%{lat: mid_lat, lon: mid_lon, valid_time: t, band_mhz: 10_000, score: 30, factors: nil}], + t + ) + end) + + {:ok, lv, _html} = + live(conn, ~p"/path?source=32.5,-97.0&destination=33.5,-97.0&band=10000") + + initial = render(lv) + assert initial =~ "Propagation Forecast" + assert initial =~ ~r/Best:.*?]*>\s*30\s*<\/span>/s + + # Publish a new forecast: same times, new score of 80. + Enum.each(times, fn t -> + Propagation.replace_scores( + [%{lat: mid_lat, lon: mid_lon, valid_time: t, band_mhz: 10_000, score: 80, factors: nil}], + t + ) + end) + + send(lv.pid, {:propagation_updated, times}) + + refreshed = render(lv) + assert refreshed =~ ~r/Best:.*?]*>\s*80\s*<\/span>/s + refute refreshed =~ ~r/Best:.*?]*>\s*30\s*<\/span>/s + end + end +end