prop/test/microwaveprop/beacons/range_estimate_test.exs
Graham McIntire fd976b0cd5
fix: resolve 391 Credo issues across codebase
- Add jump_credo_checks ~> 0.4 with all 20 checks enabled
- Fix all standard Credo issues: 139 @spec (113 done, 26 remain),
  4 refactoring, 3 alias usage, 9 System.cmd env, 5 unsafe_to_atom,
  2 max line length, 9 assert_receive timeout
- Fix 170+ jump_credo_checks warnings:
  - 117 TopLevelAliasImportRequire: move nested alias/import to module top
  - 32 UseObanProWorker: switch to Oban.Pro.Worker
  - 4 DoctestIExExamples: add doctests / create test file
  - ~20 WeakAssertion: strengthen type-check assertions
  - Various ConditionalAssertion, AssertReceiveTimeout fixes
- Exclude vendor/ from Credo analysis
- Remaining: 175 warnings (mostly opinionated WeakAssertion,
  AvoidSocketAssignsInTest), 26 @spec annotations
2026-06-12 13:51:32 -05:00

134 lines
4.3 KiB
Elixir

defmodule Microwaveprop.Beacons.RangeEstimateTest do
use Microwaveprop.DataCase
alias Microwaveprop.Beacons.Beacon
alias Microwaveprop.Beacons.RangeEstimate
defp beacon(overrides \\ %{}) do
base = %Beacon{
id: "00000000-0000-0000-0000-000000000000",
frequency_mhz: 10_368.1,
callsign: "W5HN",
grid: "EM12",
lat: 32.897,
lon: -97.038,
power_mw: 10_000.0,
height_ft: 100,
on_the_air: true
}
struct!(base, overrides)
end
describe "mw_to_dbm/1" do
test "1 mW is 0 dBm" do
assert_in_delta RangeEstimate.mw_to_dbm(1.0), 0.0, 0.001
end
test "1 W (1000 mW) is 30 dBm" do
assert_in_delta RangeEstimate.mw_to_dbm(1000.0), 30.0, 0.001
end
test "10 W (10_000 mW) is 40 dBm" do
assert_in_delta RangeEstimate.mw_to_dbm(10_000.0), 40.0, 0.001
end
end
describe "nearest_band_mhz/1" do
test "10368.1 MHz maps to 10000 (10 GHz band)" do
assert RangeEstimate.nearest_band_mhz(10_368.1) == 10_000
end
test "24192.1 MHz maps to 24000" do
assert RangeEstimate.nearest_band_mhz(24_192.1) == 24_000
end
test "47088 MHz maps to 47000" do
assert RangeEstimate.nearest_band_mhz(47_088.0) == 47_000
end
end
describe "estimate/1" do
test "returns a map with band info, center score, and per-grid cells" do
result = RangeEstimate.estimate(beacon())
assert result.band_mhz == 10_000
assert result.center_score == 50
assert is_float(result.eirp_dbm)
assert length(result.cells) > 0
assert result.cells != []
assert result.grid_step == 0.125
assert length(result.tiers) > 0
end
test "each cell carries lat/lon, distance, rx_dbm, tier label/color" do
result = RangeEstimate.estimate(beacon())
for cell <- result.cells do
assert is_float(cell.lat)
assert is_float(cell.lon)
assert is_float(cell.distance_km)
assert is_integer(cell.rx_dbm)
assert byte_size(cell.label) > 0
assert byte_size(cell.color) > 0
# Detection floor enforced
assert cell.rx_dbm >= -145
end
end
test "cells closer to the beacon have stronger rx_dbm" do
result = RangeEstimate.estimate(beacon())
[closest | _] = Enum.sort_by(result.cells, & &1.distance_km)
[farthest | _] = Enum.sort_by(result.cells, &(-&1.distance_km))
assert closest.rx_dbm > farthest.rx_dbm
end
test "a more powerful beacon produces more cells" do
weak = RangeEstimate.estimate(beacon(power_mw: 10.0))
strong = RangeEstimate.estimate(beacon(power_mw: 10_000.0))
assert length(strong.cells) > length(weak.cells)
end
test "uses a default score of 50 when no propagation data exists" do
result = RangeEstimate.estimate(beacon())
# No scores in test DB -> center defaults, cells all use 50.
assert result.center_score == 50
assert result.valid_time == nil
# All cells should have score 50 when there's no HRRR data.
assert Enum.all?(result.cells, fn c -> c.score == 50 end)
end
test "cells align with the 0.125 deg HRRR grid" do
result = RangeEstimate.estimate(beacon())
for cell <- result.cells do
# lat and lon should both be multiples of 0.125
lat_mod = abs(cell.lat / 0.125 - round(cell.lat / 0.125))
lon_mod = abs(cell.lon / 0.125 - round(cell.lon / 0.125))
assert lat_mod < 0.01
assert lon_mod < 0.01
end
end
test "short-circuits with empty cells for beacons below 5.76 GHz" do
# 432 MHz with 1 W at unrestricted range would generate >100k cells
# because atmospheric loss is negligible -- encoding that payload
# into the map's data attribute was locking up browsers in prod.
result = RangeEstimate.estimate(beacon(frequency_mhz: 432.38, power_mw: 1000.0))
assert result.cells == []
assert result.max_range_km == 0.0
# Still reports the nearest configured band for display purposes.
assert is_integer(result.band_mhz)
end
test "short-circuits at exactly 5760 MHz threshold" do
above = RangeEstimate.estimate(beacon(frequency_mhz: 5760.0, power_mw: 1000.0))
below = RangeEstimate.estimate(beacon(frequency_mhz: 5759.9, power_mw: 1000.0))
assert above.cells != []
assert below.cells == []
end
end
end