Mailglass.Adapters.Fake (Mailglass v0.1.0)

Copy Markdown View Source

In-memory, time-advanceable test adapter (TRANS-02, D-01..D-03).

The merge-blocking release gate (D-13). Every PR runs the full pipeline against this adapter. Mirrors Swoosh.Adapters.Sandbox: ownership-by-pid, $callers inheritance, allow/2 for cross-process delegation (LiveView, Playwright, Oban worker), shared mode for global tests.

Stored record shape

%{
  message: %Mailglass.Message{},
  delivery_id: Ecto.UUID.t(),
  provider_message_id: String.t(),
  recorded_at: DateTime.t()
}

Records %Mailglass.Message{} (NOT raw %Swoosh.Email{}) so assert_mail_sent(mailable: UserMailer) can recover the originating Mailable. Tenant stamped from the message at record time.

provider_message_id lets trigger_event/3 look up the Delivery row by id and simulate a Phase-4 webhook event via the REAL Events.append_multi/3 + Projector.update_projections/2 write path (D-03). This keeps the Fake in sync with the production write path.

Public API

Async: true safety

Ownership keys every ETS bucket by owner pid; each test is its own owner (via Mailglass.MailerCase setup, Plan 06). Cross-process deliveries (LiveView, Task.Supervisor, Oban worker) resolve via $callers or explicit allow/2.

Summary

Functions

Advances the process-local frozen clock. Delegates to Mailglass.Clock.Frozen.advance/1.

Clears recorded deliveries.

Returns all recorded deliveries for the current owner (or a specified owner).

Returns the most recent delivery for the current owner, or nil.

Simulates a webhook-shaped event for a previously-delivered message (D-03).

Functions

advance_time(ms)

(since 0.1.0)
@spec advance_time(integer()) :: DateTime.t()

Advances the process-local frozen clock. Delegates to Mailglass.Clock.Frozen.advance/1.

allow(owner_pid, allowed_pid)

See Mailglass.Adapters.Fake.Storage.allow/2.

checkin()

See Mailglass.Adapters.Fake.Storage.checkin/0.

checkout()

See Mailglass.Adapters.Fake.Storage.checkout/0.

clear()

clear(opts)

@spec clear(keyword() | :all) :: :ok

Clears recorded deliveries.

  • clear() — clears the current owner's bucket.
  • clear([owner: pid]) — clears the specified owner's bucket.
  • clear(:all) — clears every owner's bucket (flushes the entire ETS table).

deliveries(opts \\ [])

@spec deliveries(keyword()) :: [map()]

Returns all recorded deliveries for the current owner (or a specified owner).

Options

  • :owner — pid; defaults to self()
  • :tenant — filter by record.message.tenant_id
  • :mailable — filter by record.message.mailable
  • :recipient — filter by any address in record.message.swoosh_email.to

get_shared()

See Mailglass.Adapters.Fake.Storage.get_shared/0.

last_delivery(opts \\ [])

@spec last_delivery(keyword()) :: map() | nil

Returns the most recent delivery for the current owner, or nil.

set_shared(pid)

See Mailglass.Adapters.Fake.Storage.set_shared/1.

trigger_event(provider_message_id, type, opts \\ [])

(since 0.1.0)
@spec trigger_event(String.t(), atom(), keyword()) ::
  {:ok, Mailglass.Events.Event.t()} | {:error, term()}

Simulates a webhook-shaped event for a previously-delivered message (D-03).

Looks up the %Delivery{} row by provider_message_id, builds an %Events.Event{}, and runs it through Events.append_multi/3 + Projector.update_projections/2 inside Repo.transact/1. This is the SAME write path Phase 4 webhook ingest uses — the Fake proves the production write path.

After the transaction commits, broadcasts via Projector.broadcast_delivery_updated/3 (D-04).

Opts

  • :occurred_at — DateTime; defaults to Mailglass.Clock.utc_now()
  • :reject_reason — atom from the reject_reason closed set
  • :metadata — map stored in Event.metadata (Phase 4: raw_payload moved to mailglass_webhook_events; see D-15)

Returns

  • {:ok, %Events.Event{}} on success
  • {:error, :not_found} if provider_message_id has no matching Delivery
  • {:error, term()} for other failures