# `WhatsApp.Test`
[🔗](https://github.com/jeffhuen/whatsapp_sdk/blob/main/lib/whatsapp/test.ex#L1)

Test helpers for WhatsApp SDK.

Provides process-scoped HTTP stubs via `NimbleOwnership` for writing
`async: true` tests without hitting the real API.

## Setup

    # In test/test_helper.exs
    WhatsApp.Test.start()
    ExUnit.start()

## Usage

    defmodule MyApp.NotifierTest do
      use ExUnit.Case, async: true

      setup do
        WhatsApp.Test.stub(fn request ->
          case request.url do
            "https://graph.facebook.com/v23.0/" <> _ ->
              %{status: 200, body: ~s({"messages":[{"id":"wamid.123"}]}), headers: []}

            _ ->
              %{status: 404, body: ~s({"error":{"message":"Not found"}}), headers: []}
          end
        end)

        :ok
      end

      test "sends a message" do
        client = WhatsApp.client()
        assert {:ok, _} = MyApp.send_whatsapp(client, "Hello!")
      end
    end

## How It Works

Each test process registers a stub function via `stub/1`. The stub is stored
as metadata in a `NimbleOwnership` server, keyed by the `:http_stub` atom.

When the SDK makes an HTTP request, it calls `fetch_fun/0` to check whether
a stub exists for the current process (or any process in the `$callers` chain).
If a stub is found, the request is routed through the stub function instead
of Finch.

Because stubs are process-scoped, tests using `async: true` are fully isolated
from one another.

# `request`

```elixir
@type request() :: %{
  method: :get | :post | :put | :delete | :patch,
  url: String.t(),
  headers: [{String.t(), String.t()}],
  body: String.t() | nil | {:multipart, list()}
}
```

Stub request map passed to the stub function.

Fields match the HTTP request sent by the client:

  * `:method` - HTTP method atom (`:get`, `:post`, `:put`, `:delete`, `:patch`)
  * `:url` - Full request URL string
  * `:headers` - List of `{name, value}` header tuples
  * `:body` - Request body (string, nil, or `{:multipart, parts}`)

# `response`

```elixir
@type response() :: %{
  status: non_neg_integer(),
  body: String.t(),
  headers: [{String.t(), String.t()}]
}
```

Stub response map returned from the stub function.

Fields mirror a standard HTTP response:

  * `:status` - HTTP status code integer (e.g., `200`, `400`, `429`)
  * `:body` - Response body as a JSON string
  * `:headers` - List of `{name, value}` header tuples (default: `[]`)

# `allow`

```elixir
@spec allow(pid()) :: :ok
```

Allow another process to use the current process's stub.

Useful when your test spawns a process (like a GenServer) that
needs to use the same stub as the test process.

## Example

    setup do
      WhatsApp.Test.stub(fn _request ->
        %{status: 200, body: "{}", headers: []}
      end)

      {:ok, pid} = MyWorker.start_link()
      WhatsApp.Test.allow(pid)
      %{worker: pid}
    end

# `start`

```elixir
@spec start() :: {:ok, pid()} | :ignore | {:error, term()}
```

Start the test stub ownership server.

Call this in `test/test_helper.exs` before `ExUnit.start()`.

# `stub`

```elixir
@spec stub((request() -&gt; response())) :: :ok
```

Register a stub function for the current test process.

The function receives a request map with:

  * `:method` - HTTP method atom (`:get`, `:post`, etc.)
  * `:url` - Full request URL string
  * `:headers` - List of `{name, value}` header tuples
  * `:body` - Request body (string, nil, or `{:multipart, parts}`)

It must return a response map with:

  * `:status` - HTTP status code integer
  * `:body` - Response body string
  * `:headers` - List of `{name, value}` header tuples (default: `[]`)

## Example

    WhatsApp.Test.stub(fn _request ->
      %{status: 200, body: ~s({"success":true}), headers: []}
    end)

---

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