You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
vibed/quotesdb/.beans/quotesdb-gu9j--implement-te...

4.1 KiB

title status type priority created_at updated_at blocked_by
Implement test server harness — spawn quotesdb-api with temp SQLite DB, return base URL completed task critical 2026-03-10T23:32:09Z 2026-03-10T23:32:16Z
quotesdb-bl2g
quotesdb-aa9s
quotesdb-jpu5
Integration tests live in `tests/` and exercise the API binary against a temporary SQLite database. They run with `cargo test` and must not require a running Cloudflare environment.

Architecture decided in triage:

  • 2ab7a8: Server is spawned as a tokio task using the native Axum path (cfg-split, no workers-rs on host)
  • fba598: Isolation strategy is per-test temp SQLite file via tempfile crate (transaction rollback cannot intercept server-side pool commits; in-memory SQLite is incompatible with multi-connection SQLx pools)
  • 0d84fa: HTTP client for tests is reqwest with tokio::test
Implement `tests/helpers.rs` providing a `spawn_test_server()` async function that: 1. Creates a temporary SQLite file via `tempfile::TempDir` 2. Opens a `SqlitePool` connected to that file 3. Runs migrations via `sqlx::migrate!()` 4. Builds the Axum router via `build_router(repo)` (same router used by the API binary) 5. Binds to a random port with `TcpListener::bind("127.0.0.1:0")` 6. Spawns the server with `tokio::spawn(axum::serve(...))` 7. Returns a `TestContext` that holds the `TempDir` (RAII cleanup), base URL, and task handle ```rust // tests/helpers.rs use std::sync::Arc; use tempfile::TempDir; use tokio::net::TcpListener; use sqlx::SqlitePool;

pub struct TestContext { _db_dir: TempDir, // deleted on drop pub base_url: String, _server: tokio::task::JoinHandle<()>, }

pub async fn spawn_test_server() -> TestContext { let db_dir = TempDir::new().expect("temp dir"); let db_path = db_dir.path().join("test.sqlite"); let db_url = format!("sqlite:{}?mode=rwc", db_path.display());

let pool = SqlitePool::connect(&db_url).await.expect("pool");
sqlx::migrate!("./migrations").run(&pool).await.expect("migrations");

let repo = Arc::new(NativeRepository::new(pool));
let app = build_router(repo);

let listener = TcpListener::bind("127.0.0.1:0").await.expect("bind");
let port = listener.local_addr().unwrap().port();

let server = tokio::spawn(async move {
    axum::serve(listener, app).await.unwrap();
});

TestContext {
    _db_dir: db_dir,
    base_url: format!("http://127.0.0.1:{port}"),
    _server: server,
}

}


Usage in a test:
```rust
#[tokio::test]
async fn test_create_quote() {
    let ctx = spawn_test_server().await;
    let client = reqwest::Client::new();
    let res = client
        .put(format!("{}/api/quotes", ctx.base_url))
        .json(&serde_json::json!({"text": "hello", "author": "world"}))
        .send()
        .await
        .unwrap();
    assert_eq!(res.status(), 201);
}
- `build_router` and `NativeRepository` must be pub-accessible from the `quotesdb` crate (may require re-exports in `src/lib.rs`). - `sqlx::migrate!()` macro path is relative to the crate root — migrations must be in `migrations/` at the crate root. - Each test gets a unique `TempDir`, so parallel test execution (`cargo test`) is safe. - Do not set `--test-threads=1`; parallel execution must work. - The `_server` handle is intentionally leaked (tokio runtime drops it when the test ends). In `[dev-dependencies]` (ticket 5f5ba0): - `tempfile = "3"` - `reqwest = { version = "0.12", features = ["json"] }` - `tokio = { version = "1", features = ["full"] }` - `serde_json = "1"` Use `superpowers:test-driven-development` — the harness is itself tested by running `cargo test`. Use `superpowers:verification-before-completion` before closing. Run in order from the `quotesdb/` directory:
cargo fmt
cargo check
cargo clippy
cargo test
`test(quotesdb): implement test server harness with per-test temp SQLite DB`