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

ExUnit ergonomics for Stripe Test Clocks.

This module provides a `use`-macro that users opt into from their own
`ExUnit.CaseTemplate`. It wraps `LatticeStripe.TestHelpers.TestClock`
with automatic per-test cleanup (via a lightweight GenServer Owner),
automatic customer-to-clock linkage (closing the silent-correctness
footgun of forgetting `test_clock:` on `Customer.create/2`), and a
human-friendly `advance/2` that takes time units.

## Usage

    defmodule MyApp.StripeCase do
      use ExUnit.CaseTemplate

      using do
        quote do
          use LatticeStripe.Testing.TestClock, client: MyApp.StripeClient
        end
      end
    end

    defmodule MyApp.BillingTest do
      use MyApp.StripeCase, async: true
      setup :with_test_clock

      test "sub renews after 30 days", %{test_clock: clock} do
        customer = create_customer(clock, email: "a@b.c")
        {:ok, sub} = MyApp.Billing.subscribe(customer, "price_monthly")
        advance(clock, days: 30)
        assert {:ok, %{status: "active"}} = MyApp.Billing.get_subscription(sub.id)
      end
    end

## Client contract

The module passed as `:client` must be either:

- A `%LatticeStripe.Client{}` struct directly, or
- A module atom that exposes a public `stripe_client/0` function returning
  a `%LatticeStripe.Client{}`.

Every helper also accepts a per-call `:client` option (a `%Client{}` struct)
that wins over the compile-time binding. This supports multi-account tests.

## Cleanup strategy

Every clock created via `test_clock/1` is registered with a per-test
`LatticeStripe.Testing.TestClock.Owner` GenServer. You do not interact with it
directly — `test_clock/1` owns its lifecycle — but it is documented rather than
hidden, because it has shipped with a `@moduledoc` since 1.0.0 and is therefore
public API under `guides/api_stability.md`. On test exit
(including crash / assertion failure), the Owner deletes each
registered clock via `LatticeStripe.TestHelpers.TestClock.delete/3`.
Stripe's delete cascades to attached Customers and Subscriptions.

For SIGKILL / BEAM crash / CI timeout scenarios that bypass `on_exit`,
the `mix lattice_stripe.test_clock.cleanup` task backstops by deleting
test clocks older than a configurable threshold.

## Metadata marker (A-13g caveat)

Stripe's Test Clock API does **not** support `metadata` on create
(verified via OpenAPI spec and stripe-mock on 2026-04-11). This means
the Mix task cleanup backstop uses **age-based filtering only** and
cannot distinguish LatticeStripe-managed clocks from user-created ones.
The primary cleanup path (Owner + `on_exit`) is unaffected.

If Stripe adds metadata support in the future, this module will be
updated to tag clocks with a marker for precise Mix task filtering.

## Supported advance units (v1)

`advance/2` accepts: `:seconds`, `:minutes`, `:hours`, `:days`, or `:to`
(absolute `DateTime`). Passing `:months` or `:years` raises
`ArgumentError` -- Elixir 1.15 (the project minimum) has no calendar
shift helper, and month-length arithmetic is fiddly. For month/year
advancement, use:

    advance(clock, to: DateTime.utc_now() |> DateTime.add(86_400 * 30, :second))

## Customer-to-clock linkage (D-13h)

`create_customer/2,3` auto-injects `test_clock: clock.id` into the
customer creation params. This closes the silent-correctness footgun
where forgetting `test_clock:` means the customer runs on real time
and clock advances have no effect. Users who bypass this wrapper and
call `LatticeStripe.Customer.create/2` directly are responsible for
injecting the `test_clock` param themselves.

# `advance`

Advances a test clock by a given unit, waiting until `:ready`.

## Supported units (v1)

- `[seconds: N]`
- `[minutes: N]`
- `[hours: N]`
- `[days: N]`
- `[to: %DateTime{}]` -- absolute target

`[months: N]` and `[years: N]` raise `ArgumentError` -- Elixir 1.15 has
no calendar shift helper. Use `[to: DateTime]` with hand-computed month
math if needed.

## Options

- `:client` -- per-call client override (`%LatticeStripe.Client{}`).
  When omitted, falls back to the process-bound client from the `use` macro.

## Example

    advance(clock, days: 30)
    advance(clock, days: 30, client: my_client)

# `create_customer`

Creates a customer attached to the given test clock.

Wraps `LatticeStripe.Customer.create/2` and auto-injects
`test_clock: clock.id` into the params. This closes the D-13h
silent-correctness footgun of forgetting `test_clock:` and having
the customer run on real time.

## Example

    customer = create_customer(clock, email: "a@b.c")

# `freeze`

Waits for a test clock to reach `:ready` at its current `frozen_time`.
Useful after out-of-band clock ops.

# `test_clock`

Creates a test clock and registers it for automatic cleanup.

## Options

- `:frozen_time` -- unix timestamp integer (default: current system time)
- `:name` -- human-readable name (default: `"lattice_stripe_test"`)
- `:client` -- per-call client override (`%LatticeStripe.Client{}`)

## Example

    clock = test_clock(frozen_time: ~U[2026-01-01 00:00:00Z] |> DateTime.to_unix())

# `with_test_clock`

ExUnit setup callback. Creates a test clock and injects it into the
context as `:test_clock`.

## Usage

    setup :with_test_clock

    test "sub renews", %{test_clock: clock} do
      ...
    end

---

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