77 unit tests + 23 property tests across four parallel agents. - ContactLive.Show (72.01% → ~75%): every factor-note branch (humidity/time/td/refractivity/pressure/season/pwat), all propagation_mechanism classifications, apply_admin_edit/owner_edit/ submit_user_edit error paths, internal_network? IP shapes, expanded sounding skew-T, composite-score [0,100] property. - PathLive + BeaconLive.Show + Weather: coordinate-pair resolver, invalid grid, empty-DB edge cases for iemre_for_*, narr_for_*, nearest_native_duct_*, find_nearest_rtma, recent_surface_obs; properties for round_to_hrrr_grid/round_to_iemre_grid/band round-trip/beacon-id URL round-trip. - Viewshed + Duct + SoundingParams: find_reach_km edge cases, destination_point, effective_reach_km fractions, detect_ducts trailing-duct + guard branches; 13 properties including flat- terrain-fully-visible, thicker-duct-lower-freq, M-profile monotonicity, feature-vector deterministic length. - Workers + Mix tasks: GefsFetchWorker 500/429/403 + malformed ISO8601, HrrrNativeGridWorker snap-to-3-decimals + empty DB, IonosphereFetchWorker 404 + non-tabular body, RadarBackfill --dry-run + --year + --limit, NexradBackfill --limit 0, Backtest --feature + --all; year-filter property covering 2015..2026. 170 → 205 properties, 2666 → 2743 tests, 0 failures.
111 lines
3.8 KiB
Elixir
111 lines
3.8 KiB
Elixir
defmodule Microwaveprop.Workers.IonosphereFetchWorkerTest do
|
||
use Microwaveprop.DataCase, async: false
|
||
use Oban.Testing, repo: Microwaveprop.Repo
|
||
|
||
alias Microwaveprop.Ionosphere
|
||
alias Microwaveprop.Ionosphere.GiroClient
|
||
alias Microwaveprop.Ionosphere.Observation
|
||
alias Microwaveprop.Workers.IonosphereFetchWorker
|
||
|
||
@sample File.read!("test/fixtures/giro/mhj45_sample.txt")
|
||
|
||
describe "perform/1" do
|
||
test "pulls each configured station and upserts the parsed rows" do
|
||
Req.Test.stub(GiroClient, fn conn ->
|
||
assert conn.method == "GET"
|
||
params = Plug.Conn.fetch_query_params(conn).query_params
|
||
assert params["ursiCode"] in ~w(MHJ45 AL945)
|
||
|
||
Plug.Conn.resp(conn, 200, @sample)
|
||
end)
|
||
|
||
assert :ok = perform_job(IonosphereFetchWorker, %{})
|
||
|
||
# 25 observations per station × 2 stations = 50 rows (fixture has 25
|
||
# data lines; upserts go to each station code separately).
|
||
assert Repo.aggregate(Observation, :count, :id) == 50
|
||
assert Ionosphere.latest_observation("MHJ45")
|
||
assert Ionosphere.latest_observation("AL945")
|
||
end
|
||
|
||
test "survives a station's fetch error and still ingests the others" do
|
||
Req.Test.stub(GiroClient, fn conn ->
|
||
params = Plug.Conn.fetch_query_params(conn).query_params
|
||
|
||
if params["ursiCode"] == "MHJ45" do
|
||
Plug.Conn.resp(conn, 500, "boom")
|
||
else
|
||
Plug.Conn.resp(conn, 200, @sample)
|
||
end
|
||
end)
|
||
|
||
assert :ok = perform_job(IonosphereFetchWorker, %{})
|
||
|
||
# MHJ45 failed, AL945 ingested.
|
||
assert Ionosphere.latest_observation("MHJ45") == nil
|
||
assert Ionosphere.latest_observation("AL945")
|
||
end
|
||
|
||
test "tolerates an HTTP transport failure with :ok + no rows inserted" do
|
||
Req.Test.stub(GiroClient, fn conn ->
|
||
Req.Test.transport_error(conn, :timeout)
|
||
end)
|
||
|
||
assert :ok = perform_job(IonosphereFetchWorker, %{})
|
||
assert Repo.aggregate(Observation, :count, :id) == 0
|
||
end
|
||
end
|
||
|
||
describe "stations/0" do
|
||
test "exposes a non-empty list of CONUS ionosonde stations" do
|
||
stations = IonosphereFetchWorker.stations()
|
||
|
||
assert is_list(stations)
|
||
assert length(stations) >= 2
|
||
|
||
# Every station entry has the shape the fetcher expects, with a
|
||
# plausible CONUS lat/lon envelope.
|
||
for s <- stations do
|
||
assert is_binary(s.code)
|
||
assert is_binary(s.name)
|
||
assert is_float(s.lat) or is_integer(s.lat)
|
||
assert is_float(s.lon) or is_integer(s.lon)
|
||
assert s.lat > 20.0 and s.lat < 75.0
|
||
assert s.lon > -170.0 and s.lon < -60.0
|
||
end
|
||
end
|
||
end
|
||
|
||
describe "perform/1 additional HTTP branches" do
|
||
test "tolerates an HTTP 404 with :ok and zero rows inserted" do
|
||
# 404 is returned by GIRO for stations with no data in the window.
|
||
# The worker should treat it as a per-station no-op and return :ok.
|
||
Req.Test.stub(GiroClient, fn conn ->
|
||
Plug.Conn.send_resp(conn, 404, "not found")
|
||
end)
|
||
|
||
assert :ok = perform_job(IonosphereFetchWorker, %{})
|
||
assert Repo.aggregate(Observation, :count, :id) == 0
|
||
end
|
||
|
||
test "tolerates a non-tabular response body gracefully" do
|
||
# A body that doesn't match the expected comment/columns format
|
||
# should parse to zero rows — no crash, no inserts.
|
||
Req.Test.stub(GiroClient, fn conn ->
|
||
Plug.Conn.resp(conn, 200, "not a tabular response at all")
|
||
end)
|
||
|
||
assert :ok = perform_job(IonosphereFetchWorker, %{})
|
||
assert Repo.aggregate(Observation, :count, :id) == 0
|
||
end
|
||
|
||
test "tolerates an empty-body 200 response" do
|
||
Req.Test.stub(GiroClient, fn conn ->
|
||
Plug.Conn.resp(conn, 200, "")
|
||
end)
|
||
|
||
assert :ok = perform_job(IonosphereFetchWorker, %{})
|
||
assert Repo.aggregate(Observation, :count, :id) == 0
|
||
end
|
||
end
|
||
end
|