ullun-api 0.4.11

Express.js-inspired web framework for Rust - simple, fast, batteries-included
Documentation
# Testing Guide

## Running Tests

### Unit Tests
```bash
cargo test --lib
```

### Integration Tests
```bash
cargo test --test integration_test
```

### All Tests
```bash
cargo test --all
cargo test --all-features    # jwt, compression and websocket tests
```

### Doc Tests
```bash
cargo test --doc
```

### With Coverage
```bash
cargo install cargo-tarpaulin
cargo tarpaulin --out Html --output-dir coverage
```

## Benchmarks

```bash
cargo bench                              # everything
cargo bench --bench router_benchmark     # micro-benchmarks
cargo bench --bench http_benchmark       # end-to-end over TCP
```

View results in `target/criterion/report/index.html`

## Test Structure

```
tests/
├── integration_test.rs   # Unit-level integration tests
├── e2e_test.rs           # End-to-end HTTP tests (fixed ports)
├── middleware_test.rs    # Middleware order, groups, CORS preflight, 404 wrapping, rate limiter,
│                         #   extensions; JWT and gzip middleware (features jwt, compression)
├── static_files_test.rs  # Static serving, index files, traversal (ephemeral port)
├── common/mod.rs         # spawn(app) + fluent HTTP client for tests
├── state_test.rs         # Shared application state
├── example_orders.rs     # Checkout API (CORS, idempotency, limits)
├── example_shortener.rs  # Real-world example apps, run over real HTTP
├── example_pastebin.rs   #   (the app modules are included with #[path])
├── example_taskboard.rs  #   (feature jwt)
├── tls_test.rs           # HTTPS, reload, mutual TLS, redirect/HSTS (feature tls; rcgen certificates)
├── example_secure_api.rs #   (mutual TLS, fingerprint allow-list)
├── example_webhooks.rs   #   (real receivers: retries, signatures, dead letters, restart)
├── example_chat.rs       #   (real WebSocket clients, features websocket + jwt)
├── example_gateway.rs    #   (real upstream servers, failover, streaming relay)
├── observability_test.rs # Request ids, access log, metrics
├── example_blog.rs       #   (browser-like: cookie jar, CSRF tokens scraped from HTML)
├── health_test.rs        # Liveness/readiness probes, shutdown drain phase
├── testing_test.rs       # The in-process TestClient
├── http2_test.rs         # HTTP/2 over TLS: ALPN, multiplexing, streams, shutdown
├── client_test.rs        # Outbound HTTP client: pooling, retries, redirects, SSRF guard, HTTPS
├── cache_test.rs         # Response cache: hits, coalescing, stale-while-revalidate, bypass rules
├── cors_test.rs          # CORS (origins, preflight, Vary, credentials) and security headers / CSP nonce
├── idempotency_test.rs   # Idempotency keys: replay, 409, 422, scoping, 5xx release
├── limits_test.rs        # Timeouts, load shedding, connection caps, slowloris
├── session_test.rs       # Sessions (timeouts, fixation, isolation) and CSRF
├── example_drive.rs      #   (multipart uploads, real files on disk)
├── example_statuspage.rs #   (real local targets, SSE, scheduler, shutdown)
├── routing_test.rs       # HEAD, OPTIONS, 405
├── files_test.rs         # serve_file, ranges, conditional requests, static handler hardening
├── body_limit_test.rs    # per-route body limits
├── conditional_test.rs   # ETag middleware and If-Match preconditions
├── streaming_test.rs     # Streaming bodies, SSE, disconnect detection
├── shutdown_test.rs      # Background tasks and graceful shutdown
├── openapi_test.rs       # Generated OpenAPI document and Swagger page
├── websocket_test.rs     # Handshake, echo, origin check, size limit (feature websocket)
└── pentest.rs            # Security regression tests

benches/
├── router_benchmark.rs   # Micro-benchmarks: routing, request/response, middleware chain
└── http_benchmark.rs     # End-to-end HTTP round trips over loopback
```

## Writing Tests

### Testing Handlers

```rust
use ullun::prelude::*;

#[tokio::test]
async fn test_handler() {
    let req = Request::new(
        http::Method::GET,
        "/test".parse().unwrap(),
        http::HeaderMap::new(),
        bytes::Bytes::new(),
    );
    
    let response = my_handler(req).await.unwrap();
    assert_eq!(response.status, http::StatusCode::OK);
}
```

### Testing Error Handling

```rust
#[test]
fn test_error_mapping() {
    let err = Error::bad_request("test");
    assert_eq!(err.status_code(), 400);
    assert_eq!(err.message(), "test");
}
```

### Testing an app in-process (`ullun::testing::TestClient`)

Applications can be tested without opening a port. `TestClient` runs requests
through routing, middleware and handlers exactly like a server, so tests are fast
and cannot collide on ports:

```rust
use ullun::testing::TestClient;

#[tokio::test]
async fn creates_items() {
    let client = TestClient::new(my_app()).with_cookies(); // jar keeps sessions across calls
    let res = client.post("/items").bearer("token").json(&json!({ "name": "x" })).send().await;
    assert_eq!(res.status(), 201);
    assert_eq!(res.json::<Item>().name, "x");
}
```

It supports JSON/form/text bodies, headers, a cookie jar, a fake peer address
(`remote_addr`), the TLS flag (`https()`), HEAD/OPTIONS/405 handling, body limits
and streamed responses. Background tasks are not started and WebSocket upgrades
need a real server (`App::serve` on port 0, see below).

### Testing example apps

The services in `examples/<name>/` keep their logic in `app.rs` (`pub fn build(config) -> App`) and only wire configuration in `main.rs`. Tests include the module and run it with a fake clock:

```rust
mod common;
#[allow(dead_code)]
#[path = "../examples/shortener/app.rs"]
mod shortener;

let server = common::spawn(shortener::build(config)).await;
let res = server.post("/api/links").header("x-api-key", "k").json(&body).send().await;
assert_eq!(res.status, 201);
```

### Testing against a real server

Bind to port `0` and use `App::serve` so tests never collide on ports:

```rust
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(app.serve(listener));
// connect a hyper client to `addr`
```

See `tests/middleware_test.rs` for a complete helper.

### Testing Middleware

```rust
#[tokio::test]
async fn test_middleware() {
    async fn test_mw(req: Request, next: Next) -> Result<Response> {
        // Before
        let mut response = next.run(req).await?;
        // After
        response = response.with_header("X-Test", "value");
        Ok(response)
    }
    
    // Test middleware logic
}
```

## CI/CD Testing

Tests run automatically on:
- Pull requests to `main`
- Pushes to `main` (a release only follows when lint, tests and build all passed)
- Linux, macOS and Windows on stable Rust

See `.github/workflows/ci.yml`

## Performance Testing

### Load Testing with `wrk`

```bash
# Install wrk
brew install wrk  # macOS
sudo apt install wrk  # Linux

# Start server
cargo run --example hello_world --release

# Run load test
wrk -t12 -c400 -d30s http://localhost:3000/
```

Measured numbers and how to reproduce them: [PERFORMANCE.md](PERFORMANCE.md).

### Benchmarking Tips

1. **Always use `--release` builds**
2. **Warm up before measuring** (first few requests compile code)
3. **Test on production-like hardware**
4. **Monitor CPU and memory** during tests

## Test Coverage Goals

- **Unit tests:** >80% coverage
- **Integration tests:** All public APIs
- **Edge cases:** Error paths, invalid input
- **Security:** XSS, injection attempts

## Continuous Benchmarking

`cargo bench --no-run` runs in CI to keep the benchmarks compiling. Full runs are noisy on shared runners; run them locally and compare with `cargo bench -- --save-baseline before` / `--baseline before`.