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: "

Hello

", 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 '" 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 " 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: "

Hi

", 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