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-jpu5--triage-integ...

77 lines
3.7 KiB
Markdown

---
# quotesdb-jpu5
title: '[TRIAGE] Integration test isolation strategy — per-test temp DB vs shared DB with transaction rollback?'
status: completed
type: task
priority: critical
created_at: 2026-03-10T23:32:11Z
updated_at: 2026-03-10T23:32:11Z
---
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
Integration test isolation strategy: should each test get its own temporary database (per-test DB creation/deletion) or should tests share a database and use transaction rollback for cleanup?
</question>
<options>
1. **Per-test temp DB** — each test creates a fresh SQLite file (or in-memory DB) and drops it on cleanup. Maximum isolation, slower due to migration overhead per test.
2. **Shared DB with transaction rollback** — all tests share one DB, each test wraps its operations in a transaction that is rolled back at the end. Faster, but requires the test harness to manage transactions.
3. **Per-test in-memory SQLite** — SQLite `:memory:` database per test. Fast (no file I/O) and fully isolated. May require `--test-threads=1` if the server shares state.
</options>
<decision>
**Per-test temp SQLite file** (Option 1 variant), using the `tempfile` crate for RAII cleanup.
Rationale:
- **Transaction rollback (Option 2) is not viable** for HTTP integration tests. The test harness spawns a real Axum server as a tokio task. That server manages its own SQLx connection pool and commits transactions independently. The test client cannot intercept or roll back those server-side transactions.
- **In-memory SQLite (Option 3) conflicts with SQLx connection pools.** Each connection in a SQLx pool that opens `sqlite::memory:` gets its own isolated empty database. The test server and the test client would be talking to different databases. Named shared-cache URIs (`file:test_N?mode=memory&cache=shared`) work around this but are less-known, have edge cases in SQLx, and offer no meaningful speed advantage over a temp file on a tmpfs.
- **Per-test temp file (chosen)** works correctly with SQLx pools because all pool connections share the same file path. Migration runs once per test (≈ milliseconds for 2 tables). `TempDir` from the `tempfile` crate provides RAII cleanup — the file is deleted when the `TestContext` is dropped. Tests run in parallel safely (each has a unique path).
Implementation in test harness (`tests/helpers.rs`):
```rust
pub struct TestContext {
_db_dir: TempDir, // keeps temp file alive; deleted on drop
pub base_url: String,
_shutdown: tokio::task::JoinHandle<()>,
}
pub async fn spawn_test_server() -> TestContext {
let db_dir = TempDir::new().unwrap();
let db_path = db_dir.path().join("test.sqlite");
let pool = SqlitePool::connect(&format!("sqlite:{}", db_path.display()))
.await
.unwrap();
sqlx::migrate!("./migrations").run(&pool).await.unwrap();
let app = build_router(Arc::new(NativeRepository::new(pool)));
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let port = listener.local_addr().unwrap().port();
let handle = tokio::spawn(axum::serve(listener, app).into_future());
TestContext {
_db_dir: db_dir,
base_url: format!("http://127.0.0.1:{port}"),
_shutdown: handle,
}
}
```
Dev-dependency added: `tempfile = "3"` (tracked in ticket 5f5ba0).
</decision>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Update ticket 9b581f (test harness) with the chosen isolation strategy.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — integration-test-isolation-per-test-tempfile-sqlite`
</commit>