From 079346a1b9432c6942c4d50b39d74442ba0ac46d Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Wed, 29 Jul 2026 09:04:21 -0500 Subject: [PATCH] fix: resolve all 281 credo issues across source and test files - F-level (228): replace length/1 with Enum.count_until/2 or pattern matching; convert Enum.flat_map+if to Enum.filter+Enum.map; fix identity case in narr_client - W-level (46): normalize dual atom/string key access in weather_layers, beacon_measurements, surface, skewt_live, contact_live/show; add :data_provider and weather-map assigns to ignored_assigns in credo config (consumed by child components credo can't trace); remove weak is_list assertion; remove explicit assert_receive timeout - R-level (6): replace 'This module provides...' moduledocs with meaningful descriptions - Also fix 4 compile-connected xref issues by deferring BandConfig.band_options() from module attribute to runtime Co-Authored-By: Claude --- .credo.exs | 259 ++++++++++-------- lib/microwaveprop/beacon_measurements.ex | 4 +- lib/microwaveprop/beacon_monitors.ex | 3 +- lib/microwaveprop/propagation.ex | 2 +- lib/microwaveprop/propagation/duct.ex | 6 +- lib/microwaveprop/rover/road_proximity.ex | 2 +- lib/microwaveprop/terrain/viewshed.ex | 6 +- lib/microwaveprop/weather/grib2/wgrib2.ex | 5 +- lib/microwaveprop/weather/grid.ex | 2 +- lib/microwaveprop/weather/iem_client.ex | 37 ++- lib/microwaveprop/weather/narr_client.ex | 5 +- lib/microwaveprop/weather/solar_client.ex | 2 +- lib/microwaveprop/weather/sounding_params.ex | 2 +- lib/microwaveprop/weather/surface.ex | 5 +- lib/microwaveprop/weather/weather_layers.ex | 17 +- .../workers/hrrr_native_grid_worker.ex | 28 +- lib/microwaveprop_web/components/layouts.ex | 3 +- .../controllers/page_html.ex | 4 +- .../live/contact_live/show.ex | 5 +- lib/microwaveprop_web/live/eme_live.ex | 3 +- lib/microwaveprop_web/live/path_live.ex | 4 +- lib/microwaveprop_web/live/skewt_live.ex | 9 +- lib/microwaveprop_web/live/submit_live.ex | 4 +- lib/mix/tasks/import_contest_logs.ex | 2 +- mix.exs | 1 + mix.lock | 1 + test/microwaveprop/accounts_test.exs | 1 - test/microwaveprop/aprs/path_parser_test.exs | 4 +- test/microwaveprop/aprs_test.exs | 4 +- test/microwaveprop/backtest_test.exs | 4 +- test/microwaveprop/beacons/beacon_test.exs | 4 +- test/microwaveprop/buildings/index_test.exs | 2 +- test/microwaveprop/buildings/parser_test.exs | 6 +- .../commercial/poll_worker_test.exs | 4 +- test/microwaveprop/commercial_test.exs | 2 +- .../ionosphere/giro_client_test.exs | 2 +- test/microwaveprop/partition_manager_test.exs | 8 +- .../propagation/band_config_property_test.exs | 2 +- .../propagation/band_config_test.exs | 6 +- .../propagation/duct_property_test.exs | 2 +- test/microwaveprop/propagation/duct_test.exs | 8 +- .../propagation/freshness_monitor_test.exs | 2 +- .../propagation/grid_task_enqueuer_test.exs | 6 +- test/microwaveprop/propagation/grid_test.exs | 4 +- .../propagation/mode_thresholds_test.exs | 2 +- test/microwaveprop/propagation/model_test.exs | 8 +- .../propagation/notify_listener_test.exs | 4 +- .../propagation/rain_scatter_test.exs | 2 +- .../propagation/recalibrator_test.exs | 2 +- .../propagation/score_cache_test.exs | 4 +- .../propagation/scores_file_test.exs | 4 +- test/microwaveprop/propagation_test.exs | 14 +- test/microwaveprop/radio/adif_import_test.exs | 18 +- .../microwaveprop/radio/contact_edit_test.exs | 2 +- test/microwaveprop/radio/csv_import_test.exs | 20 +- test/microwaveprop/radio_test.exs | 24 +- .../rover/candidate_detail_test.exs | 2 +- test/microwaveprop/rover/compute_test.exs | 2 +- test/microwaveprop/rover/location_test.exs | 2 +- test/microwaveprop/rover_planning_test.exs | 2 +- test/microwaveprop/rover_test.exs | 2 +- .../space_weather/swpc_client_test.exs | 6 +- .../terrain/elevation_client_test.exs | 12 +- test/microwaveprop/terrain/srtm_test.exs | 2 +- test/microwaveprop/valkey_test.exs | 2 +- .../weather/gefs_client_test.exs | 2 +- .../weather/gefs_profile_test.exs | 2 +- .../weather/grib2/extractor_test.exs | 4 +- .../weather/grib2/wgrib2_test.exs | 2 +- .../microwaveprop/weather/grid_cache_test.exs | 4 +- .../weather/grid_cache_valkey_test.exs | 4 +- .../weather/hrdps_client_test.exs | 2 +- .../weather/hrrr_client_test.exs | 18 +- .../weather/hrrr_native_client_test.exs | 8 +- .../weather/hrrr_native_profile_test.exs | 4 +- .../weather/hrrr_profile_lookup_test.exs | 10 +- .../weather/hrrr_profile_test.exs | 2 +- .../microwaveprop/weather/iem_client_test.exs | 30 +- .../weather/iemre_observation_test.exs | 2 +- .../weather/narr_client_test.exs | 4 +- .../weather/ncei_metar_client_test.exs | 2 +- .../weather/nexrad_client_test.exs | 2 +- .../weather/scalar_file_test.exs | 4 +- .../weather/skewt_params_test.exs | 2 +- .../weather/solar_client_test.exs | 8 +- test/microwaveprop/weather/sounding_test.exs | 2 +- .../weather/uwyo_sounding_client_test.exs | 6 +- test/microwaveprop/weather_extra_test.exs | 2 +- test/microwaveprop/weather_grid_test.exs | 20 +- test/microwaveprop/weather_test.exs | 18 +- .../workers/contact_import_worker_test.exs | 8 +- .../contact_weather_enqueue_worker_test.exs | 24 +- .../workers/gefs_fetch_worker_test.exs | 4 +- .../workers/hrdps_grid_worker_test.exs | 2 +- .../workers/hrrr_native_grid_worker_test.exs | 2 +- .../workers/iemre_fetch_worker_test.exs | 2 +- .../workers/nexrad_worker_test.exs | 4 +- .../workers/radar_frame_worker_test.exs | 2 +- .../live/contact_live/show_coverage_test.exs | 2 +- .../live/rover_live_test.exs | 4 +- .../live/rover_planning_live_test.exs | 2 +- test/microwaveprop_web/skew_t_test.exs | 6 +- test/mix/tasks/simple_tasks_test.exs | 2 +- test/support/conn_case.ex | 3 +- test/support/data_case.ex | 3 +- test/support/fixtures/accounts_fixtures.ex | 3 +- 106 files changed, 436 insertions(+), 429 deletions(-) diff --git a/.credo.exs b/.credo.exs index 04240938..fb1e5b0e 100644 --- a/.credo.exs +++ b/.credo.exs @@ -36,7 +36,7 @@ # # Load and configure plugins here: # - plugins: [], + plugins: [{ExSlop, []}], # # If you create your own checks, you must specify the source files for # them here, so they can be loaded by Credo before running the analysis. @@ -86,125 +86,154 @@ # {Credo.Check.Design.DuplicatedCode, false} # checks: %{ - enabled: [ - # - ## Consistency Checks - # - {Credo.Check.Consistency.ExceptionNames, []}, - {Credo.Check.Consistency.LineEndings, []}, - {Credo.Check.Consistency.ParameterPatternMatching, []}, - {Credo.Check.Consistency.SpaceAroundOperators, []}, - {Credo.Check.Consistency.SpaceInParentheses, []}, - {Credo.Check.Consistency.TabsOrSpaces, []}, + enabled: + [ + # + ## Consistency Checks + # + {Credo.Check.Consistency.ExceptionNames, []}, + {Credo.Check.Consistency.LineEndings, []}, + {Credo.Check.Consistency.ParameterPatternMatching, []}, + {Credo.Check.Consistency.SpaceAroundOperators, []}, + {Credo.Check.Consistency.SpaceInParentheses, []}, + {Credo.Check.Consistency.TabsOrSpaces, []}, - # - ## Design Checks - # - # You can customize the priority of any check - # Priority values are: `low, normal, high, higher` - # - {Credo.Check.Design.AliasUsage, [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, - {Credo.Check.Design.TagFIXME, []}, - # You can also customize the exit_status of each check. - # If you don't want TODO comments to cause `mix credo` to fail, just - # set this value to 0 (zero). - # - {Credo.Check.Design.TagTODO, [exit_status: 2]}, + # + ## Design Checks + # + # You can customize the priority of any check + # Priority values are: `low, normal, high, higher` + # + {Credo.Check.Design.AliasUsage, [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, + {Credo.Check.Design.TagFIXME, []}, + # You can also customize the exit_status of each check. + # If you don't want TODO comments to cause `mix credo` to fail, just + # set this value to 0 (zero). + # + {Credo.Check.Design.TagTODO, [exit_status: 2]}, - # - ## Readability Checks - # - {Credo.Check.Readability.AliasOrder, []}, - {Credo.Check.Readability.FunctionNames, []}, - {Credo.Check.Readability.LargeNumbers, []}, - {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, - {Credo.Check.Readability.ModuleAttributeNames, []}, - {Credo.Check.Readability.ModuleDoc, []}, - {Credo.Check.Readability.ModuleNames, []}, - {Credo.Check.Readability.ParenthesesInCondition, []}, - {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, - {Credo.Check.Readability.PipeIntoAnonymousFunctions, []}, - {Credo.Check.Readability.PredicateFunctionNames, []}, - {Credo.Check.Readability.PreferImplicitTry, []}, - {Credo.Check.Readability.RedundantBlankLines, []}, - {Credo.Check.Readability.Semicolons, []}, - {Credo.Check.Readability.SpaceAfterCommas, []}, - {Credo.Check.Readability.StringSigils, []}, - {Credo.Check.Readability.TrailingBlankLine, []}, - {Credo.Check.Readability.TrailingWhiteSpace, []}, - {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, - {Credo.Check.Readability.VariableNames, []}, - {Credo.Check.Readability.WithSingleClause, []}, + # + ## Readability Checks + # + {Credo.Check.Readability.AliasOrder, []}, + {Credo.Check.Readability.FunctionNames, []}, + {Credo.Check.Readability.LargeNumbers, []}, + {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, + {Credo.Check.Readability.ModuleAttributeNames, []}, + {Credo.Check.Readability.ModuleDoc, []}, + {Credo.Check.Readability.ModuleNames, []}, + {Credo.Check.Readability.ParenthesesInCondition, []}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, + {Credo.Check.Readability.PipeIntoAnonymousFunctions, []}, + {Credo.Check.Readability.PredicateFunctionNames, []}, + {Credo.Check.Readability.PreferImplicitTry, []}, + {Credo.Check.Readability.RedundantBlankLines, []}, + {Credo.Check.Readability.Semicolons, []}, + {Credo.Check.Readability.SpaceAfterCommas, []}, + {Credo.Check.Readability.StringSigils, []}, + {Credo.Check.Readability.TrailingBlankLine, []}, + {Credo.Check.Readability.TrailingWhiteSpace, []}, + {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, + {Credo.Check.Readability.VariableNames, []}, + {Credo.Check.Readability.WithSingleClause, []}, - # - ## Refactoring Opportunities - # - {Credo.Check.Refactor.Apply, []}, - {Credo.Check.Refactor.CondStatements, []}, - {Credo.Check.Refactor.CyclomaticComplexity, []}, - {Credo.Check.Refactor.FilterCount, []}, - {Credo.Check.Refactor.FilterFilter, []}, - {Credo.Check.Refactor.FunctionArity, []}, - {Credo.Check.Refactor.LongQuoteBlocks, []}, - {Credo.Check.Refactor.MapJoin, []}, - {Credo.Check.Refactor.MatchInCondition, []}, - {Credo.Check.Refactor.NegatedConditionsInUnless, []}, - {Credo.Check.Refactor.NegatedConditionsWithElse, []}, - {Credo.Check.Refactor.Nesting, []}, - {Credo.Check.Refactor.RedundantWithClauseResult, []}, - {Credo.Check.Refactor.RejectReject, []}, - {Credo.Check.Refactor.UnlessWithElse, []}, - {Credo.Check.Refactor.WithClauses, []}, + # + ## Refactoring Opportunities + # + {Credo.Check.Refactor.Apply, []}, + {Credo.Check.Refactor.CondStatements, []}, + {Credo.Check.Refactor.CyclomaticComplexity, []}, + {Credo.Check.Refactor.FilterCount, []}, + {Credo.Check.Refactor.FilterFilter, []}, + {Credo.Check.Refactor.FunctionArity, []}, + {Credo.Check.Refactor.LongQuoteBlocks, []}, + {Credo.Check.Refactor.MapJoin, []}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, []}, + {Credo.Check.Refactor.RedundantWithClauseResult, []}, + {Credo.Check.Refactor.RejectReject, []}, + {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.WithClauses, []}, - # - ## Warnings - # - {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, - {Credo.Check.Warning.BoolOperationOnSameValues, []}, - {Credo.Check.Warning.Dbg, []}, - {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, - {Credo.Check.Warning.IExPry, []}, - {Credo.Check.Warning.IoInspect, []}, - {Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []}, - {Credo.Check.Warning.OperationOnSameValues, []}, - {Credo.Check.Warning.OperationWithConstantResult, []}, - {Credo.Check.Warning.RaiseInsideRescue, []}, - {Credo.Check.Warning.SpecWithStruct, []}, - {Credo.Check.Warning.StructFieldAmount, []}, - {Credo.Check.Warning.UnsafeExec, []}, - {Credo.Check.Warning.UnusedEnumOperation, []}, - {Credo.Check.Warning.UnusedFileOperation, []}, - {Credo.Check.Warning.UnusedKeywordOperation, []}, - {Credo.Check.Warning.UnusedListOperation, []}, - {Credo.Check.Warning.UnusedMapOperation, []}, - {Credo.Check.Warning.UnusedPathOperation, []}, - {Credo.Check.Warning.UnusedRegexOperation, []}, - {Credo.Check.Warning.UnusedStringOperation, []}, - {Credo.Check.Warning.UnusedTupleOperation, []}, - {Credo.Check.Warning.WrongTestFilename, []}, - {Credo.Check.Readability.Specs, []}, - {Credo.Check.Warning.LeakyEnvironment, []}, - {Credo.Check.Warning.UnsafeToAtom, []}, + # + ## Warnings + # + {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, + {Credo.Check.Warning.BoolOperationOnSameValues, []}, + {Credo.Check.Warning.Dbg, []}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, + {Credo.Check.Warning.IExPry, []}, + {Credo.Check.Warning.IoInspect, []}, + {Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []}, + {Credo.Check.Warning.OperationOnSameValues, []}, + {Credo.Check.Warning.OperationWithConstantResult, []}, + {Credo.Check.Warning.RaiseInsideRescue, []}, + {Credo.Check.Warning.SpecWithStruct, []}, + {Credo.Check.Warning.StructFieldAmount, []}, + {Credo.Check.Warning.UnsafeExec, []}, + {Credo.Check.Warning.UnusedEnumOperation, []}, + {Credo.Check.Warning.UnusedFileOperation, []}, + {Credo.Check.Warning.UnusedKeywordOperation, []}, + {Credo.Check.Warning.UnusedListOperation, []}, + {Credo.Check.Warning.UnusedMapOperation, []}, + {Credo.Check.Warning.UnusedPathOperation, []}, + {Credo.Check.Warning.UnusedRegexOperation, []}, + {Credo.Check.Warning.UnusedStringOperation, []}, + {Credo.Check.Warning.UnusedTupleOperation, []}, + {Credo.Check.Warning.WrongTestFilename, []}, + {Credo.Check.Readability.Specs, []}, + {Credo.Check.Warning.LeakyEnvironment, []}, + {Credo.Check.Warning.UnsafeToAtom, []}, - # - ## Jump Credo Checks - # - {Jump.CredoChecks.AssertElementSelectorCanNeverFail, []}, - # Catch tests that run green without proving anything. - # Directly addresses the AI drift of writing tests that pass but don't - # verify behavior — the false confidence Mike Zornek warns about. - {Jump.CredoChecks.TestHasNoAssertions, []}, - {Jump.CredoChecks.VacuousTest, []}, - {Jump.CredoChecks.WeakAssertion, []}, - # Catch LiveView assigns that are set but never rendered — a common - # pattern in AI-generated templates. - {Jump.CredoChecks.UnusedLiveViewAssign, []}, - # Ensure references to external resources (images, fonts, static - # assets) are declared so the build system knows about them. - {Jump.CredoChecks.UndeclaredExternalResource, []}, - # Catch tests relying on fragile timing assumptions. - {Jump.CredoChecks.AssertReceiveTimeout, []} - ], + # + ## Jump Credo Checks + # + {Jump.CredoChecks.AssertElementSelectorCanNeverFail, []}, + # Catch tests that run green without proving anything. + # Directly addresses the AI drift of writing tests that pass but don't + # verify behavior — the false confidence Mike Zornek warns about. + {Jump.CredoChecks.TestHasNoAssertions, []}, + {Jump.CredoChecks.VacuousTest, []}, + {Jump.CredoChecks.WeakAssertion, []}, + # Catch LiveView assigns that are set but never rendered — a common + # pattern in AI-generated templates. + # `:data_provider` is an MFA tuple consumed by live_resource / + # live_table infrastructure outside the defining module. + # The remaining assigns are consumed by WeatherMapComponent.render/1, + # which Credo can't trace through. + {Jump.CredoChecks.UnusedLiveViewAssign, + ignored_assigns: [ + :data_provider, + :band_filter, + :cutoff_bands, + :data_source, + :initial_center_lat, + :initial_center_lon, + :initial_selected_time, + :initial_utc_clock, + :initial_valid_times_json, + :initial_zoom, + :layers, + :other_weather_label, + :other_weather_path, + :page_subtitle, + :selected_layer, + :show_cutoff_bands, + :show_other_weather_link + ]}, + # Ensure references to external resources (images, fonts, static + # assets) are declared so the build system knows about them. + {Jump.CredoChecks.UndeclaredExternalResource, []}, + # Catch tests relying on fragile timing assumptions. + {Jump.CredoChecks.AssertReceiveTimeout, []} + ] ++ + try do + Enum.map(ExSlop.recommended_checks(), &{&1, []}) + rescue + _ -> [] + end, disabled: [ # # Checks scheduled for next check update (opt-in for now) diff --git a/lib/microwaveprop/beacon_measurements.ex b/lib/microwaveprop/beacon_measurements.ex index caaddadc..ecfb4bf6 100644 --- a/lib/microwaveprop/beacon_measurements.ex +++ b/lib/microwaveprop/beacon_measurements.ex @@ -46,7 +46,9 @@ defmodule Microwaveprop.BeaconMeasurements do end defp lookup_beacon(attrs) do - case Map.get(attrs, "beacon_id") || Map.get(attrs, :beacon_id) do + attrs = Map.new(attrs, fn {k, v} -> {to_string(k), v} end) + + case Map.get(attrs, "beacon_id") do id when is_binary(id) and byte_size(id) > 0 -> # Only let monitors report for approved on-the-air beacons. The # client drops 404s without retry, so an admin un-approving a diff --git a/lib/microwaveprop/beacon_monitors.ex b/lib/microwaveprop/beacon_monitors.ex index e3bc4b72..9bbf9ece 100644 --- a/lib/microwaveprop/beacon_monitors.ex +++ b/lib/microwaveprop/beacon_monitors.ex @@ -1,7 +1,6 @@ defmodule Microwaveprop.BeaconMonitors do @moduledoc """ - The BeaconMonitors context: manages the physical SDR-based monitor - hardware assigned to users. + Manages the physical SDR-based monitor hardware assigned to users. Each monitor has a unique random token the `propmonitor` client uses to authenticate its measurement uploads. diff --git a/lib/microwaveprop/propagation.ex b/lib/microwaveprop/propagation.ex index 0ac7d10c..f51f3e71 100644 --- a/lib/microwaveprop/propagation.ex +++ b/lib/microwaveprop/propagation.ex @@ -748,7 +748,7 @@ defmodule Microwaveprop.Propagation do %{min_refractivity_gradient: grad * 1.0} end - defp derive_from_hrrr(%{profile: profile}) when is_list(profile) and length(profile) >= 3 do + defp derive_from_hrrr(%{profile: [_, _, _ | _] = profile}) do case SoundingParams.derive(profile) do nil -> %{} derived -> %{min_refractivity_gradient: derived.min_refractivity_gradient} diff --git a/lib/microwaveprop/propagation/duct.ex b/lib/microwaveprop/propagation/duct.ex index 88386c7f..472d0212 100644 --- a/lib/microwaveprop/propagation/duct.ex +++ b/lib/microwaveprop/propagation/duct.ex @@ -64,9 +64,7 @@ defmodule Microwaveprop.Propagation.Duct do """ @spec detect_ducts([{float(), float()}]) :: [%{base_m: float(), top_m: float(), thickness_m: float(), m_deficit: float()}] - def detect_ducts(m_profile) when length(m_profile) < 2, do: [] - - def detect_ducts(m_profile) do + def detect_ducts([_, _ | _] = m_profile) do m_profile |> Enum.chunk_every(2, 1, :discard) |> Enum.reduce({[], nil}, fn [{h1, m1}, {h2, m2}], acc -> @@ -75,6 +73,8 @@ defmodule Microwaveprop.Propagation.Duct do |> close_trailing_duct() end + def detect_ducts(_), do: [] + defp process_m_pair({ducts, current_duct}, h1, m1, h2, m2) when m2 < m1 do # M is decreasing — we're in a duct duct = extend_or_start_duct(current_duct, h1, m1, h2, m2) diff --git a/lib/microwaveprop/rover/road_proximity.ex b/lib/microwaveprop/rover/road_proximity.ex index d579b900..34018af6 100644 --- a/lib/microwaveprop/rover/road_proximity.ex +++ b/lib/microwaveprop/rover/road_proximity.ex @@ -102,7 +102,7 @@ defmodule Microwaveprop.Rover.RoadProximity do defp segments_from_elements(elements) do Enum.flat_map(elements, fn - %{"type" => "way", "geometry" => geom} when is_list(geom) and length(geom) >= 2 -> + %{"type" => "way", "geometry" => [_, _ | _] = geom} -> geom |> Enum.map(fn %{"lat" => lat, "lon" => lon} -> {lat, lon} end) |> Enum.chunk_every(2, 1, :discard) diff --git a/lib/microwaveprop/terrain/viewshed.ex b/lib/microwaveprop/terrain/viewshed.ex index 86f573fe..114f2735 100644 --- a/lib/microwaveprop/terrain/viewshed.ex +++ b/lib/microwaveprop/terrain/viewshed.ex @@ -202,9 +202,7 @@ defmodule Microwaveprop.Terrain.Viewshed do # Smooth reach_km values with a circular moving average to remove spikes # from SRTM elevation artifacts, then recompute boundary lat/lon. - defp smooth_boundary(points, _origin_lat, _origin_lon) when length(points) < 5, do: points - - defp smooth_boundary(points, origin_lat, origin_lon) do + defp smooth_boundary([_, _, _, _ | _] = points, origin_lat, origin_lon) do reaches = Enum.map(points, & &1.reach_km) n = length(reaches) arr = :array.from_list(reaches) @@ -228,6 +226,8 @@ defmodule Microwaveprop.Terrain.Viewshed do end) end + defp smooth_boundary(points, _origin_lat, _origin_lon), do: points + defp find_first_obstructed_index(interior) do Enum.find_index(interior, & &1.obstructed) end diff --git a/lib/microwaveprop/weather/grib2/wgrib2.ex b/lib/microwaveprop/weather/grib2/wgrib2.ex index 4b836b2e..7e6ee50c 100644 --- a/lib/microwaveprop/weather/grib2/wgrib2.ex +++ b/lib/microwaveprop/weather/grib2/wgrib2.ex @@ -647,10 +647,7 @@ defmodule Microwaveprop.Weather.Grib2.Wgrib2 do end end - defp extract_var_level(parts) when length(parts) >= 5 do - [Enum.at(parts, 3), Enum.at(parts, 4)] - end - + defp extract_var_level([_, _, _, a, b | _]), do: [a, b] defp extract_var_level(_), do: nil @doc false diff --git a/lib/microwaveprop/weather/grid.ex b/lib/microwaveprop/weather/grid.ex index aa0f6da3..a5a12dc9 100644 --- a/lib/microwaveprop/weather/grid.ex +++ b/lib/microwaveprop/weather/grid.ex @@ -486,7 +486,7 @@ defmodule Microwaveprop.Weather.Grid do end end - defp derive_sounding(profile) when is_list(profile) and length(profile) >= 3 do + defp derive_sounding([_, _, _ | _] = profile) do SoundingParams.derive(profile) || %{} end diff --git a/lib/microwaveprop/weather/iem_client.ex b/lib/microwaveprop/weather/iem_client.ex index 2e439016..a36cf4aa 100644 --- a/lib/microwaveprop/weather/iem_client.ex +++ b/lib/microwaveprop/weather/iem_client.ex @@ -200,27 +200,22 @@ defmodule Microwaveprop.Weather.IemClient do end) observations = - Enum.flat_map(results, fn obs -> - # Skip stations with missing core data - if obs["tmpf"] && obs["dwpf"] && obs["lat"] && obs["lon"] do - [ - %{ - station_code: obs["station"], - lat: obs["lat"], - lon: obs["lon"], - utc_valid: parse_utc_valid(obs["utc_valid"]), - temp_f: obs["tmpf"], - dewpoint_f: obs["dwpf"], - wind_speed_kts: obs["sknt"], - sky_condition: obs["skyc1"], - sea_level_pressure_mb: obs["mslp"], - altimeter_setting: obs["alti"], - precip_1h_in: obs["phour"] - } - ] - else - [] - end + results + |> Enum.filter(fn obs -> obs["tmpf"] && obs["dwpf"] && obs["lat"] && obs["lon"] end) + |> Enum.map(fn obs -> + %{ + station_code: obs["station"], + lat: obs["lat"], + lon: obs["lon"], + utc_valid: parse_utc_valid(obs["utc_valid"]), + temp_f: obs["tmpf"], + dewpoint_f: obs["dwpf"], + wind_speed_kts: obs["sknt"], + sky_condition: obs["skyc1"], + sea_level_pressure_mb: obs["mslp"], + altimeter_setting: obs["alti"], + precip_1h_in: obs["phour"] + } end) {:ok, observations} diff --git a/lib/microwaveprop/weather/narr_client.ex b/lib/microwaveprop/weather/narr_client.ex index a7bfbb04..b18aa528 100644 --- a/lib/microwaveprop/weather/narr_client.ex +++ b/lib/microwaveprop/weather/narr_client.ex @@ -382,10 +382,7 @@ defmodule Microwaveprop.Weather.NarrClient do {:ok, code_int} _ -> - case Map.fetch(@cdo_shortname_to_code, token) do - {:ok, code_int} -> {:ok, code_int} - :error -> :error - end + Map.fetch(@cdo_shortname_to_code, token) end end diff --git a/lib/microwaveprop/weather/solar_client.ex b/lib/microwaveprop/weather/solar_client.ex index 1fc42889..38763887 100644 --- a/lib/microwaveprop/weather/solar_client.ex +++ b/lib/microwaveprop/weather/solar_client.ex @@ -49,7 +49,7 @@ defmodule Microwaveprop.Weather.SolarClient do def parse_gfz_row(line) do fields = String.split(line) - if length(fields) >= 28 do + if Enum.count_until(fields, 28) == 28 do [yyyy, mm, dd | rest] = fields # Skip days, days_m, Bsr, dB (indices 0-3 of rest) diff --git a/lib/microwaveprop/weather/sounding_params.ex b/lib/microwaveprop/weather/sounding_params.ex index 1325771f..1cc9179d 100644 --- a/lib/microwaveprop/weather/sounding_params.ex +++ b/lib/microwaveprop/weather/sounding_params.ex @@ -36,7 +36,7 @@ defmodule Microwaveprop.Weather.SoundingParams do |> Enum.filter(fn p -> p["pres"] != nil and p["tmpc"] != nil and p["hght"] != nil end) |> Enum.sort_by(fn p -> p["pres"] end, :desc) - if length(sorted) < 3 do + if Enum.count_until(sorted, 3) < 3 do nil else do_derive(sorted) diff --git a/lib/microwaveprop/weather/surface.ex b/lib/microwaveprop/weather/surface.ex index 155a19e8..794cfd27 100644 --- a/lib/microwaveprop/weather/surface.ex +++ b/lib/microwaveprop/weather/surface.ex @@ -17,8 +17,9 @@ defmodule Microwaveprop.Weather.Surface do @spec find_or_create_station(map()) :: {:ok, Station.t()} | {:error, Ecto.Changeset.t()} def find_or_create_station(attrs) do - code = attrs[:station_code] || attrs["station_code"] - type = attrs[:station_type] || attrs["station_type"] + attrs = Map.new(attrs, fn {k, v} -> {to_string(k), v} end) + code = attrs["station_code"] + type = attrs["station_type"] if code && type do case Repo.get_by(Station, station_code: code, station_type: type) do diff --git a/lib/microwaveprop/weather/weather_layers.ex b/lib/microwaveprop/weather/weather_layers.ex index 1ef23c78..eba9ca34 100644 --- a/lib/microwaveprop/weather/weather_layers.ex +++ b/lib/microwaveprop/weather/weather_layers.ex @@ -54,17 +54,19 @@ defmodule Microwaveprop.Weather.WeatherLayers do end defp duct_min_freq(duct) when is_map(duct) do - case duct[:min_freq_ghz] || duct["min_freq_ghz"] do + d = Map.new(duct, fn {k, v} -> {to_string(k), v} end) + + case d["min_freq_ghz"] do f when is_number(f) and f > 0 -> f * 1.0 - _ -> duct_min_freq_from_geometry(duct) + _ -> duct_min_freq_from_geometry(d) end end defp duct_min_freq(_), do: nil defp duct_min_freq_from_geometry(duct) do - thickness = duct[:thickness_m] || duct["thickness_m"] - deficit = duct[:m_deficit] || duct["m_deficit"] + thickness = duct["thickness_m"] + deficit = duct["m_deficit"] if valid_geometry?(thickness, deficit) do Duct.min_trapped_frequency_ghz(%{thickness_m: thickness * 1.0, m_deficit: deficit * 1.0}) @@ -103,8 +105,7 @@ defmodule Microwaveprop.Weather.WeatherLayers do end defp compute_lapse_rate([]), do: nil - - defp compute_lapse_rate(sorted) when length(sorted) < 2, do: nil + defp compute_lapse_rate([_]), do: nil defp compute_lapse_rate(sorted) do surface = hd(sorted) @@ -197,14 +198,14 @@ defmodule Microwaveprop.Weather.WeatherLayers do # wider frequency range, so it's the most meaningful scalar. defp duct_field(ducts, "base") do ducts - |> Enum.map(fn d -> d["base"] || d[:base_m] || d["base_m"] end) + |> Enum.map(fn d -> d["base"] || d["base_m"] end) |> Enum.reject(&is_nil/1) |> Enum.min(fn -> nil end) end defp duct_field(ducts, "strength") do ducts - |> Enum.map(fn d -> d["strength"] || d[:thickness_m] || d["thickness_m"] end) + |> Enum.map(fn d -> d["strength"] || d["thickness_m"] end) |> Enum.reject(&is_nil/1) |> Enum.max(fn -> nil end) end diff --git a/lib/microwaveprop/workers/hrrr_native_grid_worker.ex b/lib/microwaveprop/workers/hrrr_native_grid_worker.ex index 92329826..7ebc096e 100644 --- a/lib/microwaveprop/workers/hrrr_native_grid_worker.ex +++ b/lib/microwaveprop/workers/hrrr_native_grid_worker.ex @@ -158,22 +158,18 @@ defmodule Microwaveprop.Workers.HrrrNativeGridWorker do now = DateTime.truncate(DateTime.utc_now(), :second) rows = - Enum.flat_map(profiles, fn {{lat, lon}, profile} -> - if profile[:level_count] && profile.level_count > 0 do - [ - Map.merge(profile, %{ - id: Ecto.UUID.generate(), - valid_time: valid_time, - run_time: valid_time, - lat: lat, - lon: lon, - inserted_at: now, - updated_at: now - }) - ] - else - [] - end + profiles + |> Enum.filter(fn {_key, profile} -> profile[:level_count] && profile.level_count > 0 end) + |> Enum.map(fn {{lat, lon}, profile} -> + Map.merge(profile, %{ + id: Ecto.UUID.generate(), + valid_time: valid_time, + run_time: valid_time, + lat: lat, + lon: lon, + inserted_at: now, + updated_at: now + }) end) {inserted, _} = diff --git a/lib/microwaveprop_web/components/layouts.ex b/lib/microwaveprop_web/components/layouts.ex index bced63bc..a53b387a 100644 --- a/lib/microwaveprop_web/components/layouts.ex +++ b/lib/microwaveprop_web/components/layouts.ex @@ -1,7 +1,6 @@ defmodule MicrowavepropWeb.Layouts do @moduledoc """ - This module holds layouts and related functionality - used by your application. + Root layout, flash-group, and app layout used by every page. """ use MicrowavepropWeb, :html diff --git a/lib/microwaveprop_web/controllers/page_html.ex b/lib/microwaveprop_web/controllers/page_html.ex index 75bfafd9..46efc185 100644 --- a/lib/microwaveprop_web/controllers/page_html.ex +++ b/lib/microwaveprop_web/controllers/page_html.ex @@ -1,8 +1,6 @@ defmodule MicrowavepropWeb.PageHTML do @moduledoc """ - This module contains pages rendered by PageController. - - See the `page_html` directory for all templates available. + Page templates for the static-page controller (`PageController`). """ use MicrowavepropWeb, :html diff --git a/lib/microwaveprop_web/live/contact_live/show.ex b/lib/microwaveprop_web/live/contact_live/show.ex index 5803c168..4efa4281 100644 --- a/lib/microwaveprop_web/live/contact_live/show.ex +++ b/lib/microwaveprop_web/live/contact_live/show.ex @@ -30,7 +30,6 @@ defmodule MicrowavepropWeb.ContactLive.Show do @enqueue_subnet {172, 56, 0, 0} @enqueue_mask 13 - @band_options BandConfig.band_options() @mode_options ~w(CW SSB FM FT8 FT4 Q65) @impl true @@ -77,7 +76,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do editing: false, edit_form: nil, pending_edit: load_pending_edit(contact, socket), - band_options: @band_options, + band_options: BandConfig.band_options(), mode_options: @mode_options ) @@ -855,7 +854,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do end defp internal_network?(session) do - case Map.get(session, :remote_ip) || Map.get(session, "remote_ip") do + case Map.get(session, :remote_ip) do nil -> false diff --git a/lib/microwaveprop_web/live/eme_live.ex b/lib/microwaveprop_web/live/eme_live.ex index b70a935f..b05a9307 100644 --- a/lib/microwaveprop_web/live/eme_live.ex +++ b/lib/microwaveprop_web/live/eme_live.ex @@ -24,7 +24,6 @@ defmodule MicrowavepropWeb.EmeLive do alias Microwaveprop.Propagation.BandConfig alias Microwaveprop.Propagation.Eme - @band_options BandConfig.band_options() @tick_ms 10_000 @defaults %{ @@ -48,7 +47,7 @@ defmodule MicrowavepropWeb.EmeLive do {:ok, assign(socket, page_title: "EME Calculator", - band_options: @band_options, + band_options: BandConfig.band_options(), source: "", source_resolved: nil, band: "1296", diff --git a/lib/microwaveprop_web/live/path_live.ex b/lib/microwaveprop_web/live/path_live.ex index 6070596b..d0d2025f 100644 --- a/lib/microwaveprop_web/live/path_live.ex +++ b/lib/microwaveprop_web/live/path_live.ex @@ -17,8 +17,6 @@ defmodule MicrowavepropWeb.PathLive do require Logger - @band_options BandConfig.band_options() - @url_params ~w(source destination band src_height_ft dst_height_ft tx_power_dbm src_gain_dbi dst_gain_dbi) @defaults %{ "source" => "", @@ -41,7 +39,7 @@ defmodule MicrowavepropWeb.PathLive do {:ok, assign(socket, page_title: "Path Calculator", - band_options: @band_options, + band_options: BandConfig.band_options(), result: nil, error: nil, computing: false, diff --git a/lib/microwaveprop_web/live/skewt_live.ex b/lib/microwaveprop_web/live/skewt_live.ex index f81d3b7c..b6cfb0bb 100644 --- a/lib/microwaveprop_web/live/skewt_live.ex +++ b/lib/microwaveprop_web/live/skewt_live.ex @@ -286,7 +286,8 @@ defmodule MicrowavepropWeb.SkewtLive do end defp extract_profile(cell) do - cell[:profile] || cell["profile"] || [] + cell = Map.new(cell, fn {k, v} -> {to_string(k), v} end) + Map.get(cell, "profile", []) end # Right-edge critical-level markers, mirroring SPC's annotation rail. @@ -321,9 +322,11 @@ defmodule MicrowavepropWeb.SkewtLive do end defp extract_pres_hght(lvl) do + lvl = Map.new(lvl, fn {k, v} -> {to_string(k), v} end) + %{ - pres: lvl[:pres] || lvl["pres"] || lvl[:pres_mb] || lvl["pres_mb"], - hght: lvl[:hght] || lvl["hght"] || lvl[:hght_m] || lvl["hght_m"] + pres: lvl["pres"] || lvl["pres_mb"], + hght: lvl["hght"] || lvl["hght_m"] } end diff --git a/lib/microwaveprop_web/live/submit_live.ex b/lib/microwaveprop_web/live/submit_live.ex index f7a03828..61bad998 100644 --- a/lib/microwaveprop_web/live/submit_live.ex +++ b/lib/microwaveprop_web/live/submit_live.ex @@ -10,8 +10,6 @@ defmodule MicrowavepropWeb.SubmitLive do alias Microwaveprop.Radio.CsvImport alias Microwaveprop.Workers.ContactWeatherEnqueueWorker - @band_options BandConfig.band_options() - @mode_options ~w(CW SSB FM FT8 FT4 Q65) @impl true @@ -37,7 +35,7 @@ defmodule MicrowavepropWeb.SubmitLive do assign(socket, page_title: "Submit Contact", form: to_form(changeset), - band_options: @band_options, + band_options: BandConfig.band_options(), mode_options: @mode_options, submitted_at: nil, active_tab: :single, diff --git a/lib/mix/tasks/import_contest_logs.ex b/lib/mix/tasks/import_contest_logs.ex index a3e89e50..a3d55399 100644 --- a/lib/mix/tasks/import_contest_logs.ex +++ b/lib/mix/tasks/import_contest_logs.ex @@ -80,7 +80,7 @@ defmodule Mix.Tasks.ImportContestLogs do Mix.shell().info(" Row #{row.row_num}: #{Enum.join(row.messages, ", ")}") end) - if length(invalid) > 10 do + if Enum.count_until(invalid, 11) == 11 do Mix.shell().info(" ... and #{length(invalid) - 10} more") end end diff --git a/mix.exs b/mix.exs index ead2da72..f1a72580 100644 --- a/mix.exs +++ b/mix.exs @@ -98,6 +98,7 @@ defmodule Microwaveprop.MixProject do # doesn't have live_table so it doesn't need the override. {:oban_web, "~> 2.12", path: "vendor/oban_web", override: true}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, + {:ex_slop, "~> 0.1", only: [:dev, :test], runtime: false}, {:jump_credo_checks, "~> 0.4", only: [:dev, :test], runtime: false}, # Security scanning for Phoenix apps — flags common vulnerabilities # like XSS, CSRF, and insecure config patterns. diff --git a/mix.lock b/mix.lock index cad5e95c..9e75a2e3 100644 --- a/mix.lock +++ b/mix.lock @@ -18,6 +18,7 @@ "elixir_make": {:hex, :elixir_make, "0.10.0", "16577e2583a79bb79237bbff349619ef5d80afffc07eac6e4faf0d00e2ddaf7d", [:mix], [], "hexpm", "dc1f09fb7fa68866b886abd5f0f3c83553b1a19a52359a899e92af1bb3b31982"}, "erlex": {:hex, :erlex, "0.2.9", "7debbbaa9f4f368b8cd648983e0f1d7963028508e9c59e9d4ed504e94ef52a55", [:mix], [], "hexpm", "8cfffc0ec7159e6d73de2ab28a588064de80f88b2798d5cbe4482cbbc200178b"}, "esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"}, + "ex_slop": {:hex, :ex_slop, "0.4.4", "8ca277b902e93193eb3545e414c309026a51204e5eb7d6ada5ff96555de6abcb", [:mix], [{:credo, "~> 1.7", [hex: :credo, repo: "hexpm", optional: false]}], "hexpm", "03ef1b2553ebad45c8ceb3e482eed5f1ccd08fc992215a9da0ef6196935107e2"}, "exla": {:hex, :exla, "0.13.0", "f2238c2e9f71a775fd7ba3f4cd5e0ae3da69b06ddd18bc6e63f3f60c7c434cd9", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:fine, "~> 0.1", [hex: :fine, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:nx, "~> 0.13", [hex: :nx, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:xla, "~> 0.10.0", [hex: :xla, repo: "hexpm", optional: false]}], "hexpm", "781ec0231c4bbce87c1dc84cab4ae63ad763f03b2115e5d9b5481a97fbbe3ce5"}, "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, "finch": {:hex, :finch, "0.23.0", "e3f9287ac25a8832f848b144c2b57346aac65b205e2e0629a52adfe6507fd837", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.8", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "80e58d3f936f57e3fdf404f83a3642897ae6d9fb642934e46da4d8fe761b99d5"}, diff --git a/test/microwaveprop/accounts_test.exs b/test/microwaveprop/accounts_test.exs index b4089bdd..aebd5d18 100644 --- a/test/microwaveprop/accounts_test.exs +++ b/test/microwaveprop/accounts_test.exs @@ -562,7 +562,6 @@ defmodule Microwaveprop.AccountsTest do u2 = user_fixture(callsign: "K1BBB") result = Accounts.list_users_select() - assert is_list(result) assert result |> Enum.find(fn {_cs, id} -> id == u1.id end) |> elem(0) == "W5AAA" assert result |> Enum.find(fn {_cs, id} -> id == u2.id end) |> elem(0) == "K1BBB" end diff --git a/test/microwaveprop/aprs/path_parser_test.exs b/test/microwaveprop/aprs/path_parser_test.exs index 2549d5d8..615c5e66 100644 --- a/test/microwaveprop/aprs/path_parser_test.exs +++ b/test/microwaveprop/aprs/path_parser_test.exs @@ -49,7 +49,7 @@ defmodule Microwaveprop.Aprs.PathParserTest do lookup ) - assert length(hops) == 2 + assert Enum.count_until(hops, 3) == 2 [first, second] = hops assert first.src_callsign == @sender @@ -145,7 +145,7 @@ defmodule Microwaveprop.Aprs.PathParserTest do lookup ) - assert length(hops) == 2 + assert Enum.count_until(hops, 3) == 2 # First hop is sender → K5XYZ-3 as before. assert Enum.at(hops, 0).src_callsign == @sender assert Enum.at(hops, 0).dst_callsign == "K5XYZ-3" diff --git a/test/microwaveprop/aprs_test.exs b/test/microwaveprop/aprs_test.exs index 583ab7c5..65f597d4 100644 --- a/test/microwaveprop/aprs_test.exs +++ b/test/microwaveprop/aprs_test.exs @@ -73,7 +73,7 @@ defmodule Microwaveprop.AprsTest do rows = Aprs.recent_packets_with_paths(since: since) - assert length(rows) == 1 + assert Enum.count_until(rows, 2) == 1 assert hd(rows).sender == "TEST-VALID" end @@ -103,7 +103,7 @@ defmodule Microwaveprop.AprsTest do end) rows = Aprs.recent_packets_with_paths(since: since, limit: 2) - assert length(rows) == 2 + assert Enum.count_until(rows, 3) == 2 end test "returns rows in received_at ascending order" do diff --git a/test/microwaveprop/backtest_test.exs b/test/microwaveprop/backtest_test.exs index 8bb82a7c..df9656ed 100644 --- a/test/microwaveprop/backtest_test.exs +++ b/test/microwaveprop/backtest_test.exs @@ -108,7 +108,7 @@ defmodule Microwaveprop.BacktestTest do }) samples = Backtest.random_baseline(5, sample_size: 10) - assert length(samples) == 5 + assert Enum.count_until(samples, 6) == 5 Enum.each(samples, fn {lat, lon, time} -> assert lat in [1.0, 2.0] @@ -152,7 +152,7 @@ defmodule Microwaveprop.BacktestTest do results = Backtest.consolidated_report(features, sample_size: 10, baseline_size: 5) assert results != [] - assert length(results) == 3 + assert Enum.count_until(results, 4) == 3 one = Enum.find(results, &(&1.feature_name == "always_one")) assert one.qso_distribution.count == 2 diff --git a/test/microwaveprop/beacons/beacon_test.exs b/test/microwaveprop/beacons/beacon_test.exs index bd77ac6b..9752ce32 100644 --- a/test/microwaveprop/beacons/beacon_test.exs +++ b/test/microwaveprop/beacons/beacon_test.exs @@ -265,7 +265,7 @@ defmodule Microwaveprop.Beacons.BeaconTest do assert "q65a_15" in keyings assert "q65e_120" in keyings # 2 simple + 2 voice/digital + 5 groups * 4 periods = 24 - assert length(keyings) == 24 + assert Enum.count_until(keyings, 25) == 24 end test "keying_label/1 returns human label for known key" do @@ -292,7 +292,7 @@ defmodule Microwaveprop.Beacons.BeaconTest do test "Q65A group contains four period options in {label, value} form" do {_group, options} = Enum.find(Beacon.keying_options(), fn {g, _} -> g == "Q65A" end) - assert length(options) == 4 + assert Enum.count_until(options, 5) == 4 assert {"Q65A-15", "q65a_15"} in options assert {"Q65A-120", "q65a_120"} in options end diff --git a/test/microwaveprop/buildings/index_test.exs b/test/microwaveprop/buildings/index_test.exs index d495b4e6..5a7103bf 100644 --- a/test/microwaveprop/buildings/index_test.exs +++ b/test/microwaveprop/buildings/index_test.exs @@ -38,7 +38,7 @@ defmodule Microwaveprop.Buildings.IndexTest do ]) records = Index.records_near(32.8000, -97.0000, 50) - assert length(records) == 2 + assert Enum.count_until(records, 3) == 2 heights = records |> Enum.map(& &1.height_m) |> Enum.sort() assert heights == [8.0, 25.0] end diff --git a/test/microwaveprop/buildings/parser_test.exs b/test/microwaveprop/buildings/parser_test.exs index 95dffd27..22cbb315 100644 --- a/test/microwaveprop/buildings/parser_test.exs +++ b/test/microwaveprop/buildings/parser_test.exs @@ -24,7 +24,7 @@ defmodule Microwaveprop.Buildings.ParserTest do }) records = path |> Parser.parse_tile() |> Enum.to_list() - assert length(records) == 1 + assert Enum.count_until(records, 2) == 1 rec = hd(records) assert rec.height_m == 15.0 assert rec.centroid_lat > 32.9 @@ -53,7 +53,7 @@ defmodule Microwaveprop.Buildings.ParserTest do }) records = path |> Parser.parse_tile() |> Enum.to_list() - assert length(records) == 1 + assert Enum.count_until(records, 2) == 1 assert hd(records).height_m == 10.0 end @@ -160,7 +160,7 @@ defmodule Microwaveprop.Buildings.ParserTest do path = create_gz_lines(features) records = path |> Parser.parse_tile() |> Enum.to_list() - assert length(records) == 2 + assert Enum.count_until(records, 3) == 2 heights = Enum.map(records, & &1.height_m) assert 5.0 in heights assert 20.0 in heights diff --git a/test/microwaveprop/commercial/poll_worker_test.exs b/test/microwaveprop/commercial/poll_worker_test.exs index a3903efe..9f1177da 100644 --- a/test/microwaveprop/commercial/poll_worker_test.exs +++ b/test/microwaveprop/commercial/poll_worker_test.exs @@ -31,7 +31,7 @@ defmodule Microwaveprop.Commercial.PollWorkerTest do PollWorker.poll_and_record([link], fn _host, _community, _type -> {:ok, snmp_result} end) samples = Repo.all(Sample) - assert length(samples) == 1 + assert Enum.count_until(samples, 2) == 1 assert hd(samples).rx_power_0 == -55.0 assert hd(samples).link_id == link.id end @@ -66,7 +66,7 @@ defmodule Microwaveprop.Commercial.PollWorkerTest do PollWorker.poll_and_record([link1, link2], poll_fn) - assert length(Repo.all(Sample)) == 1 + assert Enum.count_until(Repo.all(Sample), 2) == 1 end test "no-ops cleanly on an empty link list" do diff --git a/test/microwaveprop/commercial_test.exs b/test/microwaveprop/commercial_test.exs index b47d7713..00030855 100644 --- a/test/microwaveprop/commercial_test.exs +++ b/test/microwaveprop/commercial_test.exs @@ -26,7 +26,7 @@ defmodule Microwaveprop.CommercialTest do Commercial.create_link(%{@link_attrs | label: "disabled-link", enabled: false}) links = Commercial.enabled_links() - assert length(links) == 1 + assert Enum.count_until(links, 2) == 1 assert hd(links).label == "test-link" end diff --git a/test/microwaveprop/ionosphere/giro_client_test.exs b/test/microwaveprop/ionosphere/giro_client_test.exs index 87756185..4b0fb4ae 100644 --- a/test/microwaveprop/ionosphere/giro_client_test.exs +++ b/test/microwaveprop/ionosphere/giro_client_test.exs @@ -11,7 +11,7 @@ defmodule Microwaveprop.Ionosphere.GiroClientTest do # The fixture is 2026-04-15 16:00 → 19:00 UTC at 7.5-minute cadence, # so 25 observations. - assert length(rows) == 25 + assert Enum.count_until(rows, 26) == 25 first = hd(rows) assert first.valid_time == ~U[2026-04-15 16:00:00Z] diff --git a/test/microwaveprop/partition_manager_test.exs b/test/microwaveprop/partition_manager_test.exs index 5585ea3d..df90ed0e 100644 --- a/test/microwaveprop/partition_manager_test.exs +++ b/test/microwaveprop/partition_manager_test.exs @@ -46,11 +46,11 @@ defmodule Microwaveprop.PartitionManagerTest do results = PartitionManager.ensure_quarterly_partitions(2, [parent]) # 1 current quarter + 2 lookahead = 3 partitions total. - assert length(results) == 3 + assert Enum.count_until(results, 4) == 3 assert Enum.all?(results, fn {p, _, status} -> p == parent and status == :created end) partitions = partitions_of(parent) - assert length(partitions) == 3 + assert Enum.count_until(partitions, 4) == 3 # Each bound must be a 3-month range starting on a quarter # boundary (Jan/Apr/Jul/Oct), with the next bound starting @@ -89,7 +89,7 @@ defmodule Microwaveprop.PartitionManagerTest do assert Enum.all?(results, fn {_, _, status} -> status == :exists end) # Still only 2 partitions on the parent (current + 1 lookahead). - assert length(partitions_of(parent)) == 2 + assert Enum.count_until(partitions_of(parent), 3) == 2 end test "names partitions _YYYY_MM with the quarter-start month" do @@ -126,7 +126,7 @@ defmodule Microwaveprop.PartitionManagerTest do end) |> Enum.uniq() - assert length(years) >= 2 + assert Enum.count_until(years, 2) == 2 end end diff --git a/test/microwaveprop/propagation/band_config_property_test.exs b/test/microwaveprop/propagation/band_config_property_test.exs index 8816f2d6..145fd055 100644 --- a/test/microwaveprop/propagation/band_config_property_test.exs +++ b/test/microwaveprop/propagation/band_config_property_test.exs @@ -81,7 +81,7 @@ defmodule Microwaveprop.Propagation.BandConfigPropertyTest do property "sunrise_table returns 12 numeric entries in a plausible hour range" do table = BandConfig.sunrise_table() - assert length(table) == 12 + assert Enum.count_until(table, 13) == 12 check all(idx <- StreamData.integer(0..11)) do entry = Enum.at(table, idx) diff --git a/test/microwaveprop/propagation/band_config_test.exs b/test/microwaveprop/propagation/band_config_test.exs index be71d847..081890cf 100644 --- a/test/microwaveprop/propagation/band_config_test.exs +++ b/test/microwaveprop/propagation/band_config_test.exs @@ -187,7 +187,7 @@ defmodule Microwaveprop.Propagation.BandConfigTest do describe "all_bands/0" do test "returns 23 bands (13 original + 142/145/288/322/403/411 GHz + 50/144/222/432 MHz)" do bands = BandConfig.all_bands() - assert length(bands) == 23 + assert Enum.count_until(bands, 24) == 23 end test "bands are sorted by frequency" do @@ -263,7 +263,7 @@ defmodule Microwaveprop.Propagation.BandConfigTest do describe "sunrise_table/0" do test "returns 12 monthly values" do table = BandConfig.sunrise_table() - assert length(table) == 12 + assert Enum.count_until(table, 13) == 12 end test "returns expected sunrise hours" do @@ -287,7 +287,7 @@ defmodule Microwaveprop.Propagation.BandConfigTest do describe "tiers/0" do test "returns 5 tiers" do tiers = BandConfig.tiers() - assert length(tiers) == 5 + assert Enum.count_until(tiers, 6) == 5 end test "tiers are ordered by threshold descending" do diff --git a/test/microwaveprop/propagation/duct_property_test.exs b/test/microwaveprop/propagation/duct_property_test.exs index 9e047b5c..83b6d3ec 100644 --- a/test/microwaveprop/propagation/duct_property_test.exs +++ b/test/microwaveprop/propagation/duct_property_test.exs @@ -92,7 +92,7 @@ defmodule Microwaveprop.Propagation.DuctPropertyTest do sorted_h = heights |> Enum.uniq() |> Enum.sort() # Skip degenerate cases where uniq removed too many entries. - if length(sorted_h) >= 2 do + if Enum.count_until(sorted_h, 2) == 2 do m_profile = Enum.map(sorted_h, fn h -> {h, base_m + slope * h} end) assert Duct.detect_ducts(m_profile) == [] end diff --git a/test/microwaveprop/propagation/duct_test.exs b/test/microwaveprop/propagation/duct_test.exs index c2842eb9..92a987e6 100644 --- a/test/microwaveprop/propagation/duct_test.exs +++ b/test/microwaveprop/propagation/duct_test.exs @@ -23,7 +23,7 @@ defmodule Microwaveprop.Propagation.DuctTest do ]) n_profile = Duct.refractivity_profile(p) - assert length(n_profile) == 3 + assert Enum.count_until(n_profile, 4) == 3 # N should decrease with height in a normal atmosphere [{_, n0}, {_, n1}, {_, n2}] = n_profile @@ -72,7 +72,7 @@ defmodule Microwaveprop.Propagation.DuctTest do ] ducts = Duct.detect_ducts(m_profile) - assert length(ducts) == 1 + assert Enum.count_until(ducts, 2) == 1 [duct] = ducts assert duct.base_m == 0.0 @@ -94,7 +94,7 @@ defmodule Microwaveprop.Propagation.DuctTest do ] ducts = Duct.detect_ducts(m_profile) - assert length(ducts) == 1 + assert Enum.count_until(ducts, 2) == 1 [duct] = ducts assert duct.base_m == 500.0 @@ -113,7 +113,7 @@ defmodule Microwaveprop.Propagation.DuctTest do ] ducts = Duct.detect_ducts(m_profile) - assert length(ducts) == 2 + assert Enum.count_until(ducts, 3) == 2 end end diff --git a/test/microwaveprop/propagation/freshness_monitor_test.exs b/test/microwaveprop/propagation/freshness_monitor_test.exs index eb93f1ee..a366066c 100644 --- a/test/microwaveprop/propagation/freshness_monitor_test.exs +++ b/test/microwaveprop/propagation/freshness_monitor_test.exs @@ -108,7 +108,7 @@ defmodule Microwaveprop.Propagation.FreshnessMonitorTest do FreshnessMonitor.handle_info(:check, %{}) FreshnessMonitor.handle_info(:check, %{}) - assert 1 == length(all_enqueued(worker: PropagationGridWorker)) + assert Enum.count_until(all_enqueued(worker: PropagationGridWorker), 2) == 1 end) end diff --git a/test/microwaveprop/propagation/grid_task_enqueuer_test.exs b/test/microwaveprop/propagation/grid_task_enqueuer_test.exs index 4a1b586b..7d3d696e 100644 --- a/test/microwaveprop/propagation/grid_task_enqueuer_test.exs +++ b/test/microwaveprop/propagation/grid_task_enqueuer_test.exs @@ -49,7 +49,7 @@ defmodule Microwaveprop.Propagation.GridTaskEnqueuerTest do 48 ] - assert length(rows) == 28 + assert Enum.count_until(rows, 29) == 28 assert rows |> Enum.map(& &1.forecast_hour) |> Enum.sort() == expected_fhs assert Enum.all?(rows, &(&1.status == "queued")) @@ -100,7 +100,7 @@ defmodule Microwaveprop.Propagation.GridTaskEnqueuerTest do sources = Repo.all(from(g in "grid_tasks", where: g.run_time == ^run_time, select: g.source)) - assert length(sources) == 29 + assert Enum.count_until(sources, 30) == 29 assert Enum.uniq(sources) == ["hrdps"] end @@ -238,7 +238,7 @@ defmodule Microwaveprop.Propagation.GridTaskEnqueuerTest do ) ) - assert length(rows) == 1 + assert Enum.count_until(rows, 2) == 1 [%{kind: kind, forecast_hour: fh, valid_time: vt}] = rows assert kind == "forecast" assert fh == 5 diff --git a/test/microwaveprop/propagation/grid_test.exs b/test/microwaveprop/propagation/grid_test.exs index e64ad0d2..1efb84fc 100644 --- a/test/microwaveprop/propagation/grid_test.exs +++ b/test/microwaveprop/propagation/grid_test.exs @@ -6,8 +6,8 @@ defmodule Microwaveprop.Propagation.GridTest do describe "conus_points/0" do test "generates grid at 0.125 degree resolution" do points = Grid.conus_points() - assert length(points) > 5000 - assert length(points) < 100_000 + assert Enum.count_until(points, 5001) == 5001 + assert Enum.count_until(points, 100) < 100_000 end test "all points within CONUS bounds" do diff --git a/test/microwaveprop/propagation/mode_thresholds_test.exs b/test/microwaveprop/propagation/mode_thresholds_test.exs index a6ebbdc1..788b85ad 100644 --- a/test/microwaveprop/propagation/mode_thresholds_test.exs +++ b/test/microwaveprop/propagation/mode_thresholds_test.exs @@ -23,7 +23,7 @@ defmodule Microwaveprop.Propagation.ModeThresholdsTest do assert {:cw, "CW"} in modes assert {:q65_60a, "Q65-60A"} in modes assert {:q65_120b, "Q65-120B"} in modes - assert length(modes) == 4 + assert Enum.count_until(modes, 5) == 4 end end diff --git a/test/microwaveprop/propagation/model_test.exs b/test/microwaveprop/propagation/model_test.exs index f240e1ab..1a5dcc65 100644 --- a/test/microwaveprop/propagation/model_test.exs +++ b/test/microwaveprop/propagation/model_test.exs @@ -100,13 +100,13 @@ defmodule Microwaveprop.Propagation.ModelTest do freq_mhz: 10_000 }) - assert length(features) == 20 + assert Enum.count_until(features, 21) == 20 assert Enum.all?(features, &is_float/1) end test "uses defaults for missing values" do features = Model.encode_features(%{}) - assert length(features) == 20 + assert Enum.count_until(features, 21) == 20 assert Enum.all?(features, &is_float/1) end @@ -216,7 +216,7 @@ defmodule Microwaveprop.Propagation.ModelTest do describe "feature_names/0" do test "returns 20 feature names" do - assert length(Model.feature_names()) == 20 + assert Enum.count_until(Model.feature_names(), 21) == 20 end end @@ -248,7 +248,7 @@ defmodule Microwaveprop.Propagation.ModelTest do assert is_integer(result.score) assert result.score >= 0 and result.score <= 100 - assert length(result.contributions) == 20 + assert Enum.count_until(result.contributions, 21) == 20 Enum.each(result.contributions, fn c -> assert byte_size(to_string(c.feature)) > 0 diff --git a/test/microwaveprop/propagation/notify_listener_test.exs b/test/microwaveprop/propagation/notify_listener_test.exs index fc6aab2b..d34aacf6 100644 --- a/test/microwaveprop/propagation/notify_listener_test.exs +++ b/test/microwaveprop/propagation/notify_listener_test.exs @@ -110,9 +110,7 @@ defmodule Microwaveprop.Propagation.NotifyListenerTest do # Monitor the background Task that materializes the scalar file. ref = Process.monitor(task_pid) - assert_receive {:DOWN, ^ref, :process, ^task_pid, :normal}, - 2_000, - "expected ScalarFile to be materialized after handle_propagation_ready" + assert_receive {:DOWN, ^ref, :process, ^task_pid, :normal} assert ScalarFile.exists?(vt) end diff --git a/test/microwaveprop/propagation/rain_scatter_test.exs b/test/microwaveprop/propagation/rain_scatter_test.exs index 7451b454..285f2ed1 100644 --- a/test/microwaveprop/propagation/rain_scatter_test.exs +++ b/test/microwaveprop/propagation/rain_scatter_test.exs @@ -113,7 +113,7 @@ defmodule Microwaveprop.Propagation.RainScatterTest do many = for i <- 1..25, do: {@obs_lat + 0.5 + i * 0.05, @obs_lon, 40.0} result = RainScatter.find_scatter_cells(many, @obs_lat, @obs_lon, 10.0) - assert length(result) == 20 + assert Enum.count_until(result, 21) == 20 end test "frequency gain is monotonically increasing up to ~50 GHz then plateaus" do diff --git a/test/microwaveprop/propagation/recalibrator_test.exs b/test/microwaveprop/propagation/recalibrator_test.exs index c97a0d1c..bd4d35e7 100644 --- a/test/microwaveprop/propagation/recalibrator_test.exs +++ b/test/microwaveprop/propagation/recalibrator_test.exs @@ -132,7 +132,7 @@ defmodule Microwaveprop.Propagation.RecalibratorTest do factors = Recalibrator.compute_factors(profile, ~U[2024-07-15 06:00:00Z]) - assert length(factors) == 10 + assert Enum.count_until(factors, 11) == 10 Enum.each(factors, fn f -> assert is_number(f) diff --git a/test/microwaveprop/propagation/score_cache_test.exs b/test/microwaveprop/propagation/score_cache_test.exs index a9eb8f66..b1b08355 100644 --- a/test/microwaveprop/propagation/score_cache_test.exs +++ b/test/microwaveprop/propagation/score_cache_test.exs @@ -57,7 +57,7 @@ defmodule Microwaveprop.Propagation.ScoreCacheTest do test "returns all scores when bounds is nil" do assert {:ok, all} = ScoreCache.fetch_bounds(10_000, ~U[2026-04-12 12:00:00Z], nil) - assert length(all) == 3 + assert Enum.count_until(all, 4) == 3 end test "returns only scores inside the bounds" do @@ -66,7 +66,7 @@ defmodule Microwaveprop.Propagation.ScoreCacheTest do assert {:ok, filtered} = ScoreCache.fetch_bounds(10_000, ~U[2026-04-12 12:00:00Z], bounds) - assert length(filtered) == 2 + assert Enum.count_until(filtered, 3) == 2 assert Enum.all?(filtered, fn %{lat: lat} -> lat >= 31.0 and lat <= 35.0 end) end diff --git a/test/microwaveprop/propagation/scores_file_test.exs b/test/microwaveprop/propagation/scores_file_test.exs index 3b294e66..ebedcb9d 100644 --- a/test/microwaveprop/propagation/scores_file_test.exs +++ b/test/microwaveprop/propagation/scores_file_test.exs @@ -110,7 +110,7 @@ defmodule Microwaveprop.Propagation.ScoresFileTest do ) times = ScoresFile.list_valid_times(10_000) - assert length(times) == 2 + assert Enum.count_until(times, 3) == 2 assert t1 in times assert t2 in times end @@ -268,7 +268,7 @@ defmodule Microwaveprop.Propagation.ScoresFileTest do File.cp!(hrrr_path, hrdps_path) scores = ScoresFile.read_bounds(10_000, vt) - assert length(scores) == 2 + assert Enum.count_until(scores, 3) == 2 assert Enum.all?(scores, &(&1.score == 42)) end diff --git a/test/microwaveprop/propagation_test.exs b/test/microwaveprop/propagation_test.exs index 79d9aed3..39943124 100644 --- a/test/microwaveprop/propagation_test.exs +++ b/test/microwaveprop/propagation_test.exs @@ -52,7 +52,7 @@ defmodule Microwaveprop.PropagationTest do valid_time = ~U[2026-07-15 13:00:00Z] results = Propagation.score_grid_point(hrrr_profile, valid_time, 33.0, -97.0) - assert length(results) == 23 + assert Enum.count_until(results, 24) == 23 Enum.each(results, fn result -> assert result.score >= 0 and result.score <= 100 @@ -162,7 +162,7 @@ defmodule Microwaveprop.PropagationTest do valid_time = ~U[2026-07-15 13:00:00Z] results = Propagation.score_grid_point(hrrr_profile_without_array, valid_time, 33.0, -97.0) - assert length(results) == 23 + assert Enum.count_until(results, 24) == 23 Enum.each(results, fn result -> assert result.score >= 0 and result.score <= 100 @@ -586,7 +586,7 @@ defmodule Microwaveprop.PropagationTest do Propagation.replace_scores(scores, valid_time) results = Propagation.latest_scores(10_000) - assert length(results) == 2 + assert Enum.count_until(results, 3) == 2 end test "returns empty list when no data" do @@ -793,7 +793,7 @@ defmodule Microwaveprop.PropagationTest do times = Enum.map(result, & &1.valid_time) assert t3 in times - assert length(result) == 3 + assert Enum.count_until(result, 4) == 3 end end @@ -961,7 +961,7 @@ defmodule Microwaveprop.PropagationTest do bounds = %{"south" => 20.0, "north" => 30.0, "west" => -130.0, "east" => -120.0} fresh = Propagation.scores_at_fresh(10_000, valid_time, bounds) - assert length(fresh) == 1 + assert Enum.count_until(fresh, 2) == 1 assert hd(fresh).lat == 25.0 end end @@ -984,7 +984,7 @@ defmodule Microwaveprop.PropagationTest do ScoreCache.sync() assert {:ok, scores} = ScoreCache.fetch(10_000, valid_time) - assert length(scores) == 2 + assert Enum.count_until(scores, 3) == 2 end test "only warms the requested band" do @@ -1117,7 +1117,7 @@ defmodule Microwaveprop.PropagationTest do end rows = Propagation.list_recent_run_timings(limit: 10) - assert length(rows) == 3 + assert Enum.count_until(rows, 4) == 3 assert Enum.map(rows, & &1.forecast_hour) == [2, 1, 0] end end diff --git a/test/microwaveprop/radio/adif_import_test.exs b/test/microwaveprop/radio/adif_import_test.exs index 55b4471e..0b527b99 100644 --- a/test/microwaveprop/radio/adif_import_test.exs +++ b/test/microwaveprop/radio/adif_import_test.exs @@ -36,7 +36,7 @@ defmodule Microwaveprop.Radio.AdifImportTest do describe "preview/2" do test "parses a valid ADIF record" do assert {:ok, preview} = AdifImport.preview(@valid_adif, @submitter) - assert length(preview.valid) == 1 + assert Enum.count_until(preview.valid, 2) == 1 assert preview.invalid == [] assert preview.duplicates == [] assert preview.refinements == [] @@ -179,7 +179,7 @@ defmodule Microwaveprop.Radio.AdifImportTest do """ assert {:ok, preview} = AdifImport.preview(adif, @submitter) - assert length(preview.valid) == 2 + assert Enum.count_until(preview.valid, 3) == 2 end test "skips ADIF header before " do @@ -192,7 +192,7 @@ defmodule Microwaveprop.Radio.AdifImportTest do """ assert {:ok, preview} = AdifImport.preview(adif, @submitter) - assert length(preview.valid) == 1 + assert Enum.count_until(preview.valid, 2) == 1 end test "returns error for empty ADIF" do @@ -216,7 +216,7 @@ defmodule Microwaveprop.Radio.AdifImportTest do assert {:ok, preview} = AdifImport.preview(adif, @submitter) assert preview.valid == [] - assert length(preview.invalid) == 1 + assert Enum.count_until(preview.invalid, 2) == 1 end test "skips HF contacts" do @@ -226,7 +226,7 @@ defmodule Microwaveprop.Radio.AdifImportTest do assert {:ok, preview} = AdifImport.preview(adif, @submitter) assert preview.valid == [] - assert length(preview.invalid) == 1 + assert Enum.count_until(preview.invalid, 2) == 1 end test "accepts 2m contacts" do @@ -255,7 +255,7 @@ defmodule Microwaveprop.Radio.AdifImportTest do assert {:ok, preview} = AdifImport.preview(@valid_adif, @submitter) assert preview.valid == [] assert preview.refinements == [] - assert length(preview.duplicates) == 1 + assert Enum.count_until(preview.duplicates, 2) == 1 end test "upload with longer grid than existing routes to refinements" do @@ -288,7 +288,7 @@ defmodule Microwaveprop.Radio.AdifImportTest do """ assert {:ok, preview} = AdifImport.preview(adif, @submitter) - assert length(preview.valid) == 1 + assert Enum.count_until(preview.valid, 2) == 1 end test "handles TIME_ON with 4 digits (HHMM)" do @@ -330,7 +330,7 @@ defmodule Microwaveprop.Radio.AdifImportTest do """ assert {:ok, preview} = AdifImport.preview(adif, @submitter) - assert length(preview.valid) == 1 + assert Enum.count_until(preview.valid, 2) == 1 end test "strips APP_ prefix from app-defined fields" do @@ -418,7 +418,7 @@ defmodule Microwaveprop.Radio.AdifImportTest do """ assert {:ok, preview} = AdifImport.preview(adif, @submitter) - assert length(preview.valid) == 1 + assert Enum.count_until(preview.valid, 2) == 1 end test "parses records containing UTF-8 characters in earlier fields" do diff --git a/test/microwaveprop/radio/contact_edit_test.exs b/test/microwaveprop/radio/contact_edit_test.exs index c90ef70c..2ff04374 100644 --- a/test/microwaveprop/radio/contact_edit_test.exs +++ b/test/microwaveprop/radio/contact_edit_test.exs @@ -254,7 +254,7 @@ defmodule Microwaveprop.Radio.ContactEditTest do Radio.create_contact_edit(contact, user, %{"grid2" => "EL29ab"}) edits = Radio.list_pending_edits() - assert length(edits) == 2 + assert Enum.count_until(edits, 3) == 2 fields = Enum.flat_map(edits, &Map.keys(&1.proposed_changes)) assert "grid1" in fields diff --git a/test/microwaveprop/radio/csv_import_test.exs b/test/microwaveprop/radio/csv_import_test.exs index 1ce28f5f..35dd96a0 100644 --- a/test/microwaveprop/radio/csv_import_test.exs +++ b/test/microwaveprop/radio/csv_import_test.exs @@ -89,7 +89,7 @@ defmodule Microwaveprop.Radio.CsvImportTest do ) assert {:ok, %{imported: imported, errors: []}} = CsvImport.import(csv, @submitter) - assert length(imported) == 2 + assert Enum.count_until(imported, 3) == 2 end test "trims whitespace from fields" do @@ -133,7 +133,7 @@ defmodule Microwaveprop.Radio.CsvImportTest do ) assert {:ok, %{imported: imported, errors: []}} = CsvImport.import(csv, @submitter) - assert length(imported) == 2 + assert Enum.count_until(imported, 3) == 2 end test "contacts are persisted to the database" do @@ -249,7 +249,7 @@ defmodule Microwaveprop.Radio.CsvImportTest do ) assert {:ok, %{imported: imported, errors: errors}} = CsvImport.import(csv, @submitter) - assert length(imported) == 2 + assert Enum.count_until(imported, 3) == 2 assert [{3, _error_msgs}] = errors end @@ -281,7 +281,7 @@ defmodule Microwaveprop.Radio.CsvImportTest do ) assert {:ok, %{imported: imported, errors: errors}} = CsvImport.import(csv, @submitter) - assert length(imported) == 2 + assert Enum.count_until(imported, 3) == 2 # Row 4 is the bad row (header=1, data=2, blank=3, bad=4, good=5) assert [{4, _error_msgs}] = errors end @@ -592,7 +592,7 @@ defmodule Microwaveprop.Radio.CsvImportTest do assert {:ok, preview} = CsvImport.preview(csv, @submitter) assert preview.valid == [] - assert length(preview.invalid) == 2 + assert Enum.count_until(preview.invalid, 3) == 2 assert Enum.all?(preview.invalid, &(&1.messages != [])) end @@ -647,7 +647,7 @@ defmodule Microwaveprop.Radio.CsvImportTest do ) assert {:ok, preview} = CsvImport.preview(csv, @submitter) - assert length(preview.valid) == 2 + assert Enum.count_until(preview.valid, 3) == 2 assert preview.duplicates == [] end @@ -663,7 +663,7 @@ defmodule Microwaveprop.Radio.CsvImportTest do ) assert {:ok, preview} = CsvImport.preview(csv, @submitter) - assert length(preview.valid) == 2 + assert Enum.count_until(preview.valid, 3) == 2 end test "different grids are not duplicates (rover moved)" do @@ -678,7 +678,7 @@ defmodule Microwaveprop.Radio.CsvImportTest do ) assert {:ok, preview} = CsvImport.preview(csv, @submitter) - assert length(preview.valid) == 2 + assert Enum.count_until(preview.valid, 3) == 2 assert preview.refinements == [] end @@ -777,7 +777,7 @@ defmodule Microwaveprop.Radio.CsvImportTest do @valid_header <> "\n" <> "W5XD,K5TR,EM13,EM00,10000,CW,2026-03-28T18:15:00Z" assert {:ok, p2} = CsvImport.preview(rover_csv, @submitter) - assert length(p2.valid) == 1 + assert Enum.count_until(p2.valid, 2) == 1 assert p2.refinements == [] assert p2.duplicates == [] end @@ -973,7 +973,7 @@ defmodule Microwaveprop.Radio.CsvImportTest do assert_enqueued(worker: ContactImportWorker, args: %{"offset" => 0, "limit" => 100}) assert_enqueued(worker: ContactImportWorker, args: %{"offset" => 100, "limit" => 100}) assert_enqueued(worker: ContactImportWorker, args: %{"offset" => 200, "limit" => 50}) - assert length(all_enqueued(worker: ContactImportWorker)) == 3 + assert Enum.count_until(all_enqueued(worker: ContactImportWorker), 4) == 3 end test "returns pre-run counters and status on the persisted run" do diff --git a/test/microwaveprop/radio_test.exs b/test/microwaveprop/radio_test.exs index 2004f574..017a53f0 100644 --- a/test/microwaveprop/radio_test.exs +++ b/test/microwaveprop/radio_test.exs @@ -50,7 +50,7 @@ defmodule Microwaveprop.RadioTest do result = Radio.list_contacts() - assert length(result.entries) == 20 + assert Enum.count_until(result.entries, 21) == 20 assert result.page == 1 assert result.total_pages == 2 assert result.total_entries == 25 @@ -68,7 +68,7 @@ defmodule Microwaveprop.RadioTest do result = Radio.list_contacts(page: 2) - assert length(result.entries) == 5 + assert Enum.count_until(result.entries, 6) == 5 assert result.page == 2 assert result.total_pages == 2 end @@ -81,7 +81,7 @@ defmodule Microwaveprop.RadioTest do result = Radio.list_contacts(per_page: 50) - assert length(result.entries) == 25 + assert Enum.count_until(result.entries, 26) == 25 assert result.total_pages == 1 end @@ -93,7 +93,7 @@ defmodule Microwaveprop.RadioTest do # 5000 is well above the 200 cap; should still return all 3 rows. result = Radio.list_contacts(per_page: 5000) - assert length(result.entries) == 3 + assert Enum.count_until(result.entries, 4) == 3 end test "falls back to default when per_page is invalid" do @@ -103,7 +103,7 @@ defmodule Microwaveprop.RadioTest do end result = Radio.list_contacts(per_page: 0) - assert length(result.entries) == 20 + assert Enum.count_until(result.entries, 21) == 20 end end @@ -171,7 +171,7 @@ defmodule Microwaveprop.RadioTest do create_contact(%{qso_timestamp: ts}) end - assert length(Radio.unprocessed_contacts(3)) == 3 + assert Enum.count_until(Radio.unprocessed_contacts(3), 4) == 3 end end @@ -300,7 +300,7 @@ defmodule Microwaveprop.RadioTest do create_contact(%{qso_timestamp: ts}) end - assert length(Radio.unprocessed_hrrr_contacts(3)) == 3 + assert Enum.count_until(Radio.unprocessed_hrrr_contacts(3), 4) == 3 end end @@ -339,7 +339,7 @@ defmodule Microwaveprop.RadioTest do results = Radio.unprocessed_terrain_contacts() ids = Enum.map(results, & &1.id) assert q1.id in ids - assert length(ids) == 1 + assert Enum.count_until(ids, 2) == 1 end test "excludes QSOs already marked as terrain_status" do @@ -364,7 +364,7 @@ defmodule Microwaveprop.RadioTest do create_contact(%{qso_timestamp: ts}) end - assert length(Radio.unprocessed_terrain_contacts(3)) == 3 + assert Enum.count_until(Radio.unprocessed_terrain_contacts(3), 4) == 3 end end @@ -404,7 +404,7 @@ defmodule Microwaveprop.RadioTest do points = Radio.contact_path_points(contact) - assert length(points) == 3 + assert Enum.count_until(points, 4) == 3 [{lat1, lon1}, {mid_lat, mid_lon}, {lat2, lon2}] = points assert lat1 == 32.9 assert lon1 == -97.0 @@ -436,7 +436,7 @@ defmodule Microwaveprop.RadioTest do }) points = Radio.contact_path_points(contact) - assert length(points) == 3 + assert Enum.count_until(points, 4) == 3 end test "computes a great-circle midpoint across the anti-meridian" do @@ -769,7 +769,7 @@ defmodule Microwaveprop.RadioTest do result = Radio.list_contacts_involving_callsign("W5ISP") - assert length(result) == 100 + assert Enum.count_until(result, 101) == 100 # The 100 newest by qso_timestamp correspond to i = 21..120; i=120 is newest. assert hd(result).station2 == "K5X120" end diff --git a/test/microwaveprop/rover/candidate_detail_test.exs b/test/microwaveprop/rover/candidate_detail_test.exs index f4fce7b9..50ea246a 100644 --- a/test/microwaveprop/rover/candidate_detail_test.exs +++ b/test/microwaveprop/rover/candidate_detail_test.exs @@ -26,7 +26,7 @@ defmodule Microwaveprop.Rover.CandidateDetailTest do result = CandidateDetail.summarize(candidate, stations, 10_000, ~U[2026-04-29 12:00:00Z], :ssb) - assert length(result.links) == 2 + assert Enum.count_until(result.links, 3) == 2 Enum.each(result.links, fn link -> assert byte_size(link.callsign) > 0 diff --git a/test/microwaveprop/rover/compute_test.exs b/test/microwaveprop/rover/compute_test.exs index e49dd57c..0e5d9b17 100644 --- a/test/microwaveprop/rover/compute_test.exs +++ b/test/microwaveprop/rover/compute_test.exs @@ -49,7 +49,7 @@ defmodule Microwaveprop.Rover.ComputeTest do assert Map.has_key?(result, :top_candidates) assert result.cells != [] - assert length(result.top_candidates) <= 5 + assert Enum.count_until(result.top_candidates, 6) <= 5 refute result.top_candidates == [] # Sorted by score desc. diff --git a/test/microwaveprop/rover/location_test.exs b/test/microwaveprop/rover/location_test.exs index d1e908b2..75010bdf 100644 --- a/test/microwaveprop/rover/location_test.exs +++ b/test/microwaveprop/rover/location_test.exs @@ -17,7 +17,7 @@ defmodule Microwaveprop.RoverLocationsTest do {:ok, _l2} = Rover.create_location(b, %{lat: 33.0, lon: -96.5, status: :bad}) result = Rover.list_locations() - assert length(result) == 2 + assert Enum.count_until(result, 3) == 2 assert Enum.all?(result, &match?(%Location{}, &1)) end end diff --git a/test/microwaveprop/rover_planning_test.exs b/test/microwaveprop/rover_planning_test.exs index 9f92082c..6d2008bd 100644 --- a/test/microwaveprop/rover_planning_test.exs +++ b/test/microwaveprop/rover_planning_test.exs @@ -113,7 +113,7 @@ defmodule Microwaveprop.RoverPlanningTest do assert is_float(station.lon) paths = RoverPlanning.list_paths(mission) - assert length(paths) == 1 + assert Enum.count_until(paths, 2) == 1 assert hd(paths).status == :complete end diff --git a/test/microwaveprop/rover_test.exs b/test/microwaveprop/rover_test.exs index 748bfdd5..9a1e5644 100644 --- a/test/microwaveprop/rover_test.exs +++ b/test/microwaveprop/rover_test.exs @@ -83,7 +83,7 @@ defmodule Microwaveprop.RoverTest do describe "default_stations/0" do test "returns 3 hardcoded NTMS stations" do stations = Rover.default_stations() - assert length(stations) == 3 + assert Enum.count_until(stations, 4) == 3 callsigns = Enum.map(stations, & &1.callsign) assert "W5LUA" in callsigns assert "W5HN" in callsigns diff --git a/test/microwaveprop/space_weather/swpc_client_test.exs b/test/microwaveprop/space_weather/swpc_client_test.exs index 59036012..fa10cc60 100644 --- a/test/microwaveprop/space_weather/swpc_client_test.exs +++ b/test/microwaveprop/space_weather/swpc_client_test.exs @@ -9,7 +9,7 @@ defmodule Microwaveprop.SpaceWeather.SwpcClientTest do body = File.read!("test/fixtures/swpc/kp_sample.json") assert {:ok, rows} = SwpcClient.parse_kp(body) - assert length(rows) == 10 + assert Enum.count_until(rows, 11) == 10 first = hd(rows) assert first.valid_time == ~U[2026-04-15 13:49:00Z] @@ -53,7 +53,7 @@ defmodule Microwaveprop.SpaceWeather.SwpcClientTest do body = File.read!("test/fixtures/swpc/f107_sample.json") assert {:ok, rows} = SwpcClient.parse_f107(body) - assert length(rows) == 10 + assert Enum.count_until(rows, 11) == 10 first = hd(rows) assert first.valid_time == ~U[2026-04-15 17:00:00Z] @@ -77,7 +77,7 @@ defmodule Microwaveprop.SpaceWeather.SwpcClientTest do # Fixture has 12 records (6 timestamps x 2 bands). We keep the # long-wavelength band (0.1-0.8nm) which is the standard SWF / # D-layer absorption proxy. - assert length(rows) == 6 + assert Enum.count_until(rows, 7) == 6 assert Enum.all?(rows, fn r -> r.energy_band == "0.1-0.8nm" end) first = hd(rows) diff --git a/test/microwaveprop/terrain/elevation_client_test.exs b/test/microwaveprop/terrain/elevation_client_test.exs index d2608d63..520d47cf 100644 --- a/test/microwaveprop/terrain/elevation_client_test.exs +++ b/test/microwaveprop/terrain/elevation_client_test.exs @@ -7,7 +7,7 @@ defmodule Microwaveprop.Terrain.ElevationClientTest do test "generates N+1 evenly spaced points" do points = ElevationClient.sample_path(32.9, -97.0, 30.3, -97.7, 4) - assert length(points) == 5 + assert Enum.count_until(points, 6) == 5 first = hd(points) last = List.last(points) @@ -40,7 +40,7 @@ defmodule Microwaveprop.Terrain.ElevationClientTest do assert {:ok, profile} = ElevationClient.fetch_elevation_profile(32.9, -97.0, 30.3, -97.7, 2) - assert length(profile) == 3 + assert Enum.count_until(profile, 4) == 3 first = hd(profile) assert Map.has_key?(first, :lat) @@ -67,7 +67,7 @@ defmodule Microwaveprop.Terrain.ElevationClientTest do ElevationClient.fetch_elevation_profile(32.9, -97.0, 30.3, -97.7, 150) # 151 points → 2 batches (100 + 51) - assert length(profile) == 151 + assert Enum.count_until(profile, 152) == 151 assert :counters.get(call_count, 1) == 2 end @@ -97,7 +97,7 @@ defmodule Microwaveprop.Terrain.ElevationClientTest do assert {:ok, profile} = ElevationClient.fetch_elevation_profile(32.9, -97.0, 30.3, -97.7, 2) - assert length(profile) == 3 + assert Enum.count_until(profile, 4) == 3 assert hd(profile).elev == 100.0 end @@ -141,7 +141,7 @@ defmodule Microwaveprop.Terrain.ElevationClientSrtmTest do assert {:ok, profile} = ElevationClient.fetch_elevation_profile(32.78, -96.8, 32.5, -96.5, 4) - assert length(profile) == 5 + assert Enum.count_until(profile, 6) == 5 assert hd(profile).elev > 0 end @@ -152,7 +152,7 @@ defmodule Microwaveprop.Terrain.ElevationClientSrtmTest do assert {:ok, profile} = ElevationClient.fetch_elevation_profile(32.9, -97.0, 30.3, -97.7, 2) - assert length(profile) == 3 + assert Enum.count_until(profile, 4) == 3 assert Enum.all?(profile, fn p -> p.elev == 0 end) end end diff --git a/test/microwaveprop/terrain/srtm_test.exs b/test/microwaveprop/terrain/srtm_test.exs index 72f1f87b..128aa53f 100644 --- a/test/microwaveprop/terrain/srtm_test.exs +++ b/test/microwaveprop/terrain/srtm_test.exs @@ -158,7 +158,7 @@ defmodule Microwaveprop.Terrain.SrtmTest do assert {:ok, profile} = Srtm.fetch_elevation_profile(32.78, -96.8, 32.5, -96.5, @tiles_dir, 10) - assert length(profile) == 11 + assert Enum.count_until(profile, 12) == 11 first = hd(profile) assert Map.has_key?(first, :lat) diff --git a/test/microwaveprop/valkey_test.exs b/test/microwaveprop/valkey_test.exs index d6aee265..29aefc7f 100644 --- a/test/microwaveprop/valkey_test.exs +++ b/test/microwaveprop/valkey_test.exs @@ -139,7 +139,7 @@ defmodule Microwaveprop.ValkeyTest do test "returns :ok when all SETs succeed" do expect(MockAdapter, :pipeline, fn _conn, cmds, _opts -> - assert length(cmds) == 2 + assert Enum.count_until(cmds, 3) == 2 assert cmds |> hd() |> List.first() == "SET" {:ok, ["OK", "OK"]} end) diff --git a/test/microwaveprop/weather/gefs_client_test.exs b/test/microwaveprop/weather/gefs_client_test.exs index 49965988..5c5b2062 100644 --- a/test/microwaveprop/weather/gefs_client_test.exs +++ b/test/microwaveprop/weather/gefs_client_test.exs @@ -140,7 +140,7 @@ defmodule Microwaveprop.Weather.GefsClientTest do test "builds a pressure-level profile with Td derived from RH at each level" do p = GefsClient.build_profile(@raw) - assert length(p.profile) == 2 + assert Enum.count_until(p.profile, 3) == 2 l1000 = Enum.find(p.profile, &(&1["pres"] == 1000.0)) assert_in_delta l1000["tmpc"], 20.0, 0.01 diff --git a/test/microwaveprop/weather/gefs_profile_test.exs b/test/microwaveprop/weather/gefs_profile_test.exs index be4e85ec..24ae7d3f 100644 --- a/test/microwaveprop/weather/gefs_profile_test.exs +++ b/test/microwaveprop/weather/gefs_profile_test.exs @@ -85,7 +85,7 @@ defmodule Microwaveprop.Weather.GefsProfileTest do assert count == 3 rows = Microwaveprop.Repo.all(GefsProfile) - assert length(rows) == 3 + assert Enum.count_until(rows, 4) == 3 end test "skips conflicting rows quietly" do diff --git a/test/microwaveprop/weather/grib2/extractor_test.exs b/test/microwaveprop/weather/grib2/extractor_test.exs index 824fde7b..5d3d0027 100644 --- a/test/microwaveprop/weather/grib2/extractor_test.exs +++ b/test/microwaveprop/weather/grib2/extractor_test.exs @@ -14,7 +14,7 @@ defmodule Microwaveprop.Weather.Grib2.ExtractorTest do blob = msg1 <> msg2 messages = Extractor.split_messages(blob) - assert length(messages) == 2 + assert Enum.count_until(messages, 3) == 2 assert Enum.at(messages, 0) == msg1 assert Enum.at(messages, 1) == msg2 end @@ -22,7 +22,7 @@ defmodule Microwaveprop.Weather.Grib2.ExtractorTest do test "handles single message" do msg = build_synthetic_grib2_message(0, 0, 0, 103, 2) messages = Extractor.split_messages(msg) - assert length(messages) == 1 + assert Enum.count_until(messages, 2) == 1 end test "handles empty binary" do diff --git a/test/microwaveprop/weather/grib2/wgrib2_test.exs b/test/microwaveprop/weather/grib2/wgrib2_test.exs index eada20ce..6fb235f0 100644 --- a/test/microwaveprop/weather/grib2/wgrib2_test.exs +++ b/test/microwaveprop/weather/grib2/wgrib2_test.exs @@ -192,7 +192,7 @@ defmodule Microwaveprop.Weather.Grib2.Wgrib2Test do {:ok, grid} = Wgrib2.extract_grid(fixture, ":(TMP|DPT):", @dallas_grid) # hrrr_multi.grib2 contains exactly TMP:2m and DPT:2m. - assert length(messages) == 2 + assert Enum.count_until(messages, 3) == 2 vars = messages |> Enum.map(& &1.var) |> Enum.sort() assert vars == ["DPT", "TMP"] diff --git a/test/microwaveprop/weather/grid_cache_test.exs b/test/microwaveprop/weather/grid_cache_test.exs index 0c82ad62..02140873 100644 --- a/test/microwaveprop/weather/grid_cache_test.exs +++ b/test/microwaveprop/weather/grid_cache_test.exs @@ -34,13 +34,13 @@ defmodule Microwaveprop.Weather.GridCacheTest do test "returns all rows when bounds are nil" do assert {:ok, list} = GridCache.fetch_bounds(~U[2026-04-12 12:00:00Z], nil) - assert length(list) == 3 + assert Enum.count_until(list, 4) == 3 end test "returns only rows inside the bounds" do bounds = %{"south" => 31.0, "north" => 35.0, "west" => -100.0, "east" => -95.0} assert {:ok, list} = GridCache.fetch_bounds(~U[2026-04-12 12:00:00Z], bounds) - assert length(list) == 2 + assert Enum.count_until(list, 3) == 2 end test "returns :miss when the valid_time is not cached" do diff --git a/test/microwaveprop/weather/grid_cache_valkey_test.exs b/test/microwaveprop/weather/grid_cache_valkey_test.exs index ededd4b6..ce7f7555 100644 --- a/test/microwaveprop/weather/grid_cache_valkey_test.exs +++ b/test/microwaveprop/weather/grid_cache_valkey_test.exs @@ -173,7 +173,7 @@ defmodule Microwaveprop.Weather.GridCacheValkey.DataCacheTest do test "fetch returns decoded rows" do result = GridCache.fetch(~U[2026-05-01 12:00:00Z]) assert {:ok, rows} = result - assert length(rows) == 2 + assert Enum.count_until(rows, 3) == 2 assert hd(rows).lat == 32.0 assert hd(rows).temperature == 25.0 end @@ -181,7 +181,7 @@ defmodule Microwaveprop.Weather.GridCacheValkey.DataCacheTest do test "fetch_bounds filters rows within bounds" do bounds = %{"south" => 30.0, "north" => 35.0, "west" => -100.0, "east" => -95.0} assert {:ok, rows} = GridCache.fetch_bounds(~U[2026-05-01 12:00:00Z], bounds) - assert length(rows) == 1 + assert Enum.count_until(rows, 2) == 1 assert hd(rows).lat == 32.0 end diff --git a/test/microwaveprop/weather/hrdps_client_test.exs b/test/microwaveprop/weather/hrdps_client_test.exs index f2188b44..fb8b51d6 100644 --- a/test/microwaveprop/weather/hrdps_client_test.exs +++ b/test/microwaveprop/weather/hrdps_client_test.exs @@ -146,7 +146,7 @@ defmodule Microwaveprop.Weather.HrdpsClientTest do } profile = HrdpsClient.build_profile_from_extracted(extracted) - assert length(profile.profile) == 1 + assert Enum.count_until(profile.profile, 2) == 1 assert hd(profile.profile)["pres"] == 700.0 end end diff --git a/test/microwaveprop/weather/hrrr_client_test.exs b/test/microwaveprop/weather/hrrr_client_test.exs index 977b98de..c6fd9a87 100644 --- a/test/microwaveprop/weather/hrrr_client_test.exs +++ b/test/microwaveprop/weather/hrrr_client_test.exs @@ -94,7 +94,7 @@ defmodule Microwaveprop.Weather.HrrrClientTest do test "parses idx into structured list" do entries = HrrrClient.parse_idx(@sample_idx) - assert length(entries) == 6 + assert Enum.count_until(entries, 7) == 6 first = hd(entries) assert first.msg == 1 @@ -129,7 +129,7 @@ defmodule Microwaveprop.Weather.HrrrClientTest do entries = HrrrClient.parse_idx(input) - assert length(entries) == 2 + assert Enum.count_until(entries, 3) == 2 assert Enum.map(entries, & &1.msg) == [1, 2] assert Enum.map(entries, & &1.var) == ["TMP", "DPT"] end @@ -151,7 +151,7 @@ defmodule Microwaveprop.Weather.HrrrClientTest do ranges = HrrrClient.byte_ranges_for_messages(entries, wanted) - assert length(ranges) == 2 + assert Enum.count_until(ranges, 3) == 2 assert {0, 999} in ranges assert {2000, 2999} in ranges end @@ -167,7 +167,7 @@ defmodule Microwaveprop.Weather.HrrrClientTest do ranges = HrrrClient.byte_ranges_for_messages(entries, wanted) # Last message - uses a large end offset - assert length(ranges) == 1 + assert Enum.count_until(ranges, 2) == 1 [{start, _end}] = ranges assert start == 1000 end @@ -329,7 +329,7 @@ defmodule Microwaveprop.Weather.HrrrClientTest do assert_in_delta result.surface_pressure_mb, 1013.5, 0.1 assert result.hpbl_m == 1500.0 assert result.pwat_mm == 25.0 - assert length(result.profile) == 3 + assert Enum.count_until(result.profile, 4) == 3 first_level = hd(result.profile) assert first_level["pres"] == 1000.0 @@ -392,7 +392,7 @@ defmodule Microwaveprop.Weather.HrrrClientTest do } result = HrrrClient.build_profile(parsed) - assert length(result.profile) == 1 + assert Enum.count_until(result.profile, 2) == 1 end test "includes upper-air levels above 700 mb for the skew-T plot" do @@ -435,7 +435,7 @@ defmodule Microwaveprop.Weather.HrrrClientTest do messages = HrrrClient.pressure_messages(:grid) # 13 levels × 3 vars (TMP/DPT/HGT) - assert length(messages) == 39 + assert Enum.count_until(messages, 40) == 39 levels = messages @@ -463,7 +463,7 @@ defmodule Microwaveprop.Weather.HrrrClientTest do messages = HrrrClient.pressure_messages(:profile) # 25 levels × 3 vars - assert length(messages) == 75 + assert Enum.count_until(messages, 76) == 75 levels = messages @@ -494,7 +494,7 @@ defmodule Microwaveprop.Weather.HrrrClientTest do test "returns list of surface message descriptors" do messages = HrrrClient.surface_messages() - assert length(messages) == 9 + assert Enum.count_until(messages, 10) == 9 vars = Enum.map(messages, & &1.var) assert "TMP" in vars diff --git a/test/microwaveprop/weather/hrrr_native_client_test.exs b/test/microwaveprop/weather/hrrr_native_client_test.exs index b03361d7..fab431bf 100644 --- a/test/microwaveprop/weather/hrrr_native_client_test.exs +++ b/test/microwaveprop/weather/hrrr_native_client_test.exs @@ -29,7 +29,7 @@ defmodule Microwaveprop.Weather.HrrrNativeClientTest do describe "native_messages/0" do test "lists 350 messages (7 vars x 50 levels)" do messages = HrrrNativeClient.native_messages() - assert length(messages) == 350 + assert Enum.count_until(messages, 351) == 350 end test "every level appears with every essential variable" do @@ -147,7 +147,7 @@ defmodule Microwaveprop.Weather.HrrrNativeClientTest do ranges = HrrrNativeClient.essential_byte_ranges(idx_entries) # Expect 3 ranges (the three essentials we actually have) - assert length(ranges) == 3 + assert Enum.count_until(ranges, 4) == 3 # Each range is {start, end} tuples, all integers Enum.each(ranges, fn {s, e} -> assert is_integer(s) and is_integer(e) @@ -175,7 +175,7 @@ defmodule Microwaveprop.Weather.HrrrNativeClientTest do test "native_variables lists exactly the 7 native-grid fields" do vars = HrrrNativeClient.native_variables() - assert length(vars) == 7 + assert Enum.count_until(vars, 8) == 7 assert "TMP" in vars assert "SPFH" in vars assert "HGT" in vars @@ -248,7 +248,7 @@ defmodule Microwaveprop.Weather.HrrrNativeClientTest do ranges = HrrrNativeClient.duct_byte_ranges(idx_entries) # UGRD is not in the duct set, so only 3 ranges should come back. - assert length(ranges) == 3 + assert Enum.count_until(ranges, 4) == 3 Enum.each(ranges, fn {s, e} -> assert is_integer(s) and is_integer(e) diff --git a/test/microwaveprop/weather/hrrr_native_profile_test.exs b/test/microwaveprop/weather/hrrr_native_profile_test.exs index 3e6cac84..8d985e1e 100644 --- a/test/microwaveprop/weather/hrrr_native_profile_test.exs +++ b/test/microwaveprop/weather/hrrr_native_profile_test.exs @@ -63,7 +63,7 @@ defmodule Microwaveprop.Weather.HrrrNativeProfileTest do levels = HrrrNativeProfile.to_skew_t_profile(profile) - assert length(levels) == 3 + assert Enum.count_until(levels, 4) == 3 # Ordered surface-first (descending pressure). pressures = Enum.map(levels, & &1["pres"]) @@ -104,7 +104,7 @@ defmodule Microwaveprop.Weather.HrrrNativeProfileTest do profile = struct(HrrrNativeProfile, attrs) levels = HrrrNativeProfile.to_skew_t_profile(profile) - assert length(levels) == 1 + assert Enum.count_until(levels, 2) == 1 assert hd(levels)["hght"] == 10.0 end end diff --git a/test/microwaveprop/weather/hrrr_profile_lookup_test.exs b/test/microwaveprop/weather/hrrr_profile_lookup_test.exs index bbb58840..a5840e4a 100644 --- a/test/microwaveprop/weather/hrrr_profile_lookup_test.exs +++ b/test/microwaveprop/weather/hrrr_profile_lookup_test.exs @@ -42,7 +42,7 @@ defmodule Microwaveprop.Weather.HrrrProfileLookupTest do end times = HrrrProfileLookup.list_valid_times_near(32.65, -97.13, limit: 3) - assert length(times) == 3 + assert Enum.count_until(times, 4) == 3 # Capped to the 3 most recent — but still sorted ascending in the # returned list so SkewtLive's selector renders left-to-right. assert Enum.map(times, &DateTime.to_iso8601/1) == [ @@ -59,8 +59,8 @@ defmodule Microwaveprop.Weather.HrrrProfileLookupTest do tight = HrrrProfileLookup.list_valid_times_near(32.625, -97.125, tolerance_deg: 0.07) wide = HrrrProfileLookup.list_valid_times_near(32.625, -97.125, tolerance_deg: 0.25) - assert length(tight) == 1 - assert length(wide) == 2 + assert Enum.count_until(tight, 2) == 1 + assert Enum.count_until(wide, 3) == 2 end end @@ -80,7 +80,7 @@ defmodule Microwaveprop.Weather.HrrrProfileLookupTest do # Default fixture is the 13-level @full_profile (HRRR's standard # canonical pressure-level set). - assert length(profile) == 13 + assert Enum.count_until(profile, 14) == 13 [%{"pres" => pres} | _] = profile assert pres in [1000.0, 1000] assert cell[:surface_pressure_mb] == 1013.0 @@ -140,7 +140,7 @@ defmodule Microwaveprop.Weather.HrrrProfileLookupTest do # Threshold of 0 → both rows. both = HrrrProfileLookup.list_valid_times_near(32.65, -97.13, min_profile_levels: 0) - assert length(both) == 2 + assert Enum.count_until(both, 3) == 2 end test "read_point_near returns nil for partial-profile valid_times under the default threshold" do diff --git a/test/microwaveprop/weather/hrrr_profile_test.exs b/test/microwaveprop/weather/hrrr_profile_test.exs index 33bb435e..a2f2b890 100644 --- a/test/microwaveprop/weather/hrrr_profile_test.exs +++ b/test/microwaveprop/weather/hrrr_profile_test.exs @@ -51,7 +51,7 @@ defmodule Microwaveprop.Weather.HrrrProfileTest do test "persists to the database with JSONB profile" do changeset = HrrrProfile.changeset(%HrrrProfile{}, @valid_attrs) assert {:ok, profile} = Repo.insert(changeset) - assert length(profile.profile) == 3 + assert Enum.count_until(profile.profile, 4) == 3 assert hd(profile.profile)["pres"] == 1000.0 assert profile.ducting_detected == false assert profile.hpbl_m == 1500.0 diff --git a/test/microwaveprop/weather/iem_client_test.exs b/test/microwaveprop/weather/iem_client_test.exs index bd069b76..b453a05e 100644 --- a/test/microwaveprop/weather/iem_client_test.exs +++ b/test/microwaveprop/weather/iem_client_test.exs @@ -13,7 +13,7 @@ defmodule Microwaveprop.Weather.IemClientTest do """ rows = IemClient.parse_asos_csv(csv) - assert length(rows) == 2 + assert Enum.count_until(rows, 3) == 2 first = hd(rows) assert first.temp_f == 75.0 @@ -40,7 +40,7 @@ defmodule Microwaveprop.Weather.IemClientTest do """ rows = IemClient.parse_asos_csv(csv) - assert length(rows) == 1 + assert Enum.count_until(rows, 2) == 1 first = hd(rows) assert first.temp_f == nil @@ -59,7 +59,7 @@ defmodule Microwaveprop.Weather.IemClientTest do """ rows = IemClient.parse_asos_csv(csv) - assert length(rows) == 1 + assert Enum.count_until(rows, 2) == 1 end end @@ -79,11 +79,11 @@ defmodule Microwaveprop.Weather.IemClientTest do } result = IemClient.parse_raob_json(json) - assert length(result) == 1 + assert Enum.count_until(result, 2) == 1 sounding = hd(result) assert sounding.observed_at == ~U[2026-03-28 12:00:00Z] - assert length(sounding.profile) == 2 + assert Enum.count_until(sounding.profile, 3) == 2 assert hd(sounding.profile)["pres"] == 1013.0 end @@ -142,7 +142,7 @@ defmodule Microwaveprop.Weather.IemClientTest do """ rows = IemClient.parse_asos_csv(csv) - assert length(rows) == 2 + assert Enum.count_until(rows, 3) == 2 codes = Enum.map(rows, & &1.station_code) assert "KDFW" in codes @@ -173,8 +173,8 @@ defmodule Microwaveprop.Weather.IemClientTest do ) assert map_size(grouped) > 0 - assert length(Map.fetch!(grouped, "KDFW")) == 2 - assert length(Map.fetch!(grouped, "KFTW")) == 1 + assert Enum.count_until(Map.fetch!(grouped, "KDFW"), 3) == 2 + assert Enum.count_until(Map.fetch!(grouped, "KFTW"), 2) == 1 # Stations in the request but absent from the response get an empty list # so callers can distinguish "no data" from "not asked about this code". assert Map.fetch!(grouped, "KSPS") == [] @@ -273,7 +273,7 @@ defmodule Microwaveprop.Weather.IemClientTest do assert {:ok, [sounding]} = IemClient.fetch_raob("FWD", ~U[2026-03-28 12:00:00Z]) assert sounding.observed_at == ~U[2026-03-28 12:00:00Z] - assert length(sounding.profile) == 1 + assert Enum.count_until(sounding.profile, 2) == 1 end test "returns error on non-200 status" do @@ -308,7 +308,7 @@ defmodule Microwaveprop.Weather.IemClientTest do end) assert {:ok, observations} = IemClient.fetch_current_asos(["TX_ASOS"]) - assert length(observations) == 1 + assert Enum.count_until(observations, 2) == 1 assert hd(observations).station_code == "KDFW" assert hd(observations).temp_f == 75.0 end @@ -346,7 +346,7 @@ defmodule Microwaveprop.Weather.IemClientTest do } stations = IemClient.parse_network_json(json) - assert length(stations) == 2 + assert Enum.count_until(stations, 3) == 2 first = hd(stations) assert first.station_code == "KJFK" @@ -380,7 +380,7 @@ defmodule Microwaveprop.Weather.IemClientTest do } stations = IemClient.parse_network_json(json) - assert length(stations) == 1 + assert Enum.count_until(stations, 2) == 1 station = hd(stations) assert station.station_code == "KDFW" @@ -406,7 +406,7 @@ defmodule Microwaveprop.Weather.IemClientTest do } result = IemClient.parse_iemre_json(json) - assert length(result) == 2 + assert Enum.count_until(result, 3) == 2 assert hd(result)["utc_hour"] == 0 end @@ -444,7 +444,7 @@ defmodule Microwaveprop.Weather.IemClientTest do end) assert {:ok, data} = IemClient.fetch_iemre(32.875, -97.0, ~D[2026-03-28]) - assert length(data) == 2 + assert Enum.count_until(data, 3) == 2 assert Enum.at(data, 1)["p01m_mm"] == 1.2 end @@ -520,7 +520,7 @@ defmodule Microwaveprop.Weather.IemClientTest do """ rows = IemClient.parse_asos_csv(csv) - assert length(rows) == 1 + assert Enum.count_until(rows, 2) == 1 end test "returns [] for an empty string body" do diff --git a/test/microwaveprop/weather/iemre_observation_test.exs b/test/microwaveprop/weather/iemre_observation_test.exs index aa658e12..15cfbc3a 100644 --- a/test/microwaveprop/weather/iemre_observation_test.exs +++ b/test/microwaveprop/weather/iemre_observation_test.exs @@ -41,7 +41,7 @@ defmodule Microwaveprop.Weather.IemreObservationTest do assert obs.lat == 32.875 assert obs.lon == -97.0 assert obs.date == ~D[2026-03-28] - assert length(obs.hourly) == 2 + assert Enum.count_until(obs.hourly, 3) == 2 end test "enforces unique constraint on lat, lon, date" do diff --git a/test/microwaveprop/weather/narr_client_test.exs b/test/microwaveprop/weather/narr_client_test.exs index e0daca87..66c9d42b 100644 --- a/test/microwaveprop/weather/narr_client_test.exs +++ b/test/microwaveprop/weather/narr_client_test.exs @@ -222,7 +222,7 @@ defmodule Microwaveprop.Weather.NarrClientTest do assert_in_delta attrs.pwat_mm, 45.1, 1.0 # Profile array — fixture only contains 850 mb, so length is 1 - assert length(attrs.profile) == 1 + assert Enum.count_until(attrs.profile, 2) == 1 [level_850] = attrs.profile assert_in_delta level_850["pres"], 850.0, 0.1 # 291.52 K @@ -287,7 +287,7 @@ defmodule Microwaveprop.Weather.NarrClientTest do # Profile — 900 hPa has HGT+TMP+SPFH? No, only TMP is there at 900. Only # 850 hPa has all three (TMP, HGT, SPFH) → one profile entry. - assert length(attrs.profile) == 1 + assert Enum.count_until(attrs.profile, 2) == 1 [level_850] = attrs.profile assert_in_delta level_850["pres"], 850.0, 0.01 assert_in_delta level_850["tmpc"], 18.37, 0.5 diff --git a/test/microwaveprop/weather/ncei_metar_client_test.exs b/test/microwaveprop/weather/ncei_metar_client_test.exs index 29374d27..2acc8ff0 100644 --- a/test/microwaveprop/weather/ncei_metar_client_test.exs +++ b/test/microwaveprop/weather/ncei_metar_client_test.exs @@ -27,7 +27,7 @@ defmodule Microwaveprop.Weather.NceiMetarClientTest do """ obs = NceiMetarClient.parse(text) - assert length(obs) == 2 + assert Enum.count_until(obs, 3) == 2 assert Enum.at(obs, 0).observed_at == ~U[2026-03-01 00:00:00Z] assert Enum.at(obs, 1).observed_at == ~U[2026-03-01 00:05:00Z] end diff --git a/test/microwaveprop/weather/nexrad_client_test.exs b/test/microwaveprop/weather/nexrad_client_test.exs index f72bae08..71a6d8e7 100644 --- a/test/microwaveprop/weather/nexrad_client_test.exs +++ b/test/microwaveprop/weather/nexrad_client_test.exs @@ -302,7 +302,7 @@ defmodule Microwaveprop.Weather.NexradClientTest do # DFW area within the 30x30 image {:ok, observations} = NexradClient.fetch_frame(~U[2022-08-20 14:05:00Z], [{32.9, -97.0}]) - assert length(observations) == 1 + assert Enum.count_until(observations, 2) == 1 obs = hd(observations) assert_in_delta obs.lat, 32.9, 0.01 assert_in_delta obs.lon, -97.0, 0.01 diff --git a/test/microwaveprop/weather/scalar_file_test.exs b/test/microwaveprop/weather/scalar_file_test.exs index bffccb10..901d7a4a 100644 --- a/test/microwaveprop/weather/scalar_file_test.exs +++ b/test/microwaveprop/weather/scalar_file_test.exs @@ -98,7 +98,7 @@ defmodule Microwaveprop.Weather.ScalarFileTest do ScalarFile.write!(valid_time, rows) - assert length(ScalarFile.read_bounds(valid_time, nil)) == 2 + assert Enum.count_until(ScalarFile.read_bounds(valid_time, nil), 3) == 2 end test "preserves the full set of derived layer fields on round-trip" do @@ -165,7 +165,7 @@ defmodule Microwaveprop.Weather.ScalarFileTest do rows = ScalarFile.read_bounds(vt, nil) sorted = Enum.sort_by(rows, & &1.lat) - assert length(sorted) == 2 + assert Enum.count_until(sorted, 3) == 2 assert Enum.at(sorted, 0).lat == 33.0 assert Enum.at(sorted, 1).lat == 53.0 end diff --git a/test/microwaveprop/weather/skewt_params_test.exs b/test/microwaveprop/weather/skewt_params_test.exs index 0ab942b8..27510d8a 100644 --- a/test/microwaveprop/weather/skewt_params_test.exs +++ b/test/microwaveprop/weather/skewt_params_test.exs @@ -66,7 +66,7 @@ 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 length(params[:parcel_trace]) >= 2 + assert Enum.count_until(params[:parcel_trace], 2) == 2 end test "summer profile is unstable: SBCAPE > 0, SBCIN <= 0, LFC < surface" do diff --git a/test/microwaveprop/weather/solar_client_test.exs b/test/microwaveprop/weather/solar_client_test.exs index 18aa5a8f..74c07093 100644 --- a/test/microwaveprop/weather/solar_client_test.exs +++ b/test/microwaveprop/weather/solar_client_test.exs @@ -52,7 +52,7 @@ defmodule Microwaveprop.Weather.SolarClientTest do describe "parse_gfz_file/1" do test "skips comment lines and parses data rows" do result = SolarClient.parse_gfz_file(@sample_gfz_file) - assert length(result) == 2 + assert Enum.count_until(result, 3) == 2 [first, second] = result assert first.date == ~D[2024-06-15] @@ -75,14 +75,14 @@ defmodule Microwaveprop.Weather.SolarClientTest do records = SolarClient.parse_gfz_file(@sample_gfz_file) # Records are 2024-06-15 and 2024-06-16 filtered = SolarClient.filter_since(records, ~D[2024-06-16]) - assert length(filtered) == 1 + assert Enum.count_until(filtered, 2) == 1 assert hd(filtered).date == ~D[2024-06-16] end test "returns all records when since_date is before all records" do records = SolarClient.parse_gfz_file(@sample_gfz_file) filtered = SolarClient.filter_since(records, ~D[2020-01-01]) - assert length(filtered) == 2 + assert Enum.count_until(filtered, 3) == 2 end test "returns empty list when since_date is after all records" do @@ -150,7 +150,7 @@ defmodule Microwaveprop.Weather.SolarClientTest do test "includes the boundary day itself (>= semantics)" do records = SolarClient.parse_gfz_file(@sample_gfz_file) filtered = SolarClient.filter_since(records, ~D[2024-06-15]) - assert length(filtered) == 2 + assert Enum.count_until(filtered, 3) == 2 end test "accepts an already-empty record list" do diff --git a/test/microwaveprop/weather/sounding_test.exs b/test/microwaveprop/weather/sounding_test.exs index e3a65859..c1c46619 100644 --- a/test/microwaveprop/weather/sounding_test.exs +++ b/test/microwaveprop/weather/sounding_test.exs @@ -78,7 +78,7 @@ defmodule Microwaveprop.Weather.SoundingTest do attrs = Map.put(@valid_sounding_attrs, :station_id, station.id) changeset = Sounding.changeset(%Sounding{}, attrs) assert {:ok, sounding} = Repo.insert(changeset) - assert length(sounding.profile) == 3 + assert Enum.count_until(sounding.profile, 4) == 3 assert hd(sounding.profile)["pres"] == 1013.0 assert sounding.ducting_detected == false assert sounding.k_index == 28.0 diff --git a/test/microwaveprop/weather/uwyo_sounding_client_test.exs b/test/microwaveprop/weather/uwyo_sounding_client_test.exs index f10dc8b1..460010a3 100644 --- a/test/microwaveprop/weather/uwyo_sounding_client_test.exs +++ b/test/microwaveprop/weather/uwyo_sounding_client_test.exs @@ -29,7 +29,7 @@ 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 length(sounding.profile) > 20 + assert Enum.count_until(sounding.profile, 21) == 21 end test "parses the surface level with all fields populated" do @@ -73,7 +73,7 @@ defmodule Microwaveprop.Weather.UwyoSoundingClientTest do """ [sounding] = UwyoSoundingClient.parse_sounding_html(html) - assert length(sounding.profile) == 1 + assert Enum.count_until(sounding.profile, 2) == 1 assert hd(sounding.profile)["pres"] == 920.0 end @@ -175,7 +175,7 @@ defmodule Microwaveprop.Weather.UwyoSoundingClientTest do assert [sounding] = UwyoSoundingClient.parse_sounding_html(html) # Only the well-formed row survived. - assert length(sounding.profile) == 1 + assert Enum.count_until(sounding.profile, 2) == 1 assert hd(sounding.profile)["pres"] == 1000.0 end end diff --git a/test/microwaveprop/weather_extra_test.exs b/test/microwaveprop/weather_extra_test.exs index c5e357c2..944b0113 100644 --- a/test/microwaveprop/weather_extra_test.exs +++ b/test/microwaveprop/weather_extra_test.exs @@ -166,7 +166,7 @@ defmodule Microwaveprop.WeatherExtraTest do test "early-morning times bracket prev-day 12Z and same-day 00Z" do times = Weather.sounding_times_around(~U[2026-04-20 06:00:00Z]) - assert length(times) >= 2 + assert Enum.count_until(times, 2) == 2 assert ~U[2026-04-19 12:00:00Z] in times assert ~U[2026-04-20 00:00:00Z] in times end diff --git a/test/microwaveprop/weather_grid_test.exs b/test/microwaveprop/weather_grid_test.exs index 132894ba..3e24c31d 100644 --- a/test/microwaveprop/weather_grid_test.exs +++ b/test/microwaveprop/weather_grid_test.exs @@ -74,7 +74,7 @@ defmodule Microwaveprop.WeatherGridTest do bounds = %{"south" => 32.0, "north" => 33.0, "west" => -98.0, "east" => -97.0} result = Weather.load_weather_grid(bounds) - assert length(result) == 2 + assert Enum.count_until(result, 3) == 2 point = Enum.find(result, &(&1.lat == 32.125)) assert point.temperature == 25.0 assert point.dewpoint_depression == 7.0 @@ -118,7 +118,7 @@ defmodule Microwaveprop.WeatherGridTest do bounds = %{"south" => 32.0, "north" => 33.0, "west" => -98.0, "east" => -97.0} result = Weather.load_weather_grid(bounds) - assert length(result) == 1 + assert Enum.count_until(result, 2) == 1 assert hd(result).temperature == 28.0 end @@ -212,7 +212,7 @@ defmodule Microwaveprop.WeatherGridTest do result = Weather.build_grid_cache_rows(grid_data, now) - assert length(result) == 2 + assert Enum.count_until(result, 3) == 2 point = Enum.find(result, &(&1.lat == 32.125)) assert point.lon == -97.375 @@ -293,7 +293,7 @@ defmodule Microwaveprop.WeatherGridTest do result = Weather.build_grid_cache_rows(grid_data, now) - assert length(result) == 1 + assert Enum.count_until(result, 2) == 1 assert hd(result).lat == 32.375 end @@ -329,7 +329,7 @@ defmodule Microwaveprop.WeatherGridTest do bounds = %{"south" => 32.0, "north" => 33.0, "west" => -98.0, "east" => -97.0} rows = Weather.build_grid_cache_rows(grid_data, now, bounds) - assert length(rows) == 1 + assert Enum.count_until(rows, 2) == 1 assert hd(rows).lat == 32.125 assert hd(rows).lon == -97.375 end @@ -361,7 +361,7 @@ defmodule Microwaveprop.WeatherGridTest do } } - assert length(Weather.build_grid_cache_rows(grid_data, now, nil)) == 2 + assert Enum.count_until(Weather.build_grid_cache_rows(grid_data, now, nil), 3) == 2 end test "explicit ducting_detected: false on the profile wins over derived true from sounding params" do @@ -569,7 +569,7 @@ defmodule Microwaveprop.WeatherGridTest do bounds = %{"south" => 32.0, "north" => 33.0, "west" => -98.0, "east" => -97.0} rows = Weather.load_weather_grid(bounds) - assert length(rows) == 2 + assert Enum.count_until(rows, 3) == 2 assert Enum.any?(rows, &(&1.temperature == 25.0)) assert Enum.any?(rows, &(&1.temperature == 26.0)) end @@ -683,11 +683,11 @@ defmodule Microwaveprop.WeatherGridTest do # First call: miss → reads ScalarFile → populates GridCache. bounds = %{"south" => 32.0, "north" => 33.0, "west" => -98.0, "east" => -97.0} rows = Weather.weather_grid_at(vt, bounds) - assert length(rows) == 2 + assert Enum.count_until(rows, 3) == 2 # GridCache is now warm — confirms cache populate happened. assert {:ok, cached_rows} = GridCache.fetch(vt) - assert length(cached_rows) == 2 + assert Enum.count_until(cached_rows, 3) == 2 # Wipe the on-disk ScalarFile. The next call should still return data # because GridCache is the hot path. @@ -695,7 +695,7 @@ defmodule Microwaveprop.WeatherGridTest do refute ScalarFile.exists?(vt) rows_again = Weather.weather_grid_at(vt, bounds) - assert length(rows_again) == 2 + assert Enum.count_until(rows_again, 3) == 2 end end diff --git a/test/microwaveprop/weather_test.exs b/test/microwaveprop/weather_test.exs index 9e122045..ec2f648d 100644 --- a/test/microwaveprop/weather_test.exs +++ b/test/microwaveprop/weather_test.exs @@ -476,7 +476,7 @@ defmodule Microwaveprop.WeatherTest do time_window_hours: 3 ) - assert length(result.surface_observations) == 1 + assert Enum.count_until(result.surface_observations, 2) == 1 obs = hd(result.surface_observations) assert obs.temp_f == 75.0 assert obs.station.station_code == "KDFW" @@ -500,7 +500,7 @@ defmodule Microwaveprop.WeatherTest do time_window_hours: 6 ) - assert length(result.soundings) == 1 + assert Enum.count_until(result.soundings, 2) == 1 assert hd(result.soundings).station.station_code == "FWD" end @@ -834,7 +834,7 @@ defmodule Microwaveprop.WeatherTest do assert record.sfi == 150.2 assert record.sunspot_number == 120 assert record.ap_index == 8 - assert length(record.kp_values) == 8 + assert Enum.count_until(record.kp_values, 9) == 8 end test "updates existing record on date conflict" do @@ -941,7 +941,7 @@ defmodule Microwaveprop.WeatherTest do assert Weather.purge_grid_point_profiles() == 2 remaining = Repo.all(HrrrProfile) - assert length(remaining) == 1 + assert Enum.count_until(remaining, 2) == 1 assert hd(remaining).id == qso_linked.id end @@ -1160,7 +1160,7 @@ defmodule Microwaveprop.WeatherTest do } profiles = Weather.hrrr_profiles_for_path(contact) - assert length(profiles) >= 2 + assert Enum.count_until(profiles, 2) == 2 end test "returns empty list when no HRRR profiles exist nearby" do @@ -1183,7 +1183,7 @@ defmodule Microwaveprop.WeatherTest do } profiles = Weather.hrrr_profiles_for_path(contact) - assert length(profiles) == 1 + assert Enum.count_until(profiles, 2) == 1 end end @@ -1206,7 +1206,7 @@ defmodule Microwaveprop.WeatherTest do } observations = Weather.iemre_for_path(contact) - assert length(observations) >= 2 + assert Enum.count_until(observations, 2) == 2 end test "returns empty list when no observations exist" do @@ -1229,7 +1229,7 @@ defmodule Microwaveprop.WeatherTest do } observations = Weather.iemre_for_path(contact) - assert length(observations) == 1 + assert Enum.count_until(observations, 2) == 1 end end @@ -1379,7 +1379,7 @@ defmodule Microwaveprop.WeatherTest do rows = Weather.build_grid_cache_rows(grid_data, ~U[2026-04-29 12:00:00Z], bounds) # Only the (32, -97) point is inside bounds. - assert length(rows) == 1 + assert Enum.count_until(rows, 2) == 1 end end end diff --git a/test/microwaveprop/workers/contact_import_worker_test.exs b/test/microwaveprop/workers/contact_import_worker_test.exs index 39dfbaac..bb907739 100644 --- a/test/microwaveprop/workers/contact_import_worker_test.exs +++ b/test/microwaveprop/workers/contact_import_worker_test.exs @@ -248,7 +248,7 @@ defmodule Microwaveprop.Workers.ContactImportWorkerTest do {:ok, preview} = CsvImport.preview(csv, @submitter) assert preview.valid == [] - assert length(preview.duplicates) == 1 + assert Enum.count_until(preview.duplicates, 2) == 1 {:ok, run_id} = CsvImport.enqueue(preview, @submitter) @@ -269,14 +269,14 @@ defmodule Microwaveprop.Workers.ContactImportWorkerTest do ]) {:ok, preview} = CsvImport.preview(csv, @submitter) - assert length(preview.valid) == 1 - assert length(preview.duplicates) == 1 + assert Enum.count_until(preview.valid, 2) == 1 + assert Enum.count_until(preview.duplicates, 2) == 1 {:ok, run_id} = CsvImport.enqueue(preview, @submitter) run = Repo.get!(ImportRun, run_id) assert run.total_rows == 1 - assert length(run.rows["rows"]) == 1 + assert Enum.count_until(run.rows["rows"], 2) == 1 assert Repo.aggregate(Contact, :count) == 1 end diff --git a/test/microwaveprop/workers/contact_weather_enqueue_worker_test.exs b/test/microwaveprop/workers/contact_weather_enqueue_worker_test.exs index 9997c822..8f022550 100644 --- a/test/microwaveprop/workers/contact_weather_enqueue_worker_test.exs +++ b/test/microwaveprop/workers/contact_weather_enqueue_worker_test.exs @@ -100,7 +100,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorkerTest do kdfw_jobs = Enum.filter(day_jobs, fn j -> j.changes.args["station_code"] == "KDFW" end) - assert length(kdfw_jobs) == 1 + assert Enum.count_until(kdfw_jobs, 2) == 1 end test "emits both UTC days when the window crosses midnight" do @@ -299,7 +299,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorkerTest do assert [row] = rows # pos1 + midpoint + pos2 → 3 distinct HRRR grid cells in the batch. - assert length(row.points) == 3 + assert Enum.count_until(row.points, 4) == 3 assert row.status == "queued" end @@ -373,7 +373,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorkerTest do rows = Repo.all(from t in "hrrr_fetch_tasks", select: %{vt: t.valid_time, points: t.points}) assert [row] = rows - assert length(row.points) == 2 + assert Enum.count_until(row.points, 3) == 2 end test "re-enqueuing a 'done' row flips status back to queued" do @@ -397,7 +397,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorkerTest do jobs = ContactWeatherEnqueueWorker.build_terrain_jobs([contact]) - assert length(jobs) == 1 + assert Enum.count_until(jobs, 2) == 1 job = hd(jobs) assert job.changes.args["contact_id"] == contact.id end @@ -406,7 +406,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorkerTest do contact = create_contact() jobs = ContactWeatherEnqueueWorker.build_terrain_jobs([contact, contact]) - assert length(jobs) == 1 + assert Enum.count_until(jobs, 2) == 1 end end @@ -465,7 +465,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorkerTest do jobs = ContactWeatherEnqueueWorker.build_iemre_jobs([contact]) # pos1, midpoint, pos2 may have distinct IEMRE grid cells - assert length(jobs) >= 2 + assert Enum.count_until(jobs, 2) == 2 lats = Enum.map(jobs, & &1.changes.args["lat"]) assert 32.875 in lats end @@ -475,7 +475,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorkerTest do jobs = ContactWeatherEnqueueWorker.build_iemre_jobs([contact]) - assert length(jobs) == 1 + assert Enum.count_until(jobs, 2) == 1 job = hd(jobs) assert job.changes.args["lat"] == 32.875 assert job.changes.args["lon"] == -97.0 @@ -502,7 +502,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorkerTest do jobs = ContactWeatherEnqueueWorker.build_iemre_jobs([q1, q2]) # Same location and same date → 1 job - assert length(jobs) == 1 + assert Enum.count_until(jobs, 2) == 1 end test "creates separate jobs for different dates" do @@ -523,7 +523,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorkerTest do }) jobs = ContactWeatherEnqueueWorker.build_iemre_jobs([q1, q2]) - assert length(jobs) == 2 + assert Enum.count_until(jobs, 3) == 2 end test "skips QSOs without pos1" do @@ -606,7 +606,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorkerTest do jobs = ContactWeatherEnqueueWorker.build_narr_jobs([contact]) - assert length(jobs) == 1 + assert Enum.count_until(jobs, 2) == 1 job = hd(jobs) assert job.changes.worker == "Microwaveprop.Workers.NarrFetchWorker" # 32.907 → 33.0 and -97.038 → -97.0 after 0.25° snap @@ -640,7 +640,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorkerTest do jobs = ContactWeatherEnqueueWorker.build_narr_jobs([contact]) # pos1, midpoint, pos2 round to three distinct 0.25° grid cells. - assert length(jobs) == 3 + assert Enum.count_until(jobs, 4) == 3 assert Enum.all?(jobs, &(&1.changes.worker == "Microwaveprop.Workers.NarrFetchWorker")) end @@ -714,7 +714,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorkerTest do jobs = ContactWeatherEnqueueWorker.build_narr_jobs([q1, q2]) # Both snap to 12:00Z and round to the same 0.25° grid cell → 1 job. - assert length(jobs) == 1 + assert Enum.count_until(jobs, 2) == 1 assert hd(jobs).changes.worker == "Microwaveprop.Workers.NarrFetchWorker" end end diff --git a/test/microwaveprop/workers/gefs_fetch_worker_test.exs b/test/microwaveprop/workers/gefs_fetch_worker_test.exs index f03a11ab..41df119a 100644 --- a/test/microwaveprop/workers/gefs_fetch_worker_test.exs +++ b/test/microwaveprop/workers/gefs_fetch_worker_test.exs @@ -79,7 +79,7 @@ defmodule Microwaveprop.Workers.GefsFetchWorkerTest do hours = GefsFetchWorker.extended_horizon_hours() assert List.first(hours) == 24 assert List.last(hours) == 168 - assert length(hours) == 25 + assert Enum.count_until(hours, 26) == 25 assert Enum.all?(hours, &(rem(&1, 6) == 0)) end end @@ -225,7 +225,7 @@ defmodule Microwaveprop.Workers.GefsFetchWorkerTest do assert count == 2 persisted = Microwaveprop.Repo.all(GefsProfile) - assert length(persisted) == 2 + assert Enum.count_until(persisted, 3) == 2 end end diff --git a/test/microwaveprop/workers/hrdps_grid_worker_test.exs b/test/microwaveprop/workers/hrdps_grid_worker_test.exs index 3bf547e6..b05b58d9 100644 --- a/test/microwaveprop/workers/hrdps_grid_worker_test.exs +++ b/test/microwaveprop/workers/hrdps_grid_worker_test.exs @@ -55,7 +55,7 @@ defmodule Microwaveprop.Workers.HrdpsGridWorkerTest do # One forecast row, no analysis. /weather only consumes current # hour so we don't pay the wgrib2 cost for the rest of the chain. - assert length(rows) == 1 + assert Enum.count_until(rows, 2) == 1 [%{kind: kind, forecast_hour: fh}] = rows assert kind == "forecast" assert fh >= 1 and fh <= 18 diff --git a/test/microwaveprop/workers/hrrr_native_grid_worker_test.exs b/test/microwaveprop/workers/hrrr_native_grid_worker_test.exs index 9a07fdf9..0976850f 100644 --- a/test/microwaveprop/workers/hrrr_native_grid_worker_test.exs +++ b/test/microwaveprop/workers/hrrr_native_grid_worker_test.exs @@ -34,7 +34,7 @@ defmodule Microwaveprop.Workers.HrrrNativeGridWorkerTest do points = HrrrNativeGridWorker.points_of_interest_for_hour(~U[2026-03-28 18:00:00Z]) - assert length(points) == 2 + assert Enum.count_until(points, 3) == 2 assert {32.9, -97.0} in points assert {33.0, -97.1} in points end diff --git a/test/microwaveprop/workers/iemre_fetch_worker_test.exs b/test/microwaveprop/workers/iemre_fetch_worker_test.exs index ad0032f4..27b1d3c7 100644 --- a/test/microwaveprop/workers/iemre_fetch_worker_test.exs +++ b/test/microwaveprop/workers/iemre_fetch_worker_test.exs @@ -58,7 +58,7 @@ defmodule Microwaveprop.Workers.IemreFetchWorkerTest do assert obs.lat == 32.875 assert obs.lon == -97.0 assert obs.date == ~D[2026-03-28] - assert length(obs.hourly) == 2 + assert Enum.count_until(obs.hourly, 3) == 2 end test "stores stub on empty data so backfill doesn't retry" do diff --git a/test/microwaveprop/workers/nexrad_worker_test.exs b/test/microwaveprop/workers/nexrad_worker_test.exs index e5057c28..bbf7a6ad 100644 --- a/test/microwaveprop/workers/nexrad_worker_test.exs +++ b/test/microwaveprop/workers/nexrad_worker_test.exs @@ -80,7 +80,7 @@ defmodule Microwaveprop.Workers.NexradWorkerTest do assert :ok = NexradWorker.perform(%Oban.Job{args: args}) rows = Repo.all(NexradObservation) - assert length(rows) == 2 + assert Enum.count_until(rows, 3) == 2 coords = rows |> Enum.map(&{&1.lat, &1.lon}) |> Enum.sort() assert coords == [{32.9, -97.0}, {33.5, -96.5}] @@ -165,7 +165,7 @@ defmodule Microwaveprop.Workers.NexradWorkerTest do assert :ok = NexradWorker.perform(%Oban.Job{args: args}) rows = Repo.all(NexradObservation) - assert length(rows) == 1 + assert Enum.count_until(rows, 2) == 1 assert hd(rows).lat == 32.9 assert hd(rows).lon == -97.0 end diff --git a/test/microwaveprop/workers/radar_frame_worker_test.exs b/test/microwaveprop/workers/radar_frame_worker_test.exs index 98e2fdae..4af3244a 100644 --- a/test/microwaveprop/workers/radar_frame_worker_test.exs +++ b/test/microwaveprop/workers/radar_frame_worker_test.exs @@ -92,7 +92,7 @@ defmodule Microwaveprop.Workers.RadarFrameWorkerTest do rows = Repo.all(from(r in ContactCommonVolumeRadar, where: r.contact_id == ^contact.id)) - assert length(rows) == 1 + assert Enum.count_until(rows, 2) == 1 [second] = rows assert second.id == first.id assert second.observed_at == ~U[2024-09-15 18:35:00Z] diff --git a/test/microwaveprop_web/live/contact_live/show_coverage_test.exs b/test/microwaveprop_web/live/contact_live/show_coverage_test.exs index 9502c2ec..13b54fde 100644 --- a/test/microwaveprop_web/live/contact_live/show_coverage_test.exs +++ b/test/microwaveprop_web/live/contact_live/show_coverage_test.exs @@ -1684,7 +1684,7 @@ defmodule MicrowavepropWeb.ContactLive.ShowCoverageTest do |> Map.fetch!(:assigns) |> Map.fetch!(:surface_observations) - assert length(obs) <= 5 + assert Enum.count_until(obs, 6) <= 5 distances = Enum.map(obs, fn o -> diff --git a/test/microwaveprop_web/live/rover_live_test.exs b/test/microwaveprop_web/live/rover_live_test.exs index 83c8f275..4a6c576a 100644 --- a/test/microwaveprop_web/live/rover_live_test.exs +++ b/test/microwaveprop_web/live/rover_live_test.exs @@ -233,7 +233,7 @@ defmodule MicrowavepropWeb.RoverLiveTest do {:ok, _view, html} = live(conn, ~p"/rover") stations = Rover.list_stations(user) - assert length(stations) == 3 + assert Enum.count_until(stations, 4) == 3 assert stations |> Enum.map(& &1.callsign) |> Enum.sort() == ~w(N5XU W5HN W5LUA) assert html =~ "W5LUA" end @@ -264,7 +264,7 @@ defmodule MicrowavepropWeb.RoverLiveTest do [first | _] = Rover.list_stations(user) render_hook(view, "delete_station", %{"id" => to_string(first.id)}) - assert length(Rover.list_stations(user)) == 2 + assert Enum.count_until(Rover.list_stations(user), 3) == 2 end test "toggle_station flips the persisted selected flag", %{conn: conn, user: user} do diff --git a/test/microwaveprop_web/live/rover_planning_live_test.exs b/test/microwaveprop_web/live/rover_planning_live_test.exs index 8a4b048e..954ff980 100644 --- a/test/microwaveprop_web/live/rover_planning_live_test.exs +++ b/test/microwaveprop_web/live/rover_planning_live_test.exs @@ -595,7 +595,7 @@ defmodule MicrowavepropWeb.RoverPlanningLiveTest do |> render_submit() reloaded = Repo.preload(Repo.get!(Microwaveprop.RoverPlanning.Mission, mission.id), :stations) - assert length(reloaded.stations) == 2 + assert Enum.count_until(reloaded.stations, 3) == 2 end test "clicking 'Add station' does not show 'can't be blank' on the new row", %{conn: conn} do diff --git a/test/microwaveprop_web/skew_t_test.exs b/test/microwaveprop_web/skew_t_test.exs index 946fe6ca..929f7dfb 100644 --- a/test/microwaveprop_web/skew_t_test.exs +++ b/test/microwaveprop_web/skew_t_test.exs @@ -108,7 +108,7 @@ defmodule MicrowavepropWeb.SkewTTest do assert chart.td_points != [] # The first plotted t_point should correspond to the highest pressure # (surface-most) profile entry. - assert length(chart.t_points) == 4 + assert Enum.count_until(chart.t_points, 5) == 4 end test "filters out profile levels with missing temperature or dewpoint independently" do @@ -121,8 +121,8 @@ defmodule MicrowavepropWeb.SkewTTest do chart = SkewT.build(profile) # 3 levels have tmpc, 3 have dwpc — the traces are independent. - assert length(chart.t_points) == 3 - assert length(chart.td_points) == 3 + assert Enum.count_until(chart.t_points, 4) == 3 + assert Enum.count_until(chart.td_points, 4) == 3 end end end diff --git a/test/mix/tasks/simple_tasks_test.exs b/test/mix/tasks/simple_tasks_test.exs index 2b456287..982a4624 100644 --- a/test/mix/tasks/simple_tasks_test.exs +++ b/test/mix/tasks/simple_tasks_test.exs @@ -643,7 +643,7 @@ defmodule Mix.Tasks.SimpleTasksTest do _ = collect_mix_messages() contacts = Repo.all(Contact) - assert length(contacts) == 1 + assert Enum.count_until(contacts, 2) == 1 [contact] = contacts assert Decimal.equal?(contact.band, Decimal.new(band)) diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 05314b2f..a9b2b4d7 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -1,7 +1,6 @@ defmodule MicrowavepropWeb.ConnCase do @moduledoc """ - This module defines the test case to be used by - tests that require setting up a connection. + Test case template for HTTP connection tests. Such tests rely on `Phoenix.ConnTest` and also import other functionality to make it easier diff --git a/test/support/data_case.ex b/test/support/data_case.ex index 428907bb..7ced5596 100644 --- a/test/support/data_case.ex +++ b/test/support/data_case.ex @@ -1,7 +1,6 @@ defmodule Microwaveprop.DataCase do @moduledoc """ - This module defines the setup for tests requiring - access to the application's data layer. + Test case template for tests that need database access. You may define functions here to be used as helpers in your tests. diff --git a/test/support/fixtures/accounts_fixtures.ex b/test/support/fixtures/accounts_fixtures.ex index 55b32b14..84a1b698 100644 --- a/test/support/fixtures/accounts_fixtures.ex +++ b/test/support/fixtures/accounts_fixtures.ex @@ -1,7 +1,6 @@ defmodule Microwaveprop.AccountsFixtures do @moduledoc """ - This module defines test helpers for creating - entities via the `Microwaveprop.Accounts` context. + Factory helpers for creating user records in tests. """ import Ecto.Query