From 10a4c2613c00f4a89e90a3434541f89c0d9d7c43 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Mon, 6 Apr 2026 17:14:35 +0200 Subject: [PATCH 01/24] 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 https://github.com/RevenueCat/purchases-android/pull/3300 Made-with: Cursor --- Package.resolved | 18 + Sources/Networking/Backend.swift | 9 +- .../Caching/WorkflowsCallback.swift | 28 ++ .../HTTPClient/HTTPRequestPath.swift | 22 ++ .../Operations/GetWorkflowOperation.swift | 110 ++++++ .../Operations/GetWorkflowsOperation.swift | 84 +++++ .../Responses/WorkflowsResponse.swift | 121 +++++++ .../Workflows/WorkflowCdnFetcher.swift | 33 ++ .../Workflows/WorkflowDetailProcessor.swift | 74 ++++ .../Workflows/WorkflowResponseAction.swift | 19 ++ Sources/Networking/WorkflowsAPI.swift | 81 +++++ Tests/UnitTests/Mocks/MockBackend.swift | 4 +- .../Backend/BackendGetWorkflowsTests.swift | 315 ++++++++++++++++++ .../Networking/Backend/BaseBackendTest.swift | 5 +- .../WorkflowDetailProcessorTests.swift | 135 ++++++++ .../Workflows/WorkflowResponseTests.swift | 139 ++++++++ 16 files changed, 1193 insertions(+), 4 deletions(-) create mode 100644 Sources/Networking/Caching/WorkflowsCallback.swift create mode 100644 Sources/Networking/Operations/GetWorkflowOperation.swift create mode 100644 Sources/Networking/Operations/GetWorkflowsOperation.swift create mode 100644 Sources/Networking/Responses/WorkflowsResponse.swift create mode 100644 Sources/Networking/Workflows/WorkflowCdnFetcher.swift create mode 100644 Sources/Networking/Workflows/WorkflowDetailProcessor.swift create mode 100644 Sources/Networking/Workflows/WorkflowResponseAction.swift create mode 100644 Sources/Networking/WorkflowsAPI.swift create mode 100644 Tests/UnitTests/Networking/Backend/BackendGetWorkflowsTests.swift create mode 100644 Tests/UnitTests/Networking/Workflows/WorkflowDetailProcessorTests.swift create mode 100644 Tests/UnitTests/Networking/Workflows/WorkflowResponseTests.swift diff --git a/Package.resolved b/Package.resolved index ab65af5512..71eaec8b66 100644 --- a/Package.resolved +++ b/Package.resolved @@ -36,6 +36,24 @@ "version" : "1.4.1" } }, + { + "identity" : "swift-package-manager-google-mobile-ads", + "kind" : "remoteSourceControl", + "location" : "https://github.com/googleads/swift-package-manager-google-mobile-ads.git", + "state" : { + "revision" : "8d08e4396ea955a7d2baf067c0eedfc34ca0821d", + "version" : "13.2.0" + } + }, + { + "identity" : "swift-package-manager-google-user-messaging-platform", + "kind" : "remoteSourceControl", + "location" : "https://github.com/googleads/swift-package-manager-google-user-messaging-platform.git", + "state" : { + "revision" : "13b248eaa73b7826f0efb1bcf455e251d65ecb1b", + "version" : "3.1.0" + } + }, { "identity" : "swift-snapshot-testing", "kind" : "remoteSourceControl", diff --git a/Sources/Networking/Backend.swift b/Sources/Networking/Backend.swift index af93ac1ce5..6e73225649 100644 --- a/Sources/Networking/Backend.swift +++ b/Sources/Networking/Backend.swift @@ -24,6 +24,7 @@ class Backend { let customerCenterConfig: CustomerCenterConfigAPI let redeemWebPurchaseAPI: RedeemWebPurchaseAPI let virtualCurrenciesAPI: VirtualCurrenciesAPI + let workflowsAPI: WorkflowsAPI private let config: BackendConfiguration @@ -65,6 +66,7 @@ class Backend { let customerCenterConfig = CustomerCenterConfigAPI(backendConfig: backendConfig) let redeemWebPurchaseAPI = RedeemWebPurchaseAPI(backendConfig: backendConfig) let virtualCurrenciesAPI = VirtualCurrenciesAPI(backendConfig: backendConfig) + let workflowsAPI = WorkflowsAPI(backendConfig: backendConfig) self.init(backendConfig: backendConfig, customerAPI: customer, @@ -75,7 +77,8 @@ class Backend { internalAPI: internalAPI, customerCenterConfig: customerCenterConfig, redeemWebPurchaseAPI: redeemWebPurchaseAPI, - virtualCurrenciesAPI: virtualCurrenciesAPI) + virtualCurrenciesAPI: virtualCurrenciesAPI, + workflowsAPI: workflowsAPI) } required init(backendConfig: BackendConfiguration, @@ -87,7 +90,8 @@ class Backend { internalAPI: InternalAPI, customerCenterConfig: CustomerCenterConfigAPI, redeemWebPurchaseAPI: RedeemWebPurchaseAPI, - virtualCurrenciesAPI: VirtualCurrenciesAPI) { + virtualCurrenciesAPI: VirtualCurrenciesAPI, + workflowsAPI: WorkflowsAPI) { self.config = backendConfig self.customer = customerAPI @@ -99,6 +103,7 @@ class Backend { self.customerCenterConfig = customerCenterConfig self.redeemWebPurchaseAPI = redeemWebPurchaseAPI self.virtualCurrenciesAPI = virtualCurrenciesAPI + self.workflowsAPI = workflowsAPI } func clearHTTPClientCaches() { diff --git a/Sources/Networking/Caching/WorkflowsCallback.swift b/Sources/Networking/Caching/WorkflowsCallback.swift new file mode 100644 index 0000000000..19e2ae32cf --- /dev/null +++ b/Sources/Networking/Caching/WorkflowsCallback.swift @@ -0,0 +1,28 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// WorkflowsCallback.swift +// +// Created by RevenueCat. + +import Foundation + +struct WorkflowsListCallback: CacheKeyProviding { + + let cacheKey: String + let completion: (Result) -> Void + +} + +struct WorkflowDetailCallback: CacheKeyProviding { + + let cacheKey: String + let completion: (Result) -> Void + +} diff --git a/Sources/Networking/HTTPClient/HTTPRequestPath.swift b/Sources/Networking/HTTPClient/HTTPRequestPath.swift index fb6108eba4..66459be39b 100644 --- a/Sources/Networking/HTTPClient/HTTPRequestPath.swift +++ b/Sources/Networking/HTTPClient/HTTPRequestPath.swift @@ -101,6 +101,8 @@ extension HTTPRequest { case getProductEntitlementMapping case getCustomerCenterConfig(appUserID: String) case getVirtualCurrencies(appUserID: String) + case getWorkflows(appUserID: String) + case getWorkflow(appUserID: String, workflowId: String) case postRedeemWebPurchase case postCreateTicket case isPurchaseAllowedByRestoreBehavior(appUserID: String) @@ -187,6 +189,8 @@ extension HTTPRequest.Path: HTTPRequestPath { .getProductEntitlementMapping, .getCustomerCenterConfig, .getVirtualCurrencies, + .getWorkflows, + .getWorkflow, .appHealthReport, .postCreateTicket, .isPurchaseAllowedByRestoreBehavior: @@ -213,6 +217,8 @@ extension HTTPRequest.Path: HTTPRequestPath { .getProductEntitlementMapping, .getCustomerCenterConfig, .getVirtualCurrencies, + .getWorkflows, + .getWorkflow, .appHealthReport, .postCreateTicket, .isPurchaseAllowedByRestoreBehavior: @@ -232,6 +238,7 @@ extension HTTPRequest.Path: HTTPRequestPath { .getOfferings, .getProductEntitlementMapping, .getVirtualCurrencies, + .getWorkflows, .appHealthReport, .appHealthReportAvailability, .isPurchaseAllowedByRestoreBehavior: @@ -243,6 +250,7 @@ extension HTTPRequest.Path: HTTPRequestPath { .postOfferForSigning, .postRedeemWebPurchase, .getCustomerCenterConfig, + .getWorkflow, .postCreateTicket: return false } @@ -267,6 +275,8 @@ extension HTTPRequest.Path: HTTPRequestPath { .postRedeemWebPurchase, .getProductEntitlementMapping, .getCustomerCenterConfig, + .getWorkflows, + .getWorkflow, .appHealthReport, .postCreateTicket: return false @@ -327,6 +337,12 @@ extension HTTPRequest.Path: HTTPRequestPath { case let .getVirtualCurrencies(appUserID): return "subscribers/\(Self.escape(appUserID))/virtual_currencies" + case let .getWorkflows(appUserID): + return "subscribers/\(Self.escape(appUserID))/workflows" + + case let .getWorkflow(appUserID, workflowId): + return "subscribers/\(Self.escape(appUserID))/workflows/\(Self.escape(workflowId))" + case .postCreateTicket: return "customercenter/support/create-ticket" case let .isPurchaseAllowedByRestoreBehavior(appUserID): @@ -381,6 +397,12 @@ extension HTTPRequest.Path: HTTPRequestPath { case .getVirtualCurrencies: return "get_virtual_currencies" + case .getWorkflows: + return "get_workflows" + + case .getWorkflow: + return "get_workflow" + case .appHealthReportAvailability: return "get_app_health_report_availability" diff --git a/Sources/Networking/Operations/GetWorkflowOperation.swift b/Sources/Networking/Operations/GetWorkflowOperation.swift new file mode 100644 index 0000000000..f431af53e1 --- /dev/null +++ b/Sources/Networking/Operations/GetWorkflowOperation.swift @@ -0,0 +1,110 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// GetWorkflowOperation.swift +// +// Created by RevenueCat. + +import Foundation + +final class GetWorkflowOperation: CacheableNetworkOperation { + + private let workflowDetailCallbackCache: CallbackCache + private let configuration: AppUserConfiguration + private let workflowId: String + private let detailProcessor: WorkflowDetailProcessor + + static func createFactory( + configuration: UserSpecificConfiguration, + workflowId: String, + detailProcessor: WorkflowDetailProcessor, + workflowDetailCallbackCache: CallbackCache + ) -> CacheableNetworkOperationFactory { + return .init({ cacheKey in + .init( + configuration: configuration, + workflowId: workflowId, + detailProcessor: detailProcessor, + workflowDetailCallbackCache: workflowDetailCallbackCache, + cacheKey: cacheKey + ) + }, + individualizedCacheKeyPart: "\(configuration.appUserID) \(workflowId)") + } + + private init(configuration: UserSpecificConfiguration, + workflowId: String, + detailProcessor: WorkflowDetailProcessor, + workflowDetailCallbackCache: CallbackCache, + cacheKey: String) { + self.configuration = configuration + self.workflowId = workflowId + self.detailProcessor = detailProcessor + self.workflowDetailCallbackCache = workflowDetailCallbackCache + + super.init(configuration: configuration, cacheKey: cacheKey) + } + + override func begin(completion: @escaping () -> Void) { + self.getWorkflow(completion: completion) + } + +} + +// Restating inherited @unchecked Sendable from Foundation's Operation +extension GetWorkflowOperation: @unchecked Sendable {} + +private extension GetWorkflowOperation { + + func getWorkflow(completion: @escaping () -> Void) { + let appUserID = self.configuration.appUserID + + guard appUserID.isNotEmpty else { + self.workflowDetailCallbackCache.performOnAllItemsAndRemoveFromCache(withCacheable: self) { callback in + callback.completion(.failure(.missingAppUserID())) + } + completion() + return + } + + let request = HTTPRequest( + method: .get, + path: .getWorkflow(appUserID: appUserID, workflowId: self.workflowId) + ) + + httpClient.perform(request) { (response: VerifiedHTTPResponse.Result) in + defer { + completion() + } + + self.workflowDetailCallbackCache.performOnAllItemsAndRemoveFromCache( + withCacheable: self + ) { callbackObject in + let result: Result = response + .mapError(BackendError.networkError) + .flatMap { verifiedResponse in + do { + let processed = try self.detailProcessor.process(verifiedResponse.body) + let workflow = try PublishedWorkflow.create(with: processed.workflowData) + return .success(WorkflowFetchResult( + workflow: workflow, + enrolledVariants: processed.enrolledVariants + )) + } catch { + return .failure(BackendError.networkError( + NetworkError.decoding(error, verifiedResponse.body) + )) + } + } + callbackObject.completion(result) + } + } + } + +} diff --git a/Sources/Networking/Operations/GetWorkflowsOperation.swift b/Sources/Networking/Operations/GetWorkflowsOperation.swift new file mode 100644 index 0000000000..fe1ab7babe --- /dev/null +++ b/Sources/Networking/Operations/GetWorkflowsOperation.swift @@ -0,0 +1,84 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// GetWorkflowsOperation.swift +// +// Created by RevenueCat. + +import Foundation + +final class GetWorkflowsOperation: CacheableNetworkOperation { + + private let workflowsCallbackCache: CallbackCache + private let configuration: AppUserConfiguration + + static func createFactory( + configuration: UserSpecificConfiguration, + workflowsCallbackCache: CallbackCache + ) -> CacheableNetworkOperationFactory { + return .init({ cacheKey in + .init( + configuration: configuration, + workflowsCallbackCache: workflowsCallbackCache, + cacheKey: cacheKey + ) + }, + individualizedCacheKeyPart: configuration.appUserID) + } + + private init(configuration: UserSpecificConfiguration, + workflowsCallbackCache: CallbackCache, + cacheKey: String) { + self.configuration = configuration + self.workflowsCallbackCache = workflowsCallbackCache + + super.init(configuration: configuration, cacheKey: cacheKey) + } + + override func begin(completion: @escaping () -> Void) { + self.getWorkflows(completion: completion) + } + +} + +// Restating inherited @unchecked Sendable from Foundation's Operation +extension GetWorkflowsOperation: @unchecked Sendable {} + +private extension GetWorkflowsOperation { + + func getWorkflows(completion: @escaping () -> Void) { + let appUserID = self.configuration.appUserID + + guard appUserID.isNotEmpty else { + self.workflowsCallbackCache.performOnAllItemsAndRemoveFromCache(withCacheable: self) { callback in + callback.completion(.failure(.missingAppUserID())) + } + completion() + return + } + + let request = HTTPRequest(method: .get, path: .getWorkflows(appUserID: appUserID)) + + httpClient.perform(request) { (response: VerifiedHTTPResponse.Result) in + defer { + completion() + } + + self.workflowsCallbackCache.performOnAllItemsAndRemoveFromCache( + withCacheable: self + ) { callbackObject in + callbackObject.completion(response + .map { $0.body } + .mapError(BackendError.networkError) + ) + } + } + } + +} diff --git a/Sources/Networking/Responses/WorkflowsResponse.swift b/Sources/Networking/Responses/WorkflowsResponse.swift new file mode 100644 index 0000000000..7aa2cf5083 --- /dev/null +++ b/Sources/Networking/Responses/WorkflowsResponse.swift @@ -0,0 +1,121 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// WorkflowsResponse.swift +// +// Created by RevenueCat. + +import Foundation + +// MARK: - List response + +struct WorkflowSummary { + + let id: String + let displayName: String + +} + +struct WorkflowsListResponse { + + let workflows: [WorkflowSummary] + let uiConfig: UIConfig + +} + +// MARK: - Detail models + +struct WorkflowTriggerAction { + + let type: String + let value: String? + let stepId: String? + + var resolvedTargetStepId: String? { + return value ?? stepId + } + +} + +struct WorkflowStep { + + let id: String + let type: String + let screenId: String? + @DefaultDecodable.EmptyDictionary + var paramValues: [String: AnyDecodable] + @DefaultDecodable.EmptyDictionary + var triggerActions: [String: WorkflowTriggerAction] + +} + +struct WorkflowScreen { + + let name: String? + let templateName: String + @DefaultDecodable.Zero + var revision: Int + let assetBaseURL: URL + let componentsConfig: PaywallComponentsData.ComponentsConfig + let componentsLocalizations: [PaywallComponent.LocaleID: PaywallComponent.LocalizationDictionary] + let defaultLocale: PaywallComponent.LocaleID + @DefaultDecodable.EmptyDictionary + var config: [String: AnyDecodable] + let offeringId: String? + +} + +struct PublishedWorkflow { + + let id: String + let displayName: String + let initialStepId: String + let steps: [String: WorkflowStep] + let screens: [String: WorkflowScreen] + let uiConfig: UIConfig + let contentMaxWidth: Int? + +} + +struct WorkflowFetchResult { + + let workflow: PublishedWorkflow + let enrolledVariants: [String: String]? + +} + +// MARK: - Codable + +extension WorkflowSummary: Codable, Equatable, Sendable {} +extension WorkflowsListResponse: Codable, Equatable, Sendable {} + +extension WorkflowTriggerAction: Codable, Equatable, Sendable {} +extension WorkflowStep: Codable, Equatable, Sendable {} + +extension WorkflowScreen: Codable, Equatable, Sendable { + + private enum CodingKeys: String, CodingKey { + case name + case templateName + case revision + case assetBaseURL = "assetBaseUrl" + case componentsConfig + case componentsLocalizations + case defaultLocale + case config + case offeringId + } + +} + +extension PublishedWorkflow: Codable, Equatable, Sendable {} +extension WorkflowFetchResult: Equatable, Sendable {} + +extension WorkflowsListResponse: HTTPResponseBody {} +extension PublishedWorkflow: HTTPResponseBody {} diff --git a/Sources/Networking/Workflows/WorkflowCdnFetcher.swift b/Sources/Networking/Workflows/WorkflowCdnFetcher.swift new file mode 100644 index 0000000000..163574fce6 --- /dev/null +++ b/Sources/Networking/Workflows/WorkflowCdnFetcher.swift @@ -0,0 +1,33 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// WorkflowCdnFetcher.swift +// +// Created by RevenueCat. + +import Foundation + +/// Fetches compiled workflow JSON from a CDN URL. +protocol WorkflowCdnFetcher: Sendable { + + func fetchCompiledWorkflowData(cdnUrl: String) throws -> Data + +} + +/// Direct URL fetcher — downloads from the CDN URL synchronously. +final class DirectWorkflowCdnFetcher: WorkflowCdnFetcher { + + func fetchCompiledWorkflowData(cdnUrl: String) throws -> Data { + guard let url = URL(string: cdnUrl) else { + throw URLError(.badURL) + } + return try Data(contentsOf: url) + } + +} diff --git a/Sources/Networking/Workflows/WorkflowDetailProcessor.swift b/Sources/Networking/Workflows/WorkflowDetailProcessor.swift new file mode 100644 index 0000000000..3b209a8dfe --- /dev/null +++ b/Sources/Networking/Workflows/WorkflowDetailProcessor.swift @@ -0,0 +1,74 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// WorkflowDetailProcessor.swift +// +// Created by RevenueCat. + +import Foundation + +struct WorkflowDetailProcessingResult { + + let workflowData: Data + let enrolledVariants: [String: String]? + +} + +/// Normalizes a successful workflow-detail HTTP payload: +/// `inline` (unwraps `data`) or `use_cdn` (fetches JSON from CDN). +final class WorkflowDetailProcessor: Sendable { + + private let cdnFetcher: WorkflowCdnFetcher + + init(cdnFetcher: WorkflowCdnFetcher) { + self.cdnFetcher = cdnFetcher + } + + func process(_ data: Data) throws -> WorkflowDetailProcessingResult { + let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] + guard let json else { + throw Self.processingError("Failed to parse workflow detail envelope as JSON dictionary") + } + + let enrolledVariants = (json["enrolled_variants"] as? [String: String]) + + guard let actionString = json["action"] as? String, + let action = WorkflowResponseAction(rawValue: actionString) else { + let actionValue = json["action"] as? String ?? "nil" + throw Self.processingError("Unknown workflow response action: \(actionValue)") + } + + let workflowData: Data + switch action { + case .inline: + guard let inlineData = json["data"] else { + throw Self.processingError("Missing 'data' in inline workflow response") + } + workflowData = try JSONSerialization.data(withJSONObject: inlineData) + + case .useCdn: + guard let cdnUrl = json["url"] as? String else { + throw Self.processingError("Missing 'url' in use_cdn workflow response") + } + workflowData = try self.cdnFetcher.fetchCompiledWorkflowData(cdnUrl: cdnUrl) + } + + return WorkflowDetailProcessingResult( + workflowData: workflowData, + enrolledVariants: enrolledVariants + ) + } + + private static func processingError(_ message: String) -> Error { + NSError(domain: "RevenueCat.WorkflowDetailProcessor", + code: -1, + userInfo: [NSLocalizedDescriptionKey: message]) + } + +} diff --git a/Sources/Networking/Workflows/WorkflowResponseAction.swift b/Sources/Networking/Workflows/WorkflowResponseAction.swift new file mode 100644 index 0000000000..68a697fac1 --- /dev/null +++ b/Sources/Networking/Workflows/WorkflowResponseAction.swift @@ -0,0 +1,19 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// WorkflowResponseAction.swift +// +// Created by RevenueCat. + +import Foundation + +enum WorkflowResponseAction: String { + case inline + case useCdn = "use_cdn" +} diff --git a/Sources/Networking/WorkflowsAPI.swift b/Sources/Networking/WorkflowsAPI.swift new file mode 100644 index 0000000000..30a77b1a50 --- /dev/null +++ b/Sources/Networking/WorkflowsAPI.swift @@ -0,0 +1,81 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// WorkflowsAPI.swift +// +// Created by RevenueCat. + +import Foundation + +class WorkflowsAPI { + + typealias WorkflowsListResponseHandler = Backend.ResponseHandler + typealias WorkflowDetailResponseHandler = Backend.ResponseHandler + + private let workflowsListCallbackCache: CallbackCache + private let workflowDetailCallbackCache: CallbackCache + private let backendConfig: BackendConfiguration + private let detailProcessor: WorkflowDetailProcessor + + init(backendConfig: BackendConfiguration, + cdnFetcher: WorkflowCdnFetcher = DirectWorkflowCdnFetcher()) { + self.backendConfig = backendConfig + self.workflowsListCallbackCache = .init() + self.workflowDetailCallbackCache = .init() + self.detailProcessor = WorkflowDetailProcessor(cdnFetcher: cdnFetcher) + } + + func getWorkflows(appUserID: String, + isAppBackgrounded: Bool, + completion: @escaping WorkflowsListResponseHandler) { + let config = NetworkOperation.UserSpecificConfiguration(httpClient: self.backendConfig.httpClient, + appUserID: appUserID) + let factory = GetWorkflowsOperation.createFactory( + configuration: config, + workflowsCallbackCache: self.workflowsListCallbackCache + ) + + let callback = WorkflowsListCallback(cacheKey: factory.cacheKey, completion: completion) + let cacheStatus = self.workflowsListCallbackCache.add(callback) + + self.backendConfig.addCacheableOperation( + with: factory, + delay: .default(forBackgroundedApp: isAppBackgrounded), + cacheStatus: cacheStatus + ) + } + + func getWorkflow(appUserID: String, + workflowId: String, + isAppBackgrounded: Bool, + completion: @escaping WorkflowDetailResponseHandler) { + let config = NetworkOperation.UserSpecificConfiguration(httpClient: self.backendConfig.httpClient, + appUserID: appUserID) + let factory = GetWorkflowOperation.createFactory( + configuration: config, + workflowId: workflowId, + detailProcessor: self.detailProcessor, + workflowDetailCallbackCache: self.workflowDetailCallbackCache + ) + + let callback = WorkflowDetailCallback(cacheKey: factory.cacheKey, completion: completion) + let cacheStatus = self.workflowDetailCallbackCache.add(callback) + + self.backendConfig.addCacheableOperation( + with: factory, + delay: .default(forBackgroundedApp: isAppBackgrounded), + cacheStatus: cacheStatus + ) + } + +} + +// @unchecked because: +// - Class is not `final` (it's mocked). This implicitly makes subclasses `Sendable` even if they're not thread-safe. +extension WorkflowsAPI: @unchecked Sendable {} diff --git a/Tests/UnitTests/Mocks/MockBackend.swift b/Tests/UnitTests/Mocks/MockBackend.swift index 10899ec083..c10eec0360 100644 --- a/Tests/UnitTests/Mocks/MockBackend.swift +++ b/Tests/UnitTests/Mocks/MockBackend.swift @@ -47,6 +47,7 @@ class MockBackend: Backend { let customerCenterConfig = CustomerCenterConfigAPI(backendConfig: backendConfig) let redeemWebPurchaseAPI = MockRedeemWebPurchaseAPI() let virtualCurrenciesAPI = MockVirtualCurrenciesAPI() + let workflowsAPI = WorkflowsAPI(backendConfig: backendConfig) self.init(backendConfig: backendConfig, customerAPI: customer, @@ -57,7 +58,8 @@ class MockBackend: Backend { internalAPI: internalAPI, customerCenterConfig: customerCenterConfig, redeemWebPurchaseAPI: redeemWebPurchaseAPI, - virtualCurrenciesAPI: virtualCurrenciesAPI) + virtualCurrenciesAPI: virtualCurrenciesAPI, + workflowsAPI: workflowsAPI) } override func post(receipt: EncodedAppleReceipt, diff --git a/Tests/UnitTests/Networking/Backend/BackendGetWorkflowsTests.swift b/Tests/UnitTests/Networking/Backend/BackendGetWorkflowsTests.swift new file mode 100644 index 0000000000..8f3a6033fb --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/BackendGetWorkflowsTests.swift @@ -0,0 +1,315 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// BackendGetWorkflowsTests.swift +// +// Created by RevenueCat. + +import Foundation +import Nimble +import XCTest + +@testable import RevenueCat + +class BackendGetWorkflowsTests: BaseBackendTests { + + override func createClient() -> MockHTTPClient { + super.createClient(#file) + } + + func testGetWorkflowsCallsHTTPMethod() { + self.httpClient.mock( + requestPath: .getWorkflows(appUserID: Self.userID), + response: .init(statusCode: .success, response: Self.emptyWorkflowsResponse) + ) + + let result = waitUntilValue { completed in + self.workflowsAPI.getWorkflows( + appUserID: Self.userID, + isAppBackgrounded: false, + completion: completed + ) + } + + expect(result).to(beSuccess()) + expect(self.httpClient.calls).to(haveCount(1)) + } + + func testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded() { + self.httpClient.mock( + requestPath: .getWorkflows(appUserID: Self.userID), + response: .init(statusCode: .success, response: Self.emptyWorkflowsResponse) + ) + + let result = waitUntilValue { completed in + self.workflowsAPI.getWorkflows( + appUserID: Self.userID, + isAppBackgrounded: true, + completion: completed + ) + } + + expect(result).to(beSuccess()) + expect(self.httpClient.calls).to(haveCount(1)) + expect(self.operationDispatcher.invokedDispatchOnWorkerThreadDelayParam) == .default + } + + func testGetWorkflowsCachesForSameUserID() { + self.httpClient.mock( + requestPath: .getWorkflows(appUserID: Self.userID), + response: .init(statusCode: .success, + response: Self.emptyWorkflowsResponse, + delay: .milliseconds(10)) + ) + self.workflowsAPI.getWorkflows(appUserID: Self.userID, isAppBackgrounded: false) { _ in } + self.workflowsAPI.getWorkflows(appUserID: Self.userID, isAppBackgrounded: false) { _ in } + + expect(self.httpClient.calls).toEventually(haveCount(1)) + } + + func testGetWorkflowsReturnsWorkflows() throws { + self.httpClient.mock( + requestPath: .getWorkflows(appUserID: Self.userID), + response: .init(statusCode: .success, response: Self.oneWorkflowResponse) + ) + + let result: Atomic?> = nil + self.workflowsAPI.getWorkflows(appUserID: Self.userID, isAppBackgrounded: false) { + result.value = $0 + } + + expect(result.value).toEventuallyNot(beNil()) + + let response = try XCTUnwrap(result.value?.value) + expect(response.workflows).to(haveCount(1)) + expect(response.workflows.first?.id) == "wf_1" + expect(response.workflows.first?.displayName) == "Flow A" + } + + func testGetWorkflowsNetworkErrorSendsError() { + let mockedError: NetworkError = .unexpectedResponse(nil) + + self.httpClient.mock( + requestPath: .getWorkflows(appUserID: Self.userID), + response: .init(error: mockedError) + ) + + let result = waitUntilValue { completed in + self.workflowsAPI.getWorkflows( + appUserID: Self.userID, + isAppBackgrounded: false, + completion: completed + ) + } + + expect(result).to(beFailure()) + expect(result?.error) == .networkError(mockedError) + } + + func testGetWorkflowsSkipsBackendCallIfAppUserIDIsEmpty() { + waitUntil { completed in + self.workflowsAPI.getWorkflows(appUserID: "", isAppBackgrounded: false) { _ in + completed() + } + } + + expect(self.httpClient.calls).to(beEmpty()) + } + + func testGetWorkflowsCallsCompletionWithErrorIfAppUserIDIsEmpty() { + let receivedError = waitUntilValue { completed in + self.workflowsAPI.getWorkflows(appUserID: "", isAppBackgrounded: false) { result in + completed(result.error) + } + } + + expect(receivedError) == .missingAppUserID() + } + +} + +// MARK: - GetWorkflow tests + +class BackendGetWorkflowTests: BaseBackendTests { + + override func createClient() -> MockHTTPClient { + super.createClient(#file) + } + + func testGetWorkflowInlineUnwrapsData() throws { + self.httpClient.mock( + requestPath: .getWorkflow(appUserID: Self.userID, workflowId: "wf_1"), + response: .init(statusCode: .success, response: Self.inlineEnvelopeResponse) + ) + + let result: Atomic?> = nil + self.workflowsAPI.getWorkflow( + appUserID: Self.userID, + workflowId: "wf_1", + isAppBackgrounded: false + ) { + result.value = $0 + } + + expect(result.value).toEventuallyNot(beNil()) + + let fetchResult = try XCTUnwrap(result.value?.value) + expect(fetchResult.workflow.id) == "wf_1" + expect(fetchResult.workflow.displayName) == "Test Workflow" + expect(fetchResult.workflow.initialStepId) == "step_1" + expect(fetchResult.enrolledVariants).to(beNil()) + } + + func testGetWorkflowInlineWithEnrolledVariants() throws { + self.httpClient.mock( + requestPath: .getWorkflow(appUserID: Self.userID, workflowId: "wf_1"), + response: .init(statusCode: .success, response: Self.inlineEnvelopeWithVariantsResponse) + ) + + let result: Atomic?> = nil + self.workflowsAPI.getWorkflow( + appUserID: Self.userID, + workflowId: "wf_1", + isAppBackgrounded: false + ) { + result.value = $0 + } + + expect(result.value).toEventuallyNot(beNil()) + + let fetchResult = try XCTUnwrap(result.value?.value) + expect(fetchResult.workflow.id) == "wf_1" + expect(fetchResult.enrolledVariants) == ["experiment_1": "variant_a"] + } + + func testGetWorkflowPropagatesHTTPErrors() { + self.httpClient.mock( + requestPath: .getWorkflow(appUserID: Self.userID, workflowId: "wf_missing"), + response: .init(statusCode: .notFoundError, response: ["error": "not found"] as [String: Any]) + ) + + let result = waitUntilValue { completed in + self.workflowsAPI.getWorkflow( + appUserID: Self.userID, + workflowId: "wf_missing", + isAppBackgrounded: false, + completion: completed + ) + } + + expect(result).to(beFailure()) + } + + func testGetWorkflowCachesForSameUserIDAndWorkflowId() { + self.httpClient.mock( + requestPath: .getWorkflow(appUserID: Self.userID, workflowId: "wf_1"), + response: .init(statusCode: .success, + response: Self.inlineEnvelopeResponse, + delay: .milliseconds(10)) + ) + + self.workflowsAPI.getWorkflow( + appUserID: Self.userID, + workflowId: "wf_1", + isAppBackgrounded: false + ) { _ in } + self.workflowsAPI.getWorkflow( + appUserID: Self.userID, + workflowId: "wf_1", + isAppBackgrounded: false + ) { _ in } + + expect(self.httpClient.calls).toEventually(haveCount(1)) + } + + func testGetWorkflowSkipsBackendCallIfAppUserIDIsEmpty() { + waitUntil { completed in + self.workflowsAPI.getWorkflow( + appUserID: "", + workflowId: "wf_1", + isAppBackgrounded: false + ) { _ in + completed() + } + } + + expect(self.httpClient.calls).to(beEmpty()) + } + +} + +// MARK: - Test data + +private extension BackendGetWorkflowsTests { + + static let minimalUiConfig: [String: Any] = [ + "app": [ + "colors": [:] as [String: Any], + "fonts": [:] as [String: Any] + ] as [String: Any], + "localizations": [:] as [String: Any], + "variable_config": [:] as [String: Any] + ] + + static let emptyWorkflowsResponse: [String: Any] = [ + "workflows": [] as [Any], + "ui_config": minimalUiConfig + ] + + static let oneWorkflowResponse: [String: Any] = [ + "workflows": [ + [ + "id": "wf_1", + "display_name": "Flow A" + ] as [String: Any] + ], + "ui_config": minimalUiConfig + ] + +} + +private extension BackendGetWorkflowTests { + + static let minimalUiConfig: [String: Any] = [ + "app": [ + "colors": [:] as [String: Any], + "fonts": [:] as [String: Any] + ] as [String: Any], + "localizations": [:] as [String: Any], + "variable_config": [:] as [String: Any] + ] + + static let minimalWorkflowData: [String: Any] = [ + "id": "wf_1", + "display_name": "Test Workflow", + "initial_step_id": "step_1", + "steps": [ + "step_1": [ + "id": "step_1", + "type": "screen" + ] as [String: Any] + ] as [String: Any], + "screens": [:] as [String: Any], + "ui_config": minimalUiConfig + ] + + static let inlineEnvelopeResponse: [String: Any] = [ + "action": "inline", + "data": minimalWorkflowData + ] + + static let inlineEnvelopeWithVariantsResponse: [String: Any] = [ + "action": "inline", + "data": minimalWorkflowData, + "enrolled_variants": [ + "experiment_1": "variant_a" + ] as [String: String] + ] + +} diff --git a/Tests/UnitTests/Networking/Backend/BaseBackendTest.swift b/Tests/UnitTests/Networking/Backend/BaseBackendTest.swift index e5fc8d7dfb..edec9c5868 100644 --- a/Tests/UnitTests/Networking/Backend/BaseBackendTest.swift +++ b/Tests/UnitTests/Networking/Backend/BaseBackendTest.swift @@ -36,6 +36,7 @@ class BaseBackendTests: TestCase { private(set) var customerCenterConfig: CustomerCenterConfigAPI! private(set) var redeemWebPurchaseAPI: RedeemWebPurchaseAPI! private(set) var virtualCurrenciesAPI: VirtualCurrenciesAPI! + private(set) var workflowsAPI: WorkflowsAPI! static let apiKey = "asharedsecret" static let userID = "user" @@ -92,6 +93,7 @@ class BaseBackendTests: TestCase { self.customerCenterConfig = CustomerCenterConfigAPI(backendConfig: backendConfig) self.redeemWebPurchaseAPI = RedeemWebPurchaseAPI(backendConfig: backendConfig) self.virtualCurrenciesAPI = VirtualCurrenciesAPI(backendConfig: backendConfig) + self.workflowsAPI = WorkflowsAPI(backendConfig: backendConfig) self.backend = Backend(backendConfig: backendConfig, customerAPI: customer, @@ -102,7 +104,8 @@ class BaseBackendTests: TestCase { internalAPI: self.internalAPI, customerCenterConfig: self.customerCenterConfig, redeemWebPurchaseAPI: self.redeemWebPurchaseAPI, - virtualCurrenciesAPI: self.virtualCurrenciesAPI) + virtualCurrenciesAPI: self.virtualCurrenciesAPI, + workflowsAPI: self.workflowsAPI) } var verificationMode: Configuration.EntitlementVerificationMode { diff --git a/Tests/UnitTests/Networking/Workflows/WorkflowDetailProcessorTests.swift b/Tests/UnitTests/Networking/Workflows/WorkflowDetailProcessorTests.swift new file mode 100644 index 0000000000..b1c4efe165 --- /dev/null +++ b/Tests/UnitTests/Networking/Workflows/WorkflowDetailProcessorTests.swift @@ -0,0 +1,135 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// WorkflowDetailProcessorTests.swift +// +// Created by RevenueCat. + +import Foundation +import Nimble +import XCTest + +@testable import RevenueCat + +class WorkflowDetailProcessorTests: TestCase { + + private var processor: WorkflowDetailProcessor! + private var fetchedUrls: [String] = [] + + override func setUpWithError() throws { + try super.setUpWithError() + + self.fetchedUrls = [] + let fetcher = MockWorkflowCdnFetcher { [weak self] url in + self?.fetchedUrls.append(url) + return try JSONSerialization.data(withJSONObject: ["id": "from_cdn"]) + } + self.processor = WorkflowDetailProcessor(cdnFetcher: fetcher) + } + + func testInlineUnwrapsData() throws { + let envelope: [String: Any] = [ + "action": "inline", + "data": ["id": "wf_inline"] + ] + let data = try JSONSerialization.data(withJSONObject: envelope) + + let result = try processor.process(data) + + let parsed = try JSONSerialization.jsonObject(with: result.workflowData) as? [String: Any] + expect(parsed?["id"] as? String) == "wf_inline" + expect(result.enrolledVariants).to(beNil()) + } + + func testInlineExtractsEnrolledVariants() throws { + let envelope: [String: Any] = [ + "action": "inline", + "data": ["id": "wf1"], + "enrolled_variants": ["a": "b"] + ] + let data = try JSONSerialization.data(withJSONObject: envelope) + + let result = try processor.process(data) + + expect(result.enrolledVariants) == ["a": "b"] + } + + func testUseCdnFetchesFromUrl() throws { + let envelope: [String: Any] = [ + "action": "use_cdn", + "url": "https://cdn.example/w.json", + "enrolled_variants": ["x": "y"] + ] + let data = try JSONSerialization.data(withJSONObject: envelope) + + let result = try processor.process(data) + + expect(self.fetchedUrls) == ["https://cdn.example/w.json"] + let parsed = try JSONSerialization.jsonObject(with: result.workflowData) as? [String: Any] + expect(parsed?["id"] as? String) == "from_cdn" + expect(result.enrolledVariants) == ["x": "y"] + } + + func testUnknownActionThrows() throws { + let envelope: [String: Any] = [ + "action": "other" + ] + let data = try JSONSerialization.data(withJSONObject: envelope) + + expect(try self.processor.process(data)).to(throwError()) + } + + func testUseCdnPropagatesIOError() throws { + let failingFetcher = MockWorkflowCdnFetcher { _ in + throw URLError(.notConnectedToInternet) + } + let failingProcessor = WorkflowDetailProcessor(cdnFetcher: failingFetcher) + + let envelope: [String: Any] = [ + "action": "use_cdn", + "url": "https://x" + ] + let data = try JSONSerialization.data(withJSONObject: envelope) + + expect(try failingProcessor.process(data)).to(throwError()) + } + + func testMissingDataInInlineThrows() throws { + let envelope: [String: Any] = [ + "action": "inline" + ] + let data = try JSONSerialization.data(withJSONObject: envelope) + + expect(try self.processor.process(data)).to(throwError()) + } + + func testMissingUrlInUseCdnThrows() throws { + let envelope: [String: Any] = [ + "action": "use_cdn" + ] + let data = try JSONSerialization.data(withJSONObject: envelope) + + expect(try self.processor.process(data)).to(throwError()) + } + +} + +private final class MockWorkflowCdnFetcher: WorkflowCdnFetcher { + + private let handler: (String) throws -> Data + + init(_ handler: @escaping (String) throws -> Data) { + self.handler = handler + } + + func fetchCompiledWorkflowData(cdnUrl: String) throws -> Data { + return try handler(cdnUrl) + } + +} diff --git a/Tests/UnitTests/Networking/Workflows/WorkflowResponseTests.swift b/Tests/UnitTests/Networking/Workflows/WorkflowResponseTests.swift new file mode 100644 index 0000000000..e957fe204a --- /dev/null +++ b/Tests/UnitTests/Networking/Workflows/WorkflowResponseTests.swift @@ -0,0 +1,139 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// WorkflowResponseTests.swift +// +// Created by RevenueCat. + +import Foundation +import Nimble +import XCTest + +@testable import RevenueCat + +class WorkflowResponseTests: TestCase { + + func testDecodeWorkflowsListResponse() throws { + let json = """ + { + "workflows": [ + { "id": "wf_1", "display_name": "Flow A" } + ], + "ui_config": { + "app": { "colors": {}, "fonts": {} }, + "localizations": {}, + "variable_config": {} + } + } + """.data(using: .utf8)! + + let response = try JSONDecoder.default.decode( + WorkflowsListResponse.self, from: json + ) + + expect(response.workflows).to(haveCount(1)) + expect(response.workflows.first?.id) == "wf_1" + expect(response.workflows.first?.displayName) == "Flow A" + } + + func testDecodePublishedWorkflowWithStepsAndTriggerActions() throws { + let json = """ + { + "id": "wf_test", + "display_name": "Test", + "initial_step_id": "step_1", + "steps": { + "step_1": { + "id": "step_1", + "type": "screen", + "trigger_actions": { + "btn_1": { "type": "step", "value": "step_2" } + } + } + }, + "screens": {}, + "ui_config": { + "app": { "colors": {}, "fonts": {} }, + "localizations": {}, + "variable_config": {} + }, + "content_max_width": 100 + } + """.data(using: .utf8)! + + let workflow = try JSONDecoder.default.decode( + PublishedWorkflow.self, from: json + ) + + expect(workflow.id) == "wf_test" + expect(workflow.initialStepId) == "step_1" + expect(workflow.steps["step_1"]?.triggerActions["btn_1"]?.resolvedTargetStepId) == "step_2" + expect(workflow.contentMaxWidth) == 100 + } + + func testDecodePublishedWorkflowWithoutOptionals() throws { + let json = """ + { + "id": "wf_min", + "display_name": "Minimal", + "initial_step_id": "step_1", + "steps": {}, + "screens": {}, + "ui_config": { + "app": { "colors": {}, "fonts": {} }, + "localizations": {}, + "variable_config": {} + } + } + """.data(using: .utf8)! + + let workflow = try JSONDecoder.default.decode( + PublishedWorkflow.self, from: json + ) + + expect(workflow.id) == "wf_min" + expect(workflow.contentMaxWidth).to(beNil()) + } + + func testDecodeWorkflowTriggerActionWithStepId() throws { + let json = """ + { "type": "step", "step_id": "step_3" } + """.data(using: .utf8)! + + let action = try JSONDecoder.default.decode(WorkflowTriggerAction.self, from: json) + + expect(action.type) == "step" + expect(action.resolvedTargetStepId) == "step_3" + } + + func testDecodeWorkflowTriggerActionValueTakesPrecedence() throws { + let json = """ + { "type": "step", "value": "step_2", "step_id": "step_3" } + """.data(using: .utf8)! + + let action = try JSONDecoder.default.decode(WorkflowTriggerAction.self, from: json) + + expect(action.resolvedTargetStepId) == "step_2" + } + + func testDecodeWorkflowStepDefaults() throws { + let json = """ + { "id": "step_1", "type": "screen" } + """.data(using: .utf8)! + + let step = try JSONDecoder.default.decode(WorkflowStep.self, from: json) + + expect(step.id) == "step_1" + expect(step.type) == "screen" + expect(step.screenId).to(beNil()) + expect(step.paramValues).to(beEmpty()) + expect(step.triggerActions).to(beEmpty()) + } + +} From 27ba13ecfd8a0f497baea8ca01e3f76021ff6b66 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Mon, 6 Apr 2026 17:39:03 +0200 Subject: [PATCH 02/24] fix xcodeproj --- Package.resolved | 42 ++++++++++++------ RevenueCat.xcodeproj/project.pbxproj | 64 +++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 14 deletions(-) diff --git a/Package.resolved b/Package.resolved index 71eaec8b66..44d00852ea 100644 --- a/Package.resolved +++ b/Package.resolved @@ -18,6 +18,15 @@ "version" : "2.2.2" } }, + { + "identity" : "flyingfox", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swhitty/FlyingFox.git", + "state" : { + "revision" : "f7829d4aca8cbfecb410a1cce872b1b045224aa1", + "version" : "0.16.0" + } + }, { "identity" : "nimble", "kind" : "remoteSourceControl", @@ -28,30 +37,39 @@ } }, { - "identity" : "swift-custom-dump", + "identity" : "ohhttpstubs", "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-custom-dump", + "location" : "https://github.com/AliSoftware/OHHTTPStubs.git", "state" : { - "revision" : "2a2a938798236b8fa0bc57c453ee9de9f9ec3ab0", - "version" : "1.4.1" + "revision" : "12f19662426d0434d6c330c6974d53e2eb10ecd9", + "version" : "9.1.0" } }, { - "identity" : "swift-package-manager-google-mobile-ads", + "identity" : "simpledebugger", "kind" : "remoteSourceControl", - "location" : "https://github.com/googleads/swift-package-manager-google-mobile-ads.git", + "location" : "https://github.com/EmergeTools/SimpleDebugger.git", "state" : { - "revision" : "8d08e4396ea955a7d2baf067c0eedfc34ca0821d", - "version" : "13.2.0" + "revision" : "f065263eb5db95874b690408d136b573c939db9e", + "version" : "1.0.0" } }, { - "identity" : "swift-package-manager-google-user-messaging-platform", + "identity" : "snapshotpreviews", "kind" : "remoteSourceControl", - "location" : "https://github.com/googleads/swift-package-manager-google-user-messaging-platform.git", + "location" : "https://github.com/EmergeTools/SnapshotPreviews.git", "state" : { - "revision" : "13b248eaa73b7826f0efb1bcf455e251d65ecb1b", - "version" : "3.1.0" + "revision" : "6dfb281493e48e7c09ae9717593ae0b8eb47b15c", + "version" : "0.11.0" + } + }, + { + "identity" : "swift-custom-dump", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-custom-dump", + "state" : { + "revision" : "2a2a938798236b8fa0bc57c453ee9de9f9ec3ab0", + "version" : "1.4.1" } }, { diff --git a/RevenueCat.xcodeproj/project.pbxproj b/RevenueCat.xcodeproj/project.pbxproj index c3fa7a8b4a..3ecb7b3f57 100644 --- a/RevenueCat.xcodeproj/project.pbxproj +++ b/RevenueCat.xcodeproj/project.pbxproj @@ -742,7 +742,6 @@ 5791FE4A2994453500F1FEDA /* Signing+ResponseVerification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5791FE492994453500F1FEDA /* Signing+ResponseVerification.swift */; }; 579234E327F7788900B39C68 /* BaseBackendIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 579234E127F777EE00B39C68 /* BaseBackendIntegrationTests.swift */; }; 579234E527F779FE00B39C68 /* SubscriberAttributesManagerIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 579234E427F779FE00B39C68 /* SubscriberAttributesManagerIntegrationTests.swift */; }; - EF970D13102945639A882001 /* ATTConsentStatusIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF970D13102945639A882002 /* ATTConsentStatusIntegrationTests.swift */; }; 5793397028E77A5100C1232C /* PaymentQueueWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5793396F28E77A5100C1232C /* PaymentQueueWrapperTests.swift */; }; 5793397228E77A6E00C1232C /* MockPaymentQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5793397128E77A6E00C1232C /* MockPaymentQueue.swift */; }; 579415D2293689DD00218FBC /* Codable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 579415D1293689DD00218FBC /* Codable+Extensions.swift */; }; @@ -1314,12 +1313,24 @@ B3F8418F26F3A93400E560FB /* ErrorCodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3F8418E26F3A93400E560FB /* ErrorCodeTests.swift */; }; C074AAE036B449898E1FEF58 /* CustomPaywallVariables.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B46553B99148F3A60B8BFB /* CustomPaywallVariables.swift */; }; D0DA209A2F23BBEC00BA3B77 /* AdEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA20992F23BBEC00BA3B77 /* AdEventTests.swift */; }; + DB3395CA2F840A2B0079250C /* WorkflowsCallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3395C92F840A2B0079250C /* WorkflowsCallback.swift */; }; + DB3395D22F840A400079250C /* GetWorkflowsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3395D12F840A400079250C /* GetWorkflowsOperation.swift */; }; + DB3395D32F840A400079250C /* GetWorkflowOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3395D02F840A400079250C /* GetWorkflowOperation.swift */; }; + DB3395D52F840A570079250C /* WorkflowsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3395D42F840A570079250C /* WorkflowsResponse.swift */; }; + DB3395DB2F840A790079250C /* WorkflowDetailProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3395D82F840A790079250C /* WorkflowDetailProcessor.swift */; }; + DB3395DC2F840A790079250C /* WorkflowCdnFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3395D72F840A790079250C /* WorkflowCdnFetcher.swift */; }; + DB3395DD2F840A790079250C /* WorkflowResponseAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3395D92F840A790079250C /* WorkflowResponseAction.swift */; }; + DB3395E12F840AA60079250C /* WorkflowsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3395E02F840AA60079250C /* WorkflowsAPI.swift */; }; + DB3395E32F840ACD0079250C /* BackendGetWorkflowsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3395E22F840ACD0079250C /* BackendGetWorkflowsTests.swift */; }; + DB3395E72F840ADA0079250C /* WorkflowResponseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3395E52F840ADA0079250C /* WorkflowResponseTests.swift */; }; + DB3395E82F840ADA0079250C /* WorkflowDetailProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3395E42F840ADA0079250C /* WorkflowDetailProcessorTests.swift */; }; DB36994E2F435CDC00B1F049 /* PriceFormatterExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB36994D2F435CDC00B1F049 /* PriceFormatterExtensions.swift */; }; DB55E6262ECE012600636909 /* FlexSpacer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB55E6252ECE012600636909 /* FlexSpacer.swift */; }; DBD2432F2EBDF4BC0066AC6F /* PromotionalOfferViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD243292EBDF4B80066AC6F /* PromotionalOfferViewTests.swift */; }; DBE7C6552F02D48900A28663 /* StoreProductDiscount+CustomerCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE7C6542F02D48900A28663 /* StoreProductDiscount+CustomerCenter.swift */; }; DDD45A15A641C0FF0BEF6178 /* PaywallCountdownComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A05E9E1B04B32B832DB194 /* PaywallCountdownComponent.swift */; }; ED85C2AB8EAB82D346A12569 /* CarouselStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29E428C04E073334CD13B58 /* CarouselStateTests.swift */; }; + EF970D13102945639A882001 /* ATTConsentStatusIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF970D13102945639A882002 /* ATTConsentStatusIntegrationTests.swift */; }; F516BD29282434070083480B /* StoreKit2StorefrontListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = F516BD28282434070083480B /* StoreKit2StorefrontListener.swift */; }; F52CFAFC28290AE500E8ABC5 /* StoreKit2StorefrontListenerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F516BD322828FDD90083480B /* StoreKit2StorefrontListenerTests.swift */; }; F530E4FF275646EF001AF6BD /* MacDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = F530E4FE275646EF001AF6BD /* MacDevice.swift */; }; @@ -2312,7 +2323,6 @@ 5791FE492994453500F1FEDA /* Signing+ResponseVerification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Signing+ResponseVerification.swift"; sourceTree = ""; }; 579234E127F777EE00B39C68 /* BaseBackendIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseBackendIntegrationTests.swift; sourceTree = ""; }; 579234E427F779FE00B39C68 /* SubscriberAttributesManagerIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriberAttributesManagerIntegrationTests.swift; sourceTree = ""; }; - EF970D13102945639A882002 /* ATTConsentStatusIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTConsentStatusIntegrationTests.swift; sourceTree = ""; }; 5793396F28E77A5100C1232C /* PaymentQueueWrapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentQueueWrapperTests.swift; sourceTree = ""; }; 5793397128E77A6E00C1232C /* MockPaymentQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPaymentQueue.swift; sourceTree = ""; }; 579415D1293689DD00218FBC /* Codable+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Codable+Extensions.swift"; sourceTree = ""; }; @@ -2843,10 +2853,22 @@ B3F8418E26F3A93400E560FB /* ErrorCodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorCodeTests.swift; sourceTree = ""; }; CC1A2B3C2E1234AB00AABBCC /* CustomVariablesEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomVariablesEditorView.swift; sourceTree = ""; }; D0DA20992F23BBEC00BA3B77 /* AdEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdEventTests.swift; sourceTree = ""; }; + DB3395C92F840A2B0079250C /* WorkflowsCallback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkflowsCallback.swift; sourceTree = ""; }; + DB3395D02F840A400079250C /* GetWorkflowOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetWorkflowOperation.swift; sourceTree = ""; }; + DB3395D12F840A400079250C /* GetWorkflowsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetWorkflowsOperation.swift; sourceTree = ""; }; + DB3395D42F840A570079250C /* WorkflowsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkflowsResponse.swift; sourceTree = ""; }; + DB3395D72F840A790079250C /* WorkflowCdnFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkflowCdnFetcher.swift; sourceTree = ""; }; + DB3395D82F840A790079250C /* WorkflowDetailProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkflowDetailProcessor.swift; sourceTree = ""; }; + DB3395D92F840A790079250C /* WorkflowResponseAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkflowResponseAction.swift; sourceTree = ""; }; + DB3395E02F840AA60079250C /* WorkflowsAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkflowsAPI.swift; sourceTree = ""; }; + DB3395E22F840ACD0079250C /* BackendGetWorkflowsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackendGetWorkflowsTests.swift; sourceTree = ""; }; + DB3395E42F840ADA0079250C /* WorkflowDetailProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkflowDetailProcessorTests.swift; sourceTree = ""; }; + DB3395E52F840ADA0079250C /* WorkflowResponseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkflowResponseTests.swift; sourceTree = ""; }; DB36994D2F435CDC00B1F049 /* PriceFormatterExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceFormatterExtensions.swift; sourceTree = ""; }; DB55E6252ECE012600636909 /* FlexSpacer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexSpacer.swift; sourceTree = ""; }; DBD243292EBDF4B80066AC6F /* PromotionalOfferViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromotionalOfferViewTests.swift; sourceTree = ""; }; DBE7C6542F02D48900A28663 /* StoreProductDiscount+CustomerCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StoreProductDiscount+CustomerCenter.swift"; sourceTree = ""; }; + EF970D13102945639A882002 /* ATTConsentStatusIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTConsentStatusIntegrationTests.swift; sourceTree = ""; }; EFB3CBAA73855779FE828CE2 /* ProductsFetcherSK1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsFetcherSK1.swift; sourceTree = ""; }; F1A05E9E1B04B32B832DB194 /* PaywallCountdownComponent.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PaywallCountdownComponent.swift; sourceTree = ""; }; F516BD28282434070083480B /* StoreKit2StorefrontListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKit2StorefrontListener.swift; sourceTree = ""; }; @@ -4479,6 +4501,8 @@ FDE57AA12DF88ACA00101CE2 /* VirtualCurrenciesAPI.swift */, 751192DF2E39149200E583CC /* WebBillingAPI.swift */, 1EDB6AA92DC95AB400C771A4 /* WebBillingHTTPRequestPath.swift */, + DB3395DA2F840A790079250C /* Workflows */, + DB3395E02F840AA60079250C /* WorkflowsAPI.swift */, ); path = Networking; sourceTree = ""; @@ -4500,6 +4524,7 @@ 5733B1A327FF9F8300EC2045 /* NetworkErrorTests.swift */, 5733B1A727FFBCC800EC2045 /* BackendErrorTests.swift */, 5733B1A927FFBCF900EC2045 /* BaseErrorTests.swift */, + DB3395E62F840ADA0079250C /* Workflows */, ); path = Networking; sourceTree = ""; @@ -4989,6 +5014,7 @@ 35F38B472C30104E00CD29FD /* BackendGetCustomerCenterConfigTests.swift */, FD5EB26F2DFB19B800DA1974 /* BackendGetVirtualCurrenciesTests.swift */, FD25F3772F33D502007C1A91 /* BackendIsPurchaseAllowedByRestoreBehaviorTests.swift */, + DB3395E22F840ACD0079250C /* BackendGetWorkflowsTests.swift */, ); path = Backend; sourceTree = ""; @@ -5182,6 +5208,7 @@ 57488A7E29CA145B0000EE7E /* ProductEntitlementMappingResponse.swift */, 537B4B2D2DA9662700CEFF4C /* HealthReportResponse.swift */, FDE57A9F2DF8785000101CE2 /* VirtualCurrenciesResponse.swift */, + DB3395D42F840A570079250C /* WorkflowsResponse.swift */, 755C26A32E310B19006DD0AE /* WebBillingProductsResponse.swift */, ); path = Responses; @@ -5917,6 +5944,7 @@ FDED7EC42F33DF0A00827E54 /* IsPurchaseAllowedByRestoreBehaviorCallback.swift */, 57045B3B29C51AF7001A5417 /* ProductEntitlementMappingCallback.swift */, FDE57A9D2DF8783000101CE2 /* VirtualCurrenciesCallback.swift */, + DB3395C92F840A2B0079250C /* WorkflowsCallback.swift */, ); path = Caching; sourceTree = ""; @@ -5945,6 +5973,8 @@ B34605B9279A6E380031CA74 /* PostSubscriberAttributesOperation.swift */, A55D5D65282ECCC100FA7623 /* PostAdServicesTokenOperation.swift */, A56DFDEF286643BF00EF2E32 /* PostAttributionDataOperation.swift */, + DB3395D02F840A400079250C /* GetWorkflowOperation.swift */, + DB3395D12F840A400079250C /* GetWorkflowsOperation.swift */, ); path = Operations; sourceTree = ""; @@ -6041,6 +6071,25 @@ path = Caching; sourceTree = ""; }; + DB3395DA2F840A790079250C /* Workflows */ = { + isa = PBXGroup; + children = ( + DB3395D72F840A790079250C /* WorkflowCdnFetcher.swift */, + DB3395D82F840A790079250C /* WorkflowDetailProcessor.swift */, + DB3395D92F840A790079250C /* WorkflowResponseAction.swift */, + ); + path = Workflows; + sourceTree = ""; + }; + DB3395E62F840ADA0079250C /* Workflows */ = { + isa = PBXGroup; + children = ( + DB3395E42F840ADA0079250C /* WorkflowDetailProcessorTests.swift */, + DB3395E52F840ADA0079250C /* WorkflowResponseTests.swift */, + ); + path = Workflows; + sourceTree = ""; + }; F5714EA626D7A82E00635477 /* CodableExtensions */ = { isa = PBXGroup; children = ( @@ -6953,6 +7002,7 @@ 5751379527F4C4D80064AB2C /* Optional+Extensions.swift in Sources */, B3852FA026C1ED1F005384F8 /* IdentityManager.swift in Sources */, 9A65E03625918B0500DE00B0 /* ConfigureStrings.swift in Sources */, + DB3395CA2F840A2B0079250C /* WorkflowsCallback.swift in Sources */, 4FF6E4B92B069B8C000141ED /* HTTPRequest+Signing.swift in Sources */, 57488C7729CB90F90000EE7E /* PurchasedProductsFetcher.swift in Sources */, 53E33BBB2E095B8900287158 /* SDKHealthManager.swift in Sources */, @@ -6963,6 +7013,10 @@ 354895D6267BEDE3001DC5B1 /* ReservedSubscriberAttributes.swift in Sources */, 570FAF4B2864EC2300D3C769 /* NonSubscriptionTransaction.swift in Sources */, 2D11F5E1250FF886005A70E8 /* AttributionStrings.swift in Sources */, + DB3395DB2F840A790079250C /* WorkflowDetailProcessor.swift in Sources */, + DB3395E12F840AA60079250C /* WorkflowsAPI.swift in Sources */, + DB3395DC2F840A790079250C /* WorkflowCdnFetcher.swift in Sources */, + DB3395DD2F840A790079250C /* WorkflowResponseAction.swift in Sources */, 2CD72944268A826F00BFC976 /* Date+Extensions.swift in Sources */, 57069A5428E3918400B86355 /* AsyncExtensions.swift in Sources */, B34605C5279A6E380031CA74 /* CustomerInfoResponseHandler.swift in Sources */, @@ -7053,6 +7107,8 @@ 2D1015DA275959840086173F /* StoreTransaction.swift in Sources */, 4DBF1F362B4D572400D52354 /* LocalReceiptFetcher.swift in Sources */, 1D20E1D62EBCF80E00ABE4CD /* HTTPRequestTimeoutManager.swift in Sources */, + DB3395D22F840A400079250C /* GetWorkflowsOperation.swift in Sources */, + DB3395D32F840A400079250C /* GetWorkflowOperation.swift in Sources */, 57488A7F29CA145B0000EE7E /* ProductEntitlementMappingResponse.swift in Sources */, 88E679472C7503C1007E69D5 /* PaywallStackComponent.swift in Sources */, B34605BC279A6E380031CA74 /* CallbackCache.swift in Sources */, @@ -7152,6 +7208,7 @@ F5714EA826D7A83A00635477 /* Store+Extensions.swift in Sources */, 35F82BAB26A84E130051DF03 /* Dictionary+Extensions.swift in Sources */, 4D2C7D0B2CC40A35002562BC /* PurchaseParams.swift in Sources */, + DB3395D52F840A570079250C /* WorkflowsResponse.swift in Sources */, 9A65E0A02591A23200DE00B0 /* OfferingStrings.swift in Sources */, 5774F9B62805E6CC00997128 /* CustomerInfoResponse.swift in Sources */, 03A98D302D240C41009BCA61 /* UIConfig.swift in Sources */, @@ -7315,6 +7372,7 @@ 2DDF41CB24F6F4C3005BC22D /* ASN1ObjectIdentifierBuilderTests.swift in Sources */, 5766AA5A283D4CAB00FA6091 /* IgnoreHashableTests.swift in Sources */, 0368727A2D9DCF7100506BBB /* DefaultEnumTests.swift in Sources */, + DB3395E32F840ACD0079250C /* BackendGetWorkflowsTests.swift in Sources */, 03C7305B2D35985900297FEC /* TextComponentTests.swift in Sources */, FDE57B0E2DF8BC1100101CE2 /* VirtualCurrenciesDecodingTests.swift in Sources */, 75FCD4CE2E37FBE100036C02 /* SimulatedStoreMockData.swift in Sources */, @@ -7390,6 +7448,8 @@ 75902AFA2F156865005B712A /* MockLocalTransactionMetadataStore.swift in Sources */, 57488B8B29CB756A0000EE7E /* ProductEntitlementMappingTests.swift in Sources */, 1D20E1D82EBCF82900ABE4CD /* HTTPRequestTimeoutManager.swift in Sources */, + DB3395E72F840ADA0079250C /* WorkflowResponseTests.swift in Sources */, + DB3395E82F840ADA0079250C /* WorkflowDetailProcessorTests.swift in Sources */, FD4E63422F34E0B60040BA46 /* PurchasesIsPurchaseAllowedByRestoreBehaviorTests.swift in Sources */, 57ACB12428174B9F000DCC9F /* CustomerInfo+TestExtensions.swift in Sources */, 357349012C3BEB5C000EEB86 /* CustomerCenterConfigDataTests.swift in Sources */, From 76e8094daca1236c634e814cc91e058fa10be9df Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Mon, 6 Apr 2026 17:40:26 +0200 Subject: [PATCH 03/24] reset Package.resolved --- Package.resolved | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/Package.resolved b/Package.resolved index 44d00852ea..ab65af5512 100644 --- a/Package.resolved +++ b/Package.resolved @@ -18,15 +18,6 @@ "version" : "2.2.2" } }, - { - "identity" : "flyingfox", - "kind" : "remoteSourceControl", - "location" : "https://github.com/swhitty/FlyingFox.git", - "state" : { - "revision" : "f7829d4aca8cbfecb410a1cce872b1b045224aa1", - "version" : "0.16.0" - } - }, { "identity" : "nimble", "kind" : "remoteSourceControl", @@ -36,33 +27,6 @@ "version" : "13.7.1" } }, - { - "identity" : "ohhttpstubs", - "kind" : "remoteSourceControl", - "location" : "https://github.com/AliSoftware/OHHTTPStubs.git", - "state" : { - "revision" : "12f19662426d0434d6c330c6974d53e2eb10ecd9", - "version" : "9.1.0" - } - }, - { - "identity" : "simpledebugger", - "kind" : "remoteSourceControl", - "location" : "https://github.com/EmergeTools/SimpleDebugger.git", - "state" : { - "revision" : "f065263eb5db95874b690408d136b573c939db9e", - "version" : "1.0.0" - } - }, - { - "identity" : "snapshotpreviews", - "kind" : "remoteSourceControl", - "location" : "https://github.com/EmergeTools/SnapshotPreviews.git", - "state" : { - "revision" : "6dfb281493e48e7c09ae9717593ae0b8eb47b15c", - "version" : "0.11.0" - } - }, { "identity" : "swift-custom-dump", "kind" : "remoteSourceControl", From 83cc392563b6c3c58bdd84bb8f2a6448caf2a3d2 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Tue, 7 Apr 2026 11:49:54 +0200 Subject: [PATCH 04/24] 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 --- .../Responses/WorkflowsResponse.swift | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Sources/Networking/Responses/WorkflowsResponse.swift b/Sources/Networking/Responses/WorkflowsResponse.swift index 7aa2cf5083..6546fdf3b4 100644 --- a/Sources/Networking/Responses/WorkflowsResponse.swift +++ b/Sources/Networking/Responses/WorkflowsResponse.swift @@ -31,16 +31,20 @@ struct WorkflowsListResponse { // MARK: - Detail models +struct WorkflowTrigger { + + let name: String? + let type: String + let actionId: String? + let componentId: String? + +} + struct WorkflowTriggerAction { let type: String - let value: String? let stepId: String? - var resolvedTargetStepId: String? { - return value ?? stepId - } - } struct WorkflowStep { @@ -50,8 +54,13 @@ struct WorkflowStep { let screenId: String? @DefaultDecodable.EmptyDictionary var paramValues: [String: AnyDecodable] + @DefaultDecodable.EmptyArray + var triggers: [WorkflowTrigger] + @DefaultDecodable.EmptyDictionary + var outputs: [String: AnyDecodable] @DefaultDecodable.EmptyDictionary var triggerActions: [String: WorkflowTriggerAction] + let metadata: [String: AnyDecodable]? } @@ -80,6 +89,7 @@ struct PublishedWorkflow { let screens: [String: WorkflowScreen] let uiConfig: UIConfig let contentMaxWidth: Int? + let metadata: [String: AnyDecodable]? } @@ -95,6 +105,7 @@ struct WorkflowFetchResult { extension WorkflowSummary: Codable, Equatable, Sendable {} extension WorkflowsListResponse: Codable, Equatable, Sendable {} +extension WorkflowTrigger: Codable, Equatable, Sendable {} extension WorkflowTriggerAction: Codable, Equatable, Sendable {} extension WorkflowStep: Codable, Equatable, Sendable {} From e559dcc412aadf540f5f033e0378a98764154f6d Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Tue, 7 Apr 2026 11:54:16 +0200 Subject: [PATCH 05/24] 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 --- .../Workflows/WorkflowResponseTests.swift | 96 +++++++++++++++++-- 1 file changed, 88 insertions(+), 8 deletions(-) diff --git a/Tests/UnitTests/Networking/Workflows/WorkflowResponseTests.swift b/Tests/UnitTests/Networking/Workflows/WorkflowResponseTests.swift index e957fe204a..9bd7798c50 100644 --- a/Tests/UnitTests/Networking/Workflows/WorkflowResponseTests.swift +++ b/Tests/UnitTests/Networking/Workflows/WorkflowResponseTests.swift @@ -53,7 +53,7 @@ class WorkflowResponseTests: TestCase { "id": "step_1", "type": "screen", "trigger_actions": { - "btn_1": { "type": "step", "value": "step_2" } + "btn_1": { "type": "step", "step_id": "step_2" } } } }, @@ -73,8 +73,9 @@ class WorkflowResponseTests: TestCase { expect(workflow.id) == "wf_test" expect(workflow.initialStepId) == "step_1" - expect(workflow.steps["step_1"]?.triggerActions["btn_1"]?.resolvedTargetStepId) == "step_2" + expect(workflow.steps["step_1"]?.triggerActions["btn_1"]?.stepId) == "step_2" expect(workflow.contentMaxWidth) == 100 + expect(workflow.metadata).to(beNil()) } func testDecodePublishedWorkflowWithoutOptionals() throws { @@ -101,7 +102,29 @@ class WorkflowResponseTests: TestCase { expect(workflow.contentMaxWidth).to(beNil()) } - func testDecodeWorkflowTriggerActionWithStepId() throws { + func testDecodePublishedWorkflowWithMetadata() throws { + let json = """ + { + "id": "wf_meta", + "display_name": "Meta", + "initial_step_id": "step_1", + "steps": {}, + "screens": {}, + "ui_config": { + "app": { "colors": {}, "fonts": {} }, + "localizations": {}, + "variable_config": {} + }, + "metadata": { "some_key": "some_value" } + } + """.data(using: .utf8)! + + let workflow = try JSONDecoder.default.decode(PublishedWorkflow.self, from: json) + + expect(workflow.metadata).toNot(beNil()) + } + + func testDecodeWorkflowTriggerAction() throws { let json = """ { "type": "step", "step_id": "step_3" } """.data(using: .utf8)! @@ -109,17 +132,25 @@ class WorkflowResponseTests: TestCase { let action = try JSONDecoder.default.decode(WorkflowTriggerAction.self, from: json) expect(action.type) == "step" - expect(action.resolvedTargetStepId) == "step_3" + expect(action.stepId) == "step_3" } - func testDecodeWorkflowTriggerActionValueTakesPrecedence() throws { + func testDecodeWorkflowTrigger() throws { let json = """ - { "type": "step", "value": "step_2", "step_id": "step_3" } + { + "name": "Button", + "type": "on_press", + "action_id": "btn_wagcLsIVjN", + "component_id": "wagcLsIVjN" + } """.data(using: .utf8)! - let action = try JSONDecoder.default.decode(WorkflowTriggerAction.self, from: json) + let trigger = try JSONDecoder.default.decode(WorkflowTrigger.self, from: json) - expect(action.resolvedTargetStepId) == "step_2" + expect(trigger.name) == "Button" + expect(trigger.type) == "on_press" + expect(trigger.actionId) == "btn_wagcLsIVjN" + expect(trigger.componentId) == "wagcLsIVjN" } func testDecodeWorkflowStepDefaults() throws { @@ -133,7 +164,56 @@ class WorkflowResponseTests: TestCase { expect(step.type) == "screen" expect(step.screenId).to(beNil()) expect(step.paramValues).to(beEmpty()) + expect(step.triggers).to(beEmpty()) + expect(step.outputs).to(beEmpty()) expect(step.triggerActions).to(beEmpty()) + expect(step.metadata).to(beNil()) + } + + func testDecodeWorkflowStepMatchingActualBackendResponse() throws { + let json = """ + { + "id": "bdMPgNB", + "type": "screen", + "param_values": { + "experiment_id": "expeae100d588", + "experiment_variant": "b", + "is_last_variant_step": true + }, + "triggers": [ + { + "name": "Button", + "type": "on_press", + "action_id": "btn_wagcLsIVjN", + "component_id": "wagcLsIVjN" + } + ], + "outputs": {}, + "trigger_actions": { + "btn_wagcLsIVjN": { + "type": "step", + "step_id": "ztBPCwD" + } + }, + "metadata": null, + "screen_id": "pw458e23295b7841f8" + } + """.data(using: .utf8)! + + let step = try JSONDecoder.default.decode(WorkflowStep.self, from: json) + + expect(step.id) == "bdMPgNB" + expect(step.type) == "screen" + expect(step.screenId) == "pw458e23295b7841f8" + expect(step.triggers).to(haveCount(1)) + expect(step.triggers.first?.name) == "Button" + expect(step.triggers.first?.type) == "on_press" + expect(step.triggers.first?.actionId) == "btn_wagcLsIVjN" + expect(step.triggers.first?.componentId) == "wagcLsIVjN" + expect(step.outputs).to(beEmpty()) + expect(step.triggerActions["btn_wagcLsIVjN"]?.type) == "step" + expect(step.triggerActions["btn_wagcLsIVjN"]?.stepId) == "ztBPCwD" + expect(step.metadata).to(beNil()) } } From 7765b6abe1dc1fd326b08b9d16a13bcf1f1f242d Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Tue, 7 Apr 2026 11:57:53 +0200 Subject: [PATCH 06/24] Disable file_length lint rule in HTTPRequestPath.swift Made-with: Cursor --- Sources/Networking/HTTPClient/HTTPRequestPath.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/Networking/HTTPClient/HTTPRequestPath.swift b/Sources/Networking/HTTPClient/HTTPRequestPath.swift index 66459be39b..fa5a6fff37 100644 --- a/Sources/Networking/HTTPClient/HTTPRequestPath.swift +++ b/Sources/Networking/HTTPClient/HTTPRequestPath.swift @@ -11,6 +11,8 @@ // // Created by Nacho Soto on 8/8/23. +// swiftlint:disable file_length + import Foundation protocol HTTPRequestPath { From 9e3caf8f62b195da0d83695274d3e33aca4378b7 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Tue, 7 Apr 2026 12:09:42 +0200 Subject: [PATCH 07/24] Fix GetWorkflowOperation to compute result once before distributing to callbacks CDN fetch and JSON decoding were running once per deduplicated callback. Compute the Result once outside the performOnAllItemsAndRemoveFromCache loop, matching the pattern used by GetOfferingsOperation and other operations in the codebase. Made-with: Cursor --- .../Operations/GetWorkflowOperation.swift | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/Sources/Networking/Operations/GetWorkflowOperation.swift b/Sources/Networking/Operations/GetWorkflowOperation.swift index f431af53e1..75d619d5b0 100644 --- a/Sources/Networking/Operations/GetWorkflowOperation.swift +++ b/Sources/Networking/Operations/GetWorkflowOperation.swift @@ -83,25 +83,26 @@ private extension GetWorkflowOperation { completion() } + let result: Result = response + .mapError(BackendError.networkError) + .flatMap { verifiedResponse in + do { + let processed = try self.detailProcessor.process(verifiedResponse.body) + let workflow = try PublishedWorkflow.create(with: processed.workflowData) + return .success(WorkflowFetchResult( + workflow: workflow, + enrolledVariants: processed.enrolledVariants + )) + } catch { + return .failure(BackendError.networkError( + NetworkError.decoding(error, verifiedResponse.body) + )) + } + } + self.workflowDetailCallbackCache.performOnAllItemsAndRemoveFromCache( withCacheable: self ) { callbackObject in - let result: Result = response - .mapError(BackendError.networkError) - .flatMap { verifiedResponse in - do { - let processed = try self.detailProcessor.process(verifiedResponse.body) - let workflow = try PublishedWorkflow.create(with: processed.workflowData) - return .success(WorkflowFetchResult( - workflow: workflow, - enrolledVariants: processed.enrolledVariants - )) - } catch { - return .failure(BackendError.networkError( - NetworkError.decoding(error, verifiedResponse.body) - )) - } - } callbackObject.completion(result) } } From 12498e8ab3b764975f969103d35a2bf40654b4da Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Tue, 7 Apr 2026 12:40:31 +0200 Subject: [PATCH 08/24] 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 --- .../Operations/GetWorkflowOperation.swift | 4 ++++ .../Workflows/WorkflowCdnFetcher.swift | 24 +++++++++++++++++-- .../Workflows/WorkflowDetailProcessor.swift | 14 ++++++++++- .../WorkflowDetailProcessorTests.swift | 10 ++++++-- 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/Sources/Networking/Operations/GetWorkflowOperation.swift b/Sources/Networking/Operations/GetWorkflowOperation.swift index 75d619d5b0..77abb54b89 100644 --- a/Sources/Networking/Operations/GetWorkflowOperation.swift +++ b/Sources/Networking/Operations/GetWorkflowOperation.swift @@ -93,6 +93,10 @@ private extension GetWorkflowOperation { workflow: workflow, enrolledVariants: processed.enrolledVariants )) + } catch WorkflowDetailProcessingError.cdnFetchFailed(let underlyingError) { + return .failure(BackendError.networkError( + NetworkError.networkError(underlyingError) + )) } catch { return .failure(BackendError.networkError( NetworkError.decoding(error, verifiedResponse.body) diff --git a/Sources/Networking/Workflows/WorkflowCdnFetcher.swift b/Sources/Networking/Workflows/WorkflowCdnFetcher.swift index 163574fce6..4363289402 100644 --- a/Sources/Networking/Workflows/WorkflowCdnFetcher.swift +++ b/Sources/Networking/Workflows/WorkflowCdnFetcher.swift @@ -20,14 +20,34 @@ protocol WorkflowCdnFetcher: Sendable { } -/// Direct URL fetcher — downloads from the CDN URL synchronously. +/// Direct URL fetcher — downloads from the CDN URL using URLSession. final class DirectWorkflowCdnFetcher: WorkflowCdnFetcher { func fetchCompiledWorkflowData(cdnUrl: String) throws -> Data { guard let url = URL(string: cdnUrl) else { throw URLError(.badURL) } - return try Data(contentsOf: url) + + var fetchResult: Result = .failure(URLError(.unknown)) + let semaphore = DispatchSemaphore(value: 0) + + URLSession.shared.dataTask(with: url) { data, response, error in + defer { semaphore.signal() } + + if let error = error { + fetchResult = .failure(error) + } else if let httpResponse = response as? HTTPURLResponse, + !(200..<300).contains(httpResponse.statusCode) { + fetchResult = .failure(URLError(.badServerResponse)) + } else if let data = data { + fetchResult = .success(data) + } else { + fetchResult = .failure(URLError(.unknown)) + } + }.resume() + + semaphore.wait() + return try fetchResult.get() } } diff --git a/Sources/Networking/Workflows/WorkflowDetailProcessor.swift b/Sources/Networking/Workflows/WorkflowDetailProcessor.swift index 3b209a8dfe..b153cf592c 100644 --- a/Sources/Networking/Workflows/WorkflowDetailProcessor.swift +++ b/Sources/Networking/Workflows/WorkflowDetailProcessor.swift @@ -13,6 +13,14 @@ import Foundation +/// Typed errors thrown by `WorkflowDetailProcessor` so callers can distinguish +/// CDN network failures from envelope parsing failures. +enum WorkflowDetailProcessingError: Error { + + case cdnFetchFailed(Error) + +} + struct WorkflowDetailProcessingResult { let workflowData: Data @@ -56,7 +64,11 @@ final class WorkflowDetailProcessor: Sendable { guard let cdnUrl = json["url"] as? String else { throw Self.processingError("Missing 'url' in use_cdn workflow response") } - workflowData = try self.cdnFetcher.fetchCompiledWorkflowData(cdnUrl: cdnUrl) + do { + workflowData = try self.cdnFetcher.fetchCompiledWorkflowData(cdnUrl: cdnUrl) + } catch { + throw WorkflowDetailProcessingError.cdnFetchFailed(error) + } } return WorkflowDetailProcessingResult( diff --git a/Tests/UnitTests/Networking/Workflows/WorkflowDetailProcessorTests.swift b/Tests/UnitTests/Networking/Workflows/WorkflowDetailProcessorTests.swift index b1c4efe165..16b41bd4db 100644 --- a/Tests/UnitTests/Networking/Workflows/WorkflowDetailProcessorTests.swift +++ b/Tests/UnitTests/Networking/Workflows/WorkflowDetailProcessorTests.swift @@ -85,7 +85,7 @@ class WorkflowDetailProcessorTests: TestCase { expect(try self.processor.process(data)).to(throwError()) } - func testUseCdnPropagatesIOError() throws { + func testUseCdnPropagatesIOErrorAsCdnFetchFailed() throws { let failingFetcher = MockWorkflowCdnFetcher { _ in throw URLError(.notConnectedToInternet) } @@ -97,7 +97,13 @@ class WorkflowDetailProcessorTests: TestCase { ] let data = try JSONSerialization.data(withJSONObject: envelope) - expect(try failingProcessor.process(data)).to(throwError()) + expect(try failingProcessor.process(data)).to(throwError { error in + guard case WorkflowDetailProcessingError.cdnFetchFailed(let underlying) = error else { + fail("Expected WorkflowDetailProcessingError.cdnFetchFailed, got \(error)") + return + } + expect((underlying as? URLError)?.code) == .notConnectedToInternet + }) } func testMissingDataInInlineThrows() throws { From 7c082bd990c50f607583db743ac2215dba06491b Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Tue, 7 Apr 2026 12:45:11 +0200 Subject: [PATCH 09/24] 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 --- .../Operations/GetWorkflowOperation.swift | 35 +++++++----- .../Workflows/WorkflowCdnFetcher.swift | 40 ++++++-------- .../Workflows/WorkflowDetailProcessor.swift | 4 +- .../WorkflowDetailProcessorTests.swift | 53 ++++++++----------- 4 files changed, 62 insertions(+), 70 deletions(-) diff --git a/Sources/Networking/Operations/GetWorkflowOperation.swift b/Sources/Networking/Operations/GetWorkflowOperation.swift index 77abb54b89..85bc606b8e 100644 --- a/Sources/Networking/Operations/GetWorkflowOperation.swift +++ b/Sources/Networking/Operations/GetWorkflowOperation.swift @@ -79,35 +79,42 @@ private extension GetWorkflowOperation { ) httpClient.perform(request) { (response: VerifiedHTTPResponse.Result) in - defer { + switch response { + case .failure(let networkError): + self.workflowDetailCallbackCache.performOnAllItemsAndRemoveFromCache( + withCacheable: self + ) { callbackObject in + callbackObject.completion(.failure(BackendError.networkError(networkError))) + } completion() - } - let result: Result = response - .mapError(BackendError.networkError) - .flatMap { verifiedResponse in + case .success(let verifiedResponse): + Task { + let result: Result do { - let processed = try self.detailProcessor.process(verifiedResponse.body) + let processed = try await self.detailProcessor.process(verifiedResponse.body) let workflow = try PublishedWorkflow.create(with: processed.workflowData) - return .success(WorkflowFetchResult( + result = .success(WorkflowFetchResult( workflow: workflow, enrolledVariants: processed.enrolledVariants )) } catch WorkflowDetailProcessingError.cdnFetchFailed(let underlyingError) { - return .failure(BackendError.networkError( + result = .failure(BackendError.networkError( NetworkError.networkError(underlyingError) )) } catch { - return .failure(BackendError.networkError( + result = .failure(BackendError.networkError( NetworkError.decoding(error, verifiedResponse.body) )) } - } - self.workflowDetailCallbackCache.performOnAllItemsAndRemoveFromCache( - withCacheable: self - ) { callbackObject in - callbackObject.completion(result) + self.workflowDetailCallbackCache.performOnAllItemsAndRemoveFromCache( + withCacheable: self + ) { callbackObject in + callbackObject.completion(result) + } + completion() + } } } } diff --git a/Sources/Networking/Workflows/WorkflowCdnFetcher.swift b/Sources/Networking/Workflows/WorkflowCdnFetcher.swift index 4363289402..3eb5c1a9dd 100644 --- a/Sources/Networking/Workflows/WorkflowCdnFetcher.swift +++ b/Sources/Networking/Workflows/WorkflowCdnFetcher.swift @@ -16,38 +16,32 @@ import Foundation /// Fetches compiled workflow JSON from a CDN URL. protocol WorkflowCdnFetcher: Sendable { - func fetchCompiledWorkflowData(cdnUrl: String) throws -> Data + func fetchCompiledWorkflowData(cdnUrl: String) async throws -> Data } -/// Direct URL fetcher — downloads from the CDN URL using URLSession. +/// Direct URL fetcher — downloads from the CDN URL via URLSession. final class DirectWorkflowCdnFetcher: WorkflowCdnFetcher { - func fetchCompiledWorkflowData(cdnUrl: String) throws -> Data { + func fetchCompiledWorkflowData(cdnUrl: String) async throws -> Data { guard let url = URL(string: cdnUrl) else { throw URLError(.badURL) } - var fetchResult: Result = .failure(URLError(.unknown)) - let semaphore = DispatchSemaphore(value: 0) - - URLSession.shared.dataTask(with: url) { data, response, error in - defer { semaphore.signal() } - - if let error = error { - fetchResult = .failure(error) - } else if let httpResponse = response as? HTTPURLResponse, - !(200..<300).contains(httpResponse.statusCode) { - fetchResult = .failure(URLError(.badServerResponse)) - } else if let data = data { - fetchResult = .success(data) - } else { - fetchResult = .failure(URLError(.unknown)) - } - }.resume() - - semaphore.wait() - return try fetchResult.get() + return try await withCheckedThrowingContinuation { continuation in + URLSession.shared.dataTask(with: url) { data, response, error in + if let error = error { + continuation.resume(throwing: error) + } else if let httpResponse = response as? HTTPURLResponse, + !(200..<300).contains(httpResponse.statusCode) { + continuation.resume(throwing: URLError(.badServerResponse)) + } else if let data = data { + continuation.resume(returning: data) + } else { + continuation.resume(throwing: URLError(.unknown)) + } + }.resume() + } } } diff --git a/Sources/Networking/Workflows/WorkflowDetailProcessor.swift b/Sources/Networking/Workflows/WorkflowDetailProcessor.swift index b153cf592c..f952bb5c32 100644 --- a/Sources/Networking/Workflows/WorkflowDetailProcessor.swift +++ b/Sources/Networking/Workflows/WorkflowDetailProcessor.swift @@ -38,7 +38,7 @@ final class WorkflowDetailProcessor: Sendable { self.cdnFetcher = cdnFetcher } - func process(_ data: Data) throws -> WorkflowDetailProcessingResult { + func process(_ data: Data) async throws -> WorkflowDetailProcessingResult { let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] guard let json else { throw Self.processingError("Failed to parse workflow detail envelope as JSON dictionary") @@ -65,7 +65,7 @@ final class WorkflowDetailProcessor: Sendable { throw Self.processingError("Missing 'url' in use_cdn workflow response") } do { - workflowData = try self.cdnFetcher.fetchCompiledWorkflowData(cdnUrl: cdnUrl) + workflowData = try await self.cdnFetcher.fetchCompiledWorkflowData(cdnUrl: cdnUrl) } catch { throw WorkflowDetailProcessingError.cdnFetchFailed(error) } diff --git a/Tests/UnitTests/Networking/Workflows/WorkflowDetailProcessorTests.swift b/Tests/UnitTests/Networking/Workflows/WorkflowDetailProcessorTests.swift index 16b41bd4db..2defa74bb3 100644 --- a/Tests/UnitTests/Networking/Workflows/WorkflowDetailProcessorTests.swift +++ b/Tests/UnitTests/Networking/Workflows/WorkflowDetailProcessorTests.swift @@ -33,21 +33,21 @@ class WorkflowDetailProcessorTests: TestCase { self.processor = WorkflowDetailProcessor(cdnFetcher: fetcher) } - func testInlineUnwrapsData() throws { + func testInlineUnwrapsData() async throws { let envelope: [String: Any] = [ "action": "inline", "data": ["id": "wf_inline"] ] let data = try JSONSerialization.data(withJSONObject: envelope) - let result = try processor.process(data) + let result = try await processor.process(data) let parsed = try JSONSerialization.jsonObject(with: result.workflowData) as? [String: Any] expect(parsed?["id"] as? String) == "wf_inline" expect(result.enrolledVariants).to(beNil()) } - func testInlineExtractsEnrolledVariants() throws { + func testInlineExtractsEnrolledVariants() async throws { let envelope: [String: Any] = [ "action": "inline", "data": ["id": "wf1"], @@ -55,12 +55,12 @@ class WorkflowDetailProcessorTests: TestCase { ] let data = try JSONSerialization.data(withJSONObject: envelope) - let result = try processor.process(data) + let result = try await processor.process(data) expect(result.enrolledVariants) == ["a": "b"] } - func testUseCdnFetchesFromUrl() throws { + func testUseCdnFetchesFromUrl() async throws { let envelope: [String: Any] = [ "action": "use_cdn", "url": "https://cdn.example/w.json", @@ -68,7 +68,7 @@ class WorkflowDetailProcessorTests: TestCase { ] let data = try JSONSerialization.data(withJSONObject: envelope) - let result = try processor.process(data) + let result = try await processor.process(data) expect(self.fetchedUrls) == ["https://cdn.example/w.json"] let parsed = try JSONSerialization.jsonObject(with: result.workflowData) as? [String: Any] @@ -76,28 +76,23 @@ class WorkflowDetailProcessorTests: TestCase { expect(result.enrolledVariants) == ["x": "y"] } - func testUnknownActionThrows() throws { - let envelope: [String: Any] = [ - "action": "other" - ] + func testUnknownActionThrows() async throws { + let envelope: [String: Any] = ["action": "other"] let data = try JSONSerialization.data(withJSONObject: envelope) - expect(try self.processor.process(data)).to(throwError()) + await expect { try await self.processor.process(data) }.to(throwError()) } - func testUseCdnPropagatesIOErrorAsCdnFetchFailed() throws { + func testUseCdnPropagatesIOErrorAsCdnFetchFailed() async throws { let failingFetcher = MockWorkflowCdnFetcher { _ in throw URLError(.notConnectedToInternet) } let failingProcessor = WorkflowDetailProcessor(cdnFetcher: failingFetcher) - let envelope: [String: Any] = [ - "action": "use_cdn", - "url": "https://x" - ] + let envelope: [String: Any] = ["action": "use_cdn", "url": "https://x"] let data = try JSONSerialization.data(withJSONObject: envelope) - expect(try failingProcessor.process(data)).to(throwError { error in + await expect { try await failingProcessor.process(data) }.to(throwError { error in guard case WorkflowDetailProcessingError.cdnFetchFailed(let underlying) = error else { fail("Expected WorkflowDetailProcessingError.cdnFetchFailed, got \(error)") return @@ -106,36 +101,32 @@ class WorkflowDetailProcessorTests: TestCase { }) } - func testMissingDataInInlineThrows() throws { - let envelope: [String: Any] = [ - "action": "inline" - ] + func testMissingDataInInlineThrows() async throws { + let envelope: [String: Any] = ["action": "inline"] let data = try JSONSerialization.data(withJSONObject: envelope) - expect(try self.processor.process(data)).to(throwError()) + await expect { try await self.processor.process(data) }.to(throwError()) } - func testMissingUrlInUseCdnThrows() throws { - let envelope: [String: Any] = [ - "action": "use_cdn" - ] + func testMissingUrlInUseCdnThrows() async throws { + let envelope: [String: Any] = ["action": "use_cdn"] let data = try JSONSerialization.data(withJSONObject: envelope) - expect(try self.processor.process(data)).to(throwError()) + await expect { try await self.processor.process(data) }.to(throwError()) } } private final class MockWorkflowCdnFetcher: WorkflowCdnFetcher { - private let handler: (String) throws -> Data + private let handler: (String) async throws -> Data - init(_ handler: @escaping (String) throws -> Data) { + init(_ handler: @escaping (String) async throws -> Data) { self.handler = handler } - func fetchCompiledWorkflowData(cdnUrl: String) throws -> Data { - return try handler(cdnUrl) + func fetchCompiledWorkflowData(cdnUrl: String) async throws -> Data { + return try await handler(cdnUrl) } } From 95b96c7111ac96d3fb7ec6d73224856eca8f1d6e Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Wed, 8 Apr 2026 13:44:41 +0200 Subject: [PATCH 10/24] 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 --- .../Operations/GetWorkflowOperation.swift | 77 +++++++++-------- .../Workflows/WorkflowCdnFetcher.swift | 33 ++++--- .../Workflows/WorkflowDetailProcessor.swift | 49 +++++++---- .../WorkflowDetailProcessorTests.swift | 86 ++++++++++++------- 4 files changed, 147 insertions(+), 98 deletions(-) diff --git a/Sources/Networking/Operations/GetWorkflowOperation.swift b/Sources/Networking/Operations/GetWorkflowOperation.swift index 85bc606b8e..de3b115a8f 100644 --- a/Sources/Networking/Operations/GetWorkflowOperation.swift +++ b/Sources/Networking/Operations/GetWorkflowOperation.swift @@ -79,43 +79,52 @@ private extension GetWorkflowOperation { ) httpClient.perform(request) { (response: VerifiedHTTPResponse.Result) in - switch response { - case .failure(let networkError): - self.workflowDetailCallbackCache.performOnAllItemsAndRemoveFromCache( - withCacheable: self - ) { callbackObject in - callbackObject.completion(.failure(BackendError.networkError(networkError))) - } + self.handleResponse(response, completion: completion) + } + } + + func handleResponse(_ response: VerifiedHTTPResponse.Result, completion: @escaping () -> Void) { + switch response { + case .failure(let networkError): + self.workflowDetailCallbackCache.performOnAllItemsAndRemoveFromCache( + withCacheable: self + ) { callbackObject in + callbackObject.completion(.failure(BackendError.networkError(networkError))) + } + completion() + + case .success(let verifiedResponse): + self.detailProcessor.process(verifiedResponse.body) { processingResult in + self.distribute(self.backendResult(from: processingResult, envelopeData: verifiedResponse.body)) completion() + } + } + } - case .success(let verifiedResponse): - Task { - let result: Result - do { - let processed = try await self.detailProcessor.process(verifiedResponse.body) - let workflow = try PublishedWorkflow.create(with: processed.workflowData) - result = .success(WorkflowFetchResult( - workflow: workflow, - enrolledVariants: processed.enrolledVariants - )) - } catch WorkflowDetailProcessingError.cdnFetchFailed(let underlyingError) { - result = .failure(BackendError.networkError( - NetworkError.networkError(underlyingError) - )) - } catch { - result = .failure(BackendError.networkError( - NetworkError.decoding(error, verifiedResponse.body) - )) - } - - self.workflowDetailCallbackCache.performOnAllItemsAndRemoveFromCache( - withCacheable: self - ) { callbackObject in - callbackObject.completion(result) - } - completion() - } + func backendResult( + from processingResult: Result, + envelopeData: Data + ) -> Result { + switch processingResult { + case .success(let processed): + do { + let workflow = try PublishedWorkflow.create(with: processed.workflowData) + return .success(WorkflowFetchResult(workflow: workflow, enrolledVariants: processed.enrolledVariants)) + } catch { + return .failure(BackendError.networkError(NetworkError.decoding(error, envelopeData))) } + case .failure(WorkflowDetailProcessingError.cdnFetchFailed(let underlyingError)): + return .failure(BackendError.networkError(NetworkError.networkError(underlyingError))) + case .failure(let error): + return .failure(BackendError.networkError(NetworkError.decoding(error, envelopeData))) + } + } + + func distribute(_ result: Result) { + self.workflowDetailCallbackCache.performOnAllItemsAndRemoveFromCache( + withCacheable: self + ) { callbackObject in + callbackObject.completion(result) } } diff --git a/Sources/Networking/Workflows/WorkflowCdnFetcher.swift b/Sources/Networking/Workflows/WorkflowCdnFetcher.swift index 3eb5c1a9dd..0535a0f003 100644 --- a/Sources/Networking/Workflows/WorkflowCdnFetcher.swift +++ b/Sources/Networking/Workflows/WorkflowCdnFetcher.swift @@ -16,32 +16,31 @@ import Foundation /// Fetches compiled workflow JSON from a CDN URL. protocol WorkflowCdnFetcher: Sendable { - func fetchCompiledWorkflowData(cdnUrl: String) async throws -> Data + func fetchCompiledWorkflowData(cdnUrl: String, completion: @escaping (Result) -> Void) } /// Direct URL fetcher — downloads from the CDN URL via URLSession. final class DirectWorkflowCdnFetcher: WorkflowCdnFetcher { - func fetchCompiledWorkflowData(cdnUrl: String) async throws -> Data { + func fetchCompiledWorkflowData(cdnUrl: String, completion: @escaping (Result) -> Void) { guard let url = URL(string: cdnUrl) else { - throw URLError(.badURL) + completion(.failure(URLError(.badURL))) + return } - return try await withCheckedThrowingContinuation { continuation in - URLSession.shared.dataTask(with: url) { data, response, error in - if let error = error { - continuation.resume(throwing: error) - } else if let httpResponse = response as? HTTPURLResponse, - !(200..<300).contains(httpResponse.statusCode) { - continuation.resume(throwing: URLError(.badServerResponse)) - } else if let data = data { - continuation.resume(returning: data) - } else { - continuation.resume(throwing: URLError(.unknown)) - } - }.resume() - } + URLSession.shared.dataTask(with: url) { data, response, error in + if let error = error { + completion(.failure(error)) + } else if let httpResponse = response as? HTTPURLResponse, + !(200..<300).contains(httpResponse.statusCode) { + completion(.failure(URLError(.badServerResponse))) + } else if let data = data { + completion(.success(data)) + } else { + completion(.failure(URLError(.unknown))) + } + }.resume() } } diff --git a/Sources/Networking/Workflows/WorkflowDetailProcessor.swift b/Sources/Networking/Workflows/WorkflowDetailProcessor.swift index f952bb5c32..7beabd1129 100644 --- a/Sources/Networking/Workflows/WorkflowDetailProcessor.swift +++ b/Sources/Networking/Workflows/WorkflowDetailProcessor.swift @@ -38,43 +38,56 @@ final class WorkflowDetailProcessor: Sendable { self.cdnFetcher = cdnFetcher } - func process(_ data: Data) async throws -> WorkflowDetailProcessingResult { - let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] + func process(_ data: Data, completion: @escaping (Result) -> Void) { + let json: [String: Any]? + do { + json = try JSONSerialization.jsonObject(with: data) as? [String: Any] + } catch { + completion(.failure(error)) + return + } + guard let json else { - throw Self.processingError("Failed to parse workflow detail envelope as JSON dictionary") + completion(.failure(Self.processingError("Failed to parse workflow detail envelope as JSON dictionary"))) + return } - let enrolledVariants = (json["enrolled_variants"] as? [String: String]) + let enrolledVariants = json["enrolled_variants"] as? [String: String] guard let actionString = json["action"] as? String, let action = WorkflowResponseAction(rawValue: actionString) else { let actionValue = json["action"] as? String ?? "nil" - throw Self.processingError("Unknown workflow response action: \(actionValue)") + completion(.failure(Self.processingError("Unknown workflow response action: \(actionValue)"))) + return } - let workflowData: Data switch action { case .inline: guard let inlineData = json["data"] else { - throw Self.processingError("Missing 'data' in inline workflow response") + completion(.failure(Self.processingError("Missing 'data' in inline workflow response"))) + return + } + do { + let workflowData = try JSONSerialization.data(withJSONObject: inlineData) + completion(.success(.init(workflowData: workflowData, enrolledVariants: enrolledVariants))) + } catch { + completion(.failure(error)) } - workflowData = try JSONSerialization.data(withJSONObject: inlineData) case .useCdn: guard let cdnUrl = json["url"] as? String else { - throw Self.processingError("Missing 'url' in use_cdn workflow response") + completion(.failure(Self.processingError("Missing 'url' in use_cdn workflow response"))) + return } - do { - workflowData = try await self.cdnFetcher.fetchCompiledWorkflowData(cdnUrl: cdnUrl) - } catch { - throw WorkflowDetailProcessingError.cdnFetchFailed(error) + self.cdnFetcher.fetchCompiledWorkflowData(cdnUrl: cdnUrl) { result in + switch result { + case .success(let workflowData): + completion(.success(.init(workflowData: workflowData, enrolledVariants: enrolledVariants))) + case .failure(let error): + completion(.failure(WorkflowDetailProcessingError.cdnFetchFailed(error))) + } } } - - return WorkflowDetailProcessingResult( - workflowData: workflowData, - enrolledVariants: enrolledVariants - ) } private static func processingError(_ message: String) -> Error { diff --git a/Tests/UnitTests/Networking/Workflows/WorkflowDetailProcessorTests.swift b/Tests/UnitTests/Networking/Workflows/WorkflowDetailProcessorTests.swift index 2defa74bb3..b871b502b2 100644 --- a/Tests/UnitTests/Networking/Workflows/WorkflowDetailProcessorTests.swift +++ b/Tests/UnitTests/Networking/Workflows/WorkflowDetailProcessorTests.swift @@ -26,28 +26,32 @@ class WorkflowDetailProcessorTests: TestCase { try super.setUpWithError() self.fetchedUrls = [] - let fetcher = MockWorkflowCdnFetcher { [weak self] url in + let fetcher = MockWorkflowCdnFetcher { [weak self] url, completion in self?.fetchedUrls.append(url) - return try JSONSerialization.data(withJSONObject: ["id": "from_cdn"]) + completion(.success((try? JSONSerialization.data(withJSONObject: ["id": "from_cdn"])) ?? Data())) } self.processor = WorkflowDetailProcessor(cdnFetcher: fetcher) } - func testInlineUnwrapsData() async throws { + func testInlineUnwrapsData() throws { let envelope: [String: Any] = [ "action": "inline", "data": ["id": "wf_inline"] ] let data = try JSONSerialization.data(withJSONObject: envelope) - let result = try await processor.process(data) + let result = waitUntilValue { completed in + self.processor.process(data, completion: completed) + } - let parsed = try JSONSerialization.jsonObject(with: result.workflowData) as? [String: Any] - expect(parsed?["id"] as? String) == "wf_inline" - expect(result.enrolledVariants).to(beNil()) + expect(result).to(beSuccess { value in + let parsed = try? JSONSerialization.jsonObject(with: value.workflowData) as? [String: Any] + expect(parsed?["id"] as? String) == "wf_inline" + expect(value.enrolledVariants).to(beNil()) + }) } - func testInlineExtractsEnrolledVariants() async throws { + func testInlineExtractsEnrolledVariants() throws { let envelope: [String: Any] = [ "action": "inline", "data": ["id": "wf1"], @@ -55,12 +59,16 @@ class WorkflowDetailProcessorTests: TestCase { ] let data = try JSONSerialization.data(withJSONObject: envelope) - let result = try await processor.process(data) + let result = waitUntilValue { completed in + self.processor.process(data, completion: completed) + } - expect(result.enrolledVariants) == ["a": "b"] + expect(result).to(beSuccess { value in + expect(value.enrolledVariants) == ["a": "b"] + }) } - func testUseCdnFetchesFromUrl() async throws { + func testUseCdnFetchesFromUrl() throws { let envelope: [String: Any] = [ "action": "use_cdn", "url": "https://cdn.example/w.json", @@ -68,31 +76,43 @@ class WorkflowDetailProcessorTests: TestCase { ] let data = try JSONSerialization.data(withJSONObject: envelope) - let result = try await processor.process(data) + let result = waitUntilValue { completed in + self.processor.process(data, completion: completed) + } expect(self.fetchedUrls) == ["https://cdn.example/w.json"] - let parsed = try JSONSerialization.jsonObject(with: result.workflowData) as? [String: Any] - expect(parsed?["id"] as? String) == "from_cdn" - expect(result.enrolledVariants) == ["x": "y"] + expect(result).to(beSuccess { value in + let parsed = try? JSONSerialization.jsonObject(with: value.workflowData) as? [String: Any] + expect(parsed?["id"] as? String) == "from_cdn" + expect(value.enrolledVariants) == ["x": "y"] + }) } - func testUnknownActionThrows() async throws { + func testUnknownActionThrows() throws { let envelope: [String: Any] = ["action": "other"] let data = try JSONSerialization.data(withJSONObject: envelope) - await expect { try await self.processor.process(data) }.to(throwError()) + let result = waitUntilValue { completed in + self.processor.process(data, completion: completed) + } + + expect(result).to(beFailure()) } - func testUseCdnPropagatesIOErrorAsCdnFetchFailed() async throws { - let failingFetcher = MockWorkflowCdnFetcher { _ in - throw URLError(.notConnectedToInternet) + func testUseCdnPropagatesIOErrorAsCdnFetchFailed() throws { + let failingFetcher = MockWorkflowCdnFetcher { _, completion in + completion(.failure(URLError(.notConnectedToInternet))) } let failingProcessor = WorkflowDetailProcessor(cdnFetcher: failingFetcher) let envelope: [String: Any] = ["action": "use_cdn", "url": "https://x"] let data = try JSONSerialization.data(withJSONObject: envelope) - await expect { try await failingProcessor.process(data) }.to(throwError { error in + let result = waitUntilValue { completed in + failingProcessor.process(data, completion: completed) + } + + expect(result).to(beFailure { error in guard case WorkflowDetailProcessingError.cdnFetchFailed(let underlying) = error else { fail("Expected WorkflowDetailProcessingError.cdnFetchFailed, got \(error)") return @@ -101,32 +121,40 @@ class WorkflowDetailProcessorTests: TestCase { }) } - func testMissingDataInInlineThrows() async throws { + func testMissingDataInInlineThrows() throws { let envelope: [String: Any] = ["action": "inline"] let data = try JSONSerialization.data(withJSONObject: envelope) - await expect { try await self.processor.process(data) }.to(throwError()) + let result = waitUntilValue { completed in + self.processor.process(data, completion: completed) + } + + expect(result).to(beFailure()) } - func testMissingUrlInUseCdnThrows() async throws { + func testMissingUrlInUseCdnThrows() throws { let envelope: [String: Any] = ["action": "use_cdn"] let data = try JSONSerialization.data(withJSONObject: envelope) - await expect { try await self.processor.process(data) }.to(throwError()) + let result = waitUntilValue { completed in + self.processor.process(data, completion: completed) + } + + expect(result).to(beFailure()) } } private final class MockWorkflowCdnFetcher: WorkflowCdnFetcher { - private let handler: (String) async throws -> Data + private let handler: (String, @escaping (Result) -> Void) -> Void - init(_ handler: @escaping (String) async throws -> Data) { + init(_ handler: @escaping (String, @escaping (Result) -> Void) -> Void) { self.handler = handler } - func fetchCompiledWorkflowData(cdnUrl: String) async throws -> Data { - return try await handler(cdnUrl) + func fetchCompiledWorkflowData(cdnUrl: String, completion: @escaping (Result) -> Void) { + self.handler(cdnUrl, completion) } } From ef79c3d23df08809387d3970759ba3503f787232 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:05:27 +0200 Subject: [PATCH 11/24] 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 --- Sources/Networking/Operations/GetWorkflowOperation.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Networking/Operations/GetWorkflowOperation.swift b/Sources/Networking/Operations/GetWorkflowOperation.swift index de3b115a8f..3089f74db0 100644 --- a/Sources/Networking/Operations/GetWorkflowOperation.swift +++ b/Sources/Networking/Operations/GetWorkflowOperation.swift @@ -35,7 +35,7 @@ final class GetWorkflowOperation: CacheableNetworkOperation { cacheKey: cacheKey ) }, - individualizedCacheKeyPart: "\(configuration.appUserID) \(workflowId)") + individualizedCacheKeyPart: configuration.appUserID + "\n" + workflowId) } private init(configuration: UserSpecificConfiguration, From 55db06c190d74ae7b64b2b5bc7cadf5ea1735369 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:18:16 +0200 Subject: [PATCH 12/24] PR comments --- .../Networking/HTTPClient/HTTPClient.swift | 25 +++++++++ .../Operations/GetWorkflowOperation.swift | 21 ++++---- .../Workflows/WorkflowCdnFetcher.swift | 37 +++---------- .../Workflows/WorkflowDetailProcessor.swift | 31 +++++------ Sources/Networking/WorkflowsAPI.swift | 16 +++++- Tests/UnitTests/Mocks/MockBackend.swift | 2 +- Tests/UnitTests/Mocks/MockWorkflowsAPI.swift | 54 +++++++++++++++++++ .../Networking/Backend/BaseBackendTest.swift | 4 +- .../WorkflowDetailProcessorTests.swift | 24 ++------- 9 files changed, 132 insertions(+), 82 deletions(-) create mode 100644 Tests/UnitTests/Mocks/MockWorkflowsAPI.swift diff --git a/Sources/Networking/HTTPClient/HTTPClient.swift b/Sources/Networking/HTTPClient/HTTPClient.swift index f1795abee5..197dc6e90e 100644 --- a/Sources/Networking/HTTPClient/HTTPClient.swift +++ b/Sources/Networking/HTTPClient/HTTPClient.swift @@ -219,6 +219,31 @@ extension HTTPClient { // - Class is not `final` (it's mocked). This implicitly makes subclasses `Sendable` even if they're not thread-safe. extension HTTPClient: @unchecked Sendable {} +// MARK: - CDN + +internal extension HTTPClient { + + /// Fetches raw data from an arbitrary URL using the client's configured `URLSession`. + /// + /// Use this for CDN or other non-RC-API requests where the SDK's path-based request building + /// is not applicable, but we still want the same session configuration (timeouts, connection limits). + func fetchRawData(from url: URL, completion: @escaping (Result) -> Void) { + self.session.dataTask(with: url) { data, response, error in + if let error = error { + completion(.failure(error)) + } else if let httpResponse = response as? HTTPURLResponse, + !(200..<300).contains(httpResponse.statusCode) { + completion(.failure(URLError(.badServerResponse))) + } else if let data = data { + completion(.success(data)) + } else { + completion(.failure(URLError(.unknown))) + } + }.resume() + } + +} + // MARK: - Private internal extension HTTPClient { diff --git a/Sources/Networking/Operations/GetWorkflowOperation.swift b/Sources/Networking/Operations/GetWorkflowOperation.swift index 3089f74db0..e7490ea520 100644 --- a/Sources/Networking/Operations/GetWorkflowOperation.swift +++ b/Sources/Networking/Operations/GetWorkflowOperation.swift @@ -86,17 +86,13 @@ private extension GetWorkflowOperation { func handleResponse(_ response: VerifiedHTTPResponse.Result, completion: @escaping () -> Void) { switch response { case .failure(let networkError): - self.workflowDetailCallbackCache.performOnAllItemsAndRemoveFromCache( - withCacheable: self - ) { callbackObject in - callbackObject.completion(.failure(BackendError.networkError(networkError))) - } - completion() + defer { completion() } + self.distribute(.failure(BackendError.networkError(networkError))) case .success(let verifiedResponse): self.detailProcessor.process(verifiedResponse.body) { processingResult in + defer { completion() } self.distribute(self.backendResult(from: processingResult, envelopeData: verifiedResponse.body)) - completion() } } } @@ -113,10 +109,15 @@ private extension GetWorkflowOperation { } catch { return .failure(BackendError.networkError(NetworkError.decoding(error, envelopeData))) } - case .failure(WorkflowDetailProcessingError.cdnFetchFailed(let underlyingError)): - return .failure(BackendError.networkError(NetworkError.networkError(underlyingError))) + case .failure(let processingError as WorkflowDetailProcessingError): + switch processingError { + case .cdnFetchFailed(let underlyingError): + return .failure(.networkError(NetworkError.networkError(underlyingError))) + case .invalidEnvelopeJson, .unknownAction, .missingInlineData, .missingCdnUrl: + return .failure(.networkError(NetworkError.decoding(processingError, envelopeData))) + } case .failure(let error): - return .failure(BackendError.networkError(NetworkError.decoding(error, envelopeData))) + return .failure(.networkError(NetworkError.decoding(error, envelopeData))) } } diff --git a/Sources/Networking/Workflows/WorkflowCdnFetcher.swift b/Sources/Networking/Workflows/WorkflowCdnFetcher.swift index 0535a0f003..9a8a507665 100644 --- a/Sources/Networking/Workflows/WorkflowCdnFetcher.swift +++ b/Sources/Networking/Workflows/WorkflowCdnFetcher.swift @@ -13,34 +13,9 @@ import Foundation -/// Fetches compiled workflow JSON from a CDN URL. -protocol WorkflowCdnFetcher: Sendable { - - func fetchCompiledWorkflowData(cdnUrl: String, completion: @escaping (Result) -> Void) - -} - -/// Direct URL fetcher — downloads from the CDN URL via URLSession. -final class DirectWorkflowCdnFetcher: WorkflowCdnFetcher { - - func fetchCompiledWorkflowData(cdnUrl: String, completion: @escaping (Result) -> Void) { - guard let url = URL(string: cdnUrl) else { - completion(.failure(URLError(.badURL))) - return - } - - URLSession.shared.dataTask(with: url) { data, response, error in - if let error = error { - completion(.failure(error)) - } else if let httpResponse = response as? HTTPURLResponse, - !(200..<300).contains(httpResponse.statusCode) { - completion(.failure(URLError(.badServerResponse))) - } else if let data = data { - completion(.success(data)) - } else { - completion(.failure(URLError(.unknown))) - } - }.resume() - } - -} +/// A closure that fetches compiled workflow JSON from a CDN URL. +/// +/// - Parameters: +/// - cdnUrl: The CDN URL string to fetch from. +/// - completion: Called with the raw `Data` on success, or an `Error` on failure. +typealias WorkflowCdnFetch = @Sendable (String, @escaping (Result) -> Void) -> Void diff --git a/Sources/Networking/Workflows/WorkflowDetailProcessor.swift b/Sources/Networking/Workflows/WorkflowDetailProcessor.swift index 7beabd1129..c73e1dcea3 100644 --- a/Sources/Networking/Workflows/WorkflowDetailProcessor.swift +++ b/Sources/Networking/Workflows/WorkflowDetailProcessor.swift @@ -13,11 +13,14 @@ import Foundation -/// Typed errors thrown by `WorkflowDetailProcessor` so callers can distinguish -/// CDN network failures from envelope parsing failures. +/// Typed errors thrown by `WorkflowDetailProcessor` so callers can distinguish failure modes. enum WorkflowDetailProcessingError: Error { case cdnFetchFailed(Error) + case invalidEnvelopeJson + case unknownAction(String) + case missingInlineData + case missingCdnUrl } @@ -32,10 +35,10 @@ struct WorkflowDetailProcessingResult { /// `inline` (unwraps `data`) or `use_cdn` (fetches JSON from CDN). final class WorkflowDetailProcessor: Sendable { - private let cdnFetcher: WorkflowCdnFetcher + private let cdnFetch: WorkflowCdnFetch - init(cdnFetcher: WorkflowCdnFetcher) { - self.cdnFetcher = cdnFetcher + init(cdnFetch: @escaping WorkflowCdnFetch) { + self.cdnFetch = cdnFetch } func process(_ data: Data, completion: @escaping (Result) -> Void) { @@ -43,12 +46,12 @@ final class WorkflowDetailProcessor: Sendable { do { json = try JSONSerialization.jsonObject(with: data) as? [String: Any] } catch { - completion(.failure(error)) + completion(.failure(WorkflowDetailProcessingError.invalidEnvelopeJson)) return } guard let json else { - completion(.failure(Self.processingError("Failed to parse workflow detail envelope as JSON dictionary"))) + completion(.failure(WorkflowDetailProcessingError.invalidEnvelopeJson)) return } @@ -57,14 +60,14 @@ final class WorkflowDetailProcessor: Sendable { guard let actionString = json["action"] as? String, let action = WorkflowResponseAction(rawValue: actionString) else { let actionValue = json["action"] as? String ?? "nil" - completion(.failure(Self.processingError("Unknown workflow response action: \(actionValue)"))) + completion(.failure(WorkflowDetailProcessingError.unknownAction(actionValue))) return } switch action { case .inline: guard let inlineData = json["data"] else { - completion(.failure(Self.processingError("Missing 'data' in inline workflow response"))) + completion(.failure(WorkflowDetailProcessingError.missingInlineData)) return } do { @@ -76,10 +79,10 @@ final class WorkflowDetailProcessor: Sendable { case .useCdn: guard let cdnUrl = json["url"] as? String else { - completion(.failure(Self.processingError("Missing 'url' in use_cdn workflow response"))) + completion(.failure(WorkflowDetailProcessingError.missingCdnUrl)) return } - self.cdnFetcher.fetchCompiledWorkflowData(cdnUrl: cdnUrl) { result in + self.cdnFetch(cdnUrl) { result in switch result { case .success(let workflowData): completion(.success(.init(workflowData: workflowData, enrolledVariants: enrolledVariants))) @@ -90,10 +93,4 @@ final class WorkflowDetailProcessor: Sendable { } } - private static func processingError(_ message: String) -> Error { - NSError(domain: "RevenueCat.WorkflowDetailProcessor", - code: -1, - userInfo: [NSLocalizedDescriptionKey: message]) - } - } diff --git a/Sources/Networking/WorkflowsAPI.swift b/Sources/Networking/WorkflowsAPI.swift index 30a77b1a50..3d8f288216 100644 --- a/Sources/Networking/WorkflowsAPI.swift +++ b/Sources/Networking/WorkflowsAPI.swift @@ -24,11 +24,23 @@ class WorkflowsAPI { private let detailProcessor: WorkflowDetailProcessor init(backendConfig: BackendConfiguration, - cdnFetcher: WorkflowCdnFetcher = DirectWorkflowCdnFetcher()) { + cdnFetch: WorkflowCdnFetch? = nil) { self.backendConfig = backendConfig self.workflowsListCallbackCache = .init() self.workflowDetailCallbackCache = .init() - self.detailProcessor = WorkflowDetailProcessor(cdnFetcher: cdnFetcher) + self.detailProcessor = WorkflowDetailProcessor( + cdnFetch: cdnFetch ?? Self.defaultCdnFetch(httpClient: backendConfig.httpClient) + ) + } + + private static func defaultCdnFetch(httpClient: HTTPClient) -> WorkflowCdnFetch { + return { cdnUrl, completion in + guard let url = URL(string: cdnUrl) else { + completion(.failure(URLError(.badURL))) + return + } + httpClient.fetchRawData(from: url, completion: completion) + } } func getWorkflows(appUserID: String, diff --git a/Tests/UnitTests/Mocks/MockBackend.swift b/Tests/UnitTests/Mocks/MockBackend.swift index c10eec0360..8e0656d681 100644 --- a/Tests/UnitTests/Mocks/MockBackend.swift +++ b/Tests/UnitTests/Mocks/MockBackend.swift @@ -47,7 +47,7 @@ class MockBackend: Backend { let customerCenterConfig = CustomerCenterConfigAPI(backendConfig: backendConfig) let redeemWebPurchaseAPI = MockRedeemWebPurchaseAPI() let virtualCurrenciesAPI = MockVirtualCurrenciesAPI() - let workflowsAPI = WorkflowsAPI(backendConfig: backendConfig) + let workflowsAPI = MockWorkflowsAPI() self.init(backendConfig: backendConfig, customerAPI: customer, diff --git a/Tests/UnitTests/Mocks/MockWorkflowsAPI.swift b/Tests/UnitTests/Mocks/MockWorkflowsAPI.swift new file mode 100644 index 0000000000..e9c396b46d --- /dev/null +++ b/Tests/UnitTests/Mocks/MockWorkflowsAPI.swift @@ -0,0 +1,54 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// MockWorkflowsAPI.swift +// +// Created by RevenueCat. + +import Foundation +@testable import RevenueCat + +class MockWorkflowsAPI: WorkflowsAPI { + + init() { + super.init(backendConfig: MockBackendConfiguration()) + } + + var invokedGetWorkflows = false + var invokedGetWorkflowsCount = 0 + var invokedGetWorkflowsParameters: (appUserID: String, isAppBackgrounded: Bool)? + var stubbedGetWorkflowsResult: Result? + + override func getWorkflows(appUserID: String, + isAppBackgrounded: Bool, + completion: @escaping WorkflowsListResponseHandler) { + self.invokedGetWorkflows = true + self.invokedGetWorkflowsCount += 1 + self.invokedGetWorkflowsParameters = (appUserID, isAppBackgrounded) + + completion(self.stubbedGetWorkflowsResult ?? .failure(.missingAppUserID())) + } + + var invokedGetWorkflow = false + var invokedGetWorkflowCount = 0 + var invokedGetWorkflowParameters: (appUserID: String, workflowId: String, isAppBackgrounded: Bool)? + var stubbedGetWorkflowResult: Result? + + override func getWorkflow(appUserID: String, + workflowId: String, + isAppBackgrounded: Bool, + completion: @escaping WorkflowDetailResponseHandler) { + self.invokedGetWorkflow = true + self.invokedGetWorkflowCount += 1 + self.invokedGetWorkflowParameters = (appUserID, workflowId, isAppBackgrounded) + + completion(self.stubbedGetWorkflowResult ?? .failure(.missingAppUserID())) + } + +} diff --git a/Tests/UnitTests/Networking/Backend/BaseBackendTest.swift b/Tests/UnitTests/Networking/Backend/BaseBackendTest.swift index edec9c5868..9b1a88bc7d 100644 --- a/Tests/UnitTests/Networking/Backend/BaseBackendTest.swift +++ b/Tests/UnitTests/Networking/Backend/BaseBackendTest.swift @@ -37,6 +37,7 @@ class BaseBackendTests: TestCase { private(set) var redeemWebPurchaseAPI: RedeemWebPurchaseAPI! private(set) var virtualCurrenciesAPI: VirtualCurrenciesAPI! private(set) var workflowsAPI: WorkflowsAPI! + private(set) var mockCdnFetch: WorkflowCdnFetch! static let apiKey = "asharedsecret" static let userID = "user" @@ -93,7 +94,8 @@ class BaseBackendTests: TestCase { self.customerCenterConfig = CustomerCenterConfigAPI(backendConfig: backendConfig) self.redeemWebPurchaseAPI = RedeemWebPurchaseAPI(backendConfig: backendConfig) self.virtualCurrenciesAPI = VirtualCurrenciesAPI(backendConfig: backendConfig) - self.workflowsAPI = WorkflowsAPI(backendConfig: backendConfig) + self.mockCdnFetch = { _, completion in completion(.success(Data())) } + self.workflowsAPI = WorkflowsAPI(backendConfig: backendConfig, cdnFetch: self.mockCdnFetch) self.backend = Backend(backendConfig: backendConfig, customerAPI: customer, diff --git a/Tests/UnitTests/Networking/Workflows/WorkflowDetailProcessorTests.swift b/Tests/UnitTests/Networking/Workflows/WorkflowDetailProcessorTests.swift index b871b502b2..ac180b78dd 100644 --- a/Tests/UnitTests/Networking/Workflows/WorkflowDetailProcessorTests.swift +++ b/Tests/UnitTests/Networking/Workflows/WorkflowDetailProcessorTests.swift @@ -26,11 +26,10 @@ class WorkflowDetailProcessorTests: TestCase { try super.setUpWithError() self.fetchedUrls = [] - let fetcher = MockWorkflowCdnFetcher { [weak self] url, completion in + self.processor = WorkflowDetailProcessor(cdnFetch: { [weak self] url, completion in self?.fetchedUrls.append(url) completion(.success((try? JSONSerialization.data(withJSONObject: ["id": "from_cdn"])) ?? Data())) - } - self.processor = WorkflowDetailProcessor(cdnFetcher: fetcher) + }) } func testInlineUnwrapsData() throws { @@ -100,10 +99,9 @@ class WorkflowDetailProcessorTests: TestCase { } func testUseCdnPropagatesIOErrorAsCdnFetchFailed() throws { - let failingFetcher = MockWorkflowCdnFetcher { _, completion in + let failingProcessor = WorkflowDetailProcessor(cdnFetch: { _, completion in completion(.failure(URLError(.notConnectedToInternet))) - } - let failingProcessor = WorkflowDetailProcessor(cdnFetcher: failingFetcher) + }) let envelope: [String: Any] = ["action": "use_cdn", "url": "https://x"] let data = try JSONSerialization.data(withJSONObject: envelope) @@ -144,17 +142,3 @@ class WorkflowDetailProcessorTests: TestCase { } } - -private final class MockWorkflowCdnFetcher: WorkflowCdnFetcher { - - private let handler: (String, @escaping (Result) -> Void) -> Void - - init(_ handler: @escaping (String, @escaping (Result) -> Void) -> Void) { - self.handler = handler - } - - func fetchCompiledWorkflowData(cdnUrl: String, completion: @escaping (Result) -> Void) { - self.handler(cdnUrl, completion) - } - -} From bde2ae6f1729a189639e63b854a728e8a72598f5 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:23:22 +0200 Subject: [PATCH 13/24] remove cdn fetcher --- Package.resolved | 36 +++++++++++++++++++ RevenueCat.xcodeproj/project.pbxproj | 10 +++--- .../Workflows/WorkflowCdnFetcher.swift | 21 ----------- .../Workflows/WorkflowDetailProcessor.swift | 7 ++++ 4 files changed, 49 insertions(+), 25 deletions(-) delete mode 100644 Sources/Networking/Workflows/WorkflowCdnFetcher.swift diff --git a/Package.resolved b/Package.resolved index ab65af5512..44d00852ea 100644 --- a/Package.resolved +++ b/Package.resolved @@ -18,6 +18,15 @@ "version" : "2.2.2" } }, + { + "identity" : "flyingfox", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swhitty/FlyingFox.git", + "state" : { + "revision" : "f7829d4aca8cbfecb410a1cce872b1b045224aa1", + "version" : "0.16.0" + } + }, { "identity" : "nimble", "kind" : "remoteSourceControl", @@ -27,6 +36,33 @@ "version" : "13.7.1" } }, + { + "identity" : "ohhttpstubs", + "kind" : "remoteSourceControl", + "location" : "https://github.com/AliSoftware/OHHTTPStubs.git", + "state" : { + "revision" : "12f19662426d0434d6c330c6974d53e2eb10ecd9", + "version" : "9.1.0" + } + }, + { + "identity" : "simpledebugger", + "kind" : "remoteSourceControl", + "location" : "https://github.com/EmergeTools/SimpleDebugger.git", + "state" : { + "revision" : "f065263eb5db95874b690408d136b573c939db9e", + "version" : "1.0.0" + } + }, + { + "identity" : "snapshotpreviews", + "kind" : "remoteSourceControl", + "location" : "https://github.com/EmergeTools/SnapshotPreviews.git", + "state" : { + "revision" : "6dfb281493e48e7c09ae9717593ae0b8eb47b15c", + "version" : "0.11.0" + } + }, { "identity" : "swift-custom-dump", "kind" : "remoteSourceControl", diff --git a/RevenueCat.xcodeproj/project.pbxproj b/RevenueCat.xcodeproj/project.pbxproj index 3ecb7b3f57..daf81a857b 100644 --- a/RevenueCat.xcodeproj/project.pbxproj +++ b/RevenueCat.xcodeproj/project.pbxproj @@ -1318,7 +1318,6 @@ DB3395D32F840A400079250C /* GetWorkflowOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3395D02F840A400079250C /* GetWorkflowOperation.swift */; }; DB3395D52F840A570079250C /* WorkflowsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3395D42F840A570079250C /* WorkflowsResponse.swift */; }; DB3395DB2F840A790079250C /* WorkflowDetailProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3395D82F840A790079250C /* WorkflowDetailProcessor.swift */; }; - DB3395DC2F840A790079250C /* WorkflowCdnFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3395D72F840A790079250C /* WorkflowCdnFetcher.swift */; }; DB3395DD2F840A790079250C /* WorkflowResponseAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3395D92F840A790079250C /* WorkflowResponseAction.swift */; }; DB3395E12F840AA60079250C /* WorkflowsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3395E02F840AA60079250C /* WorkflowsAPI.swift */; }; DB3395E32F840ACD0079250C /* BackendGetWorkflowsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3395E22F840ACD0079250C /* BackendGetWorkflowsTests.swift */; }; @@ -1326,6 +1325,8 @@ DB3395E82F840ADA0079250C /* WorkflowDetailProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3395E42F840ADA0079250C /* WorkflowDetailProcessorTests.swift */; }; DB36994E2F435CDC00B1F049 /* PriceFormatterExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB36994D2F435CDC00B1F049 /* PriceFormatterExtensions.swift */; }; DB55E6262ECE012600636909 /* FlexSpacer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB55E6252ECE012600636909 /* FlexSpacer.swift */; }; + DBAA1FA82F88EAEB000E8C81 /* MockWorkflowsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAA1FA72F88EAEB000E8C81 /* MockWorkflowsAPI.swift */; }; + DBAA1FA92F88EAEB000E8C81 /* MockWorkflowsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAA1FA72F88EAEB000E8C81 /* MockWorkflowsAPI.swift */; }; DBD2432F2EBDF4BC0066AC6F /* PromotionalOfferViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBD243292EBDF4B80066AC6F /* PromotionalOfferViewTests.swift */; }; DBE7C6552F02D48900A28663 /* StoreProductDiscount+CustomerCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBE7C6542F02D48900A28663 /* StoreProductDiscount+CustomerCenter.swift */; }; DDD45A15A641C0FF0BEF6178 /* PaywallCountdownComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A05E9E1B04B32B832DB194 /* PaywallCountdownComponent.swift */; }; @@ -2857,7 +2858,6 @@ DB3395D02F840A400079250C /* GetWorkflowOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetWorkflowOperation.swift; sourceTree = ""; }; DB3395D12F840A400079250C /* GetWorkflowsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetWorkflowsOperation.swift; sourceTree = ""; }; DB3395D42F840A570079250C /* WorkflowsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkflowsResponse.swift; sourceTree = ""; }; - DB3395D72F840A790079250C /* WorkflowCdnFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkflowCdnFetcher.swift; sourceTree = ""; }; DB3395D82F840A790079250C /* WorkflowDetailProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkflowDetailProcessor.swift; sourceTree = ""; }; DB3395D92F840A790079250C /* WorkflowResponseAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkflowResponseAction.swift; sourceTree = ""; }; DB3395E02F840AA60079250C /* WorkflowsAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkflowsAPI.swift; sourceTree = ""; }; @@ -2866,6 +2866,7 @@ DB3395E52F840ADA0079250C /* WorkflowResponseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkflowResponseTests.swift; sourceTree = ""; }; DB36994D2F435CDC00B1F049 /* PriceFormatterExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceFormatterExtensions.swift; sourceTree = ""; }; DB55E6252ECE012600636909 /* FlexSpacer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlexSpacer.swift; sourceTree = ""; }; + DBAA1FA72F88EAEB000E8C81 /* MockWorkflowsAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockWorkflowsAPI.swift; sourceTree = ""; }; DBD243292EBDF4B80066AC6F /* PromotionalOfferViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromotionalOfferViewTests.swift; sourceTree = ""; }; DBE7C6542F02D48900A28663 /* StoreProductDiscount+CustomerCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StoreProductDiscount+CustomerCenter.swift"; sourceTree = ""; }; EF970D13102945639A882002 /* ATTConsentStatusIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTConsentStatusIntegrationTests.swift; sourceTree = ""; }; @@ -4132,6 +4133,7 @@ FD18BF4A2DF0DA5400140FD6 /* MockVirtualCurrencyManager.swift */, FDE57AA62DF892C900101CE2 /* MockVirtualCurrenciesAPI.swift */, 75A0E7912E3D205C00A60BD5 /* MockTestStorePurchaseUI.swift */, + DBAA1FA72F88EAEB000E8C81 /* MockWorkflowsAPI.swift */, ); path = Mocks; sourceTree = ""; @@ -6074,7 +6076,6 @@ DB3395DA2F840A790079250C /* Workflows */ = { isa = PBXGroup; children = ( - DB3395D72F840A790079250C /* WorkflowCdnFetcher.swift */, DB3395D82F840A790079250C /* WorkflowDetailProcessor.swift */, DB3395D92F840A790079250C /* WorkflowResponseAction.swift */, ); @@ -6820,6 +6821,7 @@ 3543913826F90FE100E669DF /* MockIntroEligibilityCalculator.swift in Sources */, FD2046842CB833CD00166727 /* MockStoreKit2PurchaseIntentListenerDelegate.swift in Sources */, 3543914126F911CC00E669DF /* MockDeviceCache.swift in Sources */, + DBAA1FA82F88EAEB000E8C81 /* MockWorkflowsAPI.swift in Sources */, 3543913C26F9101600E669DF /* MockOperationDispatcher.swift in Sources */, 4FA4C9752A16D49E007D2803 /* MockOfflineEntitlementsManager.swift in Sources */, F5E5E2EE28479BD000216ECD /* ProductsFetcherSK2Tests.swift in Sources */, @@ -7015,7 +7017,6 @@ 2D11F5E1250FF886005A70E8 /* AttributionStrings.swift in Sources */, DB3395DB2F840A790079250C /* WorkflowDetailProcessor.swift in Sources */, DB3395E12F840AA60079250C /* WorkflowsAPI.swift in Sources */, - DB3395DC2F840A790079250C /* WorkflowCdnFetcher.swift in Sources */, DB3395DD2F840A790079250C /* WorkflowResponseAction.swift in Sources */, 2CD72944268A826F00BFC976 /* Date+Extensions.swift in Sources */, 57069A5428E3918400B86355 /* AsyncExtensions.swift in Sources */, @@ -7488,6 +7489,7 @@ B31C8BEC285BD58F001017B7 /* MockIdentityAPI.swift in Sources */, 351B513D26D4491E00BD2BD7 /* MockDeviceCache.swift in Sources */, 57910CB329C3889B006209D5 /* DispatchTimeIntervalExtensionsTests.swift in Sources */, + DBAA1FA92F88EAEB000E8C81 /* MockWorkflowsAPI.swift in Sources */, 351B515C26D44B7900BD2BD7 /* MockIntroEligibilityCalculator.swift in Sources */, 5733D01128D00354008638D8 /* PromotionalOfferTests.swift in Sources */, 57544C28285FA94B004E54D5 /* MockAttributeSyncing.swift in Sources */, diff --git a/Sources/Networking/Workflows/WorkflowCdnFetcher.swift b/Sources/Networking/Workflows/WorkflowCdnFetcher.swift deleted file mode 100644 index 9a8a507665..0000000000 --- a/Sources/Networking/Workflows/WorkflowCdnFetcher.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright RevenueCat Inc. All Rights Reserved. -// -// Licensed under the MIT License (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://opensource.org/licenses/MIT -// -// WorkflowCdnFetcher.swift -// -// Created by RevenueCat. - -import Foundation - -/// A closure that fetches compiled workflow JSON from a CDN URL. -/// -/// - Parameters: -/// - cdnUrl: The CDN URL string to fetch from. -/// - completion: Called with the raw `Data` on success, or an `Error` on failure. -typealias WorkflowCdnFetch = @Sendable (String, @escaping (Result) -> Void) -> Void diff --git a/Sources/Networking/Workflows/WorkflowDetailProcessor.swift b/Sources/Networking/Workflows/WorkflowDetailProcessor.swift index c73e1dcea3..7fbe85b8d3 100644 --- a/Sources/Networking/Workflows/WorkflowDetailProcessor.swift +++ b/Sources/Networking/Workflows/WorkflowDetailProcessor.swift @@ -13,6 +13,13 @@ import Foundation +/// A closure that fetches compiled workflow JSON from a CDN URL. +/// +/// - Parameters: +/// - cdnUrl: The CDN URL string to fetch from. +/// - completion: Called with the raw `Data` on success, or an `Error` on failure. +typealias WorkflowCdnFetch = @Sendable (String, @escaping (Result) -> Void) -> Void + /// Typed errors thrown by `WorkflowDetailProcessor` so callers can distinguish failure modes. enum WorkflowDetailProcessingError: Error { From c167f2d34e002f1e2a4d78aecb7ff108e794ee58 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Fri, 10 Apr 2026 11:38:44 +0200 Subject: [PATCH 14/24] fix response in BackendGetWorkflowsTests.swift --- .../Networking/Backend/BackendGetWorkflowsTests.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Tests/UnitTests/Networking/Backend/BackendGetWorkflowsTests.swift b/Tests/UnitTests/Networking/Backend/BackendGetWorkflowsTests.swift index 8f3a6033fb..0672498f3f 100644 --- a/Tests/UnitTests/Networking/Backend/BackendGetWorkflowsTests.swift +++ b/Tests/UnitTests/Networking/Backend/BackendGetWorkflowsTests.swift @@ -254,7 +254,10 @@ private extension BackendGetWorkflowsTests { "fonts": [:] as [String: Any] ] as [String: Any], "localizations": [:] as [String: Any], - "variable_config": [:] as [String: Any] + "variable_config": [ + "variable_compatibility_map": [:] as [String: String], + "function_compatibility_map": [:] as [String: String] + ] as [String: Any] ] static let emptyWorkflowsResponse: [String: Any] = [ @@ -282,7 +285,10 @@ private extension BackendGetWorkflowTests { "fonts": [:] as [String: Any] ] as [String: Any], "localizations": [:] as [String: Any], - "variable_config": [:] as [String: Any] + "variable_config": [ + "variable_compatibility_map": [:] as [String: String], + "function_compatibility_map": [:] as [String: String] + ] as [String: Any] ] static let minimalWorkflowData: [String: Any] = [ From 36642b12d52bd6d0bc43f29d4e6a808ab53f79fb Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Fri, 10 Apr 2026 11:50:41 +0200 Subject: [PATCH 15/24] fix WorkflowResponseTests --- .../Networking/Workflows/WorkflowResponseTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/UnitTests/Networking/Workflows/WorkflowResponseTests.swift b/Tests/UnitTests/Networking/Workflows/WorkflowResponseTests.swift index 9bd7798c50..b3ff765149 100644 --- a/Tests/UnitTests/Networking/Workflows/WorkflowResponseTests.swift +++ b/Tests/UnitTests/Networking/Workflows/WorkflowResponseTests.swift @@ -28,7 +28,7 @@ class WorkflowResponseTests: TestCase { "ui_config": { "app": { "colors": {}, "fonts": {} }, "localizations": {}, - "variable_config": {} + "variable_config": { "variable_compatibility_map": {}, "function_compatibility_map": {} } } } """.data(using: .utf8)! @@ -61,7 +61,7 @@ class WorkflowResponseTests: TestCase { "ui_config": { "app": { "colors": {}, "fonts": {} }, "localizations": {}, - "variable_config": {} + "variable_config": { "variable_compatibility_map": {}, "function_compatibility_map": {} } }, "content_max_width": 100 } @@ -89,7 +89,7 @@ class WorkflowResponseTests: TestCase { "ui_config": { "app": { "colors": {}, "fonts": {} }, "localizations": {}, - "variable_config": {} + "variable_config": { "variable_compatibility_map": {}, "function_compatibility_map": {} } } } """.data(using: .utf8)! @@ -113,7 +113,7 @@ class WorkflowResponseTests: TestCase { "ui_config": { "app": { "colors": {}, "fonts": {} }, "localizations": {}, - "variable_config": {} + "variable_config": { "variable_compatibility_map": {}, "function_compatibility_map": {} } }, "metadata": { "some_key": "some_value" } } From 613aafa374c6b20662dcf92119010f328d151d70 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:04:07 +0200 Subject: [PATCH 16/24] fix error --- Sources/Networking/Operations/GetWorkflowOperation.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Networking/Operations/GetWorkflowOperation.swift b/Sources/Networking/Operations/GetWorkflowOperation.swift index e7490ea520..0a953f38bf 100644 --- a/Sources/Networking/Operations/GetWorkflowOperation.swift +++ b/Sources/Networking/Operations/GetWorkflowOperation.swift @@ -107,7 +107,7 @@ private extension GetWorkflowOperation { let workflow = try PublishedWorkflow.create(with: processed.workflowData) return .success(WorkflowFetchResult(workflow: workflow, enrolledVariants: processed.enrolledVariants)) } catch { - return .failure(BackendError.networkError(NetworkError.decoding(error, envelopeData))) + return .failure(BackendError.networkError(NetworkError.decoding(error, processed.workflowData))) } case .failure(let processingError as WorkflowDetailProcessingError): switch processingError { From 24092911ef75060ace474eb15c5a3f19b8f82f82 Mon Sep 17 00:00:00 2001 From: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:05:05 +0200 Subject: [PATCH 17/24] [skip ci] Generating new test snapshots (#6584) --- ...lowCachesForSameUserIDAndWorkflowId.1.json | 30 +++++++++++++++++++ ...18-testGetWorkflowInlineUnwrapsData.1.json | 30 +++++++++++++++++++ ...tWorkflowInlineWithEnrolledVariants.1.json | 30 +++++++++++++++++++ ...testGetWorkflowPropagatesHTTPErrors.1.json | 30 +++++++++++++++++++ ...testGetWorkflowsCachesForSameUserID.1.json | 30 +++++++++++++++++++ ...S18-testGetWorkflowsCallsHTTPMethod.1.json | 30 +++++++++++++++++++ ...thodWithRandomDelayWhenBackgrounded.1.json | 30 +++++++++++++++++++ ...tGetWorkflowsNetworkErrorSendsError.1.json | 30 +++++++++++++++++++ ...18-testGetWorkflowsReturnsWorkflows.1.json | 30 +++++++++++++++++++ 9 files changed, 270 insertions(+) create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowInlineUnwrapsData.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowInlineWithEnrolledVariants.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowPropagatesHTTPErrors.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowsCachesForSameUserID.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowsCallsHTTPMethod.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowsNetworkErrorSendsError.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowsReturnsWorkflows.1.json diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json new file mode 100644 index 0000000000..914bd2b9d8 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowInlineUnwrapsData.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowInlineUnwrapsData.1.json new file mode 100644 index 0000000000..914bd2b9d8 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowInlineUnwrapsData.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowInlineWithEnrolledVariants.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowInlineWithEnrolledVariants.1.json new file mode 100644 index 0000000000..914bd2b9d8 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowInlineWithEnrolledVariants.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowPropagatesHTTPErrors.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowPropagatesHTTPErrors.1.json new file mode 100644 index 0000000000..fc60b3e8fe --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowPropagatesHTTPErrors.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_missing" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowsCachesForSameUserID.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowsCachesForSameUserID.1.json new file mode 100644 index 0000000000..f033aa5118 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowsCachesForSameUserID.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowsCallsHTTPMethod.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowsCallsHTTPMethod.1.json new file mode 100644 index 0000000000..f033aa5118 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowsCallsHTTPMethod.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json new file mode 100644 index 0000000000..f033aa5118 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowsNetworkErrorSendsError.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowsNetworkErrorSendsError.1.json new file mode 100644 index 0000000000..f033aa5118 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowsNetworkErrorSendsError.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowsReturnsWorkflows.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowsReturnsWorkflows.1.json new file mode 100644 index 0000000000..f033aa5118 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/tvOS18-testGetWorkflowsReturnsWorkflows.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file From 8b02381ac4c8bcf9bdd81bd36bea309e5992839d Mon Sep 17 00:00:00 2001 From: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:07:16 +0200 Subject: [PATCH 18/24] [skip ci] Generating new test snapshots (#6585) --- ...lowCachesForSameUserIDAndWorkflowId.1.json | 29 +++++++++++++++++++ ...OS-testGetWorkflowInlineUnwrapsData.1.json | 29 +++++++++++++++++++ ...tWorkflowInlineWithEnrolledVariants.1.json | 29 +++++++++++++++++++ ...testGetWorkflowPropagatesHTTPErrors.1.json | 29 +++++++++++++++++++ ...testGetWorkflowsCachesForSameUserID.1.json | 29 +++++++++++++++++++ ...cOS-testGetWorkflowsCallsHTTPMethod.1.json | 29 +++++++++++++++++++ ...thodWithRandomDelayWhenBackgrounded.1.json | 29 +++++++++++++++++++ ...tGetWorkflowsNetworkErrorSendsError.1.json | 29 +++++++++++++++++++ ...OS-testGetWorkflowsReturnsWorkflows.1.json | 29 +++++++++++++++++++ 9 files changed, 261 insertions(+) create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowInlineUnwrapsData.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowInlineWithEnrolledVariants.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowPropagatesHTTPErrors.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowsCachesForSameUserID.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowsCallsHTTPMethod.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowsNetworkErrorSendsError.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowsReturnsWorkflows.1.json diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json new file mode 100644 index 0000000000..bef852c9d8 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json @@ -0,0 +1,29 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "false", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowInlineUnwrapsData.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowInlineUnwrapsData.1.json new file mode 100644 index 0000000000..bef852c9d8 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowInlineUnwrapsData.1.json @@ -0,0 +1,29 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "false", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowInlineWithEnrolledVariants.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowInlineWithEnrolledVariants.1.json new file mode 100644 index 0000000000..bef852c9d8 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowInlineWithEnrolledVariants.1.json @@ -0,0 +1,29 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "false", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowPropagatesHTTPErrors.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowPropagatesHTTPErrors.1.json new file mode 100644 index 0000000000..8061954148 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowPropagatesHTTPErrors.1.json @@ -0,0 +1,29 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "false", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_missing" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowsCachesForSameUserID.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowsCachesForSameUserID.1.json new file mode 100644 index 0000000000..fbb355f0dc --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowsCachesForSameUserID.1.json @@ -0,0 +1,29 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "false", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowsCallsHTTPMethod.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowsCallsHTTPMethod.1.json new file mode 100644 index 0000000000..fbb355f0dc --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowsCallsHTTPMethod.1.json @@ -0,0 +1,29 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "false", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json new file mode 100644 index 0000000000..fbb355f0dc --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json @@ -0,0 +1,29 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "false", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowsNetworkErrorSendsError.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowsNetworkErrorSendsError.1.json new file mode 100644 index 0000000000..fbb355f0dc --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowsNetworkErrorSendsError.1.json @@ -0,0 +1,29 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "false", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowsReturnsWorkflows.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowsReturnsWorkflows.1.json new file mode 100644 index 0000000000..fbb355f0dc --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/macOS-testGetWorkflowsReturnsWorkflows.1.json @@ -0,0 +1,29 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "false", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file From 243c89eb115e3186db5b4c151e70f5ba1566ff94 Mon Sep 17 00:00:00 2001 From: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:07:30 +0200 Subject: [PATCH 19/24] [skip ci] Generating new test snapshots (#6586) --- ...lowCachesForSameUserIDAndWorkflowId.1.json | 30 +++++++++++++++++++ ...16-testGetWorkflowInlineUnwrapsData.1.json | 30 +++++++++++++++++++ ...tWorkflowInlineWithEnrolledVariants.1.json | 30 +++++++++++++++++++ ...testGetWorkflowPropagatesHTTPErrors.1.json | 30 +++++++++++++++++++ ...testGetWorkflowsCachesForSameUserID.1.json | 30 +++++++++++++++++++ ...S16-testGetWorkflowsCallsHTTPMethod.1.json | 30 +++++++++++++++++++ ...thodWithRandomDelayWhenBackgrounded.1.json | 30 +++++++++++++++++++ ...tGetWorkflowsNetworkErrorSendsError.1.json | 30 +++++++++++++++++++ ...16-testGetWorkflowsReturnsWorkflows.1.json | 30 +++++++++++++++++++ 9 files changed, 270 insertions(+) create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowInlineUnwrapsData.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowInlineWithEnrolledVariants.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowPropagatesHTTPErrors.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowsCachesForSameUserID.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowsCallsHTTPMethod.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowsNetworkErrorSendsError.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowsReturnsWorkflows.1.json diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json new file mode 100644 index 0000000000..9979ad3824 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "content-type" : "application/json", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-Storefront" : "USA", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Version" : "4.0.0" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowInlineUnwrapsData.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowInlineUnwrapsData.1.json new file mode 100644 index 0000000000..9979ad3824 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowInlineUnwrapsData.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "content-type" : "application/json", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-Storefront" : "USA", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Version" : "4.0.0" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowInlineWithEnrolledVariants.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowInlineWithEnrolledVariants.1.json new file mode 100644 index 0000000000..9979ad3824 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowInlineWithEnrolledVariants.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "content-type" : "application/json", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-Storefront" : "USA", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Version" : "4.0.0" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowPropagatesHTTPErrors.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowPropagatesHTTPErrors.1.json new file mode 100644 index 0000000000..d277e5d995 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowPropagatesHTTPErrors.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "content-type" : "application/json", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-Storefront" : "USA", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Version" : "4.0.0" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_missing" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowsCachesForSameUserID.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowsCachesForSameUserID.1.json new file mode 100644 index 0000000000..c4bcf00734 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowsCachesForSameUserID.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "content-type" : "application/json", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-Storefront" : "USA", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Version" : "4.0.0" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowsCallsHTTPMethod.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowsCallsHTTPMethod.1.json new file mode 100644 index 0000000000..c4bcf00734 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowsCallsHTTPMethod.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "content-type" : "application/json", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-Storefront" : "USA", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Version" : "4.0.0" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json new file mode 100644 index 0000000000..c4bcf00734 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "content-type" : "application/json", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-Storefront" : "USA", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Version" : "4.0.0" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowsNetworkErrorSendsError.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowsNetworkErrorSendsError.1.json new file mode 100644 index 0000000000..c4bcf00734 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowsNetworkErrorSendsError.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "content-type" : "application/json", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-Storefront" : "USA", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Version" : "4.0.0" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowsReturnsWorkflows.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowsReturnsWorkflows.1.json new file mode 100644 index 0000000000..c4bcf00734 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS16-testGetWorkflowsReturnsWorkflows.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "content-type" : "application/json", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-Storefront" : "USA", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Version" : "4.0.0" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file From 89f7f47ad62f216c7e20d39c8de010d1269ebef8 Mon Sep 17 00:00:00 2001 From: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:07:42 +0200 Subject: [PATCH 20/24] [skip ci] Generating new test snapshots (#6587) --- ...lowCachesForSameUserIDAndWorkflowId.1.json | 30 +++++++++++++++++++ ...26-testGetWorkflowInlineUnwrapsData.1.json | 30 +++++++++++++++++++ ...tWorkflowInlineWithEnrolledVariants.1.json | 30 +++++++++++++++++++ ...testGetWorkflowPropagatesHTTPErrors.1.json | 30 +++++++++++++++++++ ...testGetWorkflowsCachesForSameUserID.1.json | 30 +++++++++++++++++++ ...S26-testGetWorkflowsCallsHTTPMethod.1.json | 30 +++++++++++++++++++ ...thodWithRandomDelayWhenBackgrounded.1.json | 30 +++++++++++++++++++ ...tGetWorkflowsNetworkErrorSendsError.1.json | 30 +++++++++++++++++++ ...26-testGetWorkflowsReturnsWorkflows.1.json | 30 +++++++++++++++++++ 9 files changed, 270 insertions(+) create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowInlineUnwrapsData.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowInlineWithEnrolledVariants.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowPropagatesHTTPErrors.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowsCachesForSameUserID.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowsCallsHTTPMethod.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowsNetworkErrorSendsError.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowsReturnsWorkflows.1.json diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json new file mode 100644 index 0000000000..914bd2b9d8 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowInlineUnwrapsData.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowInlineUnwrapsData.1.json new file mode 100644 index 0000000000..914bd2b9d8 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowInlineUnwrapsData.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowInlineWithEnrolledVariants.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowInlineWithEnrolledVariants.1.json new file mode 100644 index 0000000000..914bd2b9d8 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowInlineWithEnrolledVariants.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowPropagatesHTTPErrors.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowPropagatesHTTPErrors.1.json new file mode 100644 index 0000000000..fc60b3e8fe --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowPropagatesHTTPErrors.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_missing" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowsCachesForSameUserID.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowsCachesForSameUserID.1.json new file mode 100644 index 0000000000..f033aa5118 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowsCachesForSameUserID.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowsCallsHTTPMethod.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowsCallsHTTPMethod.1.json new file mode 100644 index 0000000000..f033aa5118 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowsCallsHTTPMethod.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json new file mode 100644 index 0000000000..f033aa5118 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowsNetworkErrorSendsError.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowsNetworkErrorSendsError.1.json new file mode 100644 index 0000000000..f033aa5118 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowsNetworkErrorSendsError.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowsReturnsWorkflows.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowsReturnsWorkflows.1.json new file mode 100644 index 0000000000..f033aa5118 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS26-testGetWorkflowsReturnsWorkflows.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file From 92762170404366c5c5ccff187c21b41271988764 Mon Sep 17 00:00:00 2001 From: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:12:12 +0200 Subject: [PATCH 21/24] [skip ci] Generating new test snapshots (#6588) --- ...lowCachesForSameUserIDAndWorkflowId.1.json | 30 +++++++++++++++++++ ...18-testGetWorkflowInlineUnwrapsData.1.json | 30 +++++++++++++++++++ ...tWorkflowInlineWithEnrolledVariants.1.json | 30 +++++++++++++++++++ ...testGetWorkflowPropagatesHTTPErrors.1.json | 30 +++++++++++++++++++ ...testGetWorkflowsCachesForSameUserID.1.json | 30 +++++++++++++++++++ ...S18-testGetWorkflowsCallsHTTPMethod.1.json | 30 +++++++++++++++++++ ...thodWithRandomDelayWhenBackgrounded.1.json | 30 +++++++++++++++++++ ...tGetWorkflowsNetworkErrorSendsError.1.json | 30 +++++++++++++++++++ ...18-testGetWorkflowsReturnsWorkflows.1.json | 30 +++++++++++++++++++ 9 files changed, 270 insertions(+) create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowInlineUnwrapsData.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowInlineWithEnrolledVariants.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowPropagatesHTTPErrors.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowsCachesForSameUserID.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowsCallsHTTPMethod.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowsNetworkErrorSendsError.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowsReturnsWorkflows.1.json diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json new file mode 100644 index 0000000000..914bd2b9d8 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowInlineUnwrapsData.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowInlineUnwrapsData.1.json new file mode 100644 index 0000000000..914bd2b9d8 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowInlineUnwrapsData.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowInlineWithEnrolledVariants.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowInlineWithEnrolledVariants.1.json new file mode 100644 index 0000000000..914bd2b9d8 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowInlineWithEnrolledVariants.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowPropagatesHTTPErrors.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowPropagatesHTTPErrors.1.json new file mode 100644 index 0000000000..fc60b3e8fe --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowPropagatesHTTPErrors.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_missing" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowsCachesForSameUserID.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowsCachesForSameUserID.1.json new file mode 100644 index 0000000000..f033aa5118 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowsCachesForSameUserID.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowsCallsHTTPMethod.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowsCallsHTTPMethod.1.json new file mode 100644 index 0000000000..f033aa5118 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowsCallsHTTPMethod.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json new file mode 100644 index 0000000000..f033aa5118 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowsNetworkErrorSendsError.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowsNetworkErrorSendsError.1.json new file mode 100644 index 0000000000..f033aa5118 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowsNetworkErrorSendsError.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowsReturnsWorkflows.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowsReturnsWorkflows.1.json new file mode 100644 index 0000000000..f033aa5118 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS18-testGetWorkflowsReturnsWorkflows.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file From 7a40c56a50cd061ec8e873179ffcf86bf22b8847 Mon Sep 17 00:00:00 2001 From: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:12:22 +0200 Subject: [PATCH 22/24] [skip ci] Generating new test snapshots (#6589) --- ...lowCachesForSameUserIDAndWorkflowId.1.json | 30 +++++++++++++++++++ ...OS-testGetWorkflowInlineUnwrapsData.1.json | 30 +++++++++++++++++++ ...tWorkflowInlineWithEnrolledVariants.1.json | 30 +++++++++++++++++++ ...testGetWorkflowPropagatesHTTPErrors.1.json | 30 +++++++++++++++++++ ...testGetWorkflowsCachesForSameUserID.1.json | 30 +++++++++++++++++++ ...hOS-testGetWorkflowsCallsHTTPMethod.1.json | 30 +++++++++++++++++++ ...thodWithRandomDelayWhenBackgrounded.1.json | 30 +++++++++++++++++++ ...tGetWorkflowsNetworkErrorSendsError.1.json | 30 +++++++++++++++++++ ...OS-testGetWorkflowsReturnsWorkflows.1.json | 30 +++++++++++++++++++ 9 files changed, 270 insertions(+) create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowInlineUnwrapsData.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowInlineWithEnrolledVariants.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowPropagatesHTTPErrors.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowsCachesForSameUserID.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowsCallsHTTPMethod.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowsNetworkErrorSendsError.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowsReturnsWorkflows.1.json diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json new file mode 100644 index 0000000000..de46bebfdf --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "1", + "X-StoreKit2-Enabled" : "false", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowInlineUnwrapsData.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowInlineUnwrapsData.1.json new file mode 100644 index 0000000000..de46bebfdf --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowInlineUnwrapsData.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "1", + "X-StoreKit2-Enabled" : "false", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowInlineWithEnrolledVariants.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowInlineWithEnrolledVariants.1.json new file mode 100644 index 0000000000..de46bebfdf --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowInlineWithEnrolledVariants.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "1", + "X-StoreKit2-Enabled" : "false", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowPropagatesHTTPErrors.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowPropagatesHTTPErrors.1.json new file mode 100644 index 0000000000..017c030a95 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowPropagatesHTTPErrors.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "1", + "X-StoreKit2-Enabled" : "false", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_missing" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowsCachesForSameUserID.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowsCachesForSameUserID.1.json new file mode 100644 index 0000000000..a3e7ee8aff --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowsCachesForSameUserID.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "1", + "X-StoreKit2-Enabled" : "false", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowsCallsHTTPMethod.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowsCallsHTTPMethod.1.json new file mode 100644 index 0000000000..a3e7ee8aff --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowsCallsHTTPMethod.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "1", + "X-StoreKit2-Enabled" : "false", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json new file mode 100644 index 0000000000..a3e7ee8aff --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "1", + "X-StoreKit2-Enabled" : "false", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowsNetworkErrorSendsError.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowsNetworkErrorSendsError.1.json new file mode 100644 index 0000000000..a3e7ee8aff --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowsNetworkErrorSendsError.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "1", + "X-StoreKit2-Enabled" : "false", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowsReturnsWorkflows.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowsReturnsWorkflows.1.json new file mode 100644 index 0000000000..a3e7ee8aff --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/watchOS-testGetWorkflowsReturnsWorkflows.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-StoreKit-Version" : "1", + "X-StoreKit2-Enabled" : "false", + "X-Storefront" : "USA", + "X-Version" : "4.0.0", + "content-type" : "application/json" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file From ea64ecf22ca9da39e24a3eeeadb87d8916eb5d79 Mon Sep 17 00:00:00 2001 From: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com> Date: Fri, 10 Apr 2026 12:12:32 +0200 Subject: [PATCH 23/24] [skip ci] Generating new test snapshots (#6590) --- ...lowCachesForSameUserIDAndWorkflowId.1.json | 30 +++++++++++++++++++ ...17-testGetWorkflowInlineUnwrapsData.1.json | 30 +++++++++++++++++++ ...tWorkflowInlineWithEnrolledVariants.1.json | 30 +++++++++++++++++++ ...testGetWorkflowPropagatesHTTPErrors.1.json | 30 +++++++++++++++++++ ...testGetWorkflowsCachesForSameUserID.1.json | 30 +++++++++++++++++++ ...S17-testGetWorkflowsCallsHTTPMethod.1.json | 30 +++++++++++++++++++ ...thodWithRandomDelayWhenBackgrounded.1.json | 30 +++++++++++++++++++ ...tGetWorkflowsNetworkErrorSendsError.1.json | 30 +++++++++++++++++++ ...17-testGetWorkflowsReturnsWorkflows.1.json | 30 +++++++++++++++++++ 9 files changed, 270 insertions(+) create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowInlineUnwrapsData.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowInlineWithEnrolledVariants.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowPropagatesHTTPErrors.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowsCachesForSameUserID.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowsCallsHTTPMethod.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowsNetworkErrorSendsError.1.json create mode 100644 Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowsReturnsWorkflows.1.json diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json new file mode 100644 index 0000000000..9979ad3824 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowCachesForSameUserIDAndWorkflowId.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "content-type" : "application/json", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-Storefront" : "USA", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Version" : "4.0.0" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowInlineUnwrapsData.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowInlineUnwrapsData.1.json new file mode 100644 index 0000000000..9979ad3824 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowInlineUnwrapsData.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "content-type" : "application/json", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-Storefront" : "USA", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Version" : "4.0.0" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowInlineWithEnrolledVariants.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowInlineWithEnrolledVariants.1.json new file mode 100644 index 0000000000..9979ad3824 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowInlineWithEnrolledVariants.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "content-type" : "application/json", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-Storefront" : "USA", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Version" : "4.0.0" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_1" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowPropagatesHTTPErrors.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowPropagatesHTTPErrors.1.json new file mode 100644 index 0000000000..d277e5d995 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowPropagatesHTTPErrors.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "content-type" : "application/json", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-Storefront" : "USA", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Version" : "4.0.0" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows/wf_missing" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowsCachesForSameUserID.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowsCachesForSameUserID.1.json new file mode 100644 index 0000000000..c4bcf00734 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowsCachesForSameUserID.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "content-type" : "application/json", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-Storefront" : "USA", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Version" : "4.0.0" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowsCallsHTTPMethod.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowsCallsHTTPMethod.1.json new file mode 100644 index 0000000000..c4bcf00734 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowsCallsHTTPMethod.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "content-type" : "application/json", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-Storefront" : "USA", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Version" : "4.0.0" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json new file mode 100644 index 0000000000..c4bcf00734 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowsCallsHTTPMethodWithRandomDelayWhenBackgrounded.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "content-type" : "application/json", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-Storefront" : "USA", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Version" : "4.0.0" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowsNetworkErrorSendsError.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowsNetworkErrorSendsError.1.json new file mode 100644 index 0000000000..c4bcf00734 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowsNetworkErrorSendsError.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "content-type" : "application/json", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-Storefront" : "USA", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Version" : "4.0.0" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowsReturnsWorkflows.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowsReturnsWorkflows.1.json new file mode 100644 index 0000000000..c4bcf00734 --- /dev/null +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendGetWorkflowsTests/iOS17-testGetWorkflowsReturnsWorkflows.1.json @@ -0,0 +1,30 @@ +{ + "headers" : { + "Authorization" : "Bearer asharedsecret", + "content-type" : "application/json", + "X-Apple-Device-Identifier" : "5D7C0074-07E4-4564-AAA4-4008D0640881", + "X-Client-Build-Version" : "12345", + "X-Client-Bundle-ID" : "com.apple.dt.xctest.tool", + "X-Client-Version" : "17.0.0", + "X-Installation-Method" : "unknown", + "X-Is-Backgrounded" : "false", + "X-Is-Debug-Build" : "true", + "X-Is-Sandbox" : "true", + "X-Observer-Mode-Enabled" : "false", + "X-Platform" : "iOS", + "X-Platform-Device" : "arm64", + "X-Platform-Flavor" : "native", + "X-Platform-Version" : "Version 17.0.0 (Build 21A342)", + "X-Preferred-Locales" : "en_EN", + "X-Retry-Count" : "0", + "X-Storefront" : "USA", + "X-StoreKit-Version" : "2", + "X-StoreKit2-Enabled" : "true", + "X-Version" : "4.0.0" + }, + "request" : { + "body" : null, + "method" : "GET", + "url" : "https://api.revenuecat.com/v1/subscribers/user/workflows" + } +} \ No newline at end of file From e7b8a9f69feb310e3c42590ed4c8a4ff4260f271 Mon Sep 17 00:00:00 2001 From: Cesar de la Vega <664544+vegaro@users.noreply.github.com> Date: Fri, 10 Apr 2026 15:28:15 +0200 Subject: [PATCH 24/24] Test CDN mock is not re-assignable per test --- .../Backend/BackendGetWorkflowsTests.swift | 29 +++++++++++++++++++ .../Networking/Backend/BaseBackendTest.swift | 9 ++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/Tests/UnitTests/Networking/Backend/BackendGetWorkflowsTests.swift b/Tests/UnitTests/Networking/Backend/BackendGetWorkflowsTests.swift index 0672498f3f..51b240661b 100644 --- a/Tests/UnitTests/Networking/Backend/BackendGetWorkflowsTests.swift +++ b/Tests/UnitTests/Networking/Backend/BackendGetWorkflowsTests.swift @@ -228,6 +228,30 @@ class BackendGetWorkflowTests: BaseBackendTests { expect(self.httpClient.calls).toEventually(haveCount(1)) } + func testGetWorkflowUseCdnFetchesWorkflowFromCdnUrl() throws { + let cdnWorkflowData = try JSONSerialization.data(withJSONObject: Self.minimalWorkflowData) + self.stubbedCdnFetch = { _, completion in completion(.success(cdnWorkflowData)) } + + self.httpClient.mock( + requestPath: .getWorkflow(appUserID: Self.userID, workflowId: "wf_1"), + response: .init(statusCode: .success, response: Self.cdnEnvelopeResponse) + ) + + let result = waitUntilValue { completed in + self.workflowsAPI.getWorkflow( + appUserID: Self.userID, + workflowId: "wf_1", + isAppBackgrounded: false, + completion: completed + ) + } + + expect(result).to(beSuccess { fetchResult in + expect(fetchResult.workflow.id) == "wf_1" + expect(fetchResult.workflow.displayName) == "Test Workflow" + }) + } + func testGetWorkflowSkipsBackendCallIfAppUserIDIsEmpty() { waitUntil { completed in self.workflowsAPI.getWorkflow( @@ -318,4 +342,9 @@ private extension BackendGetWorkflowTests { ] as [String: String] ] + static let cdnEnvelopeResponse: [String: Any] = [ + "action": "use_cdn", + "url": "https://cdn.example/wf.json" + ] + } diff --git a/Tests/UnitTests/Networking/Backend/BaseBackendTest.swift b/Tests/UnitTests/Networking/Backend/BaseBackendTest.swift index 9b1a88bc7d..c3832c9e31 100644 --- a/Tests/UnitTests/Networking/Backend/BaseBackendTest.swift +++ b/Tests/UnitTests/Networking/Backend/BaseBackendTest.swift @@ -37,7 +37,9 @@ class BaseBackendTests: TestCase { private(set) var redeemWebPurchaseAPI: RedeemWebPurchaseAPI! private(set) var virtualCurrenciesAPI: VirtualCurrenciesAPI! private(set) var workflowsAPI: WorkflowsAPI! - private(set) var mockCdnFetch: WorkflowCdnFetch! + /// Controls what the CDN fetch returns. Tests can reassign this before triggering a `use_cdn` response + /// because the closure registered with `WorkflowsAPI` captures `self` and reads this property at call time. + var stubbedCdnFetch: WorkflowCdnFetch = { _, completion in completion(.success(Data())) } static let apiKey = "asharedsecret" static let userID = "user" @@ -94,8 +96,9 @@ class BaseBackendTests: TestCase { self.customerCenterConfig = CustomerCenterConfigAPI(backendConfig: backendConfig) self.redeemWebPurchaseAPI = RedeemWebPurchaseAPI(backendConfig: backendConfig) self.virtualCurrenciesAPI = VirtualCurrenciesAPI(backendConfig: backendConfig) - self.mockCdnFetch = { _, completion in completion(.success(Data())) } - self.workflowsAPI = WorkflowsAPI(backendConfig: backendConfig, cdnFetch: self.mockCdnFetch) + self.workflowsAPI = WorkflowsAPI(backendConfig: backendConfig, cdnFetch: { [weak self] cdnUrl, completion in + self?.stubbedCdnFetch(cdnUrl, completion) ?? completion(.success(Data())) + }) self.backend = Backend(backendConfig: backendConfig, customerAPI: customer,