# Rust testing

Test your integration without API calls using the [Rust SDK](/docs/sdk/rust). The mock is behind the `testing` feature, so enable it for tests only:

```toml
[dev-dependencies]
calmlens = { version = "0.1", features = ["testing"] }
```

## Mock client

`MockClient` answers requests from a queue and records them. It runs the real client's validation, request encoding, retries and error handling, and creates no HTTP client, so you can use it inside any async runtime.

```rust
use calmlens::{models::*, testing::MockClient};

#[tokio::test]
async fn reports_timeouts() -> calmlens::Result<()> {
    let mock = MockClient::new()?;
    mock.enqueue_timeout();
    let error = mock.events().list(ListEvents::default()).await.unwrap_err();
    assert!(error.is_timeout());
    let request = mock.last_request().unwrap();
    assert_eq!(request.path, "/v1/projects/project-1/events");
    Ok(())
}
```

Queue responses with:

| Method | Queues |
| --- | --- |
| `enqueue(status, model)` | A JSON response from a model or `serde_json::Value` |
| `enqueue_api_error(code, message)` | The API's error for `code`, with the HTTP status the API uses for it |
| `enqueue_response(MockResponse)` | A response with headers, such as a CDN response with `content-type` or a `429` with `retry-after` |
| `enqueue_raw(status, bytes)` | A raw body |
| `enqueue_timeout()`, `enqueue_transport_error(message)`, `enqueue_error(error)` | A failure |

`requests()` and `last_request()` return the captured requests with their `method`, `url`, `path`, decoded `query` (`query_param(name)`), `headers` (`header(name)`), `body` and `json()`. Streamed uploads are captured too. An empty queue fails with `sdk/transport-error` without making network calls. Mocks don't classify content or keep backend state.

`MockClient::new()` turns retries off, so each call uses one queued response. To test retries, build the mock like a real client: `MockClient::from_builder(Client::builder(key, project).max_retries(2))`.

## Code that owns its client

When the code under test takes a `Client`, pass it `mock.client()`. The returned client answers from the same queue and records its requests in the same log. With the `blocking` feature, `mock.blocking()?` returns a `blocking::Client` wired to the same queue.

```rust
use calmlens::{models::*, testing::MockClient, Client};

async fn moderate_bio(client: Client, bio: &str) -> calmlens::Result<Asset> {
    client.assets().moderate(SubmitAsset::text(bio)).await
}

#[tokio::test]
async fn approves_bios() -> calmlens::Result<()> {
    let mock = MockClient::new()?;
    mock.enqueue(200, Asset::new("asset-1", AssetStatus::Approved));
    let asset = moderate_bio(mock.client(), "Hello").await?;
    assert_eq!(asset.id, "asset-1");
    assert_eq!(mock.requests().len(), 1);
    Ok(())
}
```

## Webhook handlers

Sign test deliveries with `create_webhook_signature_header`, and build a delivery body from an event with `WebhookEventPayload::from_event`, the same conversion CalmLens applies before sending.
