- Extract scoring logic to Microwaveprop.Rover.Coverage for testability - Two-phase computation: fast pass (distance + propagation) for all grids, then SRTM terrain analysis for top 20 candidates only - Cap search radius to 300 km to keep candidate count reasonable - Run terrain analysis in Task with rescue/fallback for resilience - Background Task doesn't block LiveView process - 5 tests covering: ranked results, empty inputs, station details, band range differences, field presence
95 lines
3.1 KiB
Elixir
95 lines
3.1 KiB
Elixir
defmodule Microwaveprop.Rover.CoverageTest do
|
|
use Microwaveprop.DataCase, async: false
|
|
|
|
alias Microwaveprop.Rover.Coverage
|
|
|
|
describe "compute/2" do
|
|
test "returns ranked grids for stations within range" do
|
|
stations = [
|
|
%{label: "STA1", lat: 33.0, lon: -97.0},
|
|
%{label: "STA2", lat: 33.5, lon: -97.5}
|
|
]
|
|
|
|
result = Coverage.compute(stations, 10_000)
|
|
|
|
assert is_list(result)
|
|
assert length(result) > 0
|
|
|
|
scores = Enum.map(result, & &1.coverage_score)
|
|
assert scores == Enum.sort(scores, :desc)
|
|
|
|
first = hd(result)
|
|
assert is_binary(first.grid)
|
|
assert byte_size(first.grid) == 4
|
|
assert is_number(first.lat)
|
|
assert is_number(first.lon)
|
|
assert is_integer(first.coverage_score)
|
|
assert first.coverage_score >= 0 and first.coverage_score <= 100
|
|
assert is_integer(first.stations_in_range)
|
|
assert first.stations_in_range > 0
|
|
assert is_integer(first.workable_count)
|
|
assert is_integer(first.prop_score)
|
|
assert is_list(first.station_details)
|
|
assert is_list(first.forecast)
|
|
end
|
|
|
|
test "returns empty list with fewer than 2 stations" do
|
|
assert Coverage.compute([%{label: "STA1", lat: 33.0, lon: -97.0}], 10_000) == []
|
|
assert Coverage.compute([], 10_000) == []
|
|
end
|
|
|
|
test "station_details includes distance and in_range" do
|
|
stations = [
|
|
%{label: "STA1", lat: 33.0, lon: -97.0},
|
|
%{label: "STA2", lat: 33.5, lon: -97.0}
|
|
]
|
|
|
|
[first | _] = Coverage.compute(stations, 10_000)
|
|
|
|
assert length(first.station_details) == 2
|
|
|
|
for detail <- first.station_details do
|
|
assert Map.has_key?(detail, :label)
|
|
assert Map.has_key?(detail, :dist_km)
|
|
assert Map.has_key?(detail, :in_range)
|
|
assert is_number(detail.dist_km)
|
|
assert is_boolean(detail.in_range)
|
|
end
|
|
end
|
|
|
|
test "higher frequency bands have shorter range so fewer grids cover both stations" do
|
|
far_stations = [
|
|
%{label: "STA1", lat: 33.0, lon: -97.0},
|
|
%{label: "STA2", lat: 35.0, lon: -97.0}
|
|
]
|
|
|
|
result_10g = Coverage.compute(far_stations, 10_000)
|
|
result_241g = Coverage.compute(far_stations, 241_000)
|
|
|
|
# 10 GHz has 500 km range, 241 GHz has 50 km — far fewer grids reach both at 241
|
|
grids_reaching_both_10g = Enum.count(result_10g, &(&1.stations_in_range == 2))
|
|
grids_reaching_both_241g = Enum.count(result_241g, &(&1.stations_in_range == 2))
|
|
|
|
assert grids_reaching_both_10g >= grids_reaching_both_241g
|
|
end
|
|
|
|
test "top results have station details with expected fields" do
|
|
stations = [
|
|
%{label: "STA1", lat: 33.0, lon: -97.0},
|
|
%{label: "STA2", lat: 33.2, lon: -97.2}
|
|
]
|
|
|
|
[first | _] = Coverage.compute(stations, 10_000)
|
|
|
|
# Each station detail should have core fields
|
|
for detail <- first.station_details do
|
|
assert Map.has_key?(detail, :label)
|
|
assert Map.has_key?(detail, :dist_km)
|
|
assert Map.has_key?(detail, :in_range)
|
|
end
|
|
|
|
# At least one station should be in range
|
|
assert Enum.any?(first.station_details, & &1.in_range)
|
|
end
|
|
end
|
|
end
|