- 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.
162 lines
5.1 KiB
Elixir
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
|