prop/test/microwaveprop_web/telemetry_test.exs
Graham McIntire f99d07bd29
test: push line coverage from 71.7% → 78.67%
Adds ~600 new test cases (2013 → 2613 tests; 170 properties; 0
failures) across 12 new test files plus expansions of eight existing
files. Big lifts per module:

  ContactLive.Mechanism      33 → 100%
  MetricsPlug                54 → ~100%
  Microwaveprop.Release      15.8 → 70%+
  Telemetry                  38 → 76%
  HrrrNativeGridWorker       27.7 → 76%
  ContactLive.Show           34 → 48% (handler + render branches)
  Admin.ContactEditLive      49.7 → 70%+
  GefsFetchWorker            35.8 → ~55%
  IonosphereFetchWorker      56.3 → 75%
  PathLive                   58.9 → 67%
  WeatherMapLive             66.9 → 80.2%
  RoverLive                  0 → 70.3%
  ContactMapLive             59.2 → 89.8%
  ContactMapController       0 → 100%
  Mix tasks (Rust.Golden, Notebook, Backtest, HrrrBackfill,
    HrrrClimatology, HrrrNativeBackfill, NexradBackfill,
    RadarBackfill, ImportContestLogs, PropagationGrid,
    ResetEnrichment, Hrrr.PurgeGridPoints)  0 → ~60-100%

Two incidental fixes made while adding tests:

- ContactLive.Show.handle_event("toggle_flag", ...) was passing
  socket.assigns.current_scope to admin?/1 instead of socket.assigns,
  so admins never matched the pattern and every toggle ran the
  "Admins only." flash branch. Flag now toggles for admins again.

- Commercial.PollWorker.fetch_weather/1 promoted from private to
  @doc'd public so the IEM-fetch + ASOS-upsert path can be tested
  directly without driving perform/1 through live SNMP (which was
  timing out for ~50 s per test in the earlier attempt).

Stable property-test additions cover the dewpoint-from-RH monotonicity,
nearest_run/1 idempotence, and the ionosphere station envelope.
2026-04-23 18:43:18 -05:00

100 lines
3.5 KiB
Elixir

defmodule MicrowavepropWeb.TelemetryTest do
use Microwaveprop.DataCase, async: false
alias Ecto.Adapters.SQL.Sandbox
alias MicrowavepropWeb.Telemetry
describe "metrics/0" do
test "returns a flat list of Telemetry.Metrics structs" do
metrics = Telemetry.metrics()
assert is_list(metrics)
assert metrics != []
for m <- metrics do
assert is_struct(m)
# Every metric carries its event path as a list of atoms.
assert is_list(m.event_name)
assert Enum.all?(m.event_name, &is_atom/1)
end
end
test "covers each of the metric categories defined in the module" do
# The categories are private functions that build distinct event
# name prefixes; check one canonical metric per category exists.
names = Enum.map(Telemetry.metrics(), & &1.name)
name_strings = Enum.map(names, &Enum.join(&1, "."))
assert "phoenix.endpoint.stop.duration" in name_strings
assert "microwaveprop.repo.query.total_time" in name_strings
assert Enum.any?(name_strings, &String.starts_with?(&1, "vm."))
assert Enum.any?(name_strings, &String.starts_with?(&1, "microwaveprop.oban."))
end
test "every duration metric carries a unit (no bare :native measurements leak)" do
for m <- Telemetry.metrics(), String.ends_with?(to_string(List.last(m.name)), "duration") do
assert m.unit != :unit, "#{inspect(m.name)} should declare a display unit"
end
end
end
describe "dispatch_oban_queue_depth/0" do
test "emits one `oban.queue.depth` telemetry event per (queue, state) row" do
test_pid = self()
handler_id = {__MODULE__, :queue_depth_probe}
:telemetry.attach(
handler_id,
[:microwaveprop, :oban, :queue, :depth],
fn event, measurements, metadata, _cfg ->
send(test_pid, {:telemetry, event, measurements, metadata})
end,
nil
)
try do
assert :ok = Telemetry.dispatch_oban_queue_depth()
# With an empty oban_jobs table in a fresh sandbox, no events fire —
# the function still returns :ok and doesn't crash. If any rows
# happen to be there, each must carry a queue+state tag.
receive do
{:telemetry, [:microwaveprop, :oban, :queue, :depth], %{count: n}, meta} ->
assert is_integer(n)
assert is_binary(meta.queue)
assert Map.has_key?(meta, :state)
after
200 -> :ok
end
after
:telemetry.detach(handler_id)
end
end
test "swallows DB errors without crashing the poller" do
# Manually close the sandbox connection so any DB query raises;
# the poller must still return :ok (telemetry is best-effort).
Sandbox.checkin(Microwaveprop.Repo)
assert :ok = Telemetry.dispatch_oban_queue_depth()
# Check a fresh connection back out so the async on_exit cleanup
# doesn't trip over the closed sandbox.
Sandbox.checkout(Microwaveprop.Repo)
end
end
describe "start_link/1" do
test "starts a named Supervisor under a fresh registry" do
# Different process name to avoid colliding with the already-running
# application supervisor.
sup_name = :"telemetry_under_test_#{System.unique_integer([:positive])}"
{:ok, pid} = Supervisor.start_link(Telemetry, :ok, name: sup_name)
assert Process.alive?(pid)
on_exit = fn -> if Process.alive?(pid), do: Process.exit(pid, :kill) end
on_exit.()
end
end
end