> ## Documentation Index
> Fetch the complete documentation index at: https://docs.faremeter.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing

> Write integration tests for x402 payment flows using @faremeter/test-harness.

`@faremeter/test-harness` provides an in-process test environment that connects client, middleware, and facilitator using function adapters instead of HTTP. No real blockchain interaction, no network calls, no running servers.

```bash theme={null}
pnpm add -D @faremeter/test-harness
```

## TestHarness

The `TestHarness` class wires together a client, middleware, and facilitator in memory. It creates a `fetch` function that runs the full x402 payment flow — 402 response, payment construction, settlement — entirely in-process.

```typescript theme={null}
import {
  TestHarness,
  accepts,
  createTestPaymentHandler,
  createTestFacilitatorHandler,
} from "@faremeter/test-harness"

const harness = new TestHarness({
  accepts: [accepts({ maxAmountRequired: "10000", asset: "USDC" })],
  clientHandlers: [createTestPaymentHandler()],
  facilitatorHandlers: [createTestFacilitatorHandler({ payTo: "test-receiver" })],
})

const fetch = harness.createFetch()
const response = await fetch("http://test.local/resource")
```

### Configuration

| Option                   | Type                                    | Description                                                                                                            |
| ------------------------ | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `accepts`                | `x402PaymentRequirements[]`             | Payment requirements the test server returns. Use the `accepts()` helper for defaults.                                 |
| `clientHandlers`         | `PaymentHandlerV1[]`                    | **Required.** Client-side payment handlers (v1, internally adapted to v2). Use `createTestPaymentHandler()` for tests. |
| `facilitatorHandlers`    | `FacilitatorHandler[]`                  | **Required.** Facilitator handlers. Use `createTestFacilitatorHandler({ payTo })` for tests.                           |
| `clientInterceptors`     | `Interceptor[]`                         | Interceptors between client and middleware.                                                                            |
| `middlewareInterceptors` | `Interceptor[]`                         | Interceptors between middleware and facilitator.                                                                       |
| `settleMode`             | `"settle-only" \| "verify-then-settle"` | Whether to verify before settling. Default: `"settle-only"`.                                                           |

### Resource handler

Set a custom response for the protected resource:

```typescript theme={null}
harness.setResourceHandler(async (ctx) => {
  return new Response(JSON.stringify({ data: "protected content" }), {
    status: 200,
    headers: { "Content-Type": "application/json" },
  })
})
```

### Reset

Call `harness.reset()` between tests to clear interceptors and restore defaults.

## Test helpers

The `accepts()` and `acceptsV2()` helpers create payment requirements with sensible test defaults so you don't need to specify every field:

```typescript theme={null}
import { accepts, acceptsV2 } from "@faremeter/test-harness"

// v1 requirements with test defaults
const reqs = accepts({ maxAmountRequired: "5000", asset: "USDC" })

// v2 requirements
const reqs = acceptsV2({ amount: "5000", asset: "USDC" })
```

## Interceptors

Interceptors wrap the fetch pipeline to observe, modify, or block requests. They sit between the client and middleware, or between middleware and facilitator.

```typescript theme={null}
harness.addClientInterceptor(interceptor)      // between test code and middleware
harness.addMiddlewareInterceptor(interceptor)   // between middleware and facilitator
```

### Logging

Capture all requests and responses for inspection:

```typescript theme={null}
import { createLoggingInterceptor, createEventCollector } from "@faremeter/test-harness"

const events = createEventCollector()
const interceptor = createLoggingInterceptor(events.collect)

harness.addClientInterceptor(interceptor)

const fetch = harness.createFetch()
await fetch("http://test.local/resource")

console.log(events.events) // array of request/response log events
```

Or log to the console during debugging:

```typescript theme={null}
import { createConsoleLoggingInterceptor } from "@faremeter/test-harness"

harness.addClientInterceptor(createConsoleLoggingInterceptor("client"))
harness.addMiddlewareInterceptor(createConsoleLoggingInterceptor("facilitator"))
```

### Capturing requests

Inspect specific requests matching a pattern:

```typescript theme={null}
import { createCaptureInterceptor, matchFacilitatorSettle } from "@faremeter/test-harness"

const capture = createCaptureInterceptor(matchFacilitatorSettle)
harness.addMiddlewareInterceptor(capture.interceptor)

const fetch = harness.createFetch()
await fetch("http://test.local/resource")

console.log(capture.captured) // array of captured settle requests
```

### Simulating failures

Test error handling by making specific requests fail:

```typescript theme={null}
import { createFailureInterceptor, failOnce, failNTimes, matchFacilitatorSettle } from "@faremeter/test-harness"

// Fail all settle requests
harness.addMiddlewareInterceptor(
  createFailureInterceptor(matchFacilitatorSettle, new Error("network error"))
)

// Fail only the first settle request (retry succeeds)
harness.addMiddlewareInterceptor(
  failOnce(matchFacilitatorSettle, new Error("transient error"))
)

// Fail the first 3 attempts
harness.addMiddlewareInterceptor(
  failNTimes(matchFacilitatorSettle, 3, new Error("flaky"))
)
```

### Simulating latency

```typescript theme={null}
import { createDelayInterceptor, matchFacilitatorSettle } from "@faremeter/test-harness"

// Add 500ms delay to settle requests
harness.addMiddlewareInterceptor(
  createDelayInterceptor(matchFacilitatorSettle, 500)
)
```

### Composing interceptors

Combine multiple interceptors into one:

```typescript theme={null}
import { composeInterceptors } from "@faremeter/test-harness"

const combined = composeInterceptors(loggingInterceptor, delayInterceptor, captureInterceptor)
harness.addMiddlewareInterceptor(combined)
```

## Request matchers

Matchers are predicate functions that determine which requests an interceptor acts on:

| Matcher                     | Matches                        |
| --------------------------- | ------------------------------ |
| `matchAll`                  | Every request                  |
| `matchNone`                 | No requests                    |
| `matchFacilitator`          | Any facilitator endpoint       |
| `matchFacilitatorAccepts`   | `POST /accepts`                |
| `matchFacilitatorSettle`    | `POST /settle`                 |
| `matchFacilitatorVerify`    | `POST /verify`                 |
| `matchFacilitatorSupported` | `GET /supported`               |
| `matchResource`             | The protected resource request |

Combine matchers with `and()`, `or()`, and `not()`:

```typescript theme={null}
import { and, or, not, matchFacilitator, matchFacilitatorSettle } from "@faremeter/test-harness"

const matcher = and(matchFacilitator, not(matchFacilitatorSettle))
```

## Payer choosers

When multiple payment options are available, the payer chooser selects which one to use. The test harness provides choosers for testing different selection strategies:

| Chooser                    | Behavior                                         |
| -------------------------- | ------------------------------------------------ |
| `chooseFirst`              | Picks the first available option                 |
| `chooseCheapest`           | Picks the lowest amount                          |
| `chooseMostExpensive`      | Picks the highest amount                         |
| `chooseByScheme(scheme)`   | Returns a chooser that matches by payment scheme |
| `chooseByNetwork(network)` | Returns a chooser that matches by network name   |
| `chooseByAsset(asset)`     | Returns a chooser that matches by asset name     |
| `chooseByIndex(n)`         | Returns a chooser that picks by array index      |
| `chooseNone`               | Always throws — tests "no option" paths          |

Wrap choosers with additional behavior:

```typescript theme={null}
import { chooseFirst, chooseWithInspection } from "@faremeter/test-harness"

const chooser = chooseWithInspection((execers) => {
  console.log(`${execers.length} options available`)
}, chooseFirst)
```

## Test handlers

For testing specific facilitator behaviors, use the pre-built handler factories:

| Factory                                   | Behavior                                                       |
| ----------------------------------------- | -------------------------------------------------------------- |
| `createTestPaymentHandler()`              | Standard test handler (no crypto)                              |
| `createTestFacilitatorHandler({ payTo })` | Standard facilitator handler (no crypto). `payTo` is required. |
| `createWorkingHandler()`                  | Always succeeds                                                |
| `createNonMatchingHandler()`              | Returns no matching execers                                    |
| `createThrowingHandler(message)`          | Always throws with the given message                           |
| `createEmptyPayloadHandler()`             | Returns empty payload                                          |
| `createNullPayloadHandler()`              | Returns null payload                                           |

```typescript theme={null}
import { TestHarness, createThrowingHandler } from "@faremeter/test-harness"

const harness = new TestHarness({
  accepts: [accepts({ maxAmountRequired: "10000" })],
  clientHandlers: [createTestPaymentHandler()],
  facilitatorHandlers: [createThrowingHandler("simulated failure")],
})

const fetch = harness.createFetch()
const response = await fetch("http://test.local/resource")
// response will reflect the facilitator error
```

## Example: full test

```typescript theme={null}
import { describe, it, expect, beforeEach } from "vitest"
import {
  TestHarness,
  accepts,
  createTestPaymentHandler,
  createTestFacilitatorHandler,
  createCaptureInterceptor,
  matchFacilitatorSettle,
} from "@faremeter/test-harness"

describe("payment flow", () => {
  let harness: TestHarness

  beforeEach(() => {
    harness = new TestHarness({
      accepts: [accepts({ maxAmountRequired: "10000", asset: "USDC" })],
      clientHandlers: [createTestPaymentHandler()],
      facilitatorHandlers: [createTestFacilitatorHandler({ payTo: "test-receiver" })],
    })
    harness.setResourceHandler(async () =>
      new Response(JSON.stringify({ ok: true }), { status: 200 })
    )
  })

  it("completes payment and returns resource", async () => {
    const fetch = harness.createFetch()
    const response = await fetch("http://test.local/api/data")

    expect(response.status).toBe(200)
    expect(await response.json()).toEqual({ ok: true })
  })

  it("sends settle request to facilitator", async () => {
    const capture = createCaptureInterceptor(matchFacilitatorSettle)
    harness.addMiddlewareInterceptor(capture.interceptor)

    const fetch = harness.createFetch()
    await fetch("http://test.local/api/data")

    expect(capture.captured).toHaveLength(1)
  })
})
```

## Further reading

* [Test Harness API Reference](/api/test-harness.src) -- Complete API documentation.
* [Middleware Overview](/server/middleware-overview) -- How server-side middleware works.
* [Payment Handlers](/concepts/payment-handlers) -- The handler interface used by the test harness.
