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.0, 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, score, and a list of rings" do result = RangeEstimate.estimate(beacon()) assert result.band_mhz == 10_000 assert is_integer(result.score) or is_float(result.score) assert is_float(result.eirp_dbm) assert is_list(result.rings) assert length(result.rings) > 0 end test "rings are ordered strongest-to-weakest with increasing radius" do result = RangeEstimate.estimate(beacon()) radii = Enum.map(result.rings, & &1.radius_km) assert radii == Enum.sort(radii) end test "each ring carries a label, color, threshold, and radius_km" do result = RangeEstimate.estimate(beacon()) for ring <- result.rings do assert is_binary(ring.label) assert is_binary(ring.color) assert is_number(ring.rx_dbm) assert is_float(ring.radius_km) assert ring.radius_km >= 0.0 end end test "a more powerful beacon produces larger rings" do weak = RangeEstimate.estimate(beacon(power_mw: 10.0)) strong = RangeEstimate.estimate(beacon(power_mw: 10_000.0)) weakest_weak = List.last(weak.rings).radius_km weakest_strong = List.last(strong.rings).radius_km assert weakest_strong > weakest_weak end test "uses a default score of 50 when no propagation data exists" do result = RangeEstimate.estimate(beacon()) # No scores in test DB → score defaults assert result.score == 50 assert result.valid_time == nil end end end