test(db): add Room migration tests with MigrationTestHelper #107

Merged
JMR-dev merged 2 commits from feat-room-migration-tests into main 2026-07-02 04:28:59 +00:00
JMR-dev commented 2026-07-02 03:05:30 +00:00 (Migrated from github.com)

Closes #63

The schemas in app/schemas were exported but nothing replayed the migrations against them, and DatabaseModule deliberately has no destructive fallback — so a drifted migration would crash upgrading users at first DB open without CI noticing. This PR makes that class of bug fail in the E2E matrix instead.

Mapping to the issue

  1. androidx.room:room-testing dependency — added through gradle/libs.versions.toml reusing the existing room = "2.8.4" version, wired as androidTestImplementation. The exported schemas are shipped to the test APK via sourceSets.getByName("androidTest").assets.srcDir("$projectDir/schemas") (required for MigrationTestHelper to load them).
  2. 11 → 12 (migrate11To12_defaultsExistingFoldersToNotSpecialUse) — creates the DB at v11, inserts the account, two folder rows (INBOX + [Gmail]/Sent Mail) and a message, runs runMigrationsAndValidate(12, MIGRATION_11_12), and asserts folders.specialUse defaults to 0 on both pre-existing rows with all other data intact.
  3. Full-chain replay (migratingFromV7ReplaysEveryMigrationAndPreservesData) — creates the DB at v7 (oldest exported schema; 7–12 exist in app/schemas), seeds every v7 table like a used install, then replays the chain one step at a time so runMigrationsAndValidate diffs each intermediate schema against its exported JSON and a failure names the exact drifted step. It also writes rows mid-chain (a folder at v8, a signature at v9) and asserts at the end that: all v7 rows (including credentials) survive, 7→8 filed messages under INBOX, 8→9 backfilled default account_settings, 9→10/10→11 column defaults hold, 10→11 converted the v9 signature into the signatures table (Cheers,<br>Ada, default), and 11→12 stamped the v8 folder specialUse = 0.
  4. Future migrations are covered automatically — the migration list is discovered from Migrations.kt's file facade and the target version from the newest schema JSON in the test assets, so a new migration only needs its schema commit. A third test (migrationsFormOneUnbrokenChainUpToTheLatestExportedSchema) fails fast with a readable message if a version step has no migration or a migration's target schema was never committed.
  5. Stale comments updated — LibreMailDatabaseTest's "future migrations can be tested" KDoc and the schema-export comment in app/build.gradle.kts now point at MigrationTest.

Note for review

  • The kotlinx-serialization 1.8.1 BOM pin is required: AGP 9's consistent resolution shares androidx.savedstate's transitive 1.7.3 with the androidTest classpath, and Room 2.8's schema-bundle serializers throw AbstractMethodError below 1.8.0 when MigrationTestHelper parses the schema JSON. The pin (and the room-testing/assets wiring) is byte-identical to the hunks already on feat-fetch-all-retention (#46), so the two PRs merge cleanly in either order; once both land, the chain test automatically replays #46's 12→13 migration too.
  • Verified locally: fast gate (assembleDebug, testDebugUnitTest, lintDebug, ktlintCheck, detekt) plus :app:compileDebugAndroidTestKotlin; the migration tests themselves run in the E2E matrix (also attempted on a local GMD api29 emulator).
  • Migrations 1→2 … 6→7 predate schema export and remain a pre-existing gap (only replayable from v7 on), as noted in the issue.

🤖 Generated with Claude Code

Closes #63 The schemas in `app/schemas` were exported but nothing replayed the migrations against them, and `DatabaseModule` deliberately has no destructive fallback — so a drifted migration would crash upgrading users at first DB open without CI noticing. This PR makes that class of bug fail in the E2E matrix instead. ## Mapping to the issue 1. **`androidx.room:room-testing` dependency** — added through `gradle/libs.versions.toml` reusing the existing `room = "2.8.4"` version, wired as `androidTestImplementation`. The exported schemas are shipped to the test APK via `sourceSets.getByName("androidTest").assets.srcDir("$projectDir/schemas")` (required for `MigrationTestHelper` to load them). 2. **11 → 12** (`migrate11To12_defaultsExistingFoldersToNotSpecialUse`) — creates the DB at v11, inserts the account, two folder rows (`INBOX` + `[Gmail]/Sent Mail`) and a message, runs `runMigrationsAndValidate(12, MIGRATION_11_12)`, and asserts `folders.specialUse` defaults to 0 on both pre-existing rows with all other data intact. 3. **Full-chain replay** (`migratingFromV7ReplaysEveryMigrationAndPreservesData`) — creates the DB at v7 (oldest exported schema; 7–12 exist in `app/schemas`), seeds every v7 table like a used install, then replays the chain **one step at a time** so `runMigrationsAndValidate` diffs each intermediate schema against its exported JSON and a failure names the exact drifted step. It also writes rows mid-chain (a folder at v8, a signature at v9) and asserts at the end that: all v7 rows (including `credentials`) survive, 7→8 filed messages under `INBOX`, 8→9 backfilled default `account_settings`, 9→10/10→11 column defaults hold, 10→11 converted the v9 signature into the `signatures` table (`Cheers,<br>Ada`, default), and 11→12 stamped the v8 folder `specialUse = 0`. 4. **Future migrations are covered automatically** — the migration list is discovered from `Migrations.kt`'s file facade and the target version from the newest schema JSON in the test assets, so a new migration only needs its schema commit. A third test (`migrationsFormOneUnbrokenChainUpToTheLatestExportedSchema`) fails fast with a readable message if a version step has no migration or a migration's target schema was never committed. 5. **Stale comments updated** — `LibreMailDatabaseTest`'s "future migrations can be tested" KDoc and the schema-export comment in `app/build.gradle.kts` now point at `MigrationTest`. ## Note for review - The kotlinx-serialization 1.8.1 BOM pin is required: AGP 9's consistent resolution shares androidx.savedstate's transitive 1.7.3 with the androidTest classpath, and Room 2.8's schema-bundle serializers throw `AbstractMethodError` below 1.8.0 when `MigrationTestHelper` parses the schema JSON. The pin (and the room-testing/assets wiring) is **byte-identical to the hunks already on `feat-fetch-all-retention` (#46)**, so the two PRs merge cleanly in either order; once both land, the chain test automatically replays #46's 12→13 migration too. - Verified locally: fast gate (assembleDebug, testDebugUnitTest, lintDebug, ktlintCheck, detekt) plus `:app:compileDebugAndroidTestKotlin`; the migration tests themselves run in the E2E matrix (also attempted on a local GMD api29 emulator). - Migrations 1→2 … 6→7 predate schema export and remain a pre-existing gap (only replayable from v7 on), as noted in the issue. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.