aprs.me/test/aprsme_web/controllers/api/v1/weather_controller_test.exs

298 lines
8.5 KiB
Elixir

defmodule AprsmeWeb.Api.V1.WeatherControllerTest do
use AprsmeWeb.ConnCase, async: false
import Aprsme.PacketsFixtures
setup do
# Reset the Hammer rate-limiter ETS table so we don't trigger 429s from
# accumulated hits across the full test suite.
case :ets.info(Aprsme.RateLimiter) do
:undefined -> :ok
_ -> :ets.delete_all_objects(Aprsme.RateLimiter)
end
:ok
end
describe "GET /api/v1/weather/nearby" do
setup do
# Create a weather station packet
station =
packet_fixture(%{
sender: "TEST-1",
raw_packet: "TEST-1>APRS,TCPIP*:@121045z3745.50N/12159.75W_090/000g000t068r000p000P000h50b10120",
data_type: "weather",
lat: Decimal.new("37.7583"),
lon: Decimal.new("-121.9958"),
has_weather: true,
temperature: 68.0,
humidity: 50.0,
pressure: 1012.0,
wind_speed: 0.0,
wind_direction: 90,
rain_1h: 0.0,
rain_24h: 0.0,
rain_since_midnight: 0.0
})
# Create a non-weather packet (should not appear)
packet_fixture(%{
sender: "TEST-2",
data_type: "position",
lat: Decimal.new("37.7583"),
lon: Decimal.new("-121.9958"),
has_weather: false
})
# Create a weather station outside radius
packet_fixture(%{
sender: "TEST-3",
data_type: "weather",
lat: Decimal.new("40.0"),
lon: Decimal.new("-120.0"),
has_weather: true,
temperature: 75.0
})
%{station: station}
end
test "returns nearby weather stations with valid params", %{conn: conn, station: station} do
conn =
get(conn, ~p"/api/v1/weather/nearby", %{
"lat" => "37.7583",
"lon" => "-121.9958",
"radius" => "10"
})
assert %{
"data" => [weather_station],
"meta" => %{
"count" => 1,
"params" => %{
"latitude" => 37.7583,
"longitude" => -121.9958,
"radius_miles" => 10.0,
"hours" => 6,
"limit" => 50
}
}
} = json_response(conn, 200)
assert weather_station["callsign"] == station.sender
assert weather_station["distance_miles"] < 10.0
assert weather_station["weather"]["temperature"] == 68.0
end
test "returns 400 when lat is missing", %{conn: conn} do
conn =
get(conn, ~p"/api/v1/weather/nearby", %{
"lon" => "-121.9958",
"radius" => "10"
})
assert %{"error" => %{"message" => message}} = json_response(conn, 400)
assert message =~ "lat"
end
test "returns 400 when lon is missing", %{conn: conn} do
conn =
get(conn, ~p"/api/v1/weather/nearby", %{
"lat" => "37.7583",
"radius" => "10"
})
assert %{"error" => %{"message" => message}} = json_response(conn, 400)
assert message =~ "lon"
end
test "returns 400 when radius is missing", %{conn: conn} do
conn =
get(conn, ~p"/api/v1/weather/nearby", %{
"lat" => "37.7583",
"lon" => "-121.9958"
})
assert %{"error" => %{"message" => message}} = json_response(conn, 400)
assert message =~ "radius"
end
test "returns 422 when lat is out of range", %{conn: conn} do
conn =
get(conn, ~p"/api/v1/weather/nearby", %{
"lat" => "91",
"lon" => "-121.9958",
"radius" => "10"
})
assert %{"error" => %{"message" => message}} = json_response(conn, 422)
assert message =~ "latitude"
assert message =~ "-90"
assert message =~ "90"
end
test "returns 422 when lon is out of range", %{conn: conn} do
conn =
get(conn, ~p"/api/v1/weather/nearby", %{
"lat" => "37.7583",
"lon" => "-181",
"radius" => "10"
})
assert %{"error" => %{"message" => message}} = json_response(conn, 422)
assert message =~ "longitude"
assert message =~ "-180"
assert message =~ "180"
end
test "returns 422 when radius is zero", %{conn: conn} do
conn =
get(conn, ~p"/api/v1/weather/nearby", %{
"lat" => "37.7583",
"lon" => "-121.9958",
"radius" => "0"
})
assert %{"error" => %{"message" => message}} = json_response(conn, 422)
assert message =~ "radius"
assert message =~ "greater than 0"
end
test "returns 422 when radius is negative", %{conn: conn} do
conn =
get(conn, ~p"/api/v1/weather/nearby", %{
"lat" => "37.7583",
"lon" => "-121.9958",
"radius" => "-1"
})
assert %{"error" => %{"message" => message}} = json_response(conn, 422)
assert message =~ "radius"
assert message =~ "greater than 0"
end
test "returns 422 when radius exceeds maximum", %{conn: conn} do
conn =
get(conn, ~p"/api/v1/weather/nearby", %{
"lat" => "37.7583",
"lon" => "-121.9958",
"radius" => "1001"
})
assert %{"error" => %{"message" => message}} = json_response(conn, 422)
assert message =~ "radius"
assert message =~ "1000"
end
test "accepts optional hours parameter", %{conn: conn} do
conn =
get(conn, ~p"/api/v1/weather/nearby", %{
"lat" => "37.7583",
"lon" => "-121.9958",
"radius" => "10",
"hours" => "24"
})
assert %{"meta" => %{"params" => %{"hours" => 24}}} = json_response(conn, 200)
end
test "returns 422 when hours is out of range", %{conn: conn} do
conn =
get(conn, ~p"/api/v1/weather/nearby", %{
"lat" => "37.7583",
"lon" => "-121.9958",
"radius" => "10",
"hours" => "169"
})
assert %{"error" => %{"message" => message}} = json_response(conn, 422)
assert message =~ "hours"
assert message =~ "1"
assert message =~ "168"
end
test "accepts optional limit parameter", %{conn: conn} do
conn =
get(conn, ~p"/api/v1/weather/nearby", %{
"lat" => "37.7583",
"lon" => "-121.9958",
"radius" => "10",
"limit" => "25"
})
assert %{"meta" => %{"params" => %{"limit" => 25}}} = json_response(conn, 200)
end
test "returns 422 when limit is out of range", %{conn: conn} do
conn =
get(conn, ~p"/api/v1/weather/nearby", %{
"lat" => "37.7583",
"lon" => "-121.9958",
"radius" => "10",
"limit" => "101"
})
assert %{"error" => %{"message" => message}} = json_response(conn, 422)
assert message =~ "limit"
assert message =~ "1"
assert message =~ "100"
end
test "uses default values when optional params not provided", %{conn: conn} do
conn =
get(conn, ~p"/api/v1/weather/nearby", %{
"lat" => "37.7583",
"lon" => "-121.9958",
"radius" => "10"
})
assert %{"meta" => %{"params" => params}} = json_response(conn, 200)
assert params["hours"] == 6
assert params["limit"] == 50
end
test "returns empty list when no weather stations in range", %{conn: conn} do
conn =
get(conn, ~p"/api/v1/weather/nearby", %{
"lat" => "0.0",
"lon" => "0.0",
"radius" => "1"
})
assert %{
"data" => [],
"meta" => %{"count" => 0}
} = json_response(conn, 200)
end
test "coerces string parameters to correct types", %{conn: conn} do
conn =
get(conn, ~p"/api/v1/weather/nearby", %{
"lat" => "37.7583",
"lon" => "-121.9958",
"radius" => "10",
"hours" => "12",
"limit" => "25"
})
assert %{"meta" => %{"params" => params}} = json_response(conn, 200)
assert is_float(params["latitude"])
assert is_float(params["longitude"])
assert is_float(params["radius_miles"])
assert is_integer(params["hours"])
assert is_integer(params["limit"])
end
test "handles invalid numeric strings", %{conn: conn} do
conn =
get(conn, ~p"/api/v1/weather/nearby", %{
"lat" => "not_a_number",
"lon" => "-121.9958",
"radius" => "10"
})
assert %{"error" => %{"message" => message}} = json_response(conn, 400)
assert message =~ "lat"
end
end
end