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.
121 lines
4.0 KiB
Markdown
121 lines
4.0 KiB
Markdown
+++
|
|
title = "Implement test server harness — spawn quotesdb-api with temp SQLite DB, return base URL"
|
|
priority = 8
|
|
status = "done"
|
|
ticket_type = "task"
|
|
dependencies = ["5f5ba0", "2ab7a8", "fba598"]
|
|
+++
|
|
|
|
<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>
|