Skip to content

Generating new test snapshots for feat/workflows-network-layer - ios-26#6587

Merged
vegaro merged 1 commit into
feat/workflows-network-layerfrom
generated_snapshots/feat/workflows-network-layer-523960-ios-26
Apr 10, 2026
Merged

Generating new test snapshots for feat/workflows-network-layer - ios-26#6587
vegaro merged 1 commit into
feat/workflows-network-layerfrom
generated_snapshots/feat/workflows-network-layer-523960-ios-26

Conversation

@RCGitBot

@RCGitBot RCGitBot commented Apr 10, 2026

Copy link
Copy Markdown
Contributor

Requested by @vegaro for feat/workflows-network-layer


Note

Low Risk
Low risk: adds/updates snapshot JSON fixtures only, with no production code changes; failures would be limited to snapshot-based unit tests.

Overview
Adds new iOS 26 snapshot JSON fixtures for BackendGetWorkflowsTests, capturing expected headers and request URLs for GET /v1/subscribers/{user}/workflows and GET /v1/subscribers/{user}/workflows/{id} (including error and caching scenarios).

No runtime behavior changes; this PR only updates snapshot-based unit test expectations.

Reviewed by Cursor Bugbot for commit 816cd3b. Bugbot is set up for automated code reviews on this repo. Configure here.

@RCGitBot RCGitBot requested a review from a team as a code owner April 10, 2026 10:07
@RCGitBot RCGitBot added the test label Apr 10, 2026
@RevenueCat-Danger-Bot

Copy link
Copy Markdown
1 Error
🚫 Label the PR using one of the change type labels. If you are not sure which label to use, choose pr:other.
Label Description
pr:feat A new feature. Use along with pr:breaking to force a major release.
pr:fix A bug fix. Use along with pr:force_minor to force a minor release.
pr:other Other changes. Catch-all for anything that doesn't fit the above categories. Releases that only contain this label will not be released. Use along with pr:force_patch, or pr:force_minor to force a patch or minor release.
pr:RevenueCatUI Use along any other tag to mark a PR that only contains RevenueCatUI changes
pr:next_release Preparing a new release
pr:dependencies Updating a dependency
pr:phc_dependencies Updating purchases-hybrid-common dependency
pr:changelog_ignore The PR will not be included in the changelog. This label doesn't determine the type of bump of the version and must be combined with pr:feat, pr:fix or pr:other.

Generated by 🚫 Danger

@vegaro vegaro merged commit 89f7f47 into feat/workflows-network-layer Apr 10, 2026
4 of 14 checks passed
@vegaro vegaro deleted the generated_snapshots/feat/workflows-network-layer-523960-ios-26 branch April 10, 2026 10:07
@emerge-tools

emerge-tools Bot commented Apr 10, 2026

Copy link
Copy Markdown

4 builds increased size

Name Version Download Change Install Change Approval
RevenueCat
com.revenuecat.PaywallsTester
1.0 (1) 17.4 MB ⬆️ 89.1 kB (0.51%) 62.4 MB ⬆️ 377.5 kB (0.61%) N/A
BinarySizeTest
com.revenuecat.binary-size-test.local-source
1.0 (1) 3.9 MB ⬆️ 31.3 kB (0.81%) 11.8 MB ⬆️ 96.9 kB (0.84%) ⏳ Needs approval
BinarySizeTest
com.revenuecat.binary-size-test.cocoapods
1.0 (1) 5.9 MB ⬆️ 45.4 kB (0.77%) 26.1 MB ⬆️ 205.2 kB (0.8%) ⏳ Needs approval
BinarySizeTest
com.revenuecat.binary-size-test.spm
1.0 (1) 4.0 MB ⬆️ 30.2 kB (0.76%) 10.3 MB ⬆️ 80.2 kB (0.79%) ⏳ Needs approval

RevenueCat 1.0 (1)
com.revenuecat.PaywallsTester

⚖️ Compare build
⏱️ Analyze build performance

Total install size change: ⬆️ 377.5 kB (0.61%)
Total download size change: ⬆️ 89.1 kB (0.51%)

Largest size changes

Item Install Size Change
DYLD.String Table ⬆️ 121.2 kB
DYLD.Exports ⬆️ 9.5 kB
Code Signature ⬆️ 9.0 kB
📝 RevenueCat.WorkflowsAPI.WorkflowsAPI ⬆️ 4.6 kB
RevenueCat.WebBillingAPI.WebBillingAPI ⬇️ -1.4 kB
View Treemap

Image of diff

BinarySizeTest 1.0 (1)
com.revenuecat.binary-size-test.local-source

⚖️ Compare build
📦 Install build
⏱️ Analyze build performance

Total install size change: ⬆️ 96.9 kB (0.84%)
Total download size change: ⬆️ 31.3 kB (0.81%)

Largest size changes

Item Install Size Change
DYLD.String Table ⬆️ 10.0 kB
📝 RevenueCat.WorkflowScreen.value witness ⬆️ 10.0 kB
📝 RevenueCat.PublishedWorkflow.__derived_struct_equals ⬆️ 7.6 kB
RevenueCat.UIConfig.Equatable ⬇️ -4.9 kB
📝 RevenueCat.WorkflowScreen.__derived_struct_equals ⬆️ 4.6 kB
View Treemap

Image of diff

BinarySizeTest 1.0 (1)
com.revenuecat.binary-size-test.cocoapods

⚖️ Compare build
📦 Install build
⏱️ Analyze build performance

Total install size change: ⬆️ 205.2 kB (0.8%)
Total download size change: ⬆️ 45.4 kB (0.77%)

Largest size changes

Item Install Size Change
DYLD.String Table ⬆️ 78.0 kB
📝 RevenueCat.WorkflowScreen.value witness ⬆️ 10.0 kB
📝 RevenueCat.PublishedWorkflow.__derived_struct_equals ⬆️ 7.5 kB
RevenueCat.UIConfig.Equatable ⬇️ -4.8 kB
Code Signature ⬆️ 4.8 kB
View Treemap

Image of diff

BinarySizeTest 1.0 (1)
com.revenuecat.binary-size-test.spm

⚖️ Compare build
📦 Install build
⏱️ Analyze build performance

Total install size change: ⬆️ 80.2 kB (0.79%)
Total download size change: ⬆️ 30.2 kB (0.76%)

Largest size changes

Item Install Size Change
📝 RevenueCat.WorkflowScreen.value witness ⬆️ 10.0 kB
📝 RevenueCat.PublishedWorkflow.__derived_struct_equals ⬆️ 7.6 kB
RevenueCat.UIConfig.Equatable ⬇️ -4.9 kB
📝 RevenueCat.WorkflowScreen.__derived_struct_equals ⬆️ 4.6 kB
RevenueCat.PaywallComponent.StackComponent.StackComponent ⬇️ -4.0 kB
View Treemap

Image of diff


🛸 Powered by Emerge Tools

Comment trigger: Size diff threshold of 100.00kB exceeded

vegaro added a commit that referenced this pull request Apr 21, 2026
* Add workflows network layer for multipage paywalls

- Add `getWorkflows` and `getWorkflow` endpoint paths
- Add `WorkflowsListResponse` and `PublishedWorkflow` response models
- Add `WorkflowDetailProcessor` to handle `inline`/`use_cdn` response actions
- Add `WorkflowCdnFetcher` protocol and `DirectWorkflowCdnFetcher` implementation
- Add `GetWorkflowsOperation` and `GetWorkflowOperation` cacheable network operations
- Add `WorkflowsAPI` facade and wire into `Backend`
- Add unit tests for all new components

iOS equivalent of RevenueCat/purchases-android#3300

Made-with: Cursor

* fix xcodeproj

* reset Package.resolved

* Update WorkflowStep models to match actual backend response

- Add WorkflowTrigger struct (name, type, action_id, component_id)
- Add triggers, outputs, and metadata fields to WorkflowStep
- Add metadata field to PublishedWorkflow
- Remove value field from WorkflowTriggerAction (backend uses step_id only)

Made-with: Cursor

* Update WorkflowResponseTests to match updated models

- Replace value/resolvedTargetStepId assertions with stepId
- Remove testDecodeWorkflowTriggerActionValueTakesPrecedence (value field removed)
- Add testDecodeWorkflowTrigger for new WorkflowTrigger struct
- Add testDecodePublishedWorkflowWithMetadata
- Update testDecodeWorkflowStepDefaults to cover triggers, outputs, metadata
- Add testDecodeWorkflowStepMatchingActualBackendResponse with real backend payload

Made-with: Cursor

* Disable file_length lint rule in HTTPRequestPath.swift

Made-with: Cursor

* Fix GetWorkflowOperation to compute result once before distributing to callbacks

CDN fetch and JSON decoding were running once per deduplicated callback.
Compute the Result<WorkflowFetchResult, BackendError> once outside the
performOnAllItemsAndRemoveFromCache loop, matching the pattern used by
GetOfferingsOperation and other operations in the codebase.

Made-with: Cursor

* Fix CDN fetcher: use URLSession instead of Data(contentsOf:), classify errors correctly

- Replace Data(contentsOf:) with URLSession.shared.dataTask + DispatchSemaphore
  in DirectWorkflowCdnFetcher; gets URLSession timeout, HTTP status validation,
  and proper network stack semantics
- Add WorkflowDetailProcessingError.cdnFetchFailed typed error so CDN I/O
  failures are distinguishable from envelope parsing failures
- Catch cdnFetchFailed in GetWorkflowOperation and map to NetworkError.networkError
  instead of NetworkError.decoding, fixing misleading error classification
- Update WorkflowDetailProcessorTests to assert the typed error is thrown

Made-with: Cursor

* Replace semaphore CDN fetch with async/await using withCheckedThrowingContinuation

- Make WorkflowCdnFetcher.fetchCompiledWorkflowData async throws; use
  withCheckedThrowingContinuation to bridge URLSession.dataTask into async,
  avoiding any thread-blocking
- Make WorkflowDetailProcessor.process async throws to propagate async
- Bridge into async in GetWorkflowOperation via Task {}; completion() is
  called inside the Task after CDN fetch and decoding complete
- Update WorkflowDetailProcessorTests to async throws with await

Made-with: Cursor

* Make CDN fetcher and processor completion-handler based for consistency

Avoids Task{} in GetWorkflowOperation and keeps all operations calling
completion() synchronously from within the HTTP callback, matching every
other operation in the codebase.

- WorkflowCdnFetcher.fetchCompiledWorkflowData now takes a completion handler;
  DirectWorkflowCdnFetcher uses URLSession.dataTask (non-blocking, no semaphore)
- WorkflowDetailProcessor.process now takes a completion handler; inline action
  completes synchronously, use_cdn fans out to the fetcher callback
- GetWorkflowOperation splits into getWorkflow/handleResponse/backendResult/
  distribute helpers to stay within line-length limits
- WorkflowDetailProcessorTests updated to use waitUntilValue pattern

Made-with: Cursor

* Fix ambiguous cache key delimiter in GetWorkflowOperation

Space-separated appUserID+workflowId could collide (e.g. user 'a b' + workflow 'c'
== user 'a' + workflow 'b c'). Use newline as delimiter, matching the precedent
set by GetWebBillingProductsOperation.

Made-with: Cursor

* PR comments

* remove cdn fetcher

* fix response in BackendGetWorkflowsTests.swift

* fix WorkflowResponseTests

* fix error

* [skip ci] Generating new test snapshots (#6584)

* [skip ci] Generating new test snapshots (#6585)

* [skip ci] Generating new test snapshots (#6586)

* [skip ci] Generating new test snapshots (#6587)

* [skip ci] Generating new test snapshots (#6588)

* [skip ci] Generating new test snapshots (#6589)

* [skip ci] Generating new test snapshots (#6590)

* Test CDN mock is not re-assignable per test

* [skip ci] Generating new test snapshots (#6597)

* [skip ci] Generating new test snapshots (#6598)

* [skip ci] Generating new test snapshots (#6599)

* [skip ci] Generating new test snapshots (#6600)

* [skip ci] Generating new test snapshots (#6601)

* [skip ci] Generating new test snapshots (#6602)

* [skip ci] Generating new test snapshots (#6603)

* getWorkflow is signed

* add type parameter

* add response verification for CDN response

* remove workflows list

* add value to WorkflowTriggerAction

* step id

* hash and filerepo

* missingCdnHash

* linter and project

* fix project

* revert Package.resolved

* @unchecked Sendable

* use hash in generateOrGetCachedFileURL

* fix compilation

* skip responseVerificationMode in cdn

* change basePath of caches

* remove reserialization

* Use explicit type in GetWorkflowOperation.createFactory for greppability

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix swiftlint identifier_name violation in WorkflowDetailProcessorTests

Rename short variable `d` to `doubleValue` in two `if case .double(let d)`
patterns to satisfy the identifier_name rule requiring names >= 3 characters.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* remove default FileRepositoryType

* fix project

* fix test

* test preserves camel case

* Generating new test snapshots for `feat/workflows-network-layer` - ios-15 (#6654)

* Generating new test snapshots for `feat/workflows-network-layer` - ios-14 (#6656)

---------

Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
Co-authored-by: Facundo Menzella <facumenzella@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Facundo Menzella <facumenzella@users.noreply.github.com>
This was referenced Apr 22, 2026
facumenzella added a commit that referenced this pull request Apr 23, 2026
* Add workflows network layer for multipage paywalls

- Add `getWorkflows` and `getWorkflow` endpoint paths
- Add `WorkflowsListResponse` and `PublishedWorkflow` response models
- Add `WorkflowDetailProcessor` to handle `inline`/`use_cdn` response actions
- Add `WorkflowCdnFetcher` protocol and `DirectWorkflowCdnFetcher` implementation
- Add `GetWorkflowsOperation` and `GetWorkflowOperation` cacheable network operations
- Add `WorkflowsAPI` facade and wire into `Backend`
- Add unit tests for all new components

iOS equivalent of RevenueCat/purchases-android#3300

Made-with: Cursor

* fix xcodeproj

* reset Package.resolved

* Update WorkflowStep models to match actual backend response

- Add WorkflowTrigger struct (name, type, action_id, component_id)
- Add triggers, outputs, and metadata fields to WorkflowStep
- Add metadata field to PublishedWorkflow
- Remove value field from WorkflowTriggerAction (backend uses step_id only)

Made-with: Cursor

* Update WorkflowResponseTests to match updated models

- Replace value/resolvedTargetStepId assertions with stepId
- Remove testDecodeWorkflowTriggerActionValueTakesPrecedence (value field removed)
- Add testDecodeWorkflowTrigger for new WorkflowTrigger struct
- Add testDecodePublishedWorkflowWithMetadata
- Update testDecodeWorkflowStepDefaults to cover triggers, outputs, metadata
- Add testDecodeWorkflowStepMatchingActualBackendResponse with real backend payload

Made-with: Cursor

* Disable file_length lint rule in HTTPRequestPath.swift

Made-with: Cursor

* Fix GetWorkflowOperation to compute result once before distributing to callbacks

CDN fetch and JSON decoding were running once per deduplicated callback.
Compute the Result<WorkflowFetchResult, BackendError> once outside the
performOnAllItemsAndRemoveFromCache loop, matching the pattern used by
GetOfferingsOperation and other operations in the codebase.

Made-with: Cursor

* Fix CDN fetcher: use URLSession instead of Data(contentsOf:), classify errors correctly

- Replace Data(contentsOf:) with URLSession.shared.dataTask + DispatchSemaphore
  in DirectWorkflowCdnFetcher; gets URLSession timeout, HTTP status validation,
  and proper network stack semantics
- Add WorkflowDetailProcessingError.cdnFetchFailed typed error so CDN I/O
  failures are distinguishable from envelope parsing failures
- Catch cdnFetchFailed in GetWorkflowOperation and map to NetworkError.networkError
  instead of NetworkError.decoding, fixing misleading error classification
- Update WorkflowDetailProcessorTests to assert the typed error is thrown

Made-with: Cursor

* Replace semaphore CDN fetch with async/await using withCheckedThrowingContinuation

- Make WorkflowCdnFetcher.fetchCompiledWorkflowData async throws; use
  withCheckedThrowingContinuation to bridge URLSession.dataTask into async,
  avoiding any thread-blocking
- Make WorkflowDetailProcessor.process async throws to propagate async
- Bridge into async in GetWorkflowOperation via Task {}; completion() is
  called inside the Task after CDN fetch and decoding complete
- Update WorkflowDetailProcessorTests to async throws with await

Made-with: Cursor

* Make CDN fetcher and processor completion-handler based for consistency

Avoids Task{} in GetWorkflowOperation and keeps all operations calling
completion() synchronously from within the HTTP callback, matching every
other operation in the codebase.

- WorkflowCdnFetcher.fetchCompiledWorkflowData now takes a completion handler;
  DirectWorkflowCdnFetcher uses URLSession.dataTask (non-blocking, no semaphore)
- WorkflowDetailProcessor.process now takes a completion handler; inline action
  completes synchronously, use_cdn fans out to the fetcher callback
- GetWorkflowOperation splits into getWorkflow/handleResponse/backendResult/
  distribute helpers to stay within line-length limits
- WorkflowDetailProcessorTests updated to use waitUntilValue pattern

Made-with: Cursor

* Fix ambiguous cache key delimiter in GetWorkflowOperation

Space-separated appUserID+workflowId could collide (e.g. user 'a b' + workflow 'c'
== user 'a' + workflow 'b c'). Use newline as delimiter, matching the precedent
set by GetWebBillingProductsOperation.

Made-with: Cursor

* PR comments

* remove cdn fetcher

* fix response in BackendGetWorkflowsTests.swift

* fix WorkflowResponseTests

* fix error

* [skip ci] Generating new test snapshots (#6584)

* [skip ci] Generating new test snapshots (#6585)

* [skip ci] Generating new test snapshots (#6586)

* [skip ci] Generating new test snapshots (#6587)

* [skip ci] Generating new test snapshots (#6588)

* [skip ci] Generating new test snapshots (#6589)

* [skip ci] Generating new test snapshots (#6590)

* Test CDN mock is not re-assignable per test

* [skip ci] Generating new test snapshots (#6597)

* [skip ci] Generating new test snapshots (#6598)

* [skip ci] Generating new test snapshots (#6599)

* [skip ci] Generating new test snapshots (#6600)

* [skip ci] Generating new test snapshots (#6601)

* [skip ci] Generating new test snapshots (#6602)

* [skip ci] Generating new test snapshots (#6603)

* getWorkflow is signed

* add type parameter

* add response verification for CDN response

* remove workflows list

* add value to WorkflowTriggerAction

* step id

* hash and filerepo

* missingCdnHash

* linter and project

* fix project

* revert Package.resolved

* @unchecked Sendable

* use hash in generateOrGetCachedFileURL

* fix compilation

* skip responseVerificationMode in cdn

* change basePath of caches

* Add workflow-based paywall resolution (iOS parity with Android #3350)

Implements the workflows endpoint so paywalls can be resolved from a
workflow document rather than the offerings cache, gated behind the
ENABLE_WORKFLOWS_ENDPOINT compile flag. Validates the step→screen→
offeringId chain and maps the workflow screen into Offering.PaywallComponents
using the existing V2 rendering path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add tests for workflow networking layer and WorkflowScreenMapper

BackendGetWorkflowTests covers HTTP call, request caching, per-user and
per-workflow-ID deduplication, error propagation, empty appUserID, and
full response decoding. WorkflowScreenMapperTests verifies all
PaywallComponentsData fields and the UIConfig are mapped correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* remove reserialization

* Use explicit type in GetWorkflowOperation.createFactory for greppability

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Use explicit type in GetWorkflowOperation.createFactory for greppability

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix lint errors and refactor workflow offering resolution into PaywallViewConfiguration

- Add swiftlint:disable comments for _revision identifier name in WorkflowsResponse.swift
- Add missing_docs disable in WorkflowsResponse.swift (all @_spi(Internal) types)
- Add file_length disable in Offering.swift
- Fix identifier name 'd' → 'doubleValue' in WorkflowDetailProcessorTests.swift
- Move workflow offering resolution from PaywallView into PaywallViewConfiguration.resolveOfferingOrThrow()
- Add cachedInitialOffering() to PaywallViewConfiguration.Content
- Add PaywallViewConfigurationTests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix swiftlint identifier_name violation in WorkflowDetailProcessorTests

Rename short variable `d` to `doubleValue` in two `if case .double(let d)`
patterns to satisfy the identifier_name rule requiring names >= 3 characters.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Inject PaywallPurchasesType into offering resolution; remove dead code

- Add offerings(), cachedOfferings, and workflow() to PaywallPurchasesType
  so PaywallViewConfiguration.Content no longer calls Purchases.shared directly
- Thread purchases through cachedInitialOffering/resolveOffering/resolveOfferingOrThrow
  and all private helpers; callers pass purchaseHandler.purchasesInstance
- Expose purchasesInstance on PurchaseHandler for call sites
- Add stubs to NotConfiguredPurchases, LoadingPaywallPurchases, and MockPurchases
  (MockPurchases gets offeringsBlock/workflowBlock for test injection)
- Remove dead WorkflowDetailProcessingError.missingInlineData case
- Replace manual JSONSerialization parseEnvelope with Data.asJSONDictionary()
- Collapse double #if nesting into single compound condition
- Remove WHAT doc comment from WorkflowDetailProcessor
- Refactor resolveOffering() to delegate to resolveOfferingOrThrow()
- Parallelize workflow + offerings fetches with async let

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Move offering resolution from Content to PurchaseHandler

Resolving an offering requires a purchases instance, so it belongs on
PurchaseHandler rather than as a method on the Content enum that takes
purchases as a parameter. purchasesInstance accessor removed.

Also fix MockPurchases.map() to forward offeringsBlock, workflowBlock,
and cachedOfferings to the mapped instance, and simplify
PaywallViewConfigurationTests to use MockPurchases directly instead of
configuring a real Purchases singleton.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add clarifying comment to workflow(forOfferingIdentifier:) explaining parameter naming

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix CI failures: remove duplicate switch cases and add @_spi(Internal) to test imports

- Remove duplicate .getWorkflow cases in HTTPRequestPath.swift switches
  (auto-merge artifact that caused duplicate_conditions lint violations)
- Add @_spi(Internal) to @testable imports in three unit test files so they
  can access WorkflowFetchResult, PublishedWorkflow, and WorkflowStep which
  are now @_spi(Internal) public

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix @_spi access in WorkflowDetailProcessorTests

WorkflowResponseTests already had @_spi(Internal) @testable import RevenueCat;
WorkflowDetailProcessorTests was missing the @_spi annotation, causing
'id', 'steps', and 'initialStepId' to be inaccessible on PublishedWorkflow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix MockWorkflowsAPI.swift missing from StoreKitUnitTests and UnitTests targets

The PBXBuildFile entries for DB7EA77F2F9764F700BCC082 and DB7EA7802F9764F700BCC082
were referenced in the StoreKitUnitTests and UnitTests source phases but never
defined, causing MockWorkflowsAPI to be missing at compile time and
MockBackend.swift to fail with 'cannot find MockWorkflowsAPI in scope'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add offering_identifier field to WorkflowScreen for parity with Android

Android PR #3350 split offering_id (internal ID) from offering_identifier
(human-readable identifier used for lookup). iOS was only decoding
offering_id and using it for the offering lookup, which would fail if the
workflow response sends the public identifier in offering_identifier.

- Add offeringIdentifier (from offering_identifier) to WorkflowScreen
- Update PurchaseHandler to prefer offeringIdentifier, falling back to offeringId
- Fix typo: workflow.screelns -> workflow.screens
- Add decode tests for both offering fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix WorkflowResponseTests: add missing padding field to stack component fixtures

Stack component decoding requires a padding field; test JSON fixtures were
missing it causing keyNotFound decode errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix WorkflowResponseTests: add missing margin field to stack component fixtures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Replace ENABLE_WORKFLOWS_ENDPOINT compile flag with runtime argument check

The -EnableWorkflowsEndpoint launch argument replaces the compile-time flag,
allowing workflow resolution to be toggled without rebuilding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add Workflow presentation mode to PaywallsTester

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Remove offeringId shim, allow no-paywall offerings to open as workflows

- Drop WorkflowScreen.offeringId / offering_id CodingKey (Android already
  uses offering_identifier exclusively — aligns both platforms)
- Simplify PurchaseHandler to use screen.offeringIdentifier directly
- No-paywall offerings in PaywallsTester now render as tappable rows
  (workflow mode) instead of plain read-only text, with a context menu
  restricted to the Workflow option

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Rename WorkflowFetchResult to WorkflowDataResult, extract ProcessInfo extension

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix project

---------

Co-authored-by: Cesar de la Vega <664544+vegaro@users.noreply.github.com>
Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
facumenzella added a commit that referenced this pull request Apr 23, 2026
* Add workflows network layer for multipage paywalls

- Add `getWorkflows` and `getWorkflow` endpoint paths
- Add `WorkflowsListResponse` and `PublishedWorkflow` response models
- Add `WorkflowDetailProcessor` to handle `inline`/`use_cdn` response actions
- Add `WorkflowCdnFetcher` protocol and `DirectWorkflowCdnFetcher` implementation
- Add `GetWorkflowsOperation` and `GetWorkflowOperation` cacheable network operations
- Add `WorkflowsAPI` facade and wire into `Backend`
- Add unit tests for all new components

iOS equivalent of RevenueCat/purchases-android#3300

Made-with: Cursor

* fix xcodeproj

* reset Package.resolved

* Update WorkflowStep models to match actual backend response

- Add WorkflowTrigger struct (name, type, action_id, component_id)
- Add triggers, outputs, and metadata fields to WorkflowStep
- Add metadata field to PublishedWorkflow
- Remove value field from WorkflowTriggerAction (backend uses step_id only)

Made-with: Cursor

* Update WorkflowResponseTests to match updated models

- Replace value/resolvedTargetStepId assertions with stepId
- Remove testDecodeWorkflowTriggerActionValueTakesPrecedence (value field removed)
- Add testDecodeWorkflowTrigger for new WorkflowTrigger struct
- Add testDecodePublishedWorkflowWithMetadata
- Update testDecodeWorkflowStepDefaults to cover triggers, outputs, metadata
- Add testDecodeWorkflowStepMatchingActualBackendResponse with real backend payload

Made-with: Cursor

* Disable file_length lint rule in HTTPRequestPath.swift

Made-with: Cursor

* Fix GetWorkflowOperation to compute result once before distributing to callbacks

CDN fetch and JSON decoding were running once per deduplicated callback.
Compute the Result<WorkflowFetchResult, BackendError> once outside the
performOnAllItemsAndRemoveFromCache loop, matching the pattern used by
GetOfferingsOperation and other operations in the codebase.

Made-with: Cursor

* Fix CDN fetcher: use URLSession instead of Data(contentsOf:), classify errors correctly

- Replace Data(contentsOf:) with URLSession.shared.dataTask + DispatchSemaphore
  in DirectWorkflowCdnFetcher; gets URLSession timeout, HTTP status validation,
  and proper network stack semantics
- Add WorkflowDetailProcessingError.cdnFetchFailed typed error so CDN I/O
  failures are distinguishable from envelope parsing failures
- Catch cdnFetchFailed in GetWorkflowOperation and map to NetworkError.networkError
  instead of NetworkError.decoding, fixing misleading error classification
- Update WorkflowDetailProcessorTests to assert the typed error is thrown

Made-with: Cursor

* Replace semaphore CDN fetch with async/await using withCheckedThrowingContinuation

- Make WorkflowCdnFetcher.fetchCompiledWorkflowData async throws; use
  withCheckedThrowingContinuation to bridge URLSession.dataTask into async,
  avoiding any thread-blocking
- Make WorkflowDetailProcessor.process async throws to propagate async
- Bridge into async in GetWorkflowOperation via Task {}; completion() is
  called inside the Task after CDN fetch and decoding complete
- Update WorkflowDetailProcessorTests to async throws with await

Made-with: Cursor

* Make CDN fetcher and processor completion-handler based for consistency

Avoids Task{} in GetWorkflowOperation and keeps all operations calling
completion() synchronously from within the HTTP callback, matching every
other operation in the codebase.

- WorkflowCdnFetcher.fetchCompiledWorkflowData now takes a completion handler;
  DirectWorkflowCdnFetcher uses URLSession.dataTask (non-blocking, no semaphore)
- WorkflowDetailProcessor.process now takes a completion handler; inline action
  completes synchronously, use_cdn fans out to the fetcher callback
- GetWorkflowOperation splits into getWorkflow/handleResponse/backendResult/
  distribute helpers to stay within line-length limits
- WorkflowDetailProcessorTests updated to use waitUntilValue pattern

Made-with: Cursor

* Fix ambiguous cache key delimiter in GetWorkflowOperation

Space-separated appUserID+workflowId could collide (e.g. user 'a b' + workflow 'c'
== user 'a' + workflow 'b c'). Use newline as delimiter, matching the precedent
set by GetWebBillingProductsOperation.

Made-with: Cursor

* PR comments

* remove cdn fetcher

* fix response in BackendGetWorkflowsTests.swift

* fix WorkflowResponseTests

* fix error

* [skip ci] Generating new test snapshots (#6584)

* [skip ci] Generating new test snapshots (#6585)

* [skip ci] Generating new test snapshots (#6586)

* [skip ci] Generating new test snapshots (#6587)

* [skip ci] Generating new test snapshots (#6588)

* [skip ci] Generating new test snapshots (#6589)

* [skip ci] Generating new test snapshots (#6590)

* Test CDN mock is not re-assignable per test

* [skip ci] Generating new test snapshots (#6597)

* [skip ci] Generating new test snapshots (#6598)

* [skip ci] Generating new test snapshots (#6599)

* [skip ci] Generating new test snapshots (#6600)

* [skip ci] Generating new test snapshots (#6601)

* [skip ci] Generating new test snapshots (#6602)

* [skip ci] Generating new test snapshots (#6603)

* getWorkflow is signed

* add type parameter

* add response verification for CDN response

* remove workflows list

* add value to WorkflowTriggerAction

* step id

* hash and filerepo

* missingCdnHash

* linter and project

* fix project

* revert Package.resolved

* @unchecked Sendable

* use hash in generateOrGetCachedFileURL

* fix compilation

* skip responseVerificationMode in cdn

* change basePath of caches

* Add workflow-based paywall resolution (iOS parity with Android #3350)

Implements the workflows endpoint so paywalls can be resolved from a
workflow document rather than the offerings cache, gated behind the
ENABLE_WORKFLOWS_ENDPOINT compile flag. Validates the step→screen→
offeringId chain and maps the workflow screen into Offering.PaywallComponents
using the existing V2 rendering path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add tests for workflow networking layer and WorkflowScreenMapper

BackendGetWorkflowTests covers HTTP call, request caching, per-user and
per-workflow-ID deduplication, error propagation, empty appUserID, and
full response decoding. WorkflowScreenMapperTests verifies all
PaywallComponentsData fields and the UIConfig are mapped correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* remove reserialization

* Use explicit type in GetWorkflowOperation.createFactory for greppability

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Use explicit type in GetWorkflowOperation.createFactory for greppability

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix lint errors and refactor workflow offering resolution into PaywallViewConfiguration

- Add swiftlint:disable comments for _revision identifier name in WorkflowsResponse.swift
- Add missing_docs disable in WorkflowsResponse.swift (all @_spi(Internal) types)
- Add file_length disable in Offering.swift
- Fix identifier name 'd' → 'doubleValue' in WorkflowDetailProcessorTests.swift
- Move workflow offering resolution from PaywallView into PaywallViewConfiguration.resolveOfferingOrThrow()
- Add cachedInitialOffering() to PaywallViewConfiguration.Content
- Add PaywallViewConfigurationTests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix swiftlint identifier_name violation in WorkflowDetailProcessorTests

Rename short variable `d` to `doubleValue` in two `if case .double(let d)`
patterns to satisfy the identifier_name rule requiring names >= 3 characters.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Inject PaywallPurchasesType into offering resolution; remove dead code

- Add offerings(), cachedOfferings, and workflow() to PaywallPurchasesType
  so PaywallViewConfiguration.Content no longer calls Purchases.shared directly
- Thread purchases through cachedInitialOffering/resolveOffering/resolveOfferingOrThrow
  and all private helpers; callers pass purchaseHandler.purchasesInstance
- Expose purchasesInstance on PurchaseHandler for call sites
- Add stubs to NotConfiguredPurchases, LoadingPaywallPurchases, and MockPurchases
  (MockPurchases gets offeringsBlock/workflowBlock for test injection)
- Remove dead WorkflowDetailProcessingError.missingInlineData case
- Replace manual JSONSerialization parseEnvelope with Data.asJSONDictionary()
- Collapse double #if nesting into single compound condition
- Remove WHAT doc comment from WorkflowDetailProcessor
- Refactor resolveOffering() to delegate to resolveOfferingOrThrow()
- Parallelize workflow + offerings fetches with async let

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Move offering resolution from Content to PurchaseHandler

Resolving an offering requires a purchases instance, so it belongs on
PurchaseHandler rather than as a method on the Content enum that takes
purchases as a parameter. purchasesInstance accessor removed.

Also fix MockPurchases.map() to forward offeringsBlock, workflowBlock,
and cachedOfferings to the mapped instance, and simplify
PaywallViewConfigurationTests to use MockPurchases directly instead of
configuring a real Purchases singleton.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add clarifying comment to workflow(forOfferingIdentifier:) explaining parameter naming

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix CI failures: remove duplicate switch cases and add @_spi(Internal) to test imports

- Remove duplicate .getWorkflow cases in HTTPRequestPath.swift switches
  (auto-merge artifact that caused duplicate_conditions lint violations)
- Add @_spi(Internal) to @testable imports in three unit test files so they
  can access WorkflowFetchResult, PublishedWorkflow, and WorkflowStep which
  are now @_spi(Internal) public

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix @_spi access in WorkflowDetailProcessorTests

WorkflowResponseTests already had @_spi(Internal) @testable import RevenueCat;
WorkflowDetailProcessorTests was missing the @_spi annotation, causing
'id', 'steps', and 'initialStepId' to be inaccessible on PublishedWorkflow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix MockWorkflowsAPI.swift missing from StoreKitUnitTests and UnitTests targets

The PBXBuildFile entries for DB7EA77F2F9764F700BCC082 and DB7EA7802F9764F700BCC082
were referenced in the StoreKitUnitTests and UnitTests source phases but never
defined, causing MockWorkflowsAPI to be missing at compile time and
MockBackend.swift to fail with 'cannot find MockWorkflowsAPI in scope'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add offering_identifier field to WorkflowScreen for parity with Android

Android PR #3350 split offering_id (internal ID) from offering_identifier
(human-readable identifier used for lookup). iOS was only decoding
offering_id and using it for the offering lookup, which would fail if the
workflow response sends the public identifier in offering_identifier.

- Add offeringIdentifier (from offering_identifier) to WorkflowScreen
- Update PurchaseHandler to prefer offeringIdentifier, falling back to offeringId
- Fix typo: workflow.screelns -> workflow.screens
- Add decode tests for both offering fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix WorkflowResponseTests: add missing padding field to stack component fixtures

Stack component decoding requires a padding field; test JSON fixtures were
missing it causing keyNotFound decode errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix WorkflowResponseTests: add missing margin field to stack component fixtures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Replace ENABLE_WORKFLOWS_ENDPOINT compile flag with runtime argument check

The -EnableWorkflowsEndpoint launch argument replaces the compile-time flag,
allowing workflow resolution to be toggled without rebuilding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add Workflow presentation mode to PaywallsTester

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Remove offeringId shim, allow no-paywall offerings to open as workflows

- Drop WorkflowScreen.offeringId / offering_id CodingKey (Android already
  uses offering_identifier exclusively — aligns both platforms)
- Simplify PurchaseHandler to use screen.offeringIdentifier directly
- No-paywall offerings in PaywallsTester now render as tappable rows
  (workflow mode) instead of plain read-only text, with a context menu
  restricted to the Workflow option

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Rename WorkflowFetchResult to WorkflowDataResult, extract ProcessInfo extension

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix project

* Fix Customer Center showing active-subscriber management options to expired users

hasActiveSubscription was checking subscriptionsSection non-emptiness, but that
section is intentionally populated with the most recent expired subscription when
there are no active ones (via loadMostRecentExpiredTransaction). The fix checks
whether any entry in the section is non-expired, which correctly answers
"does this customer have an active subscription?" regardless of how many
subscriptions exist.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Cesar de la Vega <664544+vegaro@users.noreply.github.com>
Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants