defmodule AprsmeWeb.Api.V1.CallsignControllerTest do # async: false — shares the per-IP rate-limiter ETS with other API tests. # Running concurrently trips the 100/min cap and causes 429s under load. 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/callsign/:callsign" do test "returns 400 for an invalid callsign format", %{conn: conn} do conn = get(conn, ~p"/api/v1/callsign/invalid@callsign") assert conn.status == 400 body = json_response(conn, 400) assert body["error"]["message"] =~ "Invalid callsign" end test "returns empty/not-found response for an unknown callsign", %{conn: conn} do conn = get(conn, ~p"/api/v1/callsign/Z9ZZZ-99") assert conn.status == 200 body = json_response(conn, 200) # The not_found template renders {data: nil, message: "No packets..."}. assert body["data"] == nil assert body["message"] =~ "No packets found" end test "returns the most recent packet when one exists", %{conn: conn} do packet_fixture(%{sender: "W1ABC-9"}) conn = get(conn, ~p"/api/v1/callsign/W1ABC-9") assert conn.status == 200 body = json_response(conn, 200) assert body["data"]["callsign"] == "W1ABC-9" assert Map.has_key?(body["data"]["position"], "latitude") end test "normalizes the callsign to uppercase", %{conn: conn} do packet_fixture(%{sender: "K5ABC-1"}) conn = get(conn, ~p"/api/v1/callsign/k5abc-1") assert conn.status == 200 body = json_response(conn, 200) assert body["data"]["callsign"] == "K5ABC-1" end test "rejects a callsign that's too long (over 12 chars)", %{conn: conn} do # 13 chars long_callsign = "N0CALLN0CALLN" conn = get(conn, ~p"/api/v1/callsign/#{long_callsign}") assert conn.status == 400 end end end