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...

127 lines
4.1 KiB
Markdown

---
# quotesdb-gu9j
title: Implement test server harness — spawn quotesdb-api with temp SQLite DB, return base URL
status: completed
type: task
priority: critical
created_at: 2026-03-10T23:32:09Z
updated_at: 2026-03-10T23:32:16Z
blocked_by:
- quotesdb-bl2g
- quotesdb-aa9s
- quotesdb-jpu5
---
<context>
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`
</context>
<goal>
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
</goal>
<implementation>
```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);
}
```
</implementation>
<constraints>
- `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).
</constraints>
<dependencies-needed>
In `[dev-dependencies]` (ticket 5f5ba0):
- `tempfile = "3"`
- `reqwest = { version = "0.12", features = ["json"] }`
- `tokio = { version = "1", features = ["full"] }`
- `serde_json = "1"`
</dependencies-needed>
<skills>
Use `superpowers:test-driven-development` — the harness is itself tested by running `cargo test`.
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`test(quotesdb): implement test server harness with per-test temp SQLite DB`
</commit>