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

3.7 KiB

title status type priority created_at updated_at
[TRIAGE] Integration test isolation strategy — per-test temp DB vs shared DB with transaction rollback? completed task critical 2026-03-10T23:32:11Z 2026-03-10T23:32:11Z
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed. 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? 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. **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):

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

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. `chore(quotesdb): resolve triage — integration-test-isolation-per-test-tempfile-sqlite`