Pro coverage for mobile and Desktop, and a mock that can express an unverified plan - #128
Draft
mpretty-cyro wants to merge 9 commits into
Draft
Pro coverage for mobile and Desktop, and a mock that can express an unverified plan#128mpretty-cyro wants to merge 9 commits into
mpretty-cyro wants to merge 9 commits into
Conversation
Pay, lose the install, restore, still Pro - the worst outcome for a subscriber and the one the suite did not guard. Covered on all three platforms. A real grant rather than a display mock, because the point is that Pro survives onto a client that has never seen it: the restored install derives the Pro master key from the recovery phrase and asks the backend, so this exercises that derivation end to end. A mock would only convince the client that already had it. The second device stands in for a reinstall rather than a linked device - nothing is carried across but the phrase, which is what a user has after losing their phone. The assertion is the stats section of the Pro settings screen, gated on an active plan everywhere, so its presence means the restored client fetched a real entitlement rather than rendering a Pro-looking screen. restoreFromSeed on Desktop gains an optional fallback name. Restoring an account created moments earlier prompts for a display name, since its profile has not reached the network yet, and without a fallback that is an error rather than something to type past. Mobile's restore has no such escape, so it asserts the name comes back where Desktop tolerates it missing - flagged rather than reconciled. Every Desktop window now carries a launch identity, not only those from the account builders, so a spec using sessionTestTwoWindows can restart one.
Covers the warning a non-renewing subscriber gets as their access approaches its end, and that dismissing it leaves them looking like a subscriber. `proLoadingState: 'success'` is load-bearing rather than incidental: every client arms this CTA only once a status fetch has been confirmed in the current process, so a mocked expiry alone reaches the state but not the gate. Android could not express that until its debug menu gained a force-success value, and its launch extra for the access expiry was never emitted. The CTA table carries no body for this variant because the copy interpolates the remaining time, which differs per platform fixture — hence the optional body in `checkCTAStrings` on both wrappers.
Both halves of the cold-launch status fetch, against a real grant: a launch that cannot know it is expiring must not warn, and the launch after the client learns its access expiry must. Nothing covered the decline before, so a client that warned off unconfirmed state would have gone unnoticed. `makeAccountPro` gains `durationSeconds` because the QA backend's compressed clock shortens proof expiry and not the account entitlement — a nominal 1M plan really does land thirty days out, which no expiry-window spec can use. It throws when the backend grants materially more than asked, since a route that ignored the override would otherwise quietly return the full plan length.
Every real-grant spec restarted the app and assumed the client would notice. It does not: each client declines the cold-launch status fetch for an account with no local expiry and no proof, which is exactly what a grant minted straight into the backend looks like. `Pinned conversation limit (Pro)` was asserting Pro behaviour against a non-Pro client on both platforms, and passing. - Replace the restart with `observeProGrant`, which restarts and then opens Pro settings — the restart makes the visit's fetch eligible rather than discovering anything, since both clients floor further fetches at 60s from the last attempt and only the first in a process is exempt. - Assert the stats header with `skipHealing`. Healing falls back to a fuzzy id match, which on the non-Pro version of that screen resolves to a neighbouring `pro-settings-*` element — so the wait passed while the client was NeverSubscribed and every later assertion ran against a non-Pro account. - Drop the equivalent restart on Desktop, whose `waitForProActive` already reaches Pro settings.
`Pro survives a restore from seed` reaches the same entitlement through the Pro settings screen, which refreshes status on arrival — so it passes whether or not the config-driven refresh works. These specs never open settings on the restored device. The composer's character countdown is the observable: it needs no navigation and no second party, and a client that believes it is not Pro caps at 2000 chars. Covers the proof half only. The client's Pro *status* can still be unknown while the proof is good — measured on a restored Android and Desktop client, whose settings row reads "Upgrade Session" while the Pro cap applies correctly. The row that would show it puts its id on a container with no text of its own, so that assertion is not expressible yet; noted so the gap is not mistaken for coverage.
`observeProGrant` proved a grant had landed by opening Pro settings and waiting for the stats header. That reads the active plan; the row on the parent settings list reads the fetched status, and the two disagree more often than expected — a client can hold a good proof, apply the Pro message cap, and still offer to sell you Pro on that row. Asserting it costs nothing, since the row is on screen on the way out. `assertProFromSettingsRow` is separated out for reuse as a cheap "is this client Pro" precondition. It reads the parent list deliberately: no client refreshes on opening it, so it observes what the client already believed instead of provoking the answer. Tapping the row is not passive, so it reads and leaves. Requires the new `pro-menu-item-title` id on both mobile clients — the existing `pro-menu-item` sits on the tap target, which carries no text of its own, so the row's state was not readable through it.
Status and proof answer different questions. The status is the plan's state, which only the backend knows; the proof is the entitlement that travels with the message and is what a recipient validates against. A message length is the second question, so it has to read the proof. Reading the status instead leaves the two ends permanently disagreeing: measured at 3000 characters sent and 2000 stored by the recipient, with nothing shown at either end. The sender's own copy keeps the full text, so the conversation holds two different messages. RED until the compose limit reads the proof. That change is in flight on all three clients as part of the access/display split; this spec is the check on it. The fixture needs no grant and no restore — a mocked active status supplies the status half while the config stays empty, which is the state exactly.
`proBackendStatus` said what state the plan was in AND granted access, so a client with an active plan and no usable proof was unreachable by mock on every platform. That is the state the message limit is wrong in: the composer offers the Pro limit and every recipient silently truncates, because no proof travels with the message. `proProof` is the access half. A fixture that wants an ordinary Pro user now sets both; the pair is only interesting when they disagree. `none` overrides a real proof rather than falling back, since a mock a real proof could outvote is useless on the devices most worth running it on. The app-side key differs per platform because the harness already translates per platform: mockCurrentUserSessionProProof, sessionProProof, and SESSION_PRO_MOCK_PROOF. The lowercasing for Desktop is load-bearing — it throws at flag-init on anything else, so an untranslated value stops the renderer starting.
…riber" The status mock no longer grants access, so a fixture claiming an active plan renders a subscriber whose features are all switched off. Anything meaning "this user is Pro" now says so twice, once per question. `iosActiveProContext` carries it for the mobile specs, so the per-spec changes are the Desktop ones plus the mobile Pro settings screen. The no-proof message limit spec gets the opposite pairing, stated explicitly: an active plan with `proProof: 'none'`. Until the mocks were split that state could not be reached, so the spec could not have measured what it claims to — it would have passed by offering the Pro limit, which is the behaviour it exists to catch.
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.
Draft — parked for review, not ready to merge.
What this is
Pro coverage for mobile and Desktop, plus the harness changes that make the interesting Pro states reachable at all.
The clients have been split so that two questions are answered by two values:
They are meant to disagree. A plan that has lapsed while the proof still has time left displays as expired while the features keep working; that overhang is intended behaviour, not drift.
The harness change worth reviewing first
proBackendStatusused to say what state the plan was in and grant access. That made one state unreachable by mock on every platform: an active plan with no usable proof. It is the state the message limit is wrong in — the composer offers the Pro limit, the message goes out with no proof attached, and every recipient silently truncates it. Measured at 3000 characters sent and 2000 stored, with nothing shown at either end.proProofis now the access half, with its own key on each client. A fixture that means "this user is Pro" says both halves; the pair is only interesting when they disagree.proProof: 'none'overrides a real proof rather than falling back to it — a mock a real proof could outvote would be useless on the devices most worth running it on.What is deliberately not here
Scope
Everything here is test-side: specs, fixtures, locators and harness. The client behaviour it exercises is in the corresponding branches on the three client repos.