test(e2e): cover platform binary patch updates - #156
Draft
floyd-soomgo wants to merge 1 commit into
Draft
Conversation
A release published with a binary patch offers two archives of the same update, and a client that cannot use the patch has to end up with exactly the update it would have downloaded in full. Both artifacts install the same contents, so the screen cannot tell a patch install apart from a fallback - which is why nothing covered the patch path end to end until now. The mock server records every request it answers, and the runner reads that log back as the update archives the app downloaded, in order: one patch for a patch install, a patch followed by the full archive for a fallback, and the full archive alone for a release published without a patch. The local CLI config records where it stored each artifact, so the storage layout is asserted from what the CLI asked for rather than from paths the test re-derives. On top of that, a phase installs a patch update on the device and works through every way a patch can fail: a stale base bundle, a corrupt patch body, a corrupt header, a restored bundle the manifest does not describe, and an archive built for the other platform. Each of them has to fall back exactly once and leave the app running the update it advertises. The base bundle a patch is computed against is taken out of the app that is installed on the device, because a patch applies to those bytes and no others.
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.
Summary
Sixth PR of the binary differential OTA series (stacked on
feat(ios): apply binary patch updates— merge that first).The appliers landed with unit-level coverage (Android) and compile gates (iOS); this PR is the first behavioral end-to-end gate: real patch installs and failure-driven fallbacks on a physical Android device and an iOS simulator, built as a new phase of the existing Maestro E2E harness. No library code changes — the diff is
e2e/only.The central verification problem: a patch install and a fallback-then-full install produce identical UI and identical
packageHash, so the outcome cannot be told apart from the screen. The harness now answers it with a download-order log on the mock server: every scenario asserts the exact sequence of archive fetches —[patch](patch applied),[patch, full](fell back exactly once),[full](legacy path). The assertion is order- and length-sensitive and rejects an empty log, so no scenario can pass vacuously.The embedded base bundle is extracted from the installed artifact itself (APK assets via
adb shell pm path+ unzip;main.jsbundlefrom the simulator's app container) — byte-identical with the binary by construction, which is the invariant the whole feature rests on.Scenarios
[patch][patch][patch, full][patch, full][patch, full][patch, full][patch, full][full][patch]/[full]Two scenarios deserve their design notes:
patch_apply_failed), not by the final hash. The target-hash defense — the only protection when a patch applies "successfully" but produces wrong bytes — is therefore exercised by its own scenario: an intact patch whose manifest describes a different bundle, which provably fails at exactlytarget_verification_failedand nowhere earlier.bundleonce,release --skip-bundletwice (once with the base, once without). Both histories are asserted at file level — samepackageHash, exactly one carriesbinaryPatchDownloadUrl— then each is served to the same binary in turn and installs via[patch]and[full]respectively. The cross-platform archive is likewise synthesized by rewriting a real archive into the other platform's shape, so an Android run never requires an iOS build (and vice versa).Changes
e2e/mock-server/server.ts+e2e/helpers/download-order.ts— request logging on the archive routes and the order-sensitive assertion helpers (reset inside the retry closure, so a retried scenario re-baselines instead of accumulating).e2e/helpers/binary-patch-fixtures.ts— base-bundle extraction from the installed artifact, patch-release helpers, and archive mutators (body/header corruption, manifest retarget, cross-platform rewrite) that mirror the real CLI archive layout field-for-field.e2e/helpers/binary-patch-phase.ts+e2e/run.ts— the new phase, wired through the existing retry/cleanup/config conventions; storage layout (bundles/{platform}/{identifier},histories/{platform}/{identifier}/{binaryVersion}.json) pinned by assertion.e2e/flows-binary-patch/*.yaml— Maestro flows asserting real UI outcomes (updated text + metadata label, not just app-didn't-crash); the responsiveness flow is gated behind the existing--exclude-timing-sensitiveconvention.e2e/helpers/artifact-storage.ts, config template, READMEs (EN/KO) updated alongside.Test plan
Local device runs (E2E does not run in CI):
Scope note: E2E ran against the
RN0840example app on both platforms. Other example apps (older RN, Expo) share the same harness via--appbut were not exercised in this PR.