defmodule MicrowavepropWeb.Api.V1.ContactControllerTest do use MicrowavepropWeb.ConnCase, async: true alias Microwaveprop.Accounts alias Microwaveprop.AccountsFixtures alias Microwaveprop.Radio alias MicrowavepropWeb.Api.RateLimiter @valid_attrs %{ "station1" => "W5XD", "station2" => "K5XD", "qso_timestamp" => "2026-01-01T00:00:00Z", "band" => "10000", "grid1" => "EM12", "grid2" => "EM13", "mode" => "CW", "submitter_email" => "fixture@example.com" } setup %{conn: conn} do RateLimiter.reset() user = AccountsFixtures.user_fixture() {:ok, {plaintext, _r}} = Accounts.create_api_token(user, %{name: "t"}) %{conn: conn, user: user, plaintext: plaintext} end describe "GET /api/v1/contacts" do test "returns paginated public contacts", %{conn: conn, user: user} do {:ok, _} = Radio.create_contact(@valid_attrs, user.id) body = conn |> get(~p"/api/v1/contacts") |> json_response(200) assert match?(x when is_list(x), body["data"]) assert body["meta"]["page"] == 1 assert body["meta"]["per_page"] == 50 assert body["meta"]["total_entries"] >= 1 end test "honors page and per_page", %{conn: conn} do conn = get(conn, ~p"/api/v1/contacts?page=2&per_page=10") body = json_response(conn, 200) assert body["meta"]["page"] == 2 assert body["meta"]["per_page"] == 10 end test "clamps insane per_page values", %{conn: conn} do conn = get(conn, ~p"/api/v1/contacts?per_page=99999") body = json_response(conn, 200) assert body["meta"]["per_page"] == 200 end test "rejects invalid pagination values silently with defaults", %{conn: conn} do conn = get(conn, ~p"/api/v1/contacts?page=abc&per_page=zero") body = json_response(conn, 200) assert body["meta"]["page"] == 1 assert body["meta"]["per_page"] == 50 end test "supports search", %{conn: conn, user: user} do {:ok, _} = Radio.create_contact(@valid_attrs, user.id) body = conn |> get(~p"/api/v1/contacts?search=W5XD") |> json_response(200) assert body["meta"]["total_entries"] >= 1 end end describe "GET /api/v1/contacts/:id" do test "returns the contact for a known id", %{conn: conn, user: user} do {:ok, contact} = Radio.create_contact(@valid_attrs, user.id) body = conn |> get(~p"/api/v1/contacts/#{contact.id}") |> json_response(200) assert body["data"]["id"] == contact.id end test "404 for unknown id", %{conn: conn} do conn = get(conn, ~p"/api/v1/contacts/00000000-0000-0000-0000-000000000000") assert json_response(conn, 404) end test "404 hides private contacts from anonymous viewers", %{conn: conn, user: user} do {:ok, contact} = @valid_attrs |> Map.put("private", true) |> Radio.create_contact(user.id) conn = get(conn, ~p"/api/v1/contacts/#{contact.id}") assert json_response(conn, 404) end test "owner can fetch their own private contact", %{conn: conn, user: user, plaintext: pt} do {:ok, contact} = @valid_attrs |> Map.put("private", true) |> Radio.create_contact(user.id) body = conn |> put_req_header("authorization", "Bearer " <> pt) |> get(~p"/api/v1/contacts/#{contact.id}") |> json_response(200) assert body["data"]["id"] == contact.id assert body["data"]["private"] == true end test "non-owner gets 404 on someone else's private contact", %{conn: conn, user: owner} do other = AccountsFixtures.user_fixture() {:ok, {pt, _}} = Accounts.create_api_token(other, %{name: "x"}) {:ok, contact} = @valid_attrs |> Map.put("private", true) |> Radio.create_contact(owner.id) conn = conn |> put_req_header("authorization", "Bearer " <> pt) |> get(~p"/api/v1/contacts/#{contact.id}") assert json_response(conn, 404) end end describe "POST /api/v1/contacts" do test "requires auth", %{conn: conn} do conn = post(conn, ~p"/api/v1/contacts", @valid_attrs) assert json_response(conn, 401) end test "creates a contact for the authenticated user", %{conn: conn, plaintext: pt} do conn = conn |> put_req_header("authorization", "Bearer " <> pt) |> post(~p"/api/v1/contacts", @valid_attrs) body = json_response(conn, 201) assert body["data"]["station1"] == "W5XD" assert body["data"]["mine?"] == true end test "returns 422 on validation errors", %{conn: conn, plaintext: pt} do conn = conn |> put_req_header("authorization", "Bearer " <> pt) |> post(~p"/api/v1/contacts", %{"station1" => "X"}) assert json_response(conn, 422) end test "returns 409 on duplicate submission", %{conn: conn, plaintext: pt, user: user} do {:ok, _} = Radio.create_contact(@valid_attrs, user.id) conn = conn |> put_req_header("authorization", "Bearer " <> pt) |> post(~p"/api/v1/contacts", @valid_attrs) body = json_response(conn, 409) assert body["title"] == "duplicate_contact" assert body["existing"] end end end