Source: "same-class crash-state" audit (instrumented coverage for the worker cache-lock gate).
Add an instrumented test that constructs the locked state without device auth — EncryptedCacheGuard depends only on SettingsRepository + PassphraseSession, not the real Keystore / BiometricPrompt. Set encryptCache = true + appLock = true, leave PassphraseSession locked, and drive PruneWorker / BackfillWorker via TestListenableWorkerBuilder with the real guard. Assert the worker returns retry() within a bounded time AND that no libremail.db connection is opened (e.g. no keyed open occurs / the DB file's open-connection count stays 0).
Caveat: the true "WorkManager cold-starts the process with no UI" can't be reproduced in-process; the locked-PassphraseSession seam is the faithful stand-in, and the auth-bound Keystore path stays device-only (per DatabaseKeyCipher.kt:31-32). Complements the unit suite; depends on the worker-gating fix.
**Source:** "same-class crash-state" audit (instrumented coverage for the worker cache-lock gate).
**Add an instrumented test** that constructs the locked state without device auth — `EncryptedCacheGuard` depends only on `SettingsRepository` + `PassphraseSession`, not the real Keystore / BiometricPrompt. Set `encryptCache = true` + `appLock = true`, leave `PassphraseSession` locked, and drive `PruneWorker` / `BackfillWorker` via `TestListenableWorkerBuilder` with the real guard. Assert the worker returns `retry()` within a bounded time AND that **no `libremail.db` connection is opened** (e.g. no keyed open occurs / the DB file's open-connection count stays 0).
**Caveat:** the true "WorkManager cold-starts the process with no UI" can't be reproduced in-process; the locked-`PassphraseSession` seam is the faithful stand-in, and the auth-bound Keystore path stays device-only (per `DatabaseKeyCipher.kt:31-32`). Complements the unit suite; depends on the worker-gating fix.
Scope note: IdleService shares the same EncryptedCacheGuard gate but is an Android Service (not JVM-unit-testable without Robolectric), so its coverage was routed here from #225 (whose unit tests cover SyncWorker/SendWorker/PruneWorker/BackfillWorker). This instrumented ticket should therefore also drive IdleService: start it with the cache locked and assert it stopSelf()s / opens no libremail.db connection, alongside PruneWorker/BackfillWorker.
Scope note: `IdleService` shares the same `EncryptedCacheGuard` gate but is an Android `Service` (not JVM-unit-testable without Robolectric), so its coverage was routed here from #225 (whose unit tests cover SyncWorker/SendWorker/PruneWorker/BackfillWorker). This instrumented ticket should therefore also drive **IdleService**: start it with the cache locked and assert it `stopSelf()`s / opens no `libremail.db` connection, alongside PruneWorker/BackfillWorker.
Blocked on a test seam (loop finding). The worker-driven instrumented test needs to feed the realEncryptedCacheGuard a controlled appLock=true + encryptCache=true state, but the guard depends on SettingsRepository — a concrete class backed by the process-wide context.settingsDataStore singleton (not an interface). androidTest here has no MockK and no HiltAndroidTest harness, so there's no clean way to control that state without mutating the app's real settings file. (It also needs the androidx.work:work-testing dep + a TestListenableWorkerBuilder factory for the @AssistedInject worker.)
Two ways forward — a small infra decision:
Extract a SettingsRepository interface (concrete → SettingsRepositoryImpl) so a hand-rolled fake can feed the guard fixed settings. Cleanest for testing; touches all injection sites.
Adopt HiltAndroidTest with a test module overriding SettingsRepository/PassphraseSession — introduces Hilt instrumented-test infra the repo doesn't have yet.
The guard→worker wiring is already unit-covered by #225 (merged, mocked guard); this ticket's unique value is the real-collaborator path, which needs one of the seams above.
**Blocked on a test seam (loop finding).** The worker-driven instrumented test needs to feed the *real* `EncryptedCacheGuard` a controlled `appLock=true` + `encryptCache=true` state, but the guard depends on `SettingsRepository` — a concrete class backed by the process-wide `context.settingsDataStore` singleton (not an interface). androidTest here has **no MockK and no HiltAndroidTest** harness, so there's no clean way to control that state without mutating the app's real settings file. (It also needs the `androidx.work:work-testing` dep + a `TestListenableWorkerBuilder` factory for the `@AssistedInject` worker.)
Two ways forward — a small infra decision:
- **Extract a `SettingsRepository` interface** (concrete → `SettingsRepositoryImpl`) so a hand-rolled fake can feed the guard fixed settings. Cleanest for testing; touches all injection sites.
- **Adopt `HiltAndroidTest`** with a test module overriding `SettingsRepository`/`PassphraseSession` — introduces Hilt instrumented-test infra the repo doesn't have yet.
The guard→worker wiring is already unit-covered by #225 (merged, mocked guard); this ticket's unique value is the real-collaborator path, which needs one of the seams above.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Source: "same-class crash-state" audit (instrumented coverage for the worker cache-lock gate).
Add an instrumented test that constructs the locked state without device auth —
EncryptedCacheGuarddepends only onSettingsRepository+PassphraseSession, not the real Keystore / BiometricPrompt. SetencryptCache = true+appLock = true, leavePassphraseSessionlocked, and drivePruneWorker/BackfillWorkerviaTestListenableWorkerBuilderwith the real guard. Assert the worker returnsretry()within a bounded time AND that nolibremail.dbconnection is opened (e.g. no keyed open occurs / the DB file's open-connection count stays 0).Caveat: the true "WorkManager cold-starts the process with no UI" can't be reproduced in-process; the locked-
PassphraseSessionseam is the faithful stand-in, and the auth-bound Keystore path stays device-only (perDatabaseKeyCipher.kt:31-32). Complements the unit suite; depends on the worker-gating fix.Scope note:
IdleServiceshares the sameEncryptedCacheGuardgate but is an AndroidService(not JVM-unit-testable without Robolectric), so its coverage was routed here from #225 (whose unit tests cover SyncWorker/SendWorker/PruneWorker/BackfillWorker). This instrumented ticket should therefore also drive IdleService: start it with the cache locked and assert itstopSelf()s / opens nolibremail.dbconnection, alongside PruneWorker/BackfillWorker.Blocked on a test seam (loop finding). The worker-driven instrumented test needs to feed the real
EncryptedCacheGuarda controlledappLock=true+encryptCache=truestate, but the guard depends onSettingsRepository— a concrete class backed by the process-widecontext.settingsDataStoresingleton (not an interface). androidTest here has no MockK and no HiltAndroidTest harness, so there's no clean way to control that state without mutating the app's real settings file. (It also needs theandroidx.work:work-testingdep + aTestListenableWorkerBuilderfactory for the@AssistedInjectworker.)Two ways forward — a small infra decision:
SettingsRepositoryinterface (concrete →SettingsRepositoryImpl) so a hand-rolled fake can feed the guard fixed settings. Cleanest for testing; touches all injection sites.HiltAndroidTestwith a test module overridingSettingsRepository/PassphraseSession— introduces Hilt instrumented-test infra the repo doesn't have yet.The guard→worker wiring is already unit-covered by #225 (merged, mocked guard); this ticket's unique value is the real-collaborator path, which needs one of the seams above.