MailglassInbound.MailboxCase (MailglassInbound v0.2.0)

Copy Markdown View Source

The shipped ExUnit.CaseTemplate adopters use to test inbound mailboxes (ITEST-05) — the inbound analog of outbound's Mailglass.MailerCase, and the keystone that ties the inbound Testing helpers together.

use MailglassInbound.MailboxCase injects import MailglassInbound.TestAssertions and the Fixtures/Test aliases, checks out an Ecto sandbox on the adopter's configured repo, sets tenancy, resets the only process-global state the inbound fixtures touch, and tears the sandbox down on exit.

Ships in lib/ (not test/support/)

Like the other inbound Testing helpers, this case template ships in the mailglass_inbound Hex package via the files: ~w(lib …) manifest so adopters can use it from their own suites. Because it ships in lib/ it references ONLY core/runtime modules — never Oban, ExAws, or Plug.Test (Pitfall 6), so it compiles cleanly under mix compile --no-optional-deps --warnings-as-errors.

The adopter's repo is resolved from app-env

The sandbox is checked out on the repo resolved from Application.get_env(:mailglass_inbound, :repo), exactly like the MailglassInbound.Repo facade. The case template NEVER references the package's own test repo literal — the package does not own a repo (Pitfall 1). Adopters configure one in config/test.exs:

config :mailglass_inbound, :repo, MyApp.Repo

If unset, setup raises with that exact instruction. (The package's own self-tests work because config/test.exs points :repo at the support repo under test/support.)

It snapshots NO app-env

Unlike Mailglass.MailerCase (which snapshots/restores its async-mode application-env keys so deliver_later/2 runs inline), this case template writes and restores no application-env key. Inbound achieves synchronous execution structurally: MailglassInbound.Test.Ingress drives MailglassInbound.Execution.execute/2 (SYNC) directly — there is no async-mode app-env key to flip and therefore no leak surface across tests (D-47-12, T-47-13). The only teardown is Sandbox.stop_owner/1; the only shared-state hygiene is the per-setup ETS / process-dict reset below.

Default setup

Supported tags

  • @tag tenant: "acme" — override the default "test-tenant"
  • @tag tenant: :unset — disable tenancy stamping
  • @tag async: false — disable async (sandbox checks out in shared mode)

Example

defmodule MyApp.WelcomeMailboxTest do
  use MailglassInbound.MailboxCase, async: false

  test "accepts a welcome message" do
    message = Fixtures.build_inbound_message(subject: "Welcome")

    {:ok, %{outcome: %{outcome: :accept}, route: %{mailbox: MyApp.WelcomeMailbox}}} =
      Test.Ingress.receive_inbound(message, routes: my_routes())

    # ONE assertion per drive: each `assert_inbound_*` reads the captured
    # tuple with `assert_received`, which CONSUMES it from the process
    # mailbox. To run a second assertion, drive a second message (with a
    # distinct `provider_message_id` so it is a fresh receive).
    assert_inbound_received(subject: "Welcome")
  end
end