New test files: RoverPlanning.Station, LiveStashGuard, RebatchAsos, RoverMissionReconcileWorker, RoverPathProfileWorker. Extended existing suites for PathCompute (resolve_location, build_ionosphere_readout), HrdpsClient (DEPR fallback, missing-level skip), NexradClient (fetch_rain_cells HTTP error paths), UserHomeQthLookupWorker (error + Maidenhead-fallback paths), RoverPlanning.Path (statuses/0 + schema). 3512 tests passing.
212 lines
6.2 KiB
Elixir
212 lines
6.2 KiB
Elixir
defmodule Microwaveprop.Weather.RebatchAsosTest do
|
||
use Microwaveprop.DataCase, async: false
|
||
|
||
import ExUnit.CaptureIO
|
||
|
||
alias Microwaveprop.Repo
|
||
alias Microwaveprop.Weather.RebatchAsos
|
||
|
||
describe "run/1 — no pending jobs" do
|
||
test "returns zeros and prints a no-op message when no jobs exist" do
|
||
output =
|
||
capture_io(fn ->
|
||
assert summary = RebatchAsos.run()
|
||
send(self(), summary)
|
||
end)
|
||
|
||
assert_received %{found: 0, windows: 0, inserted: 0, cancelled: 0}
|
||
assert output =~ "No pending"
|
||
end
|
||
end
|
||
|
||
describe "to_day_jobs/1 — no pending jobs" do
|
||
test "returns zeros when no asos/asos_batch jobs exist" do
|
||
output =
|
||
capture_io(fn ->
|
||
assert summary = RebatchAsos.to_day_jobs()
|
||
send(self(), summary)
|
||
end)
|
||
|
||
assert_received %{source_jobs: 0, unique_day_keys: 0, inserted: 0, cancelled: 0}
|
||
assert output =~ "No pending"
|
||
end
|
||
end
|
||
|
||
describe "run/1 — dry-run with pending jobs" do
|
||
test "groups by (start_dt, end_dt) and reports without mutating" do
|
||
insert_asos_job!(%{
|
||
"fetch_type" => "asos",
|
||
"station_id" => 1,
|
||
"station_code" => "KDFW",
|
||
"start_dt" => "2026-04-18T00:00:00Z",
|
||
"end_dt" => "2026-04-18T04:00:00Z"
|
||
})
|
||
|
||
insert_asos_job!(%{
|
||
"fetch_type" => "asos",
|
||
"station_id" => 2,
|
||
"station_code" => "KDAL",
|
||
"start_dt" => "2026-04-18T00:00:00Z",
|
||
"end_dt" => "2026-04-18T04:00:00Z"
|
||
})
|
||
|
||
output =
|
||
capture_io(fn ->
|
||
summary = RebatchAsos.run(dry_run: true)
|
||
send(self(), summary)
|
||
end)
|
||
|
||
assert_received %{found: 2, windows: 1, inserted: 0, cancelled: 0}
|
||
assert output =~ "[dry-run]"
|
||
assert output =~ "2026-04-18T00:00:00Z"
|
||
# Original two jobs should still be available; nothing was cancelled.
|
||
assert pending_asos_count() == 2
|
||
end
|
||
end
|
||
|
||
describe "to_day_jobs/1 — dry-run with pending asos jobs" do
|
||
test "expands one asos job into a per-date day-key plan" do
|
||
# A single 4-hour window inside one UTC day → 1 unique (station, date).
|
||
insert_asos_job!(%{
|
||
"fetch_type" => "asos",
|
||
"station_id" => 1,
|
||
"station_code" => "KDFW",
|
||
"start_dt" => "2026-04-18T00:00:00Z",
|
||
"end_dt" => "2026-04-18T04:00:00Z"
|
||
})
|
||
|
||
output =
|
||
capture_io(fn ->
|
||
summary = RebatchAsos.to_day_jobs(dry_run: true)
|
||
send(self(), summary)
|
||
end)
|
||
|
||
assert_received %{source_jobs: 1, unique_day_keys: 1, inserted: 0, cancelled: 0}
|
||
assert output =~ "[dry-run]"
|
||
assert output =~ "KDFW"
|
||
assert output =~ "2026-04-18"
|
||
end
|
||
|
||
test "expands an asos_batch into one day-key per (station, date)" do
|
||
insert_asos_job!(%{
|
||
"fetch_type" => "asos_batch",
|
||
"station_ids" => [1, 2],
|
||
"station_codes" => ["KDFW", "KDAL"],
|
||
"start_dt" => "2026-04-18T00:00:00Z",
|
||
"end_dt" => "2026-04-18T04:00:00Z"
|
||
})
|
||
|
||
capture_io(fn ->
|
||
summary = RebatchAsos.to_day_jobs(dry_run: true)
|
||
send(self(), summary)
|
||
end)
|
||
|
||
# One asos_batch with 2 stations × 1 date → 2 unique (station, date) keys.
|
||
assert_received %{source_jobs: 1, unique_day_keys: 2}
|
||
end
|
||
|
||
test "asos jobs spanning two UTC dates produce two day-keys per station" do
|
||
insert_asos_job!(%{
|
||
"fetch_type" => "asos",
|
||
"station_id" => 1,
|
||
"station_code" => "KDFW",
|
||
"start_dt" => "2026-04-18T22:00:00Z",
|
||
"end_dt" => "2026-04-19T02:00:00Z"
|
||
})
|
||
|
||
capture_io(fn ->
|
||
summary = RebatchAsos.to_day_jobs(dry_run: true)
|
||
send(self(), summary)
|
||
end)
|
||
|
||
# 1 station × 2 dates → 2 unique keys.
|
||
assert_received %{source_jobs: 1, unique_day_keys: 2}
|
||
end
|
||
|
||
test "asos jobs missing window timestamps contribute zero day-keys" do
|
||
insert_asos_job!(%{
|
||
"fetch_type" => "asos",
|
||
"station_id" => 1,
|
||
"station_code" => "KDFW",
|
||
"start_dt" => nil,
|
||
"end_dt" => nil
|
||
})
|
||
|
||
capture_io(fn ->
|
||
summary = RebatchAsos.to_day_jobs(dry_run: true)
|
||
send(self(), summary)
|
||
end)
|
||
|
||
# Job is found but expand_tuples returns [] for nil window.
|
||
assert_received %{source_jobs: 1, unique_day_keys: 0}
|
||
end
|
||
end
|
||
|
||
describe "run/1 — apply (non-dry-run)" do
|
||
test "inserts an asos_batch job and cancels the source rows" do
|
||
Oban.Testing.with_testing_mode(:manual, fn ->
|
||
insert_asos_job!(%{
|
||
"fetch_type" => "asos",
|
||
"station_id" => 1,
|
||
"station_code" => "KDFW",
|
||
"start_dt" => "2026-04-18T00:00:00Z",
|
||
"end_dt" => "2026-04-18T04:00:00Z"
|
||
})
|
||
|
||
capture_io(fn ->
|
||
summary = RebatchAsos.run()
|
||
send(self(), summary)
|
||
end)
|
||
end)
|
||
|
||
assert_received %{found: 1, windows: 1, inserted: inserted, cancelled: cancelled}
|
||
assert inserted >= 1
|
||
assert cancelled >= 1
|
||
end
|
||
end
|
||
|
||
describe "to_day_jobs/1 — apply (non-dry-run)" do
|
||
test "inserts asos_day jobs and cancels source rows" do
|
||
Oban.Testing.with_testing_mode(:manual, fn ->
|
||
insert_asos_job!(%{
|
||
"fetch_type" => "asos",
|
||
"station_id" => 1,
|
||
"station_code" => "KDFW",
|
||
"start_dt" => "2026-04-18T00:00:00Z",
|
||
"end_dt" => "2026-04-18T04:00:00Z"
|
||
})
|
||
|
||
capture_io(fn ->
|
||
summary = RebatchAsos.to_day_jobs()
|
||
send(self(), summary)
|
||
end)
|
||
end)
|
||
|
||
assert_received %{source_jobs: 1, unique_day_keys: 1, inserted: inserted, cancelled: cancelled}
|
||
assert inserted >= 1
|
||
assert cancelled >= 1
|
||
end
|
||
end
|
||
|
||
# ── helpers ───────────────────────────────────────────────────────────
|
||
|
||
defp insert_asos_job!(args) do
|
||
{:ok, job} =
|
||
args
|
||
|> Oban.Job.new(worker: "Microwaveprop.Workers.WeatherFetchWorker", queue: "weather")
|
||
|> Repo.insert()
|
||
|
||
job
|
||
end
|
||
|
||
defp pending_asos_count do
|
||
Repo.aggregate(
|
||
Ecto.Query.from(j in Oban.Job,
|
||
where:
|
||
j.worker == "Microwaveprop.Workers.WeatherFetchWorker" and
|
||
j.state in ["available", "scheduled", "retryable"]
|
||
),
|
||
:count
|
||
)
|
||
end
|
||
end
|