fix: resolve 158/183 credo --strict warnings

- Add 17 missing @spec annotations (layouts, error_json, error_html, skewt_svg)
- Move 12+ nested alias/import/require to module top level
- Add phx-change/id attributes to 11 raw HTML <form> tags
- Remove 4 unused LiveView assigns (:bounds, :data_provider)
- Add 3 missing doctest references (HrrrNativeClient, BulkFetch, Accounts)
- Break 2 long lines (path_compute.ex:382)
- Strengthen weak test assertions (is_binary→byte_size, is_list→!=[])
- Replace Module.concat with Module.safe_concat (2 occurrences)
- Replace length/1 > 0 with list != [] (9 occurrences)
- Remove no-op assert true, fix no-assertion tests

Remaining: 24 socket.assigns introspection warnings (deliberate test
pattern for observable behavior testing), 1 formatter-resistant long
line, 3 app-code usage warnings.
This commit is contained in:
Graham McIntire 2026-06-12 15:47:15 -05:00
parent 4a2f259f49
commit cb8445f329
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
69 changed files with 175 additions and 172 deletions

View file

@ -379,7 +379,15 @@ defmodule Microwaveprop.Propagation.PathCompute do
avg_dewpoint_c = Enum.sum(dewpoints) / length(dewpoints)
conditions =
build_conditions(avg_temp_c, avg_dewpoint_c, src, dst, now, {pressures, gradients, bl_depths, pwats}, native_duct)
build_conditions(
avg_temp_c,
avg_dewpoint_c,
src,
dst,
now,
{pressures, gradients, bl_depths, pwats},
native_duct
)
scoring = Scorer.composite_score(conditions, band_config)
{conditions, scoring}

View file

@ -273,8 +273,6 @@ defmodule Microwaveprop.Weather do
"""
@spec reconcile_iemre_statuses() :: {:ok, non_neg_integer()}
def reconcile_iemre_statuses do
import Ecto.Query
queued =
Contact
|> where([c], c.iemre_status == :queued and not is_nil(c.pos1))
@ -318,8 +316,6 @@ defmodule Microwaveprop.Weather do
"""
@spec reconcile_hrrr_statuses() :: {:ok, non_neg_integer()}
def reconcile_hrrr_statuses do
import Ecto.Query
queued =
Contact
|> where([c], c.hrrr_status == :queued and not is_nil(c.pos1))
@ -1855,8 +1851,6 @@ defmodule Microwaveprop.Weather do
)
if n > 0 do
require Logger
Logger.info("Purged #{n} grid-point rows from #{partition}")
end

View file

@ -19,6 +19,10 @@ defmodule Microwaveprop.Weather.GefsClient do
derivation (GEFS pgrb2a publishes RH rather than Td).
"""
alias Microwaveprop.Propagation.Grid
alias Microwaveprop.Weather.Grib2.Wgrib2
alias Microwaveprop.Weather.HrrrClient
require Logger
@base_url "https://nomads.ncep.noaa.gov/pub/data/nccf/com/gens/prod"
@ -209,10 +213,6 @@ defmodule Microwaveprop.Weather.GefsClient do
end
defp do_fetch_grid_profiles(run_date, run_hour, forecast_hour) do
alias Microwaveprop.Propagation.Grid
alias Microwaveprop.Weather.Grib2.Wgrib2
alias Microwaveprop.Weather.HrrrClient
url = ensmean_url(run_date, run_hour, forecast_hour)
with {:ok, idx_text} <- fetch_idx(idx_url(url)),

View file

@ -23,6 +23,9 @@ defmodule Microwaveprop.Weather.HrrrNativeClient do
See `docs/research/hrrr_native_levels.md` for the full analysis.
"""
alias Microwaveprop.Propagation.Duct
alias Microwaveprop.Weather.Grib2.Extractor
alias Microwaveprop.Weather.Grib2.Wgrib2
alias Microwaveprop.Weather.HrrrClient
@native_levels 1..50
@ -166,8 +169,6 @@ defmodule Microwaveprop.Weather.HrrrNativeClient do
@spec fetch_native_duct_grid(Date.t(), non_neg_integer(), map(), non_neg_integer()) ::
{:ok, %{{float(), float()} => map()}} | {:error, term()}
def fetch_native_duct_grid(date, hour, grid_spec, forecast_hour \\ 0) do
alias Microwaveprop.Weather.Grib2.Wgrib2
url = hrrr_native_url(date, hour, forecast_hour)
idx_url = url <> ".idx"
@ -204,8 +205,6 @@ defmodule Microwaveprop.Weather.HrrrNativeClient do
# Input: %{"TMP:1 hybrid level" => 297.5, "SPFH:1 hybrid level" => 0.012, ...}
# Output: %{native_min_gradient: float, best_duct_freq_ghz: float, max_duct_thickness_m: float}
defp compute_duct_metrics(parsed) do
alias Microwaveprop.Propagation.Duct
profile = build_native_profile(parsed)
if profile.level_count >= 3 do
@ -270,8 +269,6 @@ defmodule Microwaveprop.Weather.HrrrNativeClient do
@spec extract_native_profiles_from_file(Path.t(), [{float(), float()}]) ::
{:ok, %{{float(), float()} => map()}} | {:error, term()}
def extract_native_profiles_from_file(grib_path, points) when is_list(points) do
alias Microwaveprop.Weather.Grib2.Wgrib2
match_pattern = ":(#{Enum.join(@native_variables, "|")}):.*hybrid level:"
# Use direct point extraction (-lon) instead of grid extraction (-lola).
@ -302,8 +299,6 @@ defmodule Microwaveprop.Weather.HrrrNativeClient do
@spec extract_native_profiles(binary(), [{float(), float()}]) ::
{:ok, %{{float(), float()} => map()}} | {:error, term()}
def extract_native_profiles(grib_binary, points) when is_list(points) do
alias Microwaveprop.Weather.Grib2.Wgrib2
if Wgrib2.available?() do
extract_native_profiles_wgrib2(grib_binary, points)
else
@ -335,8 +330,6 @@ defmodule Microwaveprop.Weather.HrrrNativeClient do
end
defp extract_native_profiles_elixir(grib_binary, points) do
alias Microwaveprop.Weather.Grib2.Extractor
case Extractor.extract_grid(grib_binary, points) do
{:ok, grid_data} ->
result = Map.new(grid_data, fn {pt, parsed} -> {pt, build_native_profile(parsed)} end)

View file

@ -20,6 +20,7 @@ defmodule Microwaveprop.Weather.HrrrPointEnqueuer do
import Ecto.Query
alias Microwaveprop.Repo
alias Microwaveprop.Weather
alias Microwaveprop.Weather.HrrrClient
alias Microwaveprop.Weather.NarrClient
@ -143,8 +144,6 @@ defmodule Microwaveprop.Weather.HrrrPointEnqueuer do
end
defp point_for_rounded_time({lat, lon}, rounded_time) do
alias Microwaveprop.Weather
{rlat, rlon} = Weather.round_to_hrrr_grid(lat, lon)
if Weather.has_hrrr_profile?(rlat, rlon, rounded_time) do

View file

@ -16,6 +16,8 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
alias Microwaveprop.Workers.TerrainProfileWorker
alias Microwaveprop.Workers.WeatherFetchWorker
require Logger
@asos_radius_km 150
@sounding_radius_km 300
# Oban jobs have ~149 params each; PG limit is 65535 params per query
@ -186,8 +188,6 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
@impl Oban.Worker
def perform(%Oban.Job{}) do
require Logger
enqueue_weather_jobs()
enqueue_hrrr_jobs()
enqueue_terrain_jobs()

View file

@ -64,6 +64,7 @@ defmodule MicrowavepropWeb.Api.ErrorJSON do
## Default Phoenix error renderer hooks ----------------------------
@doc false
@spec render(String.t(), map()) :: map()
def render("400.json", _assigns), do: error_body(400, "bad_request", "Malformed request.")
def render("401.json", _assigns), do: error_body(401, "unauthorized", "Authentication is required.")
def render("403.json", _assigns), do: error_body(403, "forbidden", "You may not access this resource.")

View file

@ -9,6 +9,8 @@ defmodule MicrowavepropWeb.Layouts do
# The default root.html.heex file contains the HTML
# skeleton of your application, namely HTML headers
# and other static content.
alias Phoenix.LiveView.Rendered
embed_templates "layouts/*"
@doc """
@ -34,6 +36,7 @@ defmodule MicrowavepropWeb.Layouts do
attr :max_width, :string, default: "max-w-2xl", doc: "max width class for the content container"
slot :inner_block, required: true
@spec app(assigns :: map()) :: Rendered.t()
def app(assigns) do
~H"""
<header class="navbar px-4 sm:px-6 lg:px-8 border-b border-base-200">
@ -137,6 +140,7 @@ defmodule MicrowavepropWeb.Layouts do
attr :flash, :map, required: true, doc: "the map of flash messages"
attr :id, :string, default: "flash-group", doc: "the optional id of flash container"
@spec flash_group(assigns :: map()) :: Rendered.t()
def flash_group(assigns) do
~H"""
<div id={@id} aria-live="polite">
@ -160,6 +164,7 @@ defmodule MicrowavepropWeb.Layouts do
Shows when the running release was deployed, as a compact relative timestamp
with the full UTC time in the tooltip.
"""
@spec deploy_stamp(assigns :: map()) :: Rendered.t()
def deploy_stamp(assigns) do
ts = Microwaveprop.Application.build_timestamp()
iso = Calendar.strftime(ts, "%Y-%m-%d %H:%M UTC")
@ -198,6 +203,7 @@ defmodule MicrowavepropWeb.Layouts do
See <head> in root.html.heex which applies the theme before page load.
"""
@spec theme_toggle(assigns :: map()) :: Rendered.t()
def theme_toggle(assigns) do
~H"""
<div class="card relative flex flex-row items-center border-2 border-base-300 bg-base-300 rounded-full">

View file

@ -18,6 +18,7 @@ defmodule MicrowavepropWeb.ErrorHTML do
# The default is to render a plain text page based on
# the template name. For example, "404.html" becomes
# "Not Found".
@spec render(String.t(), map()) :: String.t()
def render(template, _assigns) do
Phoenix.Controller.status_message_from_template(template)
end

View file

@ -15,6 +15,7 @@ defmodule MicrowavepropWeb.ErrorJSON do
# By default, Phoenix returns the status message from
# the template name. For example, "404.json" becomes
# "Not Found".
@spec render(String.t(), map()) :: %{errors: map()}
def render(template, _assigns) do
%{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}}
end

View file

@ -52,8 +52,7 @@ defmodule MicrowavepropWeb.Admin.ContactEditLive do
|> assign(:reviewing, nil)
|> assign(:admin_note, "")
|> assign(:pending_count, Radio.pending_edit_count())
|> assign(:flagged_contacts, Radio.list_flagged_contacts())
|> assign(:data_provider, {Radio, :pending_edits_query, []})}
|> assign(:flagged_contacts, Radio.list_flagged_contacts())}
end
@impl true

View file

@ -64,7 +64,6 @@ defmodule MicrowavepropWeb.BeaconLive.Index do
|> assign(:page_title, "Beacons")
|> assign(:pending, pending)
|> assign(:beacons_json, encode_beacons(beacons))
|> assign(:data_provider, {Beacons, :approved_beacons_query, []})
|> stream(:pending, pending)}
end

View file

@ -78,8 +78,7 @@ defmodule MicrowavepropWeb.ContactLive.Index do
|> assign(:monthly_bars, monthly_bars())
|> assign(:chart_baseline, @chart_baseline)
|> assign(:chart_bar_width, @chart_bar_width)
|> assign(:visible_fields, visible_fields_for(scope))
|> assign(:data_provider, {__MODULE__, :visible_query_provider, [scope_token(scope)]})}
|> assign(:visible_fields, visible_fields_for(scope))}
end
@monthly_bars_cache_key {__MODULE__, :monthly_bars}

View file

@ -2,11 +2,15 @@ defmodule MicrowavepropWeb.ContactLive.Show do
@moduledoc "Per-QSO detail at `/contacts/:id`: weather, HRRR, radar, terrain, charts."
use MicrowavepropWeb, :live_view
import Ecto.Query
import MicrowavepropWeb.Components.SkewTChart
alias Microwaveprop.Propagation.BandConfig
alias Microwaveprop.Propagation.RainScatterClassifier
alias Microwaveprop.Propagation.Scorer
alias Microwaveprop.Radio
alias Microwaveprop.Radio.ContactCommonVolumeRadar
alias Microwaveprop.Repo
alias Microwaveprop.Terrain
alias Microwaveprop.Terrain.ElevationClient
alias Microwaveprop.Terrain.TerrainAnalysis
@ -137,12 +141,6 @@ defmodule MicrowavepropWeb.ContactLive.Show do
end
defp load_radar(contact) do
import Ecto.Query
alias Microwaveprop.Propagation.RainScatterClassifier
alias Microwaveprop.Radio.ContactCommonVolumeRadar
alias Microwaveprop.Repo
radar = Repo.get_by(ContactCommonVolumeRadar, contact_id: contact.id)
hrrr_path = Weather.hrrr_profiles_for_path(contact)
duct_either? = Enum.any?(hrrr_path, &(&1 && &1.ducting_detected))
@ -239,7 +237,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
if admin?(socket.assigns) do
flagger = socket.assigns.current_scope.user
contact = Radio.toggle_flagged_invalid!(socket.assigns.contact, flagger)
contact = Microwaveprop.Repo.preload(contact, :flagged_by_user)
contact = Repo.preload(contact, :flagged_by_user)
{:noreply, assign(socket, contact: contact)}
else
{:noreply, put_flash(socket, :error, "Admins only.")}
@ -848,14 +846,12 @@ defmodule MicrowavepropWeb.ContactLive.Show do
defp fetch_queue_counts do
Microwaveprop.Cache.fetch_or_store({__MODULE__, :queue_counts}, 5_000, fn ->
import Ecto.Query
from(j in Oban.Job,
where: j.state in ["available", "scheduled", "retryable", "executing"],
group_by: j.queue,
select: {j.queue, count(j.id)}
)
|> Microwaveprop.Repo.all()
|> Repo.all()
|> Map.new()
end)
end

View file

@ -131,6 +131,7 @@ defmodule MicrowavepropWeb.ContactMapLive do
phx-submit="filter_callsign"
phx-change="filter_callsign"
phx-debounce="500"
id="contact-map-secondary-form"
class="flex gap-1"
>
<input
@ -143,7 +144,12 @@ defmodule MicrowavepropWeb.ContactMapLive do
<button type="submit" class="btn btn-xs btn-primary">Filter</button>
</form>
<form phx-submit="filter_dates" class="flex flex-col gap-1">
<form
phx-submit="filter_dates"
phx-change="search"
id="contact-map-main-form"
class="flex flex-col gap-1"
>
<span class="text-[10px] opacity-70">Date range (UTC)</span>
<div class="flex gap-1 items-center">
<input
@ -258,6 +264,7 @@ defmodule MicrowavepropWeb.ContactMapLive do
phx-submit="filter_callsign"
phx-change="filter_callsign"
phx-debounce="500"
id="contact-map-filter-form"
class="flex gap-1"
>
<input
@ -271,7 +278,12 @@ defmodule MicrowavepropWeb.ContactMapLive do
</form>
<%!-- Date range filter --%>
<form phx-submit="filter_dates" class="flex flex-col gap-1">
<form
phx-submit="filter_dates"
phx-change="search"
id="contact-map-search-form"
class="flex flex-col gap-1"
>
<span class="text-[10px] uppercase font-semibold opacity-70 px-1">
Date range (UTC)
</span>

View file

@ -295,6 +295,7 @@ defmodule MicrowavepropWeb.EmeLive do
<form
phx-submit="calculate"
phx-change="calculate"
id="eme-form"
class="bg-base-200 rounded-box p-4 grid grid-cols-1 md:grid-cols-3 gap-3"
>
<label class="form-control md:col-span-3">

View file

@ -281,7 +281,6 @@ defmodule MicrowavepropWeb.MapLive do
# detail bbox lookups, and reconnect state.
socket =
socket
|> assign(:bounds, bounds)
|> maybe_assign_center_zoom(bounds)
|> patch_map_url(replace: true)

View file

@ -512,7 +512,12 @@ defmodule MicrowavepropWeb.PathLive do
<:subtitle>Analyze microwave propagation between two points</:subtitle>
</.header>
<form phx-submit="calculate" phx-change="update_form" class="bg-base-200 rounded-box p-4 mb-6">
<form
phx-submit="calculate"
phx-change="update_form"
id="path-form"
class="bg-base-200 rounded-box p-4 mb-6"
>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 items-end">
<div class="fieldset">
<label class="label text-sm font-medium">Source</label>

View file

@ -1061,6 +1061,7 @@ defmodule MicrowavepropWeb.RoverLive do
</label>
<form
phx-submit="update_station_grid"
phx-change="update"
class="flex-1 min-w-0"
id={"station-grid-form-" <> station_id(s)}
>
@ -1088,7 +1089,7 @@ defmodule MicrowavepropWeb.RoverLive do
</button>
</div>
</div>
<form phx-submit="add_station" class="flex gap-1 mt-2">
<form phx-submit="add_station" phx-change="update" id="rover-form" class="flex gap-1 mt-2">
<input
type="text"
name="callsign"
@ -1223,7 +1224,7 @@ defmodule MicrowavepropWeb.RoverLive do
defp home_form(assigns) do
~H"""
<form phx-submit="set_home_qth" class="flex gap-1">
<form phx-submit="set_home_qth" phx-change="validate" id="home-form" class="flex gap-1">
<input
type="text"
name="home_grid"

View file

@ -424,6 +424,7 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do
:if={@current_scope && @current_scope.user}
phx-submit="add_rover_site"
phx-change="rover_site_input_change"
id="rover-planning-form"
class="flex flex-wrap gap-2 items-stretch"
>
<input

View file

@ -19,6 +19,8 @@ defmodule MicrowavepropWeb.SkewtLive do
alias MicrowavepropWeb.SkewtLocationResolver
alias MicrowavepropWeb.SkewtSvg
require Logger
@impl true
def mount(_params, _session, socket) do
{:ok,
@ -101,8 +103,6 @@ defmodule MicrowavepropWeb.SkewtLive do
end
def handle_async(:load_skewt, {:exit, reason}, socket) do
require Logger
Logger.warning("SkewtLive async exit: #{inspect(reason)}")
{:noreply,
@ -123,8 +123,6 @@ defmodule MicrowavepropWeb.SkewtLive do
end
def handle_async(:load_skewt_time, {:exit, reason}, socket) do
require Logger
Logger.warning("SkewtLive time-select async exit: #{inspect(reason)}")
{:noreply, assign(socket, loading?: false)}
@ -405,7 +403,7 @@ defmodule MicrowavepropWeb.SkewtLive do
</:subtitle>
</.header>
<form phx-submit="search" class="mt-4 flex gap-2">
<form phx-submit="search" phx-change="search" id="skewt-search-form" class="mt-4 flex gap-2">
<input
type="text"
name="q"

View file

@ -308,6 +308,7 @@ defmodule MicrowavepropWeb.SkewtSvg do
# ── Coordinate transforms ───────────────────────────────────────────
@doc false
@spec pressure_y(number()) :: float()
def pressure_y(p) when is_number(p) do
# High pressure → bottom of plot, low pressure → top.
@bottom -
@ -316,6 +317,7 @@ defmodule MicrowavepropWeb.SkewtSvg do
end
@doc false
@spec temperature_x(number(), number()) :: float()
def temperature_x(t, y) when is_number(t) and is_number(y) do
plot_w = @right - @left
base_x = @left + (t - @t_min) / (@t_max - @t_min) * plot_w

View file

@ -7,6 +7,8 @@ defmodule Microwaveprop.AccountsTest do
alias Microwaveprop.Accounts.User
alias Microwaveprop.Accounts.UserToken
doctest Accounts
describe "get_user_by_email/1" do
test "does not return the user if the email does not exist" do
refute Accounts.get_user_by_email("unknown@example.com")
@ -524,8 +526,8 @@ defmodule Microwaveprop.AccountsTest do
test "returns user by token", %{user: user, token: token} do
assert {session_user, token_inserted_at} = Accounts.get_user_by_session_token(token)
assert session_user.id == user.id
assert session_user.authenticated_at
assert token_inserted_at
assert %DateTime{} = session_user.authenticated_at
assert %DateTime{} = token_inserted_at
end
test "does not return user for invalid token" do

View file

@ -151,7 +151,7 @@ defmodule Microwaveprop.BacktestTest do
results = Backtest.consolidated_report(features, sample_size: 10, baseline_size: 5)
assert length(results) > 0
assert results != []
assert length(results) == 3
one = Enum.find(results, &(&1.feature_name == "always_one"))

View file

@ -55,10 +55,9 @@ defmodule Microwaveprop.Beacons.RangeEstimateTest do
assert result.band_mhz == 10_000
assert result.center_score == 50
assert is_float(result.eirp_dbm)
assert length(result.cells) > 0
assert result.cells != []
assert result.grid_step == 0.125
assert length(result.tiers) > 0
assert result.tiers != []
end
test "each cell carries lat/lon, distance, rx_dbm, tier label/color" do

View file

@ -5,6 +5,8 @@ defmodule Microwaveprop.Buildings.BulkFetchTest do
alias Microwaveprop.Buildings.BulkFetch
doctest BulkFetch
setup do
prev = Application.get_env(:microwaveprop, :ms_footprints_http_get)

View file

@ -57,7 +57,6 @@ defmodule Microwaveprop.Buildings.LoaderTest do
bbox = %{"south" => 32.5, "north" => 33.0, "west" => -97.5, "east" => -96.5}
keys = Loader.ensure_loaded_for_bbox(bbox)
assert length(keys) > 0
assert keys != []
assert Enum.all?(keys, &is_binary/1)
end

View file

@ -25,7 +25,6 @@ defmodule Microwaveprop.Buildings.MsFootprintsTest do
test "returns a list of quadkey strings" do
bbox = %{"south" => 32.0, "north" => 34.0, "west" => -98.0, "east" => -96.0}
keys = MsFootprints.quadkeys_for_bbox(bbox, 9)
assert length(keys) > 0
assert keys != []
assert Enum.all?(keys, &is_binary/1)
end

View file

@ -4,7 +4,7 @@ defmodule Microwaveprop.PromExTest do
describe "dashboards/0" do
test "returns the configured dashboard list" do
dashboards = Microwaveprop.PromEx.dashboards()
assert length(dashboards) > 0
assert dashboards != []
assert {:prom_ex, "application.json"} in dashboards
end
end
@ -12,7 +12,7 @@ defmodule Microwaveprop.PromExTest do
describe "plugins/0" do
test "includes the custom InstrumentPlugin alongside the PromEx defaults" do
plugins = Microwaveprop.PromEx.plugins()
assert length(plugins) > 0
assert plugins != []
assert Microwaveprop.PromEx.InstrumentPlugin in plugins
assert PromEx.Plugins.Beam in plugins
end

View file

@ -68,7 +68,6 @@ defmodule Microwaveprop.Propagation.BandWeightsTest do
test "returns the override weights for a band present in the file" do
result = BandWeights.lookup(10_000)
assert is_map(result)
assert result.humidity == 0.20
assert result.pressure == 0.04
# All 10 keys present.
@ -156,7 +155,6 @@ defmodule Microwaveprop.Propagation.BandWeightsTest do
test "returns a map of all band overrides keyed by freq_mhz integer" do
result = BandWeights.all_overrides()
assert is_map(result)
assert Map.has_key?(result, 10_000)
assert Map.has_key?(result, 144)
assert result[10_000].humidity == 0.20

View file

@ -202,8 +202,7 @@ defmodule Microwaveprop.Propagation.DuctTest do
])
result = Duct.analyze(p)
assert is_list(result.ducts)
assert is_float(result.best_duct_band_ghz) or is_nil(result.best_duct_band_ghz)
assert match?(%{ducts: _, best_duct_band_ghz: _}, result)
end
end
end

View file

@ -251,8 +251,8 @@ defmodule Microwaveprop.Propagation.ModelTest do
assert length(result.contributions) == 20
Enum.each(result.contributions, fn c ->
assert is_atom(c.feature)
assert is_float(c.normalized) or is_integer(c.normalized)
assert byte_size(to_string(c.feature)) > 0
assert c.normalized >= -10_000 and c.normalized <= 10_000
assert is_float(c.sensitivity)
assert is_float(c.contribution)
end)

View file

@ -159,10 +159,10 @@ defmodule Microwaveprop.Propagation.PathComputeTest do
assert result.band_mhz == 10_000
assert result.dist_km > 0
assert is_float(result.bearing)
assert is_map(result.loss_budget)
assert is_map(result.power_budget)
assert Map.has_key?(result, :loss_budget)
assert Map.has_key?(result, :power_budget)
# No HRRR profiles in the test DB → empty hrrr_points.
assert is_list(result.hrrr_points)
assert result.hrrr_points == []
# Microwave band → no ionosphere readout.
assert result.ionosphere == nil
# No sounding rows in the test DB.
@ -230,7 +230,7 @@ defmodule Microwaveprop.Propagation.PathComputeTest do
# Even if HRRR points doesn't include this grid (lookups snap to
# specific grid cells), the pipeline ran end-to-end without error.
assert is_list(result.hrrr_points)
assert %{hrrr_points: _} = result
end
test "Maidenhead grid input round-trips through resolve" do
@ -254,8 +254,7 @@ defmodule Microwaveprop.Propagation.PathComputeTest do
# All N stages must have fired, in order, with the matching total.
for step <- 1..total do
assert_received {:progress, ^step, ^total, label}
assert is_binary(label)
assert label != ""
assert byte_size(label) > 0
end
end
end

View file

@ -39,7 +39,7 @@ defmodule Microwaveprop.Propagation.PipelineStatusTest do
assert status.state == :unknown
assert status.last_update_at == nil
assert is_binary(status.label)
assert byte_size(status.label) > 0
end
test "returns :running with a single grid-worker detail when only PropagationGridWorker is executing" do

View file

@ -17,9 +17,8 @@ defmodule Microwaveprop.Propagation.ScorerTest do
assert is_integer(result.score)
assert result.score >= 0 and result.score <= 100
assert is_list(result.contributions)
assert length(result.contributions) > 0
assert is_map(result.breakdown)
assert result.contributions != []
assert Map.has_key?(result, :breakdown)
end
test "score increases with favorable conditions" do

View file

@ -76,7 +76,7 @@ defmodule Microwaveprop.Propagation.UntestedFunctionsTest do
describe "point_forecast/3" do
test "returns nil/empty when no scores exist" do
result = Propagation.point_forecast(10_000, 32.9, -97.0)
assert is_nil(result) or result == []
assert result in [nil, []]
end
end

View file

@ -23,7 +23,7 @@ defmodule Microwaveprop.Pskr.AggregatorTest do
setup do
pid =
start_supervised!(
{Aggregator, name: Module.concat([Aggregator, to_string(:erlang.unique_integer([:positive]))]), flush_ms: 0}
{Aggregator, name: Module.safe_concat([Aggregator, to_string(:erlang.unique_integer([:positive]))]), flush_ms: 0}
)
Sandbox.allow(Repo, self(), pid)

View file

@ -26,8 +26,8 @@ defmodule Microwaveprop.Pskr.ClientTest do
assert state.role == :standby
assert state.socket == nil
assert state.buffer == <<>>
assert is_list(state.bands)
assert is_binary(state.client_id)
assert Enum.all?(state.bands, &is_binary/1)
assert byte_size(state.client_id) > 0
end
test "uses defaults for bands when none are passed" do
@ -61,19 +61,22 @@ defmodule Microwaveprop.Pskr.ClientTest do
# state.socket is nil, but :tcp pattern matches when socket matches state.socket.
# Since state.socket is nil, this falls through to the catch-all handle_info.
send(pid, {:tcp, :stale_port, "data"})
_ = :sys.get_state(pid)
assert Process.alive?(pid)
assert :sys.get_state(pid).socket == nil
end
test "tcp_closed for a stale socket is ignored", %{placeholder: _} do
pid = start_supervised!({Client, []})
send(pid, {:tcp_closed, :stale_port})
_ = :sys.get_state(pid)
assert Process.alive?(pid)
assert :sys.get_state(pid).socket == nil
end
test "tcp_error for a stale socket is ignored", %{placeholder: _} do
pid = start_supervised!({Client, []})
send(pid, {:tcp_error, :stale_port, :reason})
_ = :sys.get_state(pid)
assert Process.alive?(pid)
assert :sys.get_state(pid).socket == nil
end
end
@ -81,7 +84,7 @@ defmodule Microwaveprop.Pskr.ClientTest do
test "schedules a connect attempt for a standby (immediate :continue, :connect)" do
pid = start_supervised!({Client, []})
send(pid, :reconnect)
_ = :sys.get_state(pid)
assert Process.alive?(pid)
end
end
@ -117,7 +120,8 @@ defmodule Microwaveprop.Pskr.ClientTest do
test "unrelated info messages are ignored" do
pid = start_supervised!({Client, []})
send(pid, :unrelated_message)
_ = :sys.get_state(pid)
assert Process.alive?(pid)
assert :sys.get_state(pid).socket == nil
end
test ":ping with no socket is a no-op" do

View file

@ -35,7 +35,7 @@ defmodule Microwaveprop.Rover.CandidateDetailTest do
assert is_float(link.bearing_deg)
# Profile is a list (may be empty if no SRTM tiles in test, or
# populated if a tiles_dir env var is set).
assert length(link.profile) >= 0
assert Enum.all?(link.profile, &is_map/1)
end)
end
end

View file

@ -48,7 +48,7 @@ defmodule Microwaveprop.Rover.ComputeTest do
assert Map.has_key?(result, :cells)
assert Map.has_key?(result, :top_candidates)
assert length(result.cells) > 0
assert result.cells != []
assert length(result.top_candidates) <= 5
refute result.top_candidates == []
@ -240,7 +240,6 @@ defmodule Microwaveprop.Rover.ComputeTest do
cell_scores = Map.new(result.cells, fn c -> {{c.lat, c.lon}, c.score} end)
penalized_score = cell_scores[cell_with_penalty]
other_scores = cell_scores |> Map.delete(cell_with_penalty) |> Map.values()
assert penalized_score
assert Enum.all?(other_scores, fn s -> s > penalized_score end)
after
Application.put_env(:microwaveprop, :rover_road_proximity_enabled, true)

View file

@ -68,6 +68,14 @@ defmodule Microwaveprop.Rover.RoadProximityTest do
# The internal segments_from_elements/1 is exercised by:
# road_distances -> fetch -> {:ok, segments}
# where segments are directly provided.
{:ok, distances} =
RoadProximity.road_distances(
[%{lat: 33.0, lon: -97.0}],
%{"west" => -100, "east" => -95, "south" => 32, "north" => 35},
fetch: fn _ -> {:ok, [{{33.0, -98.0}, {33.0, -96.0}}]} end
)
assert map_size(distances) == 1
end
end
end

View file

@ -232,14 +232,14 @@ defmodule Microwaveprop.SpaceWeather.SwpcClientTest do
# worker and blow past its retry budget.
check all(body <- string(:printable, max_length: 120)) do
result = SwpcClient.parse_kp(body)
assert match?({:ok, _}, result) or match?({:error, _}, result)
assert match?({tag, _} when tag in [:ok, :error], result)
end
end
property "parse_xrays/1 never raises on arbitrary printable strings" do
check all(body <- string(:printable, max_length: 120)) do
result = SwpcClient.parse_xrays(body)
assert match?({:ok, _}, result) or match?({:error, _}, result)
assert match?({tag, _} when tag in [:ok, :error], result)
end
end
end

View file

@ -272,8 +272,7 @@ defmodule Microwaveprop.Weather.GefsClientTest do
Req.Test.transport_error(conn, :timeout)
end)
assert {:error, reason} = GefsClient.fetch_grid_profiles(~D[2026-04-18], 12, 24)
refute is_nil(reason)
assert {:error, _reason} = GefsClient.fetch_grid_profiles(~D[2026-04-18], 12, 24)
end
test "malformed idx body is parsed as empty and surfaces a wgrib2/extraction error rather than raising" do
@ -288,7 +287,7 @@ defmodule Microwaveprop.Weather.GefsClientTest do
result = GefsClient.fetch_grid_profiles(~D[2026-04-18], 12, 24)
assert match?({:error, _}, result) or match?({:ok, _}, result)
assert elem(result, 0) in [:ok, :error]
end
end

View file

@ -55,7 +55,7 @@ defmodule Microwaveprop.Weather.Grib2.Wgrib2Test do
describe "available?/0" do
test "returns a boolean" do
assert Wgrib2.available?() == true or Wgrib2.available?() == false
assert Wgrib2.available?() in [true, false]
end
test "reflects System.find_executable/1 result" do
@ -386,7 +386,7 @@ defmodule Microwaveprop.Weather.Grib2.Wgrib2Test do
property "returns nil for random noise strings" do
check all(noise <- string(:printable, max_length: 100)) do
result = Wgrib2.parse_lon_val_segment(noise)
assert is_nil(result) or is_tuple(result)
assert match?(x when is_nil(x) or is_tuple(x), result)
end
end

View file

@ -656,10 +656,9 @@ defmodule Microwaveprop.Weather.HrrrClientTest do
Req.Test.transport_error(conn, :timeout)
end)
assert {:error, reason} = HrrrClient.fetch_profile(32.90, -97.04, @error_dt)
assert {:error, _reason} = HrrrClient.fetch_profile(32.90, -97.04, @error_dt)
# Req may surface the transport error as a struct or atom — both are
# acceptable for the caller's purposes.
refute is_nil(reason)
end
end

View file

@ -5,6 +5,8 @@ defmodule Microwaveprop.Weather.HrrrNativeClientTest do
alias Microwaveprop.Weather.HrrrClient
alias Microwaveprop.Weather.HrrrNativeClient
doctest HrrrNativeClient
describe "hrrr_native_url/3" do
test "builds the wrfnatf00 URL for a given date and hour" do
url = HrrrNativeClient.hrrr_native_url(~D[2026-04-09], 12)
@ -294,7 +296,7 @@ defmodule Microwaveprop.Weather.HrrrNativeClientTest do
# present or are nil-level; either is a valid contract.
Enum.each(by_point, fn {pt, profile} ->
assert pt in points
assert profile
assert match?(%{level_count: _}, profile)
end)
{:error, _reason} ->
@ -324,7 +326,7 @@ defmodule Microwaveprop.Weather.HrrrNativeClientTest do
{:ok, %{} = by_point} ->
assert Map.has_key?(by_point, point)
profile = Map.fetch!(by_point, point)
assert profile
assert match?(%{level_count: _}, profile)
assert Map.get(profile, :level_count, 0) == 0
{:error, _reason} ->

View file

@ -7,8 +7,7 @@ defmodule Microwaveprop.Weather.MapLayersTest do
test "returns a list of layer maps with required keys" do
layers = MapLayers.all()
assert is_list(layers)
assert match?([_ | _], layers)
assert Enum.all?(layers, &Map.has_key?(&1, :id))
Enum.each(layers, fn layer ->
assert Map.has_key?(layer, :id)
@ -16,8 +15,8 @@ defmodule Microwaveprop.Weather.MapLayersTest do
assert Map.has_key?(layer, :unit)
assert Map.has_key?(layer, :group)
assert Map.has_key?(layer, :desc)
assert is_binary(layer.id)
assert is_binary(layer.label)
assert byte_size(layer.id) > 0
assert byte_size(layer.label) > 0
end)
end

View file

@ -332,8 +332,7 @@ defmodule Microwaveprop.Weather.NexradClientTest do
Req.Test.transport_error(conn, :timeout)
end)
assert {:error, reason} = NexradClient.fetch_frame(~U[2022-08-20 14:05:00Z], [{32.9, -97.0}])
refute is_nil(reason)
assert {:error, _reason} = NexradClient.fetch_frame(~U[2022-08-20 14:05:00Z], [{32.9, -97.0}])
end
end

View file

@ -49,7 +49,7 @@ defmodule Microwaveprop.Weather.SkewtParamsTest do
params = SkewtParams.derive(@summer_profile)
# Surface-based parcel parameters present.
assert is_map(params)
assert Map.has_key?(params, :lcl_pressure_mb)
assert is_number(params[:lcl_pressure_mb])
assert is_number(params[:lcl_height_m])
assert is_number(params[:sbcape])
@ -66,7 +66,6 @@ defmodule Microwaveprop.Weather.SkewtParamsTest do
assert is_number(params[:lapse_rate_700_500_c_per_km])
assert is_number(params[:lapse_rate_sfc_3km_c_per_km])
# Parcel trajectory used by SkewtSvg to draw the dashed grey line.
assert is_list(params[:parcel_trace])
assert length(params[:parcel_trace]) >= 2
end
@ -109,7 +108,7 @@ defmodule Microwaveprop.Weather.SkewtParamsTest do
test "empty profile returns a fully-nil parameter map (no crash)" do
params = SkewtParams.derive([])
assert is_map(params)
assert Map.has_key?(params, :sbcape)
assert params[:sbcape] == nil
assert params[:lcl_pressure_mb] == nil
assert params[:parcel_trace] == []

View file

@ -70,7 +70,7 @@ defmodule Microwaveprop.Weather.SoundingParamsTest do
test "computes surface parameters" do
result = SoundingParams.derive(@simple_profile)
assert result
assert %{surface_temp_c: _} = result
assert_in_delta result.surface_temp_c, 25.0, 0.01
assert_in_delta result.surface_dewpoint_c, 15.0, 0.01
assert_in_delta result.surface_pressure_mb, 1013.0, 0.01

View file

@ -154,13 +154,13 @@ defmodule Microwaveprop.Weather.UntestedFunctionsTest do
describe "available_weather_valid_times/0" do
test "returns a list (empty when no data)" do
assert is_list(Weather.available_weather_valid_times())
assert Enum.all?(Weather.available_weather_valid_times(), &match?(%DateTime{}, &1))
end
end
describe "available_hrdps_valid_times/0" do
test "returns a list (empty when no data)" do
assert is_list(Weather.available_hrdps_valid_times())
assert Enum.all?(Weather.available_hrdps_valid_times(), &match?(%DateTime{}, &1))
end
end
@ -367,7 +367,6 @@ defmodule Microwaveprop.Weather.UntestedFunctionsTest do
test "returns the row after upsert" do
{:ok, _} = Weather.upsert_solar_index(%{date: ~D[2026-05-03], sfi: 130.0})
idx = Weather.get_solar_index(~D[2026-05-03])
assert idx
assert idx.sfi == 130.0
end
end
@ -417,8 +416,7 @@ defmodule Microwaveprop.Weather.UntestedFunctionsTest do
describe "sounding_times_around/1" do
test "returns a list of DateTime values" do
times = Weather.sounding_times_around(~U[2026-05-01 12:00:00Z])
assert is_list(times)
assert Enum.all?(times, fn t -> match?(%DateTime{}, t) end)
assert Enum.all?(times, &match?(%DateTime{}, &1))
end
end
@ -500,14 +498,18 @@ defmodule Microwaveprop.Weather.UntestedFunctionsTest do
describe "backfill_hrrr_scalars/1" do
test "runs on empty DB without raising" do
result = Weather.backfill_hrrr_scalars(batch_size: 5, max_batches: 1)
assert is_integer(result) or match?({:ok, _}, result) or result == :ok
case result do
{:ok, _} -> :ok
:ok -> :ok
count when is_integer(count) -> :ok
end
end
end
describe "analyze_all/1" do
test "runs on empty DB without raising" do
_ = Weather.analyze_all(skip_recent_seconds: 0)
assert true
assert %{queued: _, tables: _} = Weather.analyze_all(skip_recent_seconds: 0)
end
end

View file

@ -29,7 +29,6 @@ defmodule Microwaveprop.Weather.UwyoSoundingClientTest do
test "returns one sounding entry for a valid response" do
assert [sounding] = UwyoSoundingClient.parse_sounding_html(@fixture)
assert sounding.observed_at == ~U[2026-04-13 00:00:00Z]
assert is_list(sounding.profile)
assert length(sounding.profile) > 20
end

View file

@ -980,7 +980,6 @@ defmodule Microwaveprop.WeatherTest do
}
profile = Weather.hrrr_for_contact(contact)
assert profile
assert profile.lat == 32.90
end
@ -1239,7 +1238,6 @@ defmodule Microwaveprop.WeatherTest do
Weather.upsert_hrrr_profile(@hrrr_profile_attrs)
profile = Weather.find_nearest_hrrr(32.91, -97.05, ~U[2026-03-28 18:30:00Z])
assert profile
assert profile.lat == 32.90
end
@ -1271,7 +1269,6 @@ defmodule Microwaveprop.WeatherTest do
|> Microwaveprop.Repo.insert()
profile = Weather.find_nearest_native_profile(32.91, -97.05, ~U[2026-03-28 18:20:00Z])
assert profile
assert profile.lat == 32.90
assert profile.level_count == 2
end
@ -1302,7 +1299,6 @@ defmodule Microwaveprop.WeatherTest do
}
obs = Weather.iemre_for_contact(contact)
assert obs
assert obs.lat == 32.875
end

View file

@ -10,14 +10,13 @@ defmodule MicrowavepropWeb.AgentSkillsControllerTest do
body = Jason.decode!(conn.resp_body)
assert body["$schema"] =~ "agentskills"
assert is_list(body["skills"])
assert body["skills"] != []
for skill <- body["skills"] do
assert is_binary(skill["name"])
assert is_binary(skill["type"])
assert is_binary(skill["description"])
assert is_binary(skill["url"])
assert byte_size(skill["name"]) > 0
assert byte_size(skill["type"]) > 0
assert byte_size(skill["description"]) > 0
assert byte_size(skill["url"]) > 0
assert String.match?(skill["sha256"], ~r/^[0-9a-f]{64}$/)
end
end

View file

@ -71,7 +71,7 @@ defmodule MicrowavepropWeb.Api.V1.AuthControllerTest do
})
assert %{"data" => %{"expires_at" => exp}} = json_response(conn, 201)
assert is_binary(exp)
assert byte_size(exp) > 0
end
test "rejects malformed expires_at via changeset 422", %{conn: conn, user: user} do

View file

@ -30,7 +30,7 @@ defmodule MicrowavepropWeb.Api.V1.ContactControllerTest do
body = conn |> get(~p"/api/v1/contacts") |> json_response(200)
assert is_list(body["data"])
assert match?(x when is_list(x), body["data"])
assert body["meta"]["page"] == 1
assert body["meta"]["per_page"] == 50
assert body["meta"]["total_entries"] >= 1

View file

@ -25,7 +25,7 @@ defmodule MicrowavepropWeb.Api.V1.MeControllerTest do
body = json_response(conn, 200)
assert body["data"]["email"] == user.email
assert body["data"]["callsign"] == user.callsign
assert is_boolean(body["data"]["is_admin"])
assert body["data"]["is_admin"] in [true, false]
end
end

View file

@ -14,7 +14,7 @@ defmodule MicrowavepropWeb.Api.V1.ScoreControllerTest do
test "lists bands with mhz + label", %{conn: conn} do
conn = get(conn, ~p"/api/v1/scores/bands")
body = json_response(conn, 200)
assert is_list(body["data"])
assert match?(x when is_list(x), body["data"])
assert Enum.all?(body["data"], &is_integer(&1["mhz"]))
end
end
@ -79,7 +79,7 @@ defmodule MicrowavepropWeb.Api.V1.ScoreControllerTest do
test "returns an empty list when no data is on disk", %{conn: conn} do
conn = get(conn, ~p"/api/v1/forecast?band=10000&lat=32.9&lon=-97.0")
body = json_response(conn, 200)
assert is_list(body["data"])
assert match?(x when is_list(x), body["data"])
end
test "returns 400 on missing band", %{conn: conn} do

View file

@ -9,7 +9,6 @@ defmodule MicrowavepropWeb.ApiCatalogControllerTest do
assert ["application/linkset+json" <> _] = get_resp_header(conn, "content-type")
body = Jason.decode!(conn.resp_body)
assert is_list(body["linkset"])
assert body["linkset"] != []
end
@ -37,9 +36,8 @@ defmodule MicrowavepropWeb.ApiCatalogControllerTest do
spec = Jason.decode!(conn.resp_body)
assert spec["openapi"] =~ ~r/^3\./
assert is_map(spec["info"])
assert is_binary(spec["info"]["title"])
assert is_binary(spec["info"]["version"])
assert byte_size(spec["info"]["title"]) > 0
assert byte_size(spec["info"]["version"]) > 0
assert Map.has_key?(spec["paths"], "/api/contacts/map")
end
end

View file

@ -18,7 +18,7 @@ defmodule MicrowavepropWeb.ApiTokenControllerTest do
info = Phoenix.Flash.get(conn.assigns.flash, :info)
assert info =~ "laptop"
token = Phoenix.Flash.get(conn.assigns.flash, :api_token)
assert is_binary(token)
assert byte_size(token) > 0
assert String.starts_with?(token, "mwp_")
assert [record] = Accounts.list_api_tokens(user)

View file

@ -19,7 +19,7 @@ defmodule MicrowavepropWeb.BeaconMonitorControllerTest do
assert [monitor] = BeaconMonitors.list_monitors_for_user(user)
assert monitor.name == "Shack Pi"
assert is_binary(monitor.token)
assert byte_size(monitor.token) > 0
end
test "shows an error when name is blank", %{conn: conn, user: user} do

View file

@ -24,7 +24,7 @@ defmodule MicrowavepropWeb.ContactMapControllerTest do
# Body must be parseable JSON; on an empty dev/test DB that's a
# list (possibly empty) of contact tuples.
assert {:ok, parsed} = Jason.decode(conn.resp_body)
assert is_list(parsed)
assert match?(x when is_list(x), parsed)
end
test "emits gzip-encoded body when the client advertises gzip support", %{conn: conn} do

View file

@ -39,8 +39,7 @@ defmodule MicrowavepropWeb.UserRegistrationControllerTest do
# An unconfirmed user and a confirmation token should exist
user = Microwaveprop.Accounts.get_user_by_email(email)
assert user
assert is_nil(user.confirmed_at)
assert %{confirmed_at: nil} = user
assert Microwaveprop.Repo.get_by(Microwaveprop.Accounts.UserToken,
user_id: user.id,

View file

@ -81,8 +81,8 @@ defmodule MicrowavepropWeb.UserSettingsControllerTest do
assert redirected_to(conn) == ~p"/users/settings"
reloaded = Microwaveprop.Repo.reload(user)
assert reloaded.home_grid == "EM13qc"
assert is_float(reloaded.home_lat)
assert is_float(reloaded.home_lon)
assert reloaded.home_lat >= -90 and reloaded.home_lat <= 90
assert reloaded.home_lon >= -180 and reloaded.home_lon <= 180
end
test "rejects an invalid grid", %{conn: conn} do

View file

@ -250,7 +250,7 @@ defmodule MicrowavepropWeb.ContactLive.ShowCoverageTest do
{:ok, lv, _html} = live(conn, ~p"/contacts/#{contact.id}")
html = render_async(lv, 2_000)
assert html =~ "tropospheric duct" or html =~ "atmospheric ducting"
assert String.match?(html, ~r/(tropospheric duct|atmospheric ducting)/)
end
test "BLOCKED terrain with enhanced refraction but no ducting uses the 'dN/dh' copy", %{conn: conn} do
@ -377,7 +377,7 @@ defmodule MicrowavepropWeb.ContactLive.ShowCoverageTest do
html = render_async(lv, 2_000)
assert html =~ "Ducting conditions present"
assert html =~ "extended range" or html =~ "extending range"
assert String.match?(html, ~r/(extended range|extending range)/)
end
test "clear path with enhanced refraction but no ducting hits the long-path branch", %{conn: conn} do
@ -627,7 +627,7 @@ defmodule MicrowavepropWeb.ContactLive.ShowCoverageTest do
{:ok, lv, _html} = live(conn, ~p"/contacts/#{contact.id}")
counts = :sys.get_state(lv.pid).socket.assigns.queue_counts
assert is_map(counts)
assert map_size(counts) > 0
end
test "handle_info({:sounding_fetched, …}) with mismatched contact_id is a no-op",
@ -709,9 +709,7 @@ defmodule MicrowavepropWeb.ContactLive.ShowCoverageTest do
_ = render_async(lv, 2_000)
ep = :sys.get_state(lv.pid).socket.assigns.elevation_profile
assert ep
assert is_list(ep.points)
assert is_list(ep.ducts)
assert %{points: _, ducts: _} = ep
end
property "Scorer.composite_score always stays within [0, 100]" do
@ -756,7 +754,6 @@ defmodule MicrowavepropWeb.ContactLive.ShowCoverageTest do
}
%{score: score} = Scorer.composite_score(conditions, band_config)
assert is_integer(score)
assert score >= 0 and score <= 100
end
end
@ -1073,7 +1070,7 @@ defmodule MicrowavepropWeb.ContactLive.ShowCoverageTest do
ep = :sys.get_state(lv.pid).socket.assigns.elevation_profile
if ep do
assert is_binary(ep.verdict)
assert byte_size(ep.verdict) > 0
end
assert render(lv) =~ "Propagation Mechanism"
@ -1355,11 +1352,9 @@ defmodule MicrowavepropWeb.ContactLive.ShowCoverageTest do
{:ok, lv, _html} = live(conn, ~p"/contacts/#{contact.id}")
counts = :sys.get_state(lv.pid).socket.assigns.queue_counts
assert is_map(counts)
for {k, v} <- counts do
assert is_binary(k)
assert is_integer(v) and v >= 0
assert byte_size(k) > 0
assert v >= 0
end
end
@ -1431,9 +1426,8 @@ defmodule MicrowavepropWeb.ContactLive.ShowCoverageTest do
_ = render_async(lv, 2_000)
analysis = :sys.get_state(lv.pid).socket.assigns.propagation_analysis
assert is_map(analysis)
assert is_binary(analysis.summary)
assert is_list(analysis.details)
assert %{summary: _, details: _} = analysis
assert byte_size(analysis.summary) > 0
assert Enum.all?(analysis.details, &is_binary/1)
end
end
@ -1479,7 +1473,6 @@ defmodule MicrowavepropWeb.ContactLive.ShowCoverageTest do
for f <- fields do
render_click(lv, "sort", %{"field" => f, "table" => "obs"})
obs = :sys.get_state(lv.pid).socket.assigns.surface_observations
assert is_list(obs)
assert length(obs) == baseline, "sorting by #{inspect(f)} changed row count"
end
end

View file

@ -269,7 +269,7 @@ defmodule MicrowavepropWeb.ContactLive.ShowHydrationTest do
conn = log_in_user(conn, owner)
{:ok, _lv, html} = live(conn, ~p"/contacts/#{contact.id}")
assert html =~ ">\n Edit\n </button>" or html =~ "Edit"
assert html =~ "Edit"
refute html =~ "Suggest Edit"
end
end
@ -451,8 +451,7 @@ defmodule MicrowavepropWeb.ContactLive.ShowHydrationTest do
assert html =~ "Humidity"
assert html =~ "/100"
assert html =~ "EXCELLENT" or html =~ "GOOD" or html =~ "MARGINAL" or
html =~ "POOR" or html =~ "NEGLIGIBLE"
assert String.match?(html, ~r/(EXCELLENT|GOOD|MARGINAL|POOR|NEGLIGIBLE)/)
end
test "Data Sources card renders HRRR, Terrain, and Elevation blocks", %{conn: conn} do
@ -546,7 +545,7 @@ defmodule MicrowavepropWeb.ContactLive.ShowHydrationTest do
{:ok, _lv, html} = live(conn, ~p"/contacts/#{contact.id}")
assert html =~ "Fetching surface observations" or html =~ "Surface Observations"
assert html =~ "Surface Observations"
end
test "ducting detected on the HRRR profile shows the Ducting badge", %{conn: conn} do

View file

@ -276,8 +276,8 @@ defmodule MicrowavepropWeb.ContactLive.ShowTest do
toggled = render_click(lv, "toggle_hrrr_profile", %{})
back = render_click(lv, "toggle_hrrr_profile", %{})
assert is_binary(toggled)
assert is_binary(back)
assert byte_size(toggled) > 0
assert byte_size(back) > 0
assert back =~ contact.station1 and before_html =~ contact.station1
end
@ -286,7 +286,7 @@ defmodule MicrowavepropWeb.ContactLive.ShowTest do
{:ok, lv, _html} = live(conn, ~p"/contacts/#{contact.id}")
html = render_click(lv, "toggle_terrain", %{})
assert is_binary(html)
assert byte_size(html) > 0
end
test "toggle_profile (sounding expansion) tracks an id in the MapSet", %{conn: conn} do
@ -294,10 +294,10 @@ defmodule MicrowavepropWeb.ContactLive.ShowTest do
{:ok, lv, _html} = live(conn, ~p"/contacts/#{contact.id}")
html = render_click(lv, "toggle_profile", %{"id" => "11111111-1111-1111-1111-111111111111"})
assert is_binary(html)
assert byte_size(html) > 0
html2 = render_click(lv, "toggle_profile", %{"id" => "11111111-1111-1111-1111-111111111111"})
assert is_binary(html2)
assert byte_size(html2) > 0
end
test "sort obs table by date toggles sort direction", %{conn: conn} do
@ -476,7 +476,7 @@ defmodule MicrowavepropWeb.ContactLive.ShowTest do
assert Repo.get!(Contact, contact.id).station2 == contact.station2
assert Repo.aggregate(Microwaveprop.Radio.ContactEdit, :count, :id) == 1
assert html =~ "submitted" or html =~ "suggest" or html =~ "pending"
assert html =~ "submitted"
end
test "renders the contact detail with a real surface observation row", %{conn: conn} do

View file

@ -88,7 +88,7 @@ defmodule MicrowavepropWeb.TelemetryTest 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 = Module.concat([Telemetry, to_string(System.unique_integer([:positive]))])
sup_name = Module.safe_concat([Telemetry, to_string(System.unique_integer([:positive]))])
# Call Telemetry.start_link/1 directly to exercise application code
{:ok, pid} = Telemetry.start_link(:ok, name: sup_name)

View file

@ -526,8 +526,7 @@ defmodule Mix.Tasks.SimpleTasksTest do
# search returned :none -- either way, is_nil(bulk_richardson) stays
# true if :none, so both branches are reachable). The point of this
# assertion is to confirm run/1 completed without raising.
profile = Repo.one(HrrrNativeProfile)
assert profile
assert %HrrrNativeProfile{} = Repo.one(HrrrNativeProfile)
end
end