An easy-to-host PDS on the ATProtocol, MacOS. Grandma-approved.

docs: add test plan for MM-72 SQLite migration infrastructure

authored by malpercio.dev and committed by

Tangled f3395226 935b35d0

+130
+130
docs/test-plans/2026-03-11-MM-72.md
··· 1 + # MM-72 Test Analysis: SQLite Migration Infrastructure (Wave 1 Schema) 2 + 3 + Generated: 2026-03-11 4 + Branch: `malpercio/mm-72-sqlite-migration-infrastructure-wave-1-schema` 5 + Base: `51782727` | Head: `970a0d5f` 6 + 7 + --- 8 + 9 + ## Coverage Validation 10 + 11 + **Automated Criteria:** 9 | **Covered:** 9 | **Missing:** 0 12 + 13 + ### Covered 14 + 15 + | Criterion | Test File | Verifies | 16 + |-----------|-----------|----------| 17 + | MM-72.AC2.1 -- Migrations are idempotent (row count) | `crates/relay/src/db/mod.rs` :: `migrations_are_idempotent` | Calls `run_migrations` twice on the same in-memory pool, then asserts `SELECT COUNT(*) FROM schema_migrations` returns exactly 1. The second call hits the `pending.is_empty()` early return, confirming no duplicate rows are inserted. | 18 + | MM-72.AC2.2 -- schema_migrations records version and timestamp | `crates/relay/src/db/mod.rs` :: `schema_migrations_records_version_and_timestamp` | After `run_migrations`, queries `SELECT version, applied_at FROM schema_migrations WHERE version = 1` and asserts `version == 1` and `applied_at` is a non-empty string. | 19 + | MM-72.AC3.1 -- Handler tests compile with db field in AppState | `crates/relay/src/app.rs` :: 5 XRPC handler tests | All five tests call `test_state().await` which constructs `AppState { config, db: SqlitePool }`. The `db` field is declared as `pub db: sqlx::SqlitePool` (line 15 of `app.rs`). If the field were missing or wrongly typed, compilation would fail. All 5 tests pass at runtime. | 20 + | MM-72.AC3.2 -- SELECT 1 succeeds on AppState pool | `crates/relay/src/app.rs` :: `appstate_db_pool_is_queryable` | Constructs `AppState` via `test_state().await`, then executes `sqlx::query("SELECT 1").execute(&state.db)` and asserts it completes without error. | 21 + | MM-72.AC4.1 -- WAL mode enabled | `crates/relay/src/db/mod.rs` :: `wal_mode_enabled_on_file_pool` | Creates a file-backed pool via `open_pool` using `tempfile::tempdir()`, queries `PRAGMA journal_mode`, and asserts the result is the string `"wal"`. Uses a real file because in-memory SQLite reports `"memory"` as journal mode. | 22 + | MM-72.AC5.1 -- Migration runner tests use in-memory SQLite | `crates/relay/src/db/mod.rs` :: 5 migration tests | All five migration-related tests (`select_one_succeeds`, `migrations_apply_on_first_run`, `migrations_are_idempotent`, `schema_migrations_records_version_and_timestamp`, `server_metadata_table_exists_and_accepts_inserts`) call `in_memory_pool()` which opens `"sqlite::memory:"`. No test in this group creates files on disk. The sole file-backed test (`wal_mode_enabled_on_file_pool`) tests the pool factory, not the migration runner, and uses `tempfile` (auto-cleaned on drop). Confirmed: no `relay.db` file exists in the workspace tree after `cargo test --workspace`. | 23 + | MM-72.AC5.2 -- cargo test passes in clean environment | Full workspace | `cargo test --workspace` exits 0 with 32 tests passing (20 common, 12 relay). No `relay.db` file created anywhere in the workspace. | 24 + | MM-72.AC6.1 -- cargo clippy passes | Full workspace | `cargo clippy --workspace -- -D warnings` exits 0 with no diagnostic output. | 25 + | MM-72.AC6.2 -- cargo fmt passes | Full workspace | `cargo fmt --all --check` exits 0 with no diff output. | 26 + 27 + ### Supplementary Tests (no direct AC, supporting coverage) 28 + 29 + | Test | File | Verifies | Supports ACs | 30 + |------|------|----------|--------------| 31 + | `select_one_succeeds` | `crates/relay/src/db/mod.rs` | `SELECT 1` returns 1 on in-memory pool -- pool is functional | AC3.2, AC5.1 | 32 + | `migrations_apply_on_first_run` | `crates/relay/src/db/mod.rs` | After first `run_migrations`, `schema_migrations` has exactly 1 row | AC2.1, AC2.2 | 33 + | `server_metadata_table_exists_and_accepts_inserts` | `crates/relay/src/db/mod.rs` | After migrations, `INSERT INTO server_metadata` succeeds and value is retrievable via `SELECT` | AC1.3 (in-memory equivalent) | 34 + 35 + **Result: PASS** 36 + 37 + All 9 automatable acceptance criteria have tests that verify the specified behavior. Tests were executed and confirmed passing. 38 + 39 + --- 40 + 41 + ## Human Test Plan 42 + 43 + ### Prerequisites 44 + 45 + - Development shell activated: `nix develop --impure --accept-flake-config` 46 + - Branch `malpercio/mm-72-sqlite-migration-infrastructure-wave-1-schema` checked out at commit `970a0d5f` 47 + - All automated checks passing: 48 + - `cargo test --workspace` exits 0 (32 tests, 0 failures) 49 + - `cargo clippy --workspace -- -D warnings` exits 0 50 + - `cargo fmt --all --check` exits 0 51 + - `sqlite3` CLI available (provided by devenv) 52 + 53 + ### Phase 1: Database File Creation (AC1.1, AC1.2, AC1.3) 54 + 55 + | Step | Action | Expected | 56 + |------|--------|----------| 57 + | 1 | Run `mkdir -p /tmp/relay-test` | Directory `/tmp/relay-test` exists. | 58 + | 2 | Create config: `printf '[relay]\nbind_address = "127.0.0.1"\nport = 3000\ndata_dir = "/tmp/relay-test"\npublic_url = "https://test.example.com"\n' > /tmp/relay-test/relay.toml` | File `/tmp/relay-test/relay.toml` exists with valid TOML. | 59 + | 3 | Run `cargo run --bin relay -- --config /tmp/relay-test/relay.toml` | Relay starts. Logs include `relay starting` with `bind_address=127.0.0.1`, `port=3000`, `public_url=https://test.example.com`, followed by `listening address="127.0.0.1:3000"`. | 60 + | 4 | Press Ctrl+C after "listening" appears. | Relay logs `relay shut down` and exits cleanly (exit code 0). | 61 + | 5 | Run `ls -la /tmp/relay-test/relay.db` | File exists with non-zero size. | 62 + | 6 | Run `sqlite3 /tmp/relay-test/relay.db ".tables"` | Output includes both `schema_migrations` and `server_metadata`. | 63 + | 7 | Run `sqlite3 /tmp/relay-test/relay.db ".schema schema_migrations"` | Output: `CREATE TABLE schema_migrations (version INTEGER PRIMARY KEY, applied_at TEXT NOT NULL) WITHOUT ROWID;` | 64 + | 8 | Run `sqlite3 /tmp/relay-test/relay.db ".schema server_metadata"` | Output: `CREATE TABLE server_metadata (key TEXT NOT NULL, value TEXT NOT NULL, PRIMARY KEY (key)) WITHOUT ROWID;` | 65 + | 9 | Run `sqlite3 /tmp/relay-test/relay.db "SELECT version, applied_at FROM schema_migrations;"` | One row: `1|<datetime>` where `<datetime>` is a non-empty string close to current time (e.g. `2026-03-11 ...`). | 66 + 67 + ### Phase 2: Migration Idempotency Across Restarts (AC2.1 runtime) 68 + 69 + | Step | Action | Expected | 70 + |------|--------|----------| 71 + | 1 | Confirm Phase 1 completed. Run `sqlite3 /tmp/relay-test/relay.db "SELECT COUNT(*) FROM schema_migrations;"` | Output: `1` | 72 + | 2 | Run `cargo run --bin relay -- --config /tmp/relay-test/relay.toml` a second time. Wait for "listening" to appear. | Relay starts without errors. No migration-related error messages in logs. | 73 + | 3 | Press Ctrl+C. | Relay shuts down cleanly. | 74 + | 4 | Run `sqlite3 /tmp/relay-test/relay.db "SELECT COUNT(*) FROM schema_migrations;"` | Output: still `1` (not `2`). The second startup did not re-apply the migration. | 75 + | 5 | Run `sqlite3 /tmp/relay-test/relay.db "SELECT version, applied_at FROM schema_migrations;"` | Same row as Phase 1 Step 9. The `applied_at` timestamp has not changed. | 76 + 77 + ### Phase 3: WAL Mode on Production Database (AC4.1 supplementary) 78 + 79 + | Step | Action | Expected | 80 + |------|--------|----------| 81 + | 1 | Run `sqlite3 /tmp/relay-test/relay.db "PRAGMA journal_mode;"` | Output: `wal` | 82 + | 2 | Run `ls /tmp/relay-test/relay.db-wal` | WAL file exists (may be zero-length if checkpointed, but the file itself should be present). | 83 + 84 + ### End-to-End: Cold Start to Verified Schema 85 + 86 + **Purpose:** Validates the complete startup path from a fresh filesystem through pool creation, migration execution, and schema verification in a single pass. Exercises the `main.rs` URL formatting logic (lines 53-59), the `open_pool` -> `run_migrations` -> `AppState` construction pipeline, and confirms the running server with database-backed state can serve requests. 87 + 88 + | Step | Action | Expected | 89 + |------|--------|----------| 90 + | 1 | Run `rm -rf /tmp/relay-e2e && mkdir -p /tmp/relay-e2e` | Clean directory created. | 91 + | 2 | Run `printf '[relay]\nbind_address = "127.0.0.1"\nport = 3001\ndata_dir = "/tmp/relay-e2e"\npublic_url = "https://e2e.example.com"\n' > /tmp/relay-e2e/relay.toml` | Config file created. | 92 + | 3 | Run `cargo run --bin relay -- --config /tmp/relay-e2e/relay.toml` and wait for "listening". | Relay starts successfully with no errors. | 93 + | 4 | In a separate terminal, run `curl -s http://127.0.0.1:3001/xrpc/com.example.test` | Returns JSON with HTTP 501: `{"error":{"code":"MethodNotImplemented","message":"XRPC method \"com.example.test\" is not implemented"}}`. This confirms the full Axum router with AppState (including db pool) is functional. | 94 + | 5 | Press Ctrl+C in the relay terminal. | Relay shuts down cleanly. | 95 + | 6 | Run `sqlite3 /tmp/relay-e2e/relay.db "SELECT COUNT(*) FROM schema_migrations; SELECT COUNT(*) FROM server_metadata;"` | Output: `1` then `0` (one migration applied, server_metadata exists but is empty). | 96 + | 7 | Run `sqlite3 /tmp/relay-e2e/relay.db "PRAGMA journal_mode;"` | Output: `wal`. | 97 + | 8 | Clean up: `rm -rf /tmp/relay-e2e` | Directory removed. | 98 + 99 + ### Human Verification Required 100 + 101 + | Criterion | Why Manual | Steps | 102 + |-----------|-----------|-------| 103 + | MM-72.AC1.1 -- relay.db created on first start | Requires running the actual binary with a real config file and filesystem. The binary reads `relay.toml`, formats the database URL, calls `open_pool` with a real path, and binds a TCP listener -- none of which `cargo test` exercises. | Phase 1, Steps 3-5 | 104 + | MM-72.AC1.2 -- schema_migrations table exists in produced database | Requires the real binary to have produced the on-disk database file. The in-memory test proves the runner logic, but this criterion requires verifying the on-disk artifact. | Phase 1, Steps 6-7 | 105 + | MM-72.AC1.3 -- server_metadata table exists in produced database | Same rationale as AC1.2. The in-memory test (`server_metadata_table_exists_and_accepts_inserts`) proves the migration SQL is correct, but AC1.3 requires verifying the on-disk artifact from a real binary run. | Phase 1, Steps 6, 8 | 106 + | MM-72.AC2.1 -- Idempotent across process restarts | The unit test calls `run_migrations` twice within one process. Manual verification exercises idempotency across separate binary invocations, testing the full startup path including file-backed pool reopening and `schema_migrations` persistence on disk. | Phase 2, Steps 1-5 | 107 + | MM-72.AC4.1 -- WAL mode on production database | The automated test (`wal_mode_enabled_on_file_pool`) uses `tempfile`. This supplementary step confirms the production database file produced by the actual binary is also in WAL mode. | Phase 3, Steps 1-2 | 108 + 109 + ### Cleanup 110 + 111 + | Step | Action | 112 + |------|--------| 113 + | 1 | Run `rm -rf /tmp/relay-test /tmp/relay-e2e` | 114 + 115 + ### Traceability 116 + 117 + | Acceptance Criterion | Automated Test | Manual Step | 118 + |----------------------|----------------|-------------| 119 + | MM-72.AC1.1 (relay.db created) | -- | Phase 1, Steps 3-5 | 120 + | MM-72.AC1.2 (schema_migrations exists) | `server_metadata_table_exists_and_accepts_inserts` (indirect) | Phase 1, Steps 6-7 | 121 + | MM-72.AC1.3 (server_metadata exists) | `server_metadata_table_exists_and_accepts_inserts` | Phase 1, Steps 6, 8 | 122 + | MM-72.AC2.1 (idempotent) | `migrations_are_idempotent` | Phase 2, Steps 1-5 | 123 + | MM-72.AC2.2 (version + timestamp) | `schema_migrations_records_version_and_timestamp` | -- | 124 + | MM-72.AC3.1 (AppState compiles) | 5 XRPC handler tests via `test_state().await` | -- | 125 + | MM-72.AC3.2 (SELECT 1 on state.db) | `appstate_db_pool_is_queryable` | -- | 126 + | MM-72.AC4.1 (WAL mode) | `wal_mode_enabled_on_file_pool` | Phase 3, Steps 1-2 (supplementary) | 127 + | MM-72.AC5.1 (in-memory tests) | Code review + 5 migration tests use `in_memory_pool()` | -- | 128 + | MM-72.AC5.2 (clean cargo test) | `cargo test --workspace` exits 0, no `relay.db` | -- | 129 + | MM-72.AC6.1 (clippy) | `cargo clippy --workspace -- -D warnings` exits 0 | -- | 130 + | MM-72.AC6.2 (fmt) | `cargo fmt --all --check` exits 0 | -- |