prop/test/microwaveprop_web/api/error_json_test.exs
Graham McIntire 316fb2fbc7
Fix low-severity bugs and re-enable Credo checks
- Bug #12: Lower rate limit to 15/min
- Bug #13: Wrap model loading in Task.start
- Bug #14: Fix classify_time_period guard gap at -3.0
- Bug #15: Not applicable (Elixir has no ?? operator)
- Bug #16: Remove fallback Repo.get for station preload
- Bug #17: Extract cache_key() in ContactMapController
- Bug #18: Add has_many :contacts and :beacons to User schema
- A&D #4: Move serve_markdown_if_requested after secure headers
- Config #1: Move signing_salt to runtime.exs env var
- Config #2: Use --check-unused instead of --unused
- Config #3: Re-enable UnsafeToAtom Credo check
- Config #5: Re-enable LeakyEnvironment Credo check
- Add @spec annotations to fix re-enabled Specs violations
- Replace String.to_atom with to_existing_atom where guarded
2026-05-29 17:29:22 -05:00

104 lines
3 KiB
Elixir

defmodule MicrowavepropWeb.Api.ErrorJSONTest do
use ExUnit.Case, async: true
import Plug.Conn
import Plug.Test
alias Ecto.Changeset
alias MicrowavepropWeb.Api.ErrorJSON
defmodule Dummy do
@moduledoc false
use Ecto.Schema
import Changeset
embedded_schema do
field :name, :string
field :age, :integer
end
@spec cs(map()) :: Changeset.t()
def cs(attrs) do
%__MODULE__{}
|> cast(attrs, [:name, :age])
|> validate_required([:name])
|> validate_number(:age, greater_than: 0)
end
end
describe "send_problem/4" do
test "sets content-type, status, halts, and emits a problem+json body" do
conn = conn(:get, "/")
conn = ErrorJSON.send_problem(conn, 401, "unauthorized", "Token missing.")
assert conn.halted
assert conn.status == 401
assert get_resp_header(conn, "content-type") == ["application/problem+json; charset=utf-8"]
assert %{
"type" => "about:blank",
"title" => "unauthorized",
"status" => 401,
"detail" => "Token missing."
} = Jason.decode!(conn.resp_body)
end
test "merges extra fields into the body" do
conn = ErrorJSON.send_problem(conn(:get, "/"), 422, "x", "y", %{errors: %{a: ["b"]}})
assert %{"errors" => %{"a" => ["b"]}} = Jason.decode!(conn.resp_body)
end
end
describe "send_changeset/2" do
test "renders flat field error map at status 422" do
cs = Dummy.cs(%{age: -1})
conn = ErrorJSON.send_changeset(conn(:get, "/"), cs)
assert conn.status == 422
body = Jason.decode!(conn.resp_body)
assert body["title"] == "validation_failed"
assert %{"name" => ["can't be blank"], "age" => ["must be greater than 0"]} = body["errors"]
end
end
describe "translate_errors/1" do
test "interpolates option placeholders into messages" do
cs =
%Changeset{}
|> Map.put(:errors, name: {"too short %{count}", [count: 3]})
|> Map.put(:types, %{name: :string})
|> Map.put(:data, %{})
assert %{name: ["too short 3"]} = ErrorJSON.translate_errors(cs)
end
end
describe "render/2" do
for {tpl, expected_status, expected_title} <- [
{"400.json", 400, "bad_request"},
{"401.json", 401, "unauthorized"},
{"403.json", 403, "forbidden"},
{"404.json", 404, "not_found"},
{"405.json", 405, "method_not_allowed"},
{"415.json", 415, "unsupported_media_type"},
{"429.json", 429, "too_many_requests"},
{"500.json", 500, "internal_server_error"}
] do
test "produces a problem map for #{tpl}" do
body = ErrorJSON.render(unquote(tpl), %{})
assert body.status == unquote(expected_status)
assert body.title == unquote(expected_title)
end
end
test "falls back generically for unknown templates" do
body = ErrorJSON.render("418.json", %{})
assert body.status == 418
assert body.title == "im_a_teapot"
end
end
end