Coverage:
- Adds focused tests across 13 files covering previously-uncovered
branches: Packet changeset normalisation (course, wind_direction,
struct data_extended); Packets store_packet edge cases (nested
position shapes, ssid handling, string-keyed raw_packet);
DeviceIdentification HTTP success/404 via Req plug stub; ResendAdapter
catch-all body, deliver_many, nil/binary formatters; ApiDocsLive
packets with missing fields; AprsSymbol normalize helpers;
BadPacketsLive refresh and postgres_notify; MobileChannel handle_info
catch-all and postgres_packet path; PacketProcessor existing-marker
movement detection; PacketReplay sanitize_value type clauses;
InsertOptimizer GenServer lifecycle and negative-duration metric;
PageController shutdown-handler integration; UrlParams delegated
helpers.
- mix.exs: configure test_coverage with summary threshold of 87 and
ignore_modules for test fixtures and macro-only template modules.
Bug fix:
- MapLive.Index handle_info: add a clause for the raw {:new_deployment,
_} tuple. Phoenix.PubSub.broadcast delivers the raw payload to plain
subscribers, but the existing handler only matched the %Broadcast{}
fast-lane wrapper. DeploymentNotifier broadcasts via the raw path,
which crashed the LiveView under test concurrency.
Submodule:
- Update .gitmodules vendor/aprs URL from
https://github.com/aprsme/aprs.git to
ssh://git@codeberg.org/gmcintire/aprs.git.
198 lines
6.2 KiB
Elixir
198 lines
6.2 KiB
Elixir
defmodule Aprsme.Swoosh.ResendAdapterTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
alias Aprsme.Swoosh.ResendAdapter
|
|
alias Swoosh.Email
|
|
|
|
defp stub_config(plug_name) do
|
|
[api_key: "re_test_key", plug: {Req.Test, plug_name}]
|
|
end
|
|
|
|
defp new_email(opts \\ []) do
|
|
defaults = [
|
|
from: {"Sender Name", "sender@example.com"},
|
|
to: [{"Recipient", "recipient@example.com"}],
|
|
subject: "Test Subject",
|
|
html_body: "<p>Hello</p>",
|
|
text_body: "Hello"
|
|
]
|
|
|
|
Email.new(Keyword.merge(defaults, opts))
|
|
end
|
|
|
|
describe "deliver/2" do
|
|
test "posts to Resend API and returns id on success" do
|
|
Req.Test.stub(__MODULE__, fn conn ->
|
|
Req.Test.json(conn, %{"id" => "msg_abc123"})
|
|
end)
|
|
|
|
email = new_email()
|
|
|
|
assert {:ok, %{id: "msg_abc123"}} = ResendAdapter.deliver(email, stub_config(__MODULE__))
|
|
end
|
|
|
|
test "returns error on API error response" do
|
|
Req.Test.stub(__MODULE__, fn conn ->
|
|
conn
|
|
|> Plug.Conn.put_status(422)
|
|
|> Req.Test.json(%{"name" => "validation_error", "message" => "Invalid email"})
|
|
end)
|
|
|
|
email = new_email()
|
|
|
|
assert {:error, {"validation_error", "Invalid email"}} =
|
|
ResendAdapter.deliver(email, stub_config(__MODULE__))
|
|
end
|
|
|
|
test "returns error on network failure" do
|
|
Req.Test.stub(__MODULE__, fn conn ->
|
|
Req.Test.transport_error(conn, :econnrefused)
|
|
end)
|
|
|
|
email = new_email()
|
|
|
|
assert {:error, %Req.TransportError{reason: :econnrefused}} =
|
|
ResendAdapter.deliver(email, stub_config(__MODULE__))
|
|
end
|
|
|
|
test "formats named sender as 'Name <email>'" do
|
|
Req.Test.stub(__MODULE__, fn conn ->
|
|
{:ok, body, conn} = Plug.Conn.read_body(conn)
|
|
decoded = Jason.decode!(body)
|
|
send(self(), {:request_body, decoded})
|
|
Req.Test.json(conn, %{"id" => "msg_1"})
|
|
end)
|
|
|
|
email = new_email(from: {"My App", "noreply@example.com"})
|
|
ResendAdapter.deliver(email, stub_config(__MODULE__))
|
|
|
|
assert_received {:request_body, body}
|
|
assert body["from"] == "My App <noreply@example.com>"
|
|
end
|
|
|
|
test "formats bare email sender without name prefix" do
|
|
Req.Test.stub(__MODULE__, fn conn ->
|
|
{:ok, body, conn} = Plug.Conn.read_body(conn)
|
|
decoded = Jason.decode!(body)
|
|
send(self(), {:request_body, decoded})
|
|
Req.Test.json(conn, %{"id" => "msg_1"})
|
|
end)
|
|
|
|
email = new_email(from: {"", "noreply@example.com"})
|
|
ResendAdapter.deliver(email, stub_config(__MODULE__))
|
|
|
|
assert_received {:request_body, body}
|
|
assert body["from"] == "noreply@example.com"
|
|
end
|
|
|
|
test "formats list of recipients as list of email strings" do
|
|
Req.Test.stub(__MODULE__, fn conn ->
|
|
{:ok, body, conn} = Plug.Conn.read_body(conn)
|
|
decoded = Jason.decode!(body)
|
|
send(self(), {:request_body, decoded})
|
|
Req.Test.json(conn, %{"id" => "msg_1"})
|
|
end)
|
|
|
|
email =
|
|
new_email(to: [{"Alice", "alice@example.com"}, {"Bob", "bob@example.com"}])
|
|
|
|
ResendAdapter.deliver(email, stub_config(__MODULE__))
|
|
|
|
assert_received {:request_body, body}
|
|
assert body["to"] == ["alice@example.com", "bob@example.com"]
|
|
end
|
|
|
|
test "omits nil fields from request body" do
|
|
Req.Test.stub(__MODULE__, fn conn ->
|
|
{:ok, body, conn} = Plug.Conn.read_body(conn)
|
|
decoded = Jason.decode!(body)
|
|
send(self(), {:request_body, decoded})
|
|
Req.Test.json(conn, %{"id" => "msg_1"})
|
|
end)
|
|
|
|
email = new_email()
|
|
ResendAdapter.deliver(email, stub_config(__MODULE__))
|
|
|
|
assert_received {:request_body, body}
|
|
refute Map.has_key?(body, "cc")
|
|
refute Map.has_key?(body, "bcc")
|
|
refute Map.has_key?(body, "reply_to")
|
|
end
|
|
end
|
|
|
|
describe "validate_config/1" do
|
|
test "returns :ok when api_key is present" do
|
|
assert :ok = ResendAdapter.validate_config(api_key: "re_abc")
|
|
end
|
|
|
|
test "returns error when api_key is missing" do
|
|
assert {:error, _reason} = ResendAdapter.validate_config([])
|
|
end
|
|
end
|
|
|
|
describe "deliver/2 catch-all body branch" do
|
|
test "returns the raw body as the error when shape is unrecognized" do
|
|
Req.Test.stub(__MODULE__, fn conn ->
|
|
conn
|
|
|> Plug.Conn.put_status(500)
|
|
|> Req.Test.json(%{"unexpected" => "shape"})
|
|
end)
|
|
|
|
email = new_email()
|
|
assert {:error, %{"unexpected" => "shape"}} = ResendAdapter.deliver(email, stub_config(__MODULE__))
|
|
end
|
|
end
|
|
|
|
describe "deliver_many/2" do
|
|
test "delivers each email and returns the collected ids" do
|
|
Req.Test.stub(__MODULE__, fn conn ->
|
|
Req.Test.json(conn, %{"id" => "msg_many"})
|
|
end)
|
|
|
|
emails = [new_email(), new_email(subject: "Second")]
|
|
|
|
assert {:ok, [%{id: "msg_many"}, %{id: "msg_many"}]} =
|
|
ResendAdapter.deliver_many(emails, stub_config(__MODULE__))
|
|
end
|
|
|
|
test "halts and returns the first error" do
|
|
Req.Test.stub(__MODULE__, fn conn ->
|
|
conn
|
|
|> Plug.Conn.put_status(422)
|
|
|> Req.Test.json(%{"name" => "validation_error", "message" => "boom"})
|
|
end)
|
|
|
|
emails = [new_email(), new_email()]
|
|
|
|
assert {:error, {"validation_error", "boom"}} =
|
|
ResendAdapter.deliver_many(emails, stub_config(__MODULE__))
|
|
end
|
|
end
|
|
|
|
describe "format_sender/1 and format_recipients/1 nil/binary branches" do
|
|
test "deliver/2 sends nil and binary fields through the formatters" do
|
|
Req.Test.stub(__MODULE__, fn conn ->
|
|
{:ok, body, conn} = Plug.Conn.read_body(conn)
|
|
send(self(), {:request_body, Jason.decode!(body)})
|
|
Req.Test.json(conn, %{"id" => "msg_xyz"})
|
|
end)
|
|
|
|
email =
|
|
Email.new(
|
|
from: "bare@example.com",
|
|
to: "single@example.com",
|
|
subject: "Subject",
|
|
html_body: "<p>Hi</p>",
|
|
text_body: "Hi"
|
|
)
|
|
|
|
assert {:ok, _} = ResendAdapter.deliver(email, api_key: "re_test_key", plug: {Req.Test, __MODULE__})
|
|
|
|
assert_received {:request_body, body}
|
|
assert body["from"] == "bare@example.com"
|
|
# Swoosh always wraps `to` in a list; format_recipients/1 maps over it,
|
|
# exercising the binary-recipient clause for each element.
|
|
assert body["to"] == ["single@example.com"]
|
|
end
|
|
end
|
|
end
|