test(receipts): cover PostReceiptHelper workflow metadata resolution#3636
Merged
vegaro merged 3 commits intoJun 23, 2026
Conversation
Adds PostReceiptHelper-layer coverage for the workflow metadata threading introduced in #3603, which was only exercised at the Backend wire-format and PaywallViewModel event layers. - posts workflow metadata resolved from the live presented paywall event - omits workflow metadata when the presented event has no workflow - replays cached workflow metadata on unsynced retry (postRemainingCachedTransactionMetadata) Makes mockPostReceiptSuccess workflow-agnostic (workflowMetadata = any()) so the success path can post non-null workflow metadata. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qCKrb3h5JJyBvgP9sw11m
…ries current step Adds a regression test for withCurrentWorkflowMetadata covering the review finding #2 (CLOSE/EXIT_OFFER after a same-fingerprint step change). Both workflow steps render the same screen so the presentation fingerprint is identical and the impression is de-duped, forcing the code through the withCurrentWorkflowMetadata branch (asserted via "no new impression"). The exit offer event then must carry the updated step id. Verified RED -> GREEN: with withCurrentWorkflowMetadata neutralized the test fails; with it restored it passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qCKrb3h5JJyBvgP9sw11m
The existing `purchase initiated after same paywall workflow re-presentation` test pointed its two steps at different screens (screen-1 vs screen-2), which produce different presentation fingerprints. The second impression therefore took the else branch and recreated fresh event data, so the test passed even with withCurrentWorkflowMetadata removed (verified: it stayed green when the line was neutralized). It did not guard the feature it was named for. Rework it so both steps render the same screen (identical fingerprint), add a "no new impression" assertion to prove the de-dup branch was taken, and confirm RED -> GREEN: with withCurrentWorkflowMetadata neutralized it now fails (expected "step-2" but was "step-1"); restored, it passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qCKrb3h5JJyBvgP9sw11m
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## cesar/receipt-presented-paywall-workflow-ids #3636 +/- ##
=============================================================================
Coverage 80.28% 80.28%
=============================================================================
Files 380 380
Lines 15582 15582
Branches 2179 2179
=============================================================================
Hits 12510 12510
Misses 2203 2203
Partials 869 869 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
vegaro
pushed a commit
that referenced
this pull request
Jun 23, 2026
### Motivation Follow-up test coverage for #3603. That PR's refactor commit (`b2bfd86`) fixed a bug where `workflowId`/`stepId` were `@Transient` on the cached transaction metadata and were lost on the disk round-trip, so `presented_workflow_id` / `presented_step_id` would be sent as `null` on retry or unsynced replay. The field was promoted to a first-class `workflowMetadata` on `LocalTransactionMetadata` with `@SerialName("workflow_metadata")`, but there was no regression test asserting it actually survives serialization. ### What this adds One test in `LocalTransactionMetadataStoreTest`, `cacheLocalTransactionMetadata round-trips workflow metadata`, mirroring the existing `handles paywall data` test. The JSON written to `SharedPreferences` is captured and fed back to `getLocalTransactionMetadata`, so the real `JsonTools.json` encode/decode runs (only SharedPreferences I/O is mocked). ### Verification (RED → GREEN) - **RED**: with `workflowMetadata` marked `@Transient`, the test fails at the assertion (`retrieved?.workflowMetadata` is `null`). - **GREEN**: with `@SerialName("workflow_metadata")` (current state), it passes. Ran `:purchases:testDefaultsBc8DebugUnitTest --tests "...LocalTransactionMetadataStoreTest"` locally (JDK 21). Companion to #3636 (which covers the `PostReceiptHelper` threading and replay paths). Together they close the two test gaps identified in review of #3603. <details> <summary>AI session context</summary> **Task:** Review #3603 ("is anything missing?"), then close the identified test gaps. **Findings from review of #3603:** 1. (important) Disk round-trip of `workflowMetadata` untested, the exact bug `b2bfd86` fixed. `LocalTransactionMetadataStoreTest` had the analogous `handles paywall data` round-trip test but nothing for `workflowMetadata`. 2. (important) `PostReceiptHelper` threading untested (only an `any()` count bumped). → covered by #3636. **This PR** addresses finding #1. **Decisions:** - Reused the existing capture-the-written-JSON pattern so the test exercises real serialization, not a mock, which is what makes it a genuine guard for the `@Transient` regression. - Based on `cesar/receipt-presented-paywall-workflow-ids` because the `workflowMetadata` field only exists there, not on `main`. **Verified:** RED→GREEN as described above. Production code (`LocalTransactionMetadata.kt`) left unchanged; only the test file is added. **Not done:** The remaining review items on #3603 were questions, not test gaps (all-or-nothing `WorkflowMetadata.from`, impression/purchase step-id divergence under dedup, `pr:other` vs `pr:fix` label). Not addressed here. 🤖 Generated with [Claude Code](https://claude.com/claude-code) </details> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
39976ae
into
cesar/receipt-presented-paywall-workflow-ids
37 checks passed
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.
Motivation
Follow-up to #3603 (cc @vegaro). That PR threads
presented_workflow_id/presented_step_idthrough the post-receipt pipeline. While reviewing it I found two coverage gaps, one of which is significant. This PR targets the #3603 branch so the tests land with the feature.Changes
1.
PostReceiptHelperresolution coverage (PostReceiptHelperTest)The new resolution logic in
PostReceiptHelperwas only exercised indirectly (BackendTestcovers the wire format given explicit metadata;PaywallViewModelWorkflowTestcovers event creation). Added:WorkflowMetadata.from)postRemainingCachedTransactionMetadata)Supporting:
mockPostReceiptSuccessnow matchesworkflowMetadata = any()(an omitted defaulted arg in MockK bindseq(null)), so the success path can post non-null workflow metadata. Existing tests unaffected.2. Fixed a vacuous re-presentation test + added EXIT_OFFER coverage (
PaywallViewModelWorkflowTest)The feature's own regression test,
purchase initiated after same paywall workflow re-presentation carries current step metadata, did not actually exercisewithCurrentWorkflowMetadata. Its two steps pointed at different screens (screen-1vsscreen-2), producing different presentation fingerprints, so the second impression took the else branch and recreated fresh event data, reading the live step. The test passed even withwithCurrentWorkflowMetadataremoved.EXIT_OFFERafter the same-fingerprint re-presentation.withCurrentWorkflowMetadataneutralized they fail (expected "step-2" but was "step-1"); restored, they pass.Validation
./gradlew :purchases:testDefaultsBc8DebugUnitTest --tests "com.revenuecat.purchases.PostReceiptHelperTest", 86 tests, 0 failures../gradlew :ui:revenuecatui:testDefaultsBc8DebugUnitTest --tests "com.revenuecat.purchases.ui.revenuecatui.data.PaywallViewModelWorkflowTest", 0 failures.withCurrentWorkflowMetadataline (run under JDK 21).Important
Before #3603 merges: its
withCurrentWorkflowMetadatachange had no test that exercised it (the existing regression test was vacuous). This PR closes that gap. Recommend not approving #3603 until these tests are in.AI session context
AI Context
Metadata
cesar/receipt-presented-paywall-workflow-ids, the Addpresented_workflow_idandpresented_step_idto/receipts#3603 branch)facu/pr3603-postreceipt-workflow-testsGoal
Close test-coverage gaps found while reviewing #3603: (a)
PostReceiptHelper-layer workflow-metadata resolution, and (b) thewithCurrentWorkflowMetadatare-presentation behavior, whose existing regression test turned out to be vacuous.Initial Prompt
Review #3603. The review surfaced coverage gaps; follow-ups were "write a test for 1", "open a draft targeting this pr and mention this", then "write it [finding #2] and put it in the same pr".
Important Follow-up Prompts
presented_workflow_idandpresented_step_idto/receipts#3603 branch.Agent Contribution
PostReceiptHelperTesttests + mademockPostReceiptSuccessworkflow-agnostic.EXIT_OFFERre-presentation test inPaywallViewModelWorkflowTest.withCurrentWorkflowMetadatabranch; reworked it to share one screen and added a "no new impression" guard.Human Decisions
presented_workflow_idandpresented_step_idto/receipts#3603 feature branch rather than pushing onto @vegaro's branch.Key Implementation Decisions
screenId.withCurrentWorkflowMetadatalives) only runs when fingerprints match; different screens silently routed the test through fresh re-creation.mockPostReceiptSuccessusesworkflowMetadata = any().eq(null).Files / Symbols Touched
purchases/src/test/java/com/revenuecat/purchases/PostReceiptHelperTest.ktmockPostReceiptSuccess, new@Tests (presented-paywall resolution, no-workflow omission,postRemainingCachedTransactionMetadata).ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/data/PaywallViewModelWorkflowTest.ktwithCurrentWorkflowMetadata; add EXIT_OFFER coverage.purchase initiated after same paywall workflow re-presentation...,exit offer after same paywall workflow re-presentation....Dependencies / Config / Migrations
Validation
:purchases:testDefaultsBc8DebugUnitTest --tests "...PostReceiptHelperTest": 86 tests, 0 failures.:ui:revenuecatui:testDefaultsBc8DebugUnitTest --tests "...PaywallViewModelWorkflowTest": 0 failures.withCurrentWorkflowMetadataneutralized, both re-presentation tests fail (expected "step-2" but was "step-1"); restored, they pass.Validation Gaps
PostReceiptHelperis exercised via the live and retry paths but not via a dedicated synchronous-purchase test wheregetLocalTransactionMetadatareturns metadata..sdkmanrcpins21.0.6-librca); default daemon here was JDK 17, which Robolectric rejects for Android SDK 36.Review Focus
mockPostReceiptSuccess'sworkflowMetadata = any()relaxation does not weaken the negative-case test (...has no workflow), which assertsworkflowMetadata = nullexplicitly.Risks / Reviewer Notes
*Test.ktfiles (the PaywallViewModel edit during RED→GREEN was reverted; prod tree confirmed clean).Non-goals / Out of Scope
withCurrentWorkflowMetadataitself is unchanged.Omitted Context
Note
Low Risk
Only test files change; behavior under test lives in the feature branch, not in this diff.
Overview
Test-only follow-up that locks in workflow
presented_workflow_id/presented_step_idbehavior from the parent feature PR—no production code changes.PostReceiptHelperTestadds coverage for howworkflowMetadatais chosen when callingbackend.postReceiptData: from the presented paywall event when workflow ids are present,nullwhen they are not, and replay fromLocalTransactionMetadataonpostRemainingCachedTransactionMetadata.mockPostReceiptSuccessnow stubsworkflowMetadata = any()so success-path mocks accept non-null metadata (MockK default binding issue).PaywallViewModelWorkflowTestfixes a regression test that never hit the impression de-dup path: both workflow steps now share the same screen sowithCurrentWorkflowMetadatamust refresh step ids on re-presentation. Adds a no new IMPRESSION assertion and a siblingEXIT_OFFERtest for the same scenario.Reviewed by Cursor Bugbot for commit 8527bcd. Bugbot is set up for automated code reviews on this repo. Configure here.