# `LatticeStripe.Testing`
[🔗](https://github.com/szTheory/lattice_stripe/blob/v2.2.2/lib/lattice_stripe/testing.ex#L1)

Test helpers for apps using LatticeStripe.

This module ships with the LatticeStripe hex package so downstream users can
construct realistic Stripe webhook events in their test suites without needing
to know Stripe's HMAC signing scheme.

## Usage

    # High-level: get a typed %Event{} struct for testing event handlers
    import LatticeStripe.Testing

    test "handles payment_intent.succeeded webhook" do
      event = generate_webhook_event("payment_intent.succeeded", %{
        "id" => "pi_test_123",
        "amount" => 2000,
        "currency" => "usd",
        "status" => "succeeded"
      })

      assert {:ok, :processed} = MyApp.Webhooks.handle(event)
    end

    # Low-level: get a signed raw payload + header for Plug-level testing
    test "verifies webhook signature via Plug" do
      {payload, sig_header} = generate_webhook_payload(
        "customer.created",
        %{"id" => "cus_test_456", "email" => "new@example.com"},
        secret: "whsec_test_secret"
      )

      conn =
        Plug.Test.conn(:post, "/webhooks", payload)
        |> Plug.Conn.put_req_header("stripe-signature", sig_header)
        |> MyApp.Router.call([])

      assert conn.status == 200
    end

## Important Note

This module is intended for use in test environments only. It ships in `lib/`
(not `test/support/`) so downstream users can import it without configuring
custom `elixirc_paths` — but it has no side effects and is safe to include in
production releases.

# `active_entitlement`

```elixir
@spec active_entitlement(map()) :: LatticeStripe.Entitlements.ActiveEntitlement.t()
```

Converts a canonical ActiveEntitlement fixture map into
`%LatticeStripe.Entitlements.ActiveEntitlement{}`.

# `active_entitlement_summary`

```elixir
@spec active_entitlement_summary(map()) ::
  LatticeStripe.Entitlements.ActiveEntitlementSummary.t()
```

Converts a canonical ActiveEntitlementSummary fixture map into
`%LatticeStripe.Entitlements.ActiveEntitlementSummary{}`.

# `credit_note`

```elixir
@spec credit_note(map()) :: LatticeStripe.CreditNote.t()
```

Converts a canonical CreditNote fixture map into `%LatticeStripe.CreditNote{}`.

# `customer`

```elixir
@spec customer(map()) :: LatticeStripe.Customer.t()
```

Converts a canonical Customer fixture map into `%LatticeStripe.Customer{}`.

# `dispute`

```elixir
@spec dispute(map()) :: LatticeStripe.Dispute.t()
```

Converts a canonical Dispute fixture map into `%LatticeStripe.Dispute{}`.

# `event_notification`

```elixir
@spec event_notification(map()) :: LatticeStripe.EventNotification.t()
```

Converts a canonical thin-event notification fixture map into
`%LatticeStripe.EventNotification{}`.

Direct typed-builder parallel to `dispute/1`, `customer/1`, etc. — use this when
your test needs a typed `%EventNotification{}` value without going through
signature verification.

## Example

    notif = LatticeStripe.Testing.event_notification(%{
      "id" => "evt_test_account_updated",
      "object" => "v2.core.event",
      "type" => "v2.core.account.updated"
    })

    assert notif.type == "v2.core.account.updated"

When you need a signed wire-format payload (for example to exercise a Phoenix
controller calling `Webhook.parse_event_notification/4`), use
`generate_thin_event_payload/3` instead.

# `feature`

```elixir
@spec feature(map()) :: LatticeStripe.Entitlements.Feature.t()
```

Converts a canonical Feature fixture map into
`%LatticeStripe.Entitlements.Feature{}`.

`entitlements.feature` is deliberately absent from the internal ObjectTypes
dispatch registry (it is not a webhook `data.object` payload), so this wrapper is
the only typed decode path the public surface offers for it.

# `file`

```elixir
@spec file(map()) :: LatticeStripe.File.t()
```

Converts a canonical File fixture map into `%LatticeStripe.File{}`.

# `file_link`

```elixir
@spec file_link(map()) :: LatticeStripe.FileLink.t()
```

Converts a canonical FileLink fixture map into `%LatticeStripe.FileLink{}`.

# `generate_thin_event_payload`

```elixir
@spec generate_thin_event_payload(String.t(), map() | nil, keyword()) ::
  {String.t(), String.t()}
```

Generates a signed **thin-event** webhook payload pair for Plug-level testing.

Thin-event counterpart to `generate_webhook_payload/3` — produces a wire-format
`/v2/events` notification payload (`object: "v2.core.event"`, ISO 8601 `created`,
no `data` or `pending_webhooks` keys) and a matching `Stripe-Signature` header.
Returns `{payload_string, signature_header_value}` that round-trips through
`LatticeStripe.Webhook.parse_event_notification/4` without modification.

Use `generate_webhook_payload/3` for snapshot v1 webhooks (`construct_event/4`)
and this helper for thin-event v2 webhooks (`parse_event_notification/4`).
Calling the wrong helper produces a structurally-valid payload that decodes to
a mostly-`nil` struct — keep snapshot and thin-event test paths obviously
distinct in your test suite.

## Parameters

- `type` - Stripe thin-event type string, e.g. `"v2.core.account.updated"`
- `related_object_data` - The `related_object` map (`%{"id" => ..., "type" => ...,
  "url" => ...}`) or `nil` for snapshot-style v2 events (default: `nil`)
- `opts` - Options:
  - `:secret` - Webhook signing secret (required; raises `KeyError` if absent)
  - `:timestamp` - Unix-seconds timestamp integer used both to sign the payload
    and to derive the ISO 8601 `created` field (default: current system time)
  - `:id` - Event ID string (default: `"evt_test_" <> random_hex(16)`)
  - `:context` - Free-form `context` string (default: `nil`)
  - `:livemode` - boolean (default: `false`)

## Returns

`{raw_payload_string, stripe_signature_header_value}` — the same shape as
`generate_webhook_payload/3`.

## Example

    {payload, sig_header} =
      LatticeStripe.Testing.generate_thin_event_payload(
        "v2.core.account.updated",
        %{
          "id" => "acct_test_123",
          "type" => "v2.core.account",
          "url" => "/v2/core/accounts/acct_test_123"
        },
        secret: "whsec_test"
      )

    {:ok, notif} =
      LatticeStripe.Webhook.parse_event_notification(payload, sig_header, "whsec_test")

    assert notif.type == "v2.core.account.updated"
    assert notif.related_object.id == "acct_test_123"

Pass `nil` for `related_object_data` to produce a snapshot-style v2 event (the
notification will have `related_object: nil`; adopters dispatch these to
`Webhook.fetch_event/3` rather than `fetch_related_object/3`).

# `generate_webhook_event`

```elixir
@spec generate_webhook_event(String.t(), map(), keyword()) :: LatticeStripe.Event.t()
```

Builds a `%LatticeStripe.Event{}` struct for the given event type and object data.

Constructs a realistic Stripe event shape without making any HTTP calls.
The `data.object` map is whatever you pass as `object_data`.

## Parameters

- `type` - Stripe event type string, e.g. `"payment_intent.succeeded"`
- `object_data` - The `data.object` map for the event (default: `%{}`)
- `opts` - Options:
  - `:id` - Event ID string (default: `"evt_test_" <> random_hex(16)`)
  - `:api_version` - API version string (default: `"2026-03-25.dahlia"`)
  - `:livemode` - boolean (default: `false`)

## Returns

A `%LatticeStripe.Event{}` struct.

Canonical raw maps from `LatticeStripe.Testing.Fixtures.*` compose directly
with this helper.

## Example

    file = LatticeStripe.Testing.Fixtures.File.file_json()

    event = LatticeStripe.Testing.generate_webhook_event(
      "payment_intent.succeeded",
      file
    )
    assert event.type == "payment_intent.succeeded"
    assert event.data["object"]["id"] == file["id"]

# `generate_webhook_payload`

```elixir
@spec generate_webhook_payload(String.t(), map(), keyword()) ::
  {String.t(), String.t()}
```

Generates a signed webhook payload pair for Plug-level testing.

Returns `{payload_string, signature_header_value}` where the signature
is computed using `Webhook.generate_test_signature/3`. The returned pair
passes `Webhook.construct_event/4` without modification.

The raw event map is JSON-encoded directly (before `Event.from_map/1`) to
avoid round-trip encoding issues with the `%Event{}` struct.

## Parameters

- `type` - Stripe event type string, e.g. `"customer.created"`
- `object_data` - The `data.object` map for the event (default: `%{}`)
- `opts` - Options:
  - `:secret` - Webhook signing secret (required)
  - `:timestamp` - Unix timestamp integer to embed in signature (default: current time)
  - Other opts (`:id`, `:api_version`, `:livemode`) are forwarded to `generate_webhook_event/3`

## Returns

`{raw_payload_string, stripe_signature_header_value}`

Canonical raw maps from `LatticeStripe.Testing.Fixtures.*` compose directly
with this helper.

## Example

    dispute = LatticeStripe.Testing.Fixtures.Dispute.dispute_json()

    {payload, sig_header} = LatticeStripe.Testing.generate_webhook_payload(
      "payment_intent.succeeded",
      dispute,
      secret: "whsec_test"
    )

    {:ok, event} = LatticeStripe.Webhook.construct_event(payload, sig_header, "whsec_test")
    assert event.type == "payment_intent.succeeded"

# `invoice`

```elixir
@spec invoice(map()) :: LatticeStripe.Invoice.t()
```

Converts a canonical Invoice fixture map into `%LatticeStripe.Invoice{}`.

# `mandate`

```elixir
@spec mandate(map()) :: LatticeStripe.Mandate.t()
```

Converts a canonical Mandate fixture map into `%LatticeStripe.Mandate{}`.

# `meter_error_report`

```elixir
@spec meter_error_report(map()) :: LatticeStripe.Billing.MeterErrorReport.t()
```

Converts a canonical MeterErrorReport `data` map into
`%LatticeStripe.Billing.MeterErrorReport{}`.

Wraps `from_map/1`, the low-level constructor: `:meter` is **always** `nil` here
because the meter id lives in the event envelope, not in `data`. When you have the
whole `v2.core.event` (see
`LatticeStripe.Testing.Fixtures.MeterErrorReport.meter_error_report_event_json/1`),
call `LatticeStripe.Billing.MeterErrorReport.from_event/1` instead — it is the only
path that can populate `:meter`.

# `meter_event`

```elixir
@spec meter_event(map()) :: LatticeStripe.Billing.MeterEvent.t()
```

Converts a canonical MeterEvent fixture map into
`%LatticeStripe.Billing.MeterEvent{}`.

# `meter_event_summary`

```elixir
@spec meter_event_summary(map()) :: LatticeStripe.Billing.MeterEventSummary.t()
```

Converts a canonical MeterEventSummary fixture map into
`%LatticeStripe.Billing.MeterEventSummary{}`.

# `payment_intent`

```elixir
@spec payment_intent(map()) :: LatticeStripe.PaymentIntent.t()
```

Converts a canonical PaymentIntent fixture map into `%LatticeStripe.PaymentIntent{}`.

# `quote`

```elixir
@spec quote(map()) :: LatticeStripe.Quote.t()
```

Converts a canonical Quote fixture map into `%LatticeStripe.Quote{}`.

# `setup_attempt`

```elixir
@spec setup_attempt(map()) :: LatticeStripe.SetupAttempt.t()
```

Converts a canonical SetupAttempt fixture map into `%LatticeStripe.SetupAttempt{}`.

# `subscription`

```elixir
@spec subscription(map()) :: LatticeStripe.Subscription.t()
```

Converts a canonical Subscription fixture map into `%LatticeStripe.Subscription{}`.

# `tax_calculation`

```elixir
@spec tax_calculation(map()) :: LatticeStripe.Tax.Calculation.t()
```

Converts a canonical Tax Calculation fixture map into `%LatticeStripe.Tax.Calculation{}`.

# `tax_id`

```elixir
@spec tax_id(map()) :: LatticeStripe.TaxId.t()
```

Converts a canonical TaxId fixture map into `%LatticeStripe.TaxId{}`.

# `tax_transaction`

```elixir
@spec tax_transaction(map()) :: LatticeStripe.Tax.Transaction.t()
```

Converts a canonical Tax Transaction fixture map into `%LatticeStripe.Tax.Transaction{}`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
