prop/test/microwaveprop_web/controllers/api/v1/contact_controller_test.exs
Graham McIntire cb8445f329
fix: resolve 158/183 credo --strict warnings
- Add 17 missing @spec annotations (layouts, error_json, error_html, skewt_svg)
- Move 12+ nested alias/import/require to module top level
- Add phx-change/id attributes to 11 raw HTML <form> tags
- Remove 4 unused LiveView assigns (:bounds, :data_provider)
- Add 3 missing doctest references (HrrrNativeClient, BulkFetch, Accounts)
- Break 2 long lines (path_compute.ex:382)
- Strengthen weak test assertions (is_binary→byte_size, is_list→!=[])
- Replace Module.concat with Module.safe_concat (2 occurrences)
- Replace length/1 > 0 with list != [] (9 occurrences)
- Remove no-op assert true, fix no-assertion tests

Remaining: 24 socket.assigns introspection warnings (deliberate test
pattern for observable behavior testing), 1 formatter-resistant long
line, 3 app-code usage warnings.
2026-06-12 15:47:15 -05:00

162 lines
5.1 KiB
Elixir

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