Pro: QA backend override, a proof-acquisition floor, and a unit-test class-init fix - #2153
Open
mpretty-cyro wants to merge 8 commits into
Open
Conversation
Lets a QA Pro backend be targeted without rebuilding, matching the iOS customProBackendUrl/customProBackendPubkey launch variables. Both values are required together: a QA URL paired with the production signing key reads every QA-signed proof as invalid and silently strips Pro content.
…ation
Reading a SessionProtocol constant runs System.loadLibrary("session_util"), so
doing it from the companion's initialiser made the class impossible to
initialise wherever the native library is absent -- every JVM unit test. Mockito
could not instrument it, and the nine tests constructing a ConversationViewModel
failed with NoClassDefFoundError.
The constants stay single-sourced from libsession; they are just read on first
use rather than on class load.
jagerman
force-pushed
the
feature/pro-backend-override-qa
branch
from
August 6, 2026 18:08
924aa0b to
5906064
Compare
A successful generate force-refreshes get_pro_status, which asks libsession for a renewal target, and `proofExpiry - PRO_RENEWAL_LEAD` is permanently in the past for any proof living less than the 60-minute lead -- so the worker rescheduled itself immediately and looped. Mirrors iOS SessionProManager.reconcileProofRenewal and Desktop, constants included: 60s while covered, 15s * attempt capped at 900s while dark, and re-arming rather than dropping the work, since `target <= now` is also the normal renewal-due signal. The state is in-memory as it is on the other two platforms; a process restart costs one extra request rather than a loop.
The debug menu already drives these states through preferences that ProStatusManager and ProSettingsViewModel read; they were just unreachable from an automated launch, so the Appium suite could only cover Pro screens on iOS. sessionProBackendStatus and sessionProLoadingState are named for the state being simulated rather than for the preference behind them, matching the keys iOS already accepts, so one cross-platform test has one setup that means the same thing on both. `useActual` clears an override. Values are mapped explicitly rather than derived from enum names, so renaming a case cannot silently change what a test asks for.
Two of the three EXPIRING labels claimed 14 days while the code used 2, which is how a reader (and a test author) ends up with the wrong value: the label looks authoritative and is the first thing you see. EXPIRING_LATER moves 40 -> 30 days so both platforms can assert the same rendered string. It already sat outside the 7-day window that gates the expiring CTA and still does, so its behaviour is unchanged -- EXPIRING keeps its 2 days precisely because it is inside that window and is the only way to trigger the CTA by hand.
It sat on the ComposeView host in XML while the tap target is the Avatar inside it. Compose publishes its own semantics tree, so whether the host's description survived depended on composition timing -- intermittently leaving the avatar unlabelled for accessibility services, and unfindable by anything addressing it by description. Removed from the XML rather than left in both places: the same description on two nodes of one tree is the ambiguity being fixed, not redundancy. Nothing read it there -- the id is used as a constraint anchor and for setThemedContent only.
mpretty-cyro
marked this pull request as ready for review
August 7, 2026 07:07
… failing quietly - `sessionProBackendStatus=active` mapped to AUTO_GOOGLE, an auto-renewing plan with a fixed +14d renewal rendering `proAutoRenewTime`. iOS's `active` is not auto-renewing, so the shared token meant different things per platform. Map it to EXPIRING_GOOGLE_LATER, which matches iOS. Side effects, neither asserted today: the cancel-access row disappears (it is auto-renew only, as on iOS) and the plan length becomes 12 months. - New `sessionProAccessExpiry` extra, applied over whichever fixture was selected: epoch seconds, or a relative `+<n><s|m|h|d>`, or `useactual`. Rejects a resolved instant more than ten years out, which catches milliseconds passed as seconds. Seconds because that is what iOS's field is; the preference keeps storing millis internally. - Log loudly on any unrecognised `session*` extra. Previously a typo was a silent no-op, so it failed as a wrong assertion rather than a setup error. - EXPIRED and EXPIRED_APPLE were labelled "Expired 2 days ago" while the code used now - 14 days. Correct the labels; the offsets are the contract. - Build every instant in the debug branch from `snodeClock`, read once per recomputation. The render side already used it, so the fixture's device-clock instants left the offset between the two clocks in the result — a 30-day fixture could render "31 days". `quickRefundExpiry` had the same defect. - Robolectric coverage over the new extra, driven through `QaLaunchConfig.apply` with real Intents so the parse, the range guard and the preference write are tested together.
… remaining surfaces - `SessionProSettingsHeader` applied `clearAndSetSemantics` to the whole column, which erases descendants rather than merging them — so the status banner was absent from the tree entirely, text and all, and TalkBack announced "Session Pro" over a message users never heard. Narrow it to the decorative logo and badge, leaving `extraContent` a sibling inside the column that carries `onSizeChanged` (moving it out would change the gradient ratio). - Tag both banner variants with `pro-settings-status-banner`. One id for the slot; the four states are told apart by their text. - Section-header ids for Stats, Manage and Pro Beta Features, via one optional `CategoryCell` parameter so the other call sites are untouched. - Name the conversation-header badge, matching iOS.
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.
Three independent changes, all Pro-adjacent.
QA backend override
Lets a QA Pro backend be targeted without rebuilding, via
QaLaunchConfig— matching iOS'scustomProBackendUrl/customProBackendPubkeylaunch variables, so the Appium suite canpoint both platforms at the same backend with one switch.
Both values are required together: a QA URL paired with the production signing key reads
every QA-signed proof as invalid and silently strips Pro content.
Proof-acquisition floor
ProProofGenerationWorkerhad no floor, and the path is a closed loop: a successfulgenerate force-refreshes
get_pro_status, the fetch asks libsession for a renewal target,and
target = proofExpiry - PRO_RENEWAL_LEADis permanently in the past whenever a prooflives for less than the 60-minute lead — so it reschedules immediately, forever. Measured at
~9-11 requests/second against a backend issuing 5-minute proofs.
The floor mirrors iOS (
SessionProManager.reconcileProofRenewal) and Desktop(
state/ducks/proBackendData.ts) exactly —60swhile covered,15s × attemptcapped at900swhile dark,darkAttemptreset on cover. The constants carry a comment naming them across-client contract so nobody tunes one in isolation.
Two properties worth preserving in any refactor:
target <= nowis the normal "renewal due" signal and dropping it would break realrenewals.
purchase, or a server-side grant the backend hasn't ingested — settles at 15-minute
spacing rather than hammering.
Placed at the top of
doWork()rather than in the scheduler, which covers every paththat can schedule acquisition (both
FetchProStatusWorkersites andProStatusManager.onPurchaseInFlight) rather than one branch, and matches where iOS putsit. State lives in the existing
pro_statename/value table, persisted rather thanin-memory because WorkManager can run the worker across process death and an in-memory
counter would reset and defeat the pacing. No migration.
Unit-test class-init fix
Reading a
SessionProtocolconstant runsSystem.loadLibrary("session_util"), so doing itfrom
ProStatusManager's companion initialiser made the class impossible to initialisewherever the native library is absent — every JVM unit test. Mockito could not instrument
it, and the nine tests constructing a
ConversationViewModelfailed withNoClassDefFoundError.The constants stay single-sourced from libsession; they are just read on first use rather
than on class load.
Testing
assemblePlayDebugandassemblePlayAutomaticQagreen. 198/198 unit tests pass (was189/198 before the class-init fix).