# atrg-testing
**Test utilities for at-rust-go: mock clients, fake Jetstream, and in-memory test harness.**
Part of [at-rust-go (atrg)](https://github.com/tellmeY18/at-rust-go) — a batteries-included AT Protocol backend framework for Rust.
## What this crate provides
- **`test_state()`** — builds an `AppState` backed by in-memory SQLite with migrations pre-applied and no outbound network calls.
- **`test_app()`** — returns a ready-to-use `(AtrgApp, AppState)` pair for `tower::ServiceExt::oneshot` testing.
- **`seed_session()`** — inserts a fake authenticated session into the test database so handlers that require `AuthUser` / `RequireAuth` work without an OAuth round-trip.
- **`MockAtprotoClient`** — programmable stub for AT Protocol XRPC calls (`get_record`, `put_record`, etc.). Records every call for assertions.
- **`FakeJetstream`** — feeds deterministic `JetstreamEvent`s into your `on_event` handler without touching the network.
## Usage
Add `atrg-testing` as a dev dependency:
```toml
[dev-dependencies]
atrg-testing = { version = "0.1" }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
tower = { version = "0.5", features = ["util"] }
http-body-util = "0.1"
```
### Testing a JSON handler
```rust
use axum::{routing::get, Json, Router};
use atrg_core::AppState;
use atrg_testing::test_state;
use tower::ServiceExt;
async fn hello() -> Json<serde_json::Value> {
Json(serde_json::json!({ "status": "ok" }))
}
#[tokio::test]
async fn test_hello_handler() {
let state = test_state().await;
let app = Router::new()
.route("/", get(hello))
.with_state(state);
let response = app
.oneshot(
axum::http::Request::builder()
.uri("/")
.body(axum::body::Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), 200);
}
```
### Testing an authenticated handler
```rust
use atrg_testing::{test_state, seed_session};
#[tokio::test]
async fn test_authenticated_route() {
let state = test_state().await;
// Seed a session so RequireAuth succeeds
let session_id = seed_session(
&state.db,
"did:plc:testuser",
"test.handle",
)
.await;
// Use session_id as a Bearer token or cookie in your request
}
```
### Using FakeJetstream
```rust
use atrg_testing::FakeJetstream;
#[tokio::test]
async fn test_event_handler() {
let state = test_state().await;
let mut fake = FakeJetstream::new();
fake.push_commit("did:plc:abc", "app.bsky.feed.post", "rkey123", serde_json::json!({
"text": "hello from test",
"createdAt": "2025-01-01T00:00:00Z",
}));
// Feed events into your handler
for event in fake.drain() {
my_app::handle_event(event, state.clone()).await.unwrap();
}
}
```
## Design philosophy
- **Zero network access.** Every component is backed by in-memory stores and stubs.
- **Deterministic.** No randomness, no real clocks unless you opt in.
- **Fast.** In-memory SQLite + `oneshot` means sub-millisecond test execution.
## Related crates
| `atrg-core` | `AppState` and app builder under test |
| `atrg-auth` | Session types used by `seed_session` |
| `atrg-db` | In-memory SQLite pool and migrations |
| `atrg-stream` | Real Jetstream consumer (replaced by `FakeJetstream` in tests) |
## License
LGPL-3.0-only — see [LICENSE](https://github.com/tellmeY18/at-rust-go/blob/main/LICENSE).