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