MailglassInbound.TestAssertions (MailglassInbound v0.2.0)

Copy Markdown View Source

Inbound test assertions (ITEST-01..04) — the inbound mirror of Mailglass.TestAssertions.

Adopters import MailglassInbound.TestAssertions (or use the MailglassInbound.MailboxCase that imports it) and assert over the capture tuple emitted by MailglassInbound.Test.Ingress:

send(self(), {:inbound, message, outcome, route})

These assertions read that tuple with assert_received/refute_received, so every assertion is process-localasync: true tests see only their own captures.

Matcher styles (assert_inbound_received)

# 1. Presence (bare call)
assert_inbound_received()

# 2. Keyword match
assert_inbound_received(subject: "Re: ticket #42", from: "alice@example.com")

# 3. Struct pattern (macro — no explicit quoting)
assert_inbound_received(%{subject: "Re: ticket #42"})

# 4. Predicate fn
assert_inbound_received(fn msg -> String.contains?(msg.subject, "ticket") end)

Supported keyword keys: :subject, :from, :to, :tenant, :provider, :envelope_recipient. :from/:to match against the address-shaped lists ([%{address: ...}]). Any other key flunks with a clear message.

Outcome assertions (ITEST-02)

assert_inbound_accepted/0, _ignored/0, _rejected/0, _bounced/0 key off the persisted ExecutionRun.outcome enum carried by the captured outcome map (%{outcome: :accept | :ignore | :reject | :bounce | …}). The raw mailbox atoms :accept | :ignore | {:reject, _} | {:bounce, _} normalize to that enum in Execution.execute/2, so the assertion can never drift from what was written.

Routing assertions (ITEST-03)

assert_inbound_routed_to/1 matches the captured route %{status: :matched, mailbox: ^expected}; assert_inbound_no_match/0 matches %{status: :no_match}.

Negative assertion (ITEST-04)

assert_no_inbound_received/0 refutes any captured {:inbound, …} tuple.

These are ExUnit assertions, not domain errors

Every failure is an ExUnit.AssertionError raised by assert/flunk — NOT a %MailglassInbound.Error{}. The errors-as-contract rule (CLAUDE.md) governs the library's runtime API surface; test assertions are a test-time tool, so they speak ExUnit. ExUnit.Assertions is imported from the bundled OTP :ex_unit app — no Hex dependency is added (D-47-02).

PII posture

Failure messages embed caller-supplied matcher values (e.g. a :subject or :from address) so a failing adopter test has the context it needs. Those values appear only in the adopter's own test output — never in telemetry, log streams, or cross-tenant surfaces (T-47-09). The assertions add no telemetry.

Summary

Functions

Asserts the next captured inbound was accepted (outcome == :accept).

Asserts the next captured inbound was bounced (outcome == :bounce).

Asserts the next captured inbound was ignored (outcome == :ignore).

Asserts the next captured inbound did not match any route (route %{status: :no_match}).

Asserts that at least one inbound message was captured in the current test process. See the moduledoc for the four matcher styles.

Asserts that an inbound message matching the given matcher was captured.

Asserts the next captured inbound was rejected (outcome == :reject).

Asserts the next captured inbound was routed to expected_mailbox (route %{status: :matched, mailbox: ^expected_mailbox}).

Asserts that NO inbound message was captured in the current test process.

Functions

assert_inbound_accepted()

(since 0.1.0) (macro)

Asserts the next captured inbound was accepted (outcome == :accept).

Reads the oldest unconsumed capture (assert_received is FIFO and consumes the matched tuple), so drive one message per outcome assertion.

assert_inbound_bounced()

(since 0.1.0) (macro)

Asserts the next captured inbound was bounced (outcome == :bounce).

Reads the oldest unconsumed capture (assert_received is FIFO and consumes the matched tuple), so drive one message per outcome assertion.

assert_inbound_ignored()

(since 0.1.0) (macro)

Asserts the next captured inbound was ignored (outcome == :ignore).

Reads the oldest unconsumed capture (assert_received is FIFO and consumes the matched tuple), so drive one message per outcome assertion.

assert_inbound_no_match()

(since 0.1.0) (macro)

Asserts the next captured inbound did not match any route (route %{status: :no_match}).

Reads the oldest unconsumed capture (assert_received is FIFO and consumes the matched tuple), so drive one message per routing assertion.

assert_inbound_received()

(since 0.1.0) (macro)

Asserts that at least one inbound message was captured in the current test process. See the moduledoc for the four matcher styles.

Style 1: presence (bare call)

assert_inbound_received()

assert_inbound_received(pattern)

(since 0.1.0) (macro)

Asserts that an inbound message matching the given matcher was captured.

Style 2: keyword list

assert_inbound_received(subject: "Welcome", from: "alice@example.com")

Style 3: struct pattern (no quoting needed)

assert_inbound_received(%{subject: "Welcome"})

Style 4: predicate function

assert_inbound_received(fn msg -> msg.tenant_id == "acme" end)

assert_inbound_rejected()

(since 0.1.0) (macro)

Asserts the next captured inbound was rejected (outcome == :reject).

Reads the oldest unconsumed capture (assert_received is FIFO and consumes the matched tuple), so drive one message per outcome assertion.

assert_inbound_routed_to(expected_mailbox)

(since 0.1.0) (macro)

Asserts the next captured inbound was routed to expected_mailbox (route %{status: :matched, mailbox: ^expected_mailbox}).

Reads the oldest unconsumed capture (assert_received is FIFO and consumes the matched tuple), so drive one message per routing assertion.

assert_no_inbound_received()

(since 0.1.0) (macro)

Asserts that NO inbound message was captured in the current test process.