defmodule Microwaveprop.Terrain.ElevationClientTest do use ExUnit.Case, async: true alias Microwaveprop.Terrain.ElevationClient describe "sample_path/5" 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 first = hd(points) last = List.last(points) assert_in_delta first.lat, 32.9, 0.001 assert_in_delta first.lon, -97.0, 0.001 assert_in_delta first.d, 0.0, 0.001 assert_in_delta last.lat, 30.3, 0.001 assert_in_delta last.lon, -97.7, 0.001 assert_in_delta last.d, 1.0, 0.001 end test "midpoint is correct" do points = ElevationClient.sample_path(0.0, 0.0, 10.0, 10.0, 2) mid = Enum.at(points, 1) assert_in_delta mid.lat, 5.0, 0.001 assert_in_delta mid.lon, 5.0, 0.001 assert_in_delta mid.d, 0.5, 0.001 end end describe "fetch_elevation_profile/5" do test "returns profile with elevations from Open-Meteo" do Req.Test.stub(ElevationClient, fn conn -> Req.Test.json(conn, %{"elevation" => [200.0, 250.0, 180.0]}) end) assert {:ok, profile} = ElevationClient.fetch_elevation_profile(32.9, -97.0, 30.3, -97.7, 2) assert length(profile) == 3 first = hd(profile) assert Map.has_key?(first, :lat) assert Map.has_key?(first, :lon) assert Map.has_key?(first, :elev) assert Map.has_key?(first, :dist_km) assert first.elev == 200.0 assert_in_delta first.dist_km, 0.0, 0.001 end test "batches requests when more than 100 points" do call_count = :counters.new(1, [:atomics]) Req.Test.stub(ElevationClient, fn conn -> :counters.add(call_count, 1, 1) # Return 101 elevations for first batch, or however many requested params = Plug.Conn.fetch_query_params(conn).query_params lat_count = params["latitude"] |> String.split(",") |> length() elevations = List.duplicate(100.0, lat_count) Req.Test.json(conn, %{"elevation" => elevations}) end) assert {:ok, profile} = ElevationClient.fetch_elevation_profile(32.9, -97.0, 30.3, -97.7, 150) # 151 points → 2 batches (100 + 51) assert length(profile) == 151 assert :counters.get(call_count, 1) == 2 end test "falls back to Open-Topo-Data on Open-Meteo failure" do call_count = :counters.new(1, [:atomics]) Req.Test.stub(ElevationClient, fn conn -> :counters.add(call_count, 1, 1) current = :counters.get(call_count, 1) if current == 1 do # First call (Open-Meteo) fails Plug.Conn.send_resp(conn, 500, "error") else # Second call (Open-Topo-Data) succeeds Req.Test.json(conn, %{ "status" => "OK", "results" => [ %{"elevation" => 100.0}, %{"elevation" => 150.0}, %{"elevation" => 120.0} ] }) end end) assert {:ok, profile} = ElevationClient.fetch_elevation_profile(32.9, -97.0, 30.3, -97.7, 2) assert length(profile) == 3 assert hd(profile).elev == 100.0 end test "returns error when both APIs fail" do Req.Test.stub(ElevationClient, fn conn -> Plug.Conn.send_resp(conn, 500, "error") end) assert {:error, _reason} = ElevationClient.fetch_elevation_profile(32.9, -97.0, 30.3, -97.7, 2) end end end defmodule Microwaveprop.Terrain.ElevationClientSrtmTest do use ExUnit.Case, async: false alias Microwaveprop.Terrain.ElevationClient @tiles_dir Path.expand("~/srtm/tiles") @has_tiles File.exists?(Path.join(@tiles_dir, "N32W097.hgt")) setup do prev = Application.get_env(:microwaveprop, :srtm_tiles_dir) on_exit(fn -> if prev, do: Application.put_env(:microwaveprop, :srtm_tiles_dir, prev), else: Application.delete_env(:microwaveprop, :srtm_tiles_dir) end) :ok end describe "SRTM integration" do @tag :srtm @tag skip: !@has_tiles && "SRTM tiles not available" test "uses local SRTM tiles when configured" do Application.put_env(:microwaveprop, :srtm_tiles_dir, @tiles_dir) assert {:ok, profile} = ElevationClient.fetch_elevation_profile(32.78, -96.8, 32.5, -96.5, 4) assert length(profile) == 5 assert hd(profile).elev > 0 end @tag :srtm test "returns zero elevation when SRTM tiles missing (no API fallback)" do Application.put_env(:microwaveprop, :srtm_tiles_dir, "/nonexistent/tiles") assert {:ok, profile} = ElevationClient.fetch_elevation_profile(32.9, -97.0, 30.3, -97.7, 2) assert length(profile) == 3 assert Enum.all?(profile, fn p -> p.elev == 0 end) end end end