prop/test/microwaveprop_web/live/weather_ca_map_live_test.exs
Graham McIntire 4901c83916
fix(weather-ca): drop the 1h freshness cutoff so HRDPS data renders
The 1-hour valid_time filter inherited from /weather is wrong for
HRDPS: the model publishes 4x/day with 3-4h publication latency and
the chain step adds ~13 min on top, so the freshest data on disk is
routinely 4-6h old. With the 1h cutoff in place, /weather-ca's
timeline was empty whenever there wasn't an active chain step running,
which left the JS hook with nothing to fetch. The sidebar already
shows the selected valid_time prominently — users can see for
themselves how stale the data is and don't need the page to hide it.
2026-04-30 12:24:34 -05:00

89 lines
3 KiB
Elixir

defmodule MicrowavepropWeb.WeatherCaMapLiveTest do
# async: false because we touch the shared scalar / profile dirs.
use MicrowavepropWeb.ConnCase, async: false
import Phoenix.LiveViewTest
alias Microwaveprop.Propagation.ProfilesFile
alias Microwaveprop.Weather.GridCache
alias Microwaveprop.Weather.ScalarFile
setup do
GridCache.clear()
on_exit(fn ->
GridCache.clear()
ProfilesFile.prune_older_than(~U[2099-12-31 23:59:59Z])
end)
:ok
end
test "mounts at /weather-ca with Canadian default viewport", %{conn: conn} do
{:ok, _lv, html} = live(conn, ~p"/weather-ca")
# Default viewport is centered roughly on central Canada at a low
# zoom — different from /weather's DFW@z7. The exact numbers are
# asserted so a routing accident (mounting WeatherMapLive at this
# path by mistake) is caught.
assert html =~ ~s(data-initial-lat="55.0")
assert html =~ ~s(data-initial-lon="-100.0")
assert html =~ ~s(data-initial-zoom="4")
end
test "renders the weather map element with data-source=hrdps", %{conn: conn} do
{:ok, _lv, html} = live(conn, ~p"/weather-ca")
# The JS hook reads data-source and appends &source=hrdps to its cell
# fetches. Without this attribute the page would silently fall back
# to HRRR-only reads and never show Canadian data.
assert html =~ ~s(data-source="hrdps")
end
test "viewport_changed event push_patches /weather-ca, not /weather", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/weather-ca")
render_hook(lv, "viewport_changed", %{"lat" => 60.0, "lon" => -110.0, "zoom" => 5})
assert_patch(lv, "/weather-ca?layer=temperature&lat=60.0&lon=-110.0&zoom=5")
end
test "select_layer push_patches a /weather-ca URL", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/weather-ca")
render_hook(lv, "select_layer", %{"layer" => "pwat"})
assert_patch(lv, "/weather-ca?layer=pwat&lat=55.0&lon=-100.0&zoom=4")
end
test "shows HRDPS valid_times even when several hours old (no 1h cutoff)", %{conn: conn} do
# HRDPS publishes 4x/day with ~3-4h latency, plus our chain step
# takes ~13 min. The 1-hour cutoff /weather uses would routinely
# leave /weather-ca empty between fresh chain steps. Hand-write a
# 4-hour-old HRDPS chunk and confirm it lands in the timeline.
now =
DateTime.utc_now()
|> DateTime.truncate(:second)
|> Map.put(:minute, 0)
|> Map.put(:second, 0)
stale_t = DateTime.add(now, -4 * 3600, :second)
hrdps_dir = ScalarFile.dir_for_hrdps(stale_t)
File.mkdir_p!(hrdps_dir)
payload =
Msgpax.pack!(
[%{"lat" => 53.0, "lon" => -60.0, "valid_time" => DateTime.to_iso8601(stale_t), "temperature" => 8.0}],
iodata: false
)
File.write!(Path.join(hrdps_dir, "10_-12.mp.gz"), :zlib.gzip(payload))
on_exit(fn -> File.rm_rf!(hrdps_dir) end)
{:ok, _lv, html} = live(conn, ~p"/weather-ca")
iso = DateTime.to_iso8601(stale_t)
assert html =~ iso, "4-hour-old HRDPS valid_time should appear in /weather-ca timeline"
end
end