Conversation
TurmaApplication.onCreate fires a fire-and-forget Updater.check(), and Robolectric rebuilds the Application per test METHOD, so the updater's ~15-min throttle never applies — every test hit live api.github.com (measured 45 anonymous CONNECTs per suite, from shared-IP CI runners, against GitHub's 60 req/hour/IP unauthenticated limit). Give check() a quiet-in-tests seam (Option A from the ticket, the smallest one, mirroring the recordCoercion seam XERK-262 added hub-side): check() no-ops at its very top — before the throttle/force branch — when the `turma.updater.disabled` system property is "true", and app/build.gradle.kts's testOptions sets it JVM-wide for the test task. Nothing in production sets it, so production behavior is unchanged. Regression test (net/UpdaterTestSeamTest) pins both halves: the property reaches the test JVM, and check() short-circuits under it. The no-op is asserted on lastCheckAt (0 iff check() never proceeded), not on state — a check that RAN and whose fetch failed also lands on Hidden, so state alone would pass with the guard removed. lastCheckAt is exposed @VisibleForTesting for this. Harness rule added to .claude/rules/android.md so it is not reintroduced. Verified: gradle :app:testDebugUnitTest green (450 tests); the ticket's CONNECT-proxy repro shows api.github.com calls drop 45 -> 0; and removing either the gradle line or the production guard makes the suite fail.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
TurmaApplication.onCreatefires a fire-and-forgetUpdater.check()(the XERK-11 in-app updater), which fetcheshttps://api.github.com/repos/…/releases. Robolectric rebuilds theApplicationper test METHOD, so the updater's ~15-minute throttle never applies and every test hits liveapi.github.com— measured 45 anonymous CONNECTs per:app:testDebugUnitTestrun, from shared-IP CI runners, against GitHub's 60 req/hour/IP unauthenticated limit. It also showed up as an "Update available" banner in a Robolectric screen test. Filed by the QA pass on XERK-262 (PR #439); Low because the suite never depended on that egress, but unsolicited third-party traffic worth removing.The fix — Option A (the ticket's preferred, smallest seam)
net/Updater.kt—check()no-ops at its very top, before the throttle/force branch, when theturma.updater.disabledsystem property is"true". New constDISABLE_PROPERTY. Nothing in production sets it, so production behavior is unchanged (a real device's start-of-app check is untouched).app/build.gradle.kts—testOptions.unitTests.all { it.systemProperty("turma.updater.disabled", "true") }sets it for the whole test JVM.net/UpdaterTestSeamTest.kt— regression test pinning both halves: the property reaches the test JVM, andcheck(force=true)short-circuits under it. The no-op is asserted onlastCheckAt(0 iffcheck()never proceeded past its guards), not onstate— a check that ran and whose fetch failed (offline/blackholed CI) also lands onHidden, so a state assertion would be a tautology that passes with the guard removed.lastCheckAtis exposed@VisibleForTestingfor this..claude/rules/android.md— harness rule added so the egress is not reintroduced.No user-facing surface changes → no
android/PARITY.mdor web-side change needed.Verification (adversarial QA + delta, both ran the build)
gradle :app:testDebugUnitTest— green, 450 tests / 0 failures;UpdaterTestSeamTest2/2.JAVA_TOOL_OPTIONS):api.github.comCONNECTs 45 → 0 with the fix.it.systemProperty(...)gradle line →disablePropertyIsSetForTheTestJvmfails and 45 CONNECTs return;check()→checkNoOpsUnderTheDisablePropertyfails onlastCheckAt(expected:<0> but was:<…>). (First QA pass caught that an earlier state-only assertion did not catch this; fixed and re-verified PASS.)android.md) and the livefetchLatestI/O path (deliberately blackholed — the suite must not depend on egress). Both unchanged by this diff.