defmodule Microwaveprop.Workers.RtmaFetchWorkerTest do use Microwaveprop.DataCase, async: false use Oban.Testing, repo: Microwaveprop.Repo alias Microwaveprop.Weather.RtmaClient alias Microwaveprop.Weather.RtmaObservation alias Microwaveprop.Workers.RtmaFetchWorker describe "perform/1" do test "skips the fetch (no HTTP) when a matching row already exists in the snapped bounding box" do valid_time = ~U[2026-04-15 18:00:00Z] # Pre-populate a row at the snapped coordinates (32.9 → 32.9 at 1/40°, # -97.0 → -97.0). The worker's bounding-box short-circuit must find it # and return :ok without contacting RtmaClient. %RtmaObservation{} |> RtmaObservation.changeset(%{ lat: 32.9, lon: -97.0, valid_time: valid_time, temp_c: 20.0 }) |> Repo.insert!() Req.Test.stub(RtmaClient, fn _conn -> raise "RtmaClient should not be contacted when an observation already exists" end) args = %{ "lat" => 32.9, "lon" => -97.0, "valid_time" => DateTime.to_iso8601(valid_time) } assert :ok = RtmaFetchWorker.perform(%Oban.Job{args: args}) assert Repo.aggregate(RtmaObservation, :count, :id) == 1 end # With garbage-bytes-on-the-wire (no real GRIB2 fixture), the extractor # yields no fields and RtmaClient returns {:error, "RTMA: no data ..."}, # which the worker surfaces as {:error, _}. This characterizes that the # worker propagates the client error (and does NOT insert a blank row). test "returns {:error, _} and inserts nothing when the client fails to extract" do idx = """ 1:0:d=2026041518:TMP:2 m above ground:anl: 2:100:d=2026041518:DPT:2 m above ground:anl: """ Req.Test.stub(RtmaClient, fn conn -> if String.ends_with?(conn.request_path, ".idx") do Plug.Conn.send_resp(conn, 200, idx) else Plug.Conn.send_resp(conn, 206, :binary.copy(<<0>>, 1024)) end end) args = %{ "lat" => 32.9, "lon" => -97.0, "valid_time" => "2026-04-15T18:00:00Z" } assert {:error, reason} = RtmaFetchWorker.perform(%Oban.Job{args: args}) assert reason =~ "RTMA: no data" assert Repo.aggregate(RtmaObservation, :count, :id) == 0 end test "returns {:error, _} when upstream idx returns 404" do Req.Test.stub(RtmaClient, fn conn -> if String.ends_with?(conn.request_path, ".idx") do Plug.Conn.send_resp(conn, 404, "not yet published") else Plug.Conn.send_resp(conn, 500, "unexpected") end end) args = %{ "lat" => 32.9, "lon" => -97.0, "valid_time" => "2026-04-15T18:00:00Z" } assert {:error, reason} = RtmaFetchWorker.perform(%Oban.Job{args: args}) assert reason =~ "RTMA idx HTTP 404" assert Repo.aggregate(RtmaObservation, :count, :id) == 0 end test "raises FunctionClauseError when args are missing required keys" do # The worker only matches the shape with lat/lon/valid_time — missing # keys must crash loudly rather than silently succeed. This pins the # current fail-fast contract. assert_raise FunctionClauseError, fn -> RtmaFetchWorker.perform(%Oban.Job{args: %{}}) end end test "raises when valid_time is not an ISO8601 string" do # DateTime.from_iso8601/1 returns {:error, _}, and the `{:ok, _, _} =` # match raises — another fail-fast contract worth pinning. assert_raise MatchError, fn -> RtmaFetchWorker.perform(%Oban.Job{ args: %{"lat" => 32.9, "lon" => -97.0, "valid_time" => "not-a-timestamp"} }) end end end end