prop/test/microwaveprop/terrain/terrain_profile_test.exs
Graham McIntire 45e2e69361
Add SRTM terrain path profiles for QSOs
Fetch elevation data along the path between two stations via the
Open-Meteo Elevation API (with Open-Topo-Data fallback), compute
Fresnel zone clearance, earth bulge, and knife-edge diffraction loss,
and store the results per QSO.

- terrain_profiles table with migration and TerrainProfile schema
- ElevationClient with batched API calls and fallback
- TerrainAnalysis with Fresnel/diffraction physics (ITU-R P.526-15)
- TerrainProfileWorker on Oban :terrain queue
- QsoWeatherEnqueueWorker enqueues terrain jobs automatically
- QSO show page displays verdict badge and collapsible elevation table
- Reorder show page: terrain, soundings, solar, HRRR, surface obs
- Fix Dockerfile wgrib2 build (add cmake dependency)
2026-03-29 16:22:29 -05:00

94 lines
2.2 KiB
Elixir

defmodule Microwaveprop.Terrain.TerrainProfileTest do
use Microwaveprop.DataCase, async: true
alias Microwaveprop.Radio.Qso
alias Microwaveprop.Terrain.TerrainProfile
@qso_attrs %{
station1: "W5XD",
station2: "K5TR",
qso_timestamp: ~U[2026-03-28 18:00:00Z],
mode: "CW",
band: Decimal.new("1296"),
pos1: %{"lat" => 32.9, "lon" => -97.0},
pos2: %{"lat" => 30.3, "lon" => -97.7}
}
defp create_qso do
{:ok, qso} =
%Qso{}
|> Qso.changeset(@qso_attrs)
|> Repo.insert()
qso
end
describe "changeset/2" do
test "valid with required fields" do
qso = create_qso()
attrs = %{
qso_id: qso.id,
sample_count: 65,
path_points: [%{"lat" => 32.9, "lon" => -97.0, "elev" => 200.0, "dist_km" => 0.0}],
verdict: "CLEAR"
}
changeset = TerrainProfile.changeset(%TerrainProfile{}, attrs)
assert changeset.valid?
end
test "invalid without required fields" do
changeset = TerrainProfile.changeset(%TerrainProfile{}, %{})
refute changeset.valid?
errors = errors_on(changeset)
assert errors[:qso_id]
assert errors[:sample_count]
assert errors[:path_points]
assert errors[:verdict]
end
test "valid with all optional fields" do
qso = create_qso()
attrs = %{
qso_id: qso.id,
sample_count: 65,
path_points: [%{"lat" => 32.9, "lon" => -97.0, "elev" => 200.0, "dist_km" => 0.0}],
verdict: "BLOCKED",
max_elevation_m: 450.0,
min_clearance_m: -12.5,
diffraction_db: 8.3,
fresnel_hit_count: 3,
obstructed_count: 1
}
changeset = TerrainProfile.changeset(%TerrainProfile{}, attrs)
assert changeset.valid?
end
test "enforces unique qso_id constraint on insert" do
qso = create_qso()
attrs = %{
qso_id: qso.id,
sample_count: 65,
path_points: [],
verdict: "CLEAR"
}
{:ok, _} =
%TerrainProfile{}
|> TerrainProfile.changeset(attrs)
|> Repo.insert()
{:error, changeset} =
%TerrainProfile{}
|> TerrainProfile.changeset(attrs)
|> Repo.insert()
assert errors_on(changeset)[:qso_id]
end
end
end