298 lines
8.5 KiB
Elixir
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
|