# `AttestoMCP.Test.DPoPAssertions`
[🔗](https://github.com/XukuLLC/attesto_mcp/blob/v1.0.5/lib/attesto_mcp/test/dpop_assertions.ex#L2)

Shipped ExUnit assertions for the DPoP sender-constraint contract.

RFC 9449 binds an access token to the client key that signed a DPoP proof
via the token's `cnf.jkt` claim (RFC 7800). Two properties of that contract
are worth a host MCP server proving on its own pipeline, because getting
either wrong silently downgrades sender-constrained tokens to bearer tokens:

  1. A DPoP-bound token presented as a plain `Bearer` token (no proof) MUST
    be rejected. Otherwise a captured token is usable without the key.
  2. A DPoP-bound token presented as `DPoP` with a valid proof for the live
    request MUST be accepted.

These helpers drive a host's already-wired authentication plug against both
cases so the host's exact `:config`, `:replay_check`, `:htu`, and error
rendering are exercised. They build the token and proof with
`AttestoMCP.Test.Factory` and use only Attesto's published DPoP API
(`Attesto.DPoP.compute_ath/1` via the factory), so they do not depend on any
Attesto-internal test scaffolding.

The module compiles only when `ExUnit` is loaded, so it adds nothing to a
host's production build.

## Usage

    defmodule MyApp.MCPAuthTest do
      use ExUnit.Case
      import AttestoMCP.Test.DPoPAssertions

      setup do
        %{config: AttestoMCP.Test.Factory.config()}
      end

      test "MCP endpoint enforces DPoP binding", %{config: config} do
        plug = fn conn ->
          MyAppWeb.MCPAuth.call(conn, MyAppWeb.MCPAuth.init([]))
        end

        assert_dpop_bound_bearer_rejected(plug, config)
        assert_dpop_proof_accepted(plug, config)
      end
    end

`plug_fun` is a one-arity function that runs the host's authentication
pipeline on a `Plug.Conn` and returns the resulting conn. A host that
requires `:replay_check` for DPoP requests must wire it inside `plug_fun`.

# `plug_fun`

```elixir
@type plug_fun() :: (Plug.Conn.t() -&gt; Plug.Conn.t())
```

# `assert_dpop_bound_bearer_rejected`

```elixir
@spec assert_dpop_bound_bearer_rejected(plug_fun(), Attesto.Config.t(), keyword()) ::
  Plug.Conn.t()
```

Assert that a DPoP-bound token presented as a plain `Bearer` token (with no
proof) is rejected by the host pipeline.

Options:

  * `:path` - request path (default `"/mcp"`).
  * `:method` - request method (default `:post`).
  * `:scopes` - scopes minted into the token (default the factory default).

Returns the resulting halted `Plug.Conn` for further assertions.

# `assert_dpop_proof_accepted`

```elixir
@spec assert_dpop_proof_accepted(plug_fun(), Attesto.Config.t(), keyword()) ::
  Plug.Conn.t()
```

Assert that a DPoP-bound token presented as `DPoP` with a valid proof for
the live request is accepted by the host pipeline.

Options:

  * `:path` - request path (default `"/mcp"`).
  * `:method` - request method (default `:post`).
  * `:scopes` - scopes minted into the token (default the factory default).
  * `:htu` - the `htu` claim of the proof (default the factory audience). It
    must match the host's computed request URI.

Returns the resulting non-halted `Plug.Conn` for further assertions.

---

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