132 lines
No EOL
3.7 KiB
Elixir
132 lines
No EOL
3.7 KiB
Elixir
defmodule AprsmeWeb.Plugs.IPGeolocationTest do
|
|
use AprsmeWeb.ConnCase
|
|
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
|
|
|
|
alias AprsmeWeb.Plugs.IPGeolocation
|
|
|
|
@test_ip {204, 110, 191, 254} # Test IP address
|
|
|
|
describe "call/2" do
|
|
test "adds geolocation data to session when successful", %{conn: conn} do
|
|
use_cassette "ip_geolocation_success" do
|
|
conn =
|
|
conn
|
|
|> Map.put(:remote_ip, @test_ip)
|
|
|> IPGeolocation.call([])
|
|
|
|
geo = get_session(conn, :ip_geolocation)
|
|
assert is_map(geo)
|
|
assert is_number(geo["lat"])
|
|
assert is_number(geo["lng"])
|
|
# Test IP should geolocate to somewhere in the US
|
|
assert geo["lat"] >= 24 and geo["lat"] <= 49
|
|
assert geo["lng"] >= -125 and geo["lng"] <= -66
|
|
end
|
|
end
|
|
|
|
test "skips geolocation for local IP addresses", %{conn: conn} do
|
|
conn =
|
|
conn
|
|
|> Map.put(:remote_ip, {127, 0, 0, 1})
|
|
|> IPGeolocation.call([])
|
|
|
|
assert get_session(conn, :ip_geolocation) == nil
|
|
end
|
|
|
|
test "handles API failure gracefully", %{conn: conn} do
|
|
use_cassette "ip_geolocation_failure" do
|
|
conn =
|
|
conn
|
|
|> Map.put(:remote_ip, {192, 0, 2, 1})
|
|
|> IPGeolocation.call([])
|
|
|
|
assert get_session(conn, :ip_geolocation) == nil
|
|
end
|
|
end
|
|
|
|
test "validates latitude and longitude bounds", %{conn: conn} do
|
|
use_cassette "ip_geolocation_invalid_coords" do
|
|
conn =
|
|
conn
|
|
|> Map.put(:remote_ip, {192, 0, 2, 2})
|
|
|> IPGeolocation.call([])
|
|
|
|
assert get_session(conn, :ip_geolocation) == nil
|
|
end
|
|
end
|
|
|
|
test "handles IPv6 addresses", %{conn: conn} do
|
|
use_cassette "ip_geolocation_ipv6" do
|
|
conn =
|
|
conn
|
|
|> Map.put(:remote_ip, {0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888})
|
|
|> IPGeolocation.call([])
|
|
|
|
geo = get_session(conn, :ip_geolocation)
|
|
assert is_map(geo)
|
|
assert is_number(geo["lat"])
|
|
assert is_number(geo["lng"])
|
|
end
|
|
end
|
|
|
|
test "uses forwarded IP when behind proxy", %{conn: conn} do
|
|
use_cassette "ip_geolocation_forwarded" do
|
|
conn =
|
|
conn
|
|
|> Map.put(:remote_ip, {10, 0, 0, 1})
|
|
|> put_req_header("x-forwarded-for", "204.110.191.254, 10.0.0.1")
|
|
|> IPGeolocation.call([])
|
|
|
|
geo = get_session(conn, :ip_geolocation)
|
|
assert is_map(geo)
|
|
assert is_number(geo["lat"])
|
|
assert is_number(geo["lng"])
|
|
end
|
|
end
|
|
|
|
test "caches geolocation in session", %{conn: conn} do
|
|
use_cassette "ip_geolocation_cached" do
|
|
# First request should call API
|
|
conn =
|
|
conn
|
|
|> Map.put(:remote_ip, @test_ip)
|
|
|> IPGeolocation.call([])
|
|
|> fetch_session()
|
|
|
|
geo1 = get_session(conn, :ip_geolocation)
|
|
|
|
# Second request with same session should not call API
|
|
conn2 =
|
|
conn
|
|
|> recycle()
|
|
|> Map.put(:remote_ip, @test_ip)
|
|
|> IPGeolocation.call([])
|
|
|
|
geo2 = get_session(conn2, :ip_geolocation)
|
|
|
|
# Should return the same cached data
|
|
assert geo2 == geo1
|
|
end
|
|
end
|
|
|
|
test "skips non-root paths", %{conn: conn} do
|
|
conn =
|
|
conn
|
|
|> Map.put(:remote_ip, @test_ip)
|
|
|> Map.put(:request_path, "/about")
|
|
|> IPGeolocation.call([])
|
|
|
|
assert get_session(conn, :ip_geolocation) == nil
|
|
end
|
|
|
|
test "skips non-GET requests", %{conn: conn} do
|
|
conn =
|
|
conn
|
|
|> Map.put(:remote_ip, @test_ip)
|
|
|> Map.put(:method, "POST")
|
|
|> IPGeolocation.call([])
|
|
|
|
assert get_session(conn, :ip_geolocation) == nil
|
|
end
|
|
end
|
|
end |