From 351efb1ae4f7b4806d4c67c0e364d365992125ef Mon Sep 17 00:00:00 2001 From: Bryan Dubno Date: Mon, 20 Nov 2023 09:53:56 -0800 Subject: [PATCH] Internal purchase controller refactor --- .../App Session/AppSessionManager.swift | 30 ++- .../TrackableSuperwallEvent.swift | 2 +- .../Analytics/SessionEventsManager.swift | 37 +-- .../Analytics/SessionEventsQueue.swift | 6 +- .../Product/TriggerSessionProduct.swift | 2 +- .../TriggerSessionManager.swift | 14 +- .../SuperwallKit/Config/ConfigManager.swift | 50 ++-- Sources/SuperwallKit/Debug/DebugManager.swift | 11 +- .../Debug/DebugViewController.swift | 42 ++-- .../Dependencies/DependencyContainer.swift | 223 ++++++++---------- .../Dependencies/FactoryProtocols.swift | 17 +- .../Identity/IdentityManager.swift | 30 ++- .../SuperwallKit/Models/Paywall/Paywall.swift | 2 +- .../Network/Device Helper/DeviceHelper.swift | 21 +- .../Paywall/Manager/PaywallManager.swift | 13 +- .../Internal/Operators/EvaluateRules.swift | 6 +- .../Internal/Operators/GetExperiment.swift | 2 +- .../Internal/Operators/GetPresenter.swift | 2 +- .../Paywall/Presentation/PaywallInfo.swift | 4 +- .../ExpressionEvaluator.swift | 14 +- .../Presentation/Rule Logic/RuleLogic.swift | 17 +- .../Operators/AddPaywallProducts.swift | 5 +- .../Request/PaywallRequestManager.swift | 22 +- .../PaywallViewController.swift | 23 +- .../PaywallMessageHandler.swift | 12 +- .../View Controller/Web View/SWWebView.swift | 2 +- .../SuperwallKit/Storage/EventsQueue.swift | 20 +- Sources/SuperwallKit/Storage/Storage.swift | 2 +- .../AutomaticPurchaseController.swift | 75 ++++++ .../StoreKit/InternalPurchaseController.swift | 110 +-------- .../Receipt Manager/ReceiptManager.swift | 29 ++- .../PurchaseControllerObjcAdapter.swift | 53 +++++ .../StoreKit/StoreKitManager.swift | 66 +----- .../Purchasing/ProductPurchaserSK1.swift | 36 +-- .../Transactions/TransactionManager.swift | 42 ++-- Sources/SuperwallKit/Superwall.swift | 12 +- .../App Session/AppSessionManagerMock.swift | 26 -- .../App Session/AppSessionManagerTests.swift | 7 +- .../TrackingLogicTests.swift | 5 +- .../SessionEventsManagerTests.swift | 91 +++---- .../Assignments/AssignmentLogicTests.swift | 67 +++--- .../ExpressionEvaluatorTests.swift | 152 ++++-------- .../Config/ConfigLogicTests.swift | 1 - .../Config/ConfigManagerTests.swift | 54 ++--- .../ConfirmHoldoutAssignmentTests.swift | 47 +--- ...onfirmPaywallAssignmentOperatorTests.swift | 45 +--- .../Operators/GetPaywallVcOperatorTests.swift | 15 +- .../PresentPaywallOperatorTests.swift | 10 +- .../View Controller/SurveyManagerTests.swift | 50 +--- .../PaywallMessageHandlerTests.swift | 45 +--- .../Storage/StorageTests.swift | 13 +- .../Receipt Manager/ReceiptManagerTests.swift | 31 ++- .../StoreKit/StoreKitManagerTests.swift | 32 ++- 53 files changed, 731 insertions(+), 1014 deletions(-) create mode 100644 Sources/SuperwallKit/StoreKit/AutomaticPurchaseController.swift create mode 100644 Sources/SuperwallKit/StoreKit/PurchaseControllerObjcAdapter.swift diff --git a/Sources/SuperwallKit/Analytics/App Session/AppSessionManager.swift b/Sources/SuperwallKit/Analytics/App Session/AppSessionManager.swift index 88c46e4fe..d4e5c39b0 100644 --- a/Sources/SuperwallKit/Analytics/App Session/AppSessionManager.swift +++ b/Sources/SuperwallKit/Analytics/App Session/AppSessionManager.swift @@ -26,19 +26,23 @@ class AppSessionManager { private var didTrackAppLaunch = false private var cancellable: AnyCancellable? - private unowned let configManager: ConfigManager - private unowned let storage: Storage - private unowned let delegate: AppManagerDelegate & DeviceHelperFactory & UserAttributesEventFactory - - init( - configManager: ConfigManager, - identityManager: IdentityManager, - storage: Storage, - delegate: AppManagerDelegate & DeviceHelperFactory & UserAttributesEventFactory - ) { - self.configManager = configManager - self.storage = storage - self.delegate = delegate + private var configManager: ConfigManager { + return factory.configManager + } + + private var storage: Storage { + return factory.storage + } + + private var delegate: AppManagerDelegate & DeviceHelperFactory & UserAttributesEventFactory { + return factory + } + + let factory: DependencyContainer + + init(factory: DependencyContainer) { + self.factory = factory + Task { await addActiveStateObservers() } diff --git a/Sources/SuperwallKit/Analytics/Internal Tracking/Trackable Events/TrackableSuperwallEvent.swift b/Sources/SuperwallKit/Analytics/Internal Tracking/Trackable Events/TrackableSuperwallEvent.swift index 21edf9cd2..dcdb15637 100644 --- a/Sources/SuperwallKit/Analytics/Internal Tracking/Trackable Events/TrackableSuperwallEvent.swift +++ b/Sources/SuperwallKit/Analytics/Internal Tracking/Trackable Events/TrackableSuperwallEvent.swift @@ -272,7 +272,7 @@ enum InternalSuperwallEvent { } let triggerName: String var customParameters: [String: Any] = [:] - unowned let sessionEventsManager: SessionEventsManager + let sessionEventsManager: SessionEventsManager func getSuperwallParameters() async -> [String: Any] { var params: [String: Any] = [ diff --git a/Sources/SuperwallKit/Analytics/SessionEventsManager.swift b/Sources/SuperwallKit/Analytics/SessionEventsManager.swift index 594f102ec..ceb752331 100644 --- a/Sources/SuperwallKit/Analytics/SessionEventsManager.swift +++ b/Sources/SuperwallKit/Analytics/SessionEventsManager.swift @@ -20,27 +20,28 @@ class SessionEventsManager { /// The trigger session manager. lazy var triggerSession = factory.makeTriggerSessionManager() + private var cancellables: [AnyCancellable] = [] + /// A queue of trigger session events that get sent to the server. - private let queue: SessionEnqueuable + private lazy var queue: SessionEnqueuable = { + return SessionEventsQueue(storage: storage, network: network, configManager: configManager) + }() - private var cancellables: [AnyCancellable] = [] + private var network: Network { + return factory.network + } + + private var storage: Storage { + return factory.storage + } + + private var configManager: ConfigManager { + return factory.configManager + } + + private var factory: DependencyContainer - private unowned let network: Network - private unowned let storage: Storage - private unowned let configManager: ConfigManager - private unowned let factory: TriggerSessionManagerFactory - - init( - queue: SessionEnqueuable, - storage: Storage, - network: Network, - configManager: ConfigManager, - factory: TriggerSessionManagerFactory - ) { - self.queue = queue - self.storage = storage - self.network = network - self.configManager = configManager + init(factory: DependencyContainer) { self.factory = factory Task { diff --git a/Sources/SuperwallKit/Analytics/SessionEventsQueue.swift b/Sources/SuperwallKit/Analytics/SessionEventsQueue.swift index 30da45e79..023da1f9c 100644 --- a/Sources/SuperwallKit/Analytics/SessionEventsQueue.swift +++ b/Sources/SuperwallKit/Analytics/SessionEventsQueue.swift @@ -40,9 +40,9 @@ actor SessionEventsQueue: SessionEnqueuable { private var willResignActiveObserver: AnyCancellable? private lazy var lastTwentySessions = LimitedQueue(limit: 20) private lazy var lastTwentyTransactions = LimitedQueue(limit: 20) - private unowned let storage: Storage - private unowned let network: Network - private unowned let configManager: ConfigManager + private let storage: Storage + private let network: Network + private let configManager: ConfigManager deinit { timer?.invalidate() diff --git a/Sources/SuperwallKit/Analytics/Trigger Session Manager/Model/Transaction/Product/TriggerSessionProduct.swift b/Sources/SuperwallKit/Analytics/Trigger Session Manager/Model/Transaction/Product/TriggerSessionProduct.swift index d61d4bd4f..7524d51fa 100644 --- a/Sources/SuperwallKit/Analytics/Trigger Session Manager/Model/Transaction/Product/TriggerSessionProduct.swift +++ b/Sources/SuperwallKit/Analytics/Trigger Session Manager/Model/Transaction/Product/TriggerSessionProduct.swift @@ -91,7 +91,7 @@ extension TriggerSession.Transaction { type: introductoryPrice.type ) - self.introductoryRedeemable = await Superwall.shared.dependencyContainer.storeKitManager.isFreeTrialAvailable(for: product) + self.introductoryRedeemable = await Superwall.shared.dependencyContainer.receiptManager.isFreeTrialAvailable(for: product) self.hasIntroductoryOffer = true } else { self.hasIntroductoryOffer = false diff --git a/Sources/SuperwallKit/Analytics/Trigger Session Manager/TriggerSessionManager.swift b/Sources/SuperwallKit/Analytics/Trigger Session Manager/TriggerSessionManager.swift index 5ecc9c292..4987572ba 100644 --- a/Sources/SuperwallKit/Analytics/Trigger Session Manager/TriggerSessionManager.swift +++ b/Sources/SuperwallKit/Analytics/Trigger Session Manager/TriggerSessionManager.swift @@ -17,12 +17,12 @@ actor TriggerSessionManager { /// The active trigger session. var activeTriggerSession: TriggerSession? - private unowned let storage: Storage - private unowned let configManager: ConfigManager - private unowned let appSessionManager: AppSessionManager - private unowned let identityManager: IdentityManager - private unowned let delegate: SessionEventsDelegate - private unowned let sessionEventsManager: SessionEventsManager + private let storage: Storage + private let configManager: ConfigManager + private let appSessionManager: AppSessionManager + private let identityManager: IdentityManager + private let delegate: SessionEventsDelegate + private let sessionEventsManager: SessionEventsManager /// A local count for transactions used within the trigger session. private var transactionCount: TriggerSession.Transaction.Count? @@ -132,7 +132,7 @@ actor TriggerSessionManager { /// - presentationInfo: Information about the paywall presentation. /// - presentingViewController: What view the paywall will be presented on, if any. /// - paywall: The response from the server associated with the paywall - func activateSession( + @discardableResult func activateSession( for presentationInfo: PresentationInfo, on presentingViewController: UIViewController? = nil, paywall: Paywall? = nil, diff --git a/Sources/SuperwallKit/Config/ConfigManager.swift b/Sources/SuperwallKit/Config/ConfigManager.swift index 4050f77ba..11d5abee7 100644 --- a/Sources/SuperwallKit/Config/ConfigManager.swift +++ b/Sources/SuperwallKit/Config/ConfigManager.swift @@ -26,7 +26,7 @@ class ConfigManager { } /// Options for configuring the SDK. - var options = SuperwallOptions() + var options: SuperwallOptions /// A dictionary of triggers by their event name. @DispatchQueueBacked @@ -38,37 +38,42 @@ class ConfigManager { @DispatchQueueBacked var unconfirmedAssignments: [Experiment.ID: Experiment.Variant] = [:] - private unowned let storeKitManager: StoreKitManager - private unowned let storage: Storage - private unowned let network: Network - private unowned let paywallManager: PaywallManager + private var storeKitManager: StoreKitManager { + return factory.storeKitManager + } + + private var receiptManager: ReceiptManager { + return factory.receiptManager + } + + private var storage: Storage { + return factory.storage + } + + private var network: Network { + return factory.network + } + + private var paywallManager: PaywallManager { + return factory.paywallManager + } /// A task that is non-`nil` when preloading all paywalls. private var currentPreloadingTask: Task? - private let factory: RequestFactory & RuleAttributesFactory + private let factory: DependencyContainer init( - options: SuperwallOptions?, - storeKitManager: StoreKitManager, - storage: Storage, - network: Network, - paywallManager: PaywallManager, - factory: RequestFactory & RuleAttributesFactory + options: SuperwallOptions, + factory: DependencyContainer ) { - if let options = options { - self.options = options - } - self.storeKitManager = storeKitManager - self.storage = storage - self.network = network - self.paywallManager = paywallManager + self.options = options self.factory = factory } func fetchConfiguration() async { do { - await storeKitManager.loadPurchasedProducts() + await receiptManager.loadPurchasedProducts() let config = try await network.getConfig { [weak self] in self?.configState.send(.retrying) @@ -238,10 +243,7 @@ class ConfigManager { .throwableAsync() else { return } - let expressionEvaluator = ExpressionEvaluator( - storage: storage, - factory: factory - ) + let expressionEvaluator = ExpressionEvaluator(factory: factory) let triggers = ConfigLogic.filterTriggers( config.triggers, removing: config.preloadingDisabled diff --git a/Sources/SuperwallKit/Debug/DebugManager.swift b/Sources/SuperwallKit/Debug/DebugManager.swift index 7dbe256b6..c82e961bb 100644 --- a/Sources/SuperwallKit/Debug/DebugManager.swift +++ b/Sources/SuperwallKit/Debug/DebugManager.swift @@ -12,14 +12,13 @@ final class DebugManager { @MainActor var viewController: DebugViewController? var isDebuggerLaunched = false - private unowned let storage: Storage - private unowned let factory: ViewControllerFactory + private var storage: Storage { + return factory.storage + } + private let factory: DependencyContainer - init( - storage: Storage, - factory: ViewControllerFactory + init(factory: DependencyContainer ) { - self.storage = storage self.factory = factory } diff --git a/Sources/SuperwallKit/Debug/DebugViewController.swift b/Sources/SuperwallKit/Debug/DebugViewController.swift index 2b09d7181..49fa03894 100644 --- a/Sources/SuperwallKit/Debug/DebugViewController.swift +++ b/Sources/SuperwallKit/Debug/DebugViewController.swift @@ -120,26 +120,28 @@ final class DebugViewController: UIViewController { private var cancellable: AnyCancellable? private var initialLocaleIdentifier: String? - private unowned let storeKitManager: StoreKitManager - private unowned let network: Network - private unowned let paywallRequestManager: PaywallRequestManager - private unowned let paywallManager: PaywallManager - private unowned let debugManager: DebugManager - private let factory: RequestFactory & ViewControllerFactory - - init( - storeKitManager: StoreKitManager, - network: Network, - paywallRequestManager: PaywallRequestManager, - paywallManager: PaywallManager, - debugManager: DebugManager, - factory: RequestFactory & ViewControllerFactory - ) { - self.storeKitManager = storeKitManager - self.network = network - self.paywallRequestManager = paywallRequestManager - self.paywallManager = paywallManager - self.debugManager = debugManager + private var storeKitManager: StoreKitManager { + return factory.storeKitManager + } + + private var network: Network { + return factory.network + } + + private var paywallRequestManager: PaywallRequestManager { + return factory.paywallRequestManager + } + + private var paywallManager: PaywallManager { + return factory.paywallManager + } + + private var debugManager: DebugManager { + return factory.debugManager + } + private let factory: DependencyContainer + + init(factory: DependencyContainer) { self.factory = factory super.init(nibName: nil, bundle: nil) } diff --git a/Sources/SuperwallKit/Dependencies/DependencyContainer.swift b/Sources/SuperwallKit/Dependencies/DependencyContainer.swift index 60e593211..3245048f6 100644 --- a/Sources/SuperwallKit/Dependencies/DependencyContainer.swift +++ b/Sources/SuperwallKit/Dependencies/DependencyContainer.swift @@ -1,6 +1,6 @@ // // File.swift -// +// // // Created by Yusuf Tör on 23/12/2022. // @@ -16,116 +16,121 @@ import StoreKit /// This conforms to protocol factory methods which can be used to make objects that have /// dependencies injected into them. /// -/// Objects only need `unowned` references to the dependencies injected into them because -/// `DependencyContainer` is owned by the `Superwall` class. -final class DependencyContainer { - // swiftlint:disable implicitly_unwrapped_optional - var configManager: ConfigManager! - var identityManager: IdentityManager! - var storeKitManager: StoreKitManager! - var appSessionManager: AppSessionManager! - var sessionEventsManager: SessionEventsManager! - var storage: Storage! - var network: Network! - var paywallManager: PaywallManager! - var paywallRequestManager: PaywallRequestManager! - var deviceHelper: DeviceHelper! - var queue: EventsQueue! - var debugManager: DebugManager! - var api: Api! - var transactionManager: TransactionManager! - var delegateAdapter: SuperwallDelegateAdapter! - // swiftlint:enable implicitly_unwrapped_optional +class DependencyContainer { + lazy var storeKitManager: StoreKitManager = self.makeStoreKitManager() + lazy var receiptManager: ReceiptManager = self.makeReceiptManager() + lazy var storage: Storage = self.makeStorage() + lazy var network: Network = self.makeNetwork() + lazy var paywallRequestManager: PaywallRequestManager = self.makePaywallRequestManager() + lazy var paywallManager: PaywallManager = self.makePaywallManager() + lazy var configManager: ConfigManager = self.makeConfigManager() + lazy var api: Api = self.makeApi() + lazy var deviceHelper: DeviceHelper = self.makeDeviceHelper() + lazy var queue: EventsQueue = self.makeQueue() + lazy var identityManager: IdentityManager = self.makeIdentityManager() + lazy var sessionEventsManager: SessionEventsManager = self.makeSessionEventsManager() + lazy var productsFetcher: ProductsFetcherSK1 = self.makeProductsFetcher() + lazy var productsPurchaser: ProductPurchaserSK1 = self.makeProductsPurchaser() + lazy var purchaseController: PurchaseController = self.makePurchaseController() + lazy var appSessionManager: AppSessionManager = self.makeAppSessionManager() + lazy var debugManager: DebugManager = self.makeDebugManager() + lazy var transactionManager: TransactionManager = self.makeTransactionManager() + lazy var delegateAdapter: SuperwallDelegateAdapter = self.makeDelegateAdapter() + + let options: SuperwallOptions + private let controller: PurchaseController? init( - swiftPurchaseController: PurchaseController? = nil, - objcPurchaseController: PurchaseControllerObjc? = nil, + purchaseController controller: PurchaseController? = nil, options: SuperwallOptions? = nil ) { - let purchaseController = InternalPurchaseController( - factory: self, - swiftPurchaseController: swiftPurchaseController, - objcPurchaseController: objcPurchaseController - ) - storeKitManager = StoreKitManager(purchaseController: purchaseController) - delegateAdapter = SuperwallDelegateAdapter() - storage = Storage(factory: self) - network = Network(factory: self) - - paywallRequestManager = PaywallRequestManager( - storeKitManager: storeKitManager, - network: network, - factory: self - ) - paywallManager = PaywallManager( - factory: self, - paywallRequestManager: paywallRequestManager - ) + self.controller = controller + self.options = options ?? SuperwallOptions() + } +} - configManager = ConfigManager( - options: options, - storeKitManager: storeKitManager, - storage: storage, - network: network, - paywallManager: paywallManager, - factory: self - ) +// MARK: - Factory - api = Api(networkEnvironment: configManager.options.networkEnvironment) +extension DependencyContainer { + private func makeProductsFetcher() -> ProductsFetcherSK1 { + return ProductsFetcherSK1() + } - deviceHelper = DeviceHelper( - api: api, - storage: storage, - factory: self - ) + private func makeStoreKitManager() -> StoreKitManager { + return StoreKitManager(factory: self) + } - queue = EventsQueue( - network: network, - configManager: configManager - ) + private func makeReceiptManager() -> ReceiptManager { + return ReceiptManager(factory: self) + } - identityManager = IdentityManager( - deviceHelper: deviceHelper, - storage: storage, - configManager: configManager - ) + private func makeStorage() -> Storage { + return Storage(factory: self) + } - sessionEventsManager = SessionEventsManager( - queue: SessionEventsQueue( - storage: storage, - network: network, - configManager: configManager - ), - storage: storage, - network: network, - configManager: configManager, - factory: self - ) + private func makeNetwork() -> Network { + return Network(factory: self) + } - // Must be after session events - appSessionManager = AppSessionManager( - configManager: configManager, - identityManager: identityManager, - storage: storage, - delegate: self - ) + private func makePaywallRequestManager() -> PaywallRequestManager { + return PaywallRequestManager(factory: self) + } - debugManager = DebugManager( - storage: storage, - factory: self - ) + private func makePaywallManager() -> PaywallManager { + return PaywallManager(factory: self) + } - transactionManager = TransactionManager( - storeKitManager: storeKitManager, - sessionEventsManager: sessionEventsManager, - factory: self - ) + private func makeConfigManager() -> ConfigManager { + return ConfigManager(options: options, factory: self) + } - // Initialise the product purchaser so that it can immediately start listening to transactions. - _ = storeKitManager.purchaseController.productPurchaser + private func makeApi() -> Api { + return Api(networkEnvironment: configManager.options.networkEnvironment) + } + + private func makeDeviceHelper() -> DeviceHelper { + return DeviceHelper(factory: self) + } + + private func makeQueue() -> EventsQueue { + return EventsQueue(factory: self) + } + + private func makeIdentityManager() -> IdentityManager { + return IdentityManager(factory: self) + } + + private func makeSessionEventsManager() -> SessionEventsManager { + return SessionEventsManager(factory: self) + } + + private func makeProductsPurchaser() -> ProductPurchaserSK1 { + return ProductPurchaserSK1(factory: self) + } + + private func makePurchaseController() -> PurchaseController { + return controller ?? AutomaticPurchaseController(factory: self) + } + + private func makeAppSessionManager() -> AppSessionManager { + return AppSessionManager(factory: self) + } + + private func makeDebugManager() -> DebugManager { + return DebugManager(factory: self) + } + + private func makeTransactionManager() -> TransactionManager { + return TransactionManager(factory: self) + } + + private func makeDelegateAdapter() -> SuperwallDelegateAdapter { + return SuperwallDelegateAdapter() } } +// TODO: Consider making the below extensions protocols instead of factories + // MARK: - IdentityInfoFactory extension DependencyContainer: IdentityInfoFactory { func makeIdentityInfo() -> IdentityInfo { @@ -193,10 +198,7 @@ extension DependencyContainer: ViewControllerFactory { withCache cache: PaywallViewControllerCache?, delegate: PaywallViewControllerDelegateAdapter? ) -> PaywallViewController { - let messageHandler = PaywallMessageHandler( - sessionEventsManager: sessionEventsManager, - factory: self - ) + let messageHandler = PaywallMessageHandler(factory: self) let webView = SWWebView( isMac: deviceHelper.isMac, sessionEventsManager: sessionEventsManager, @@ -222,14 +224,7 @@ extension DependencyContainer: ViewControllerFactory { @MainActor func makeDebugViewController(withDatabaseId id: String?) -> DebugViewController { - let viewController = DebugViewController( - storeKitManager: storeKitManager, - network: network, - paywallRequestManager: paywallRequestManager, - paywallManager: paywallManager, - debugManager: debugManager, - factory: self - ) + let viewController = DebugViewController(factory: self) viewController.paywallDatabaseId = id viewController.modalPresentationStyle = .overFullScreen return viewController @@ -423,17 +418,6 @@ extension DependencyContainer: StoreTransactionFactory { } } -// MARK: - Product Purchaser Factory -extension DependencyContainer: ProductPurchaserFactory { - func makeSK1ProductPurchaser() -> ProductPurchaserSK1 { - return ProductPurchaserSK1( - storeKitManager: storeKitManager, - sessionEventsManager: sessionEventsManager, - factory: self - ) - } -} - // MARK: - Options Factory extension DependencyContainer: OptionsFactory { func makeSuperwallOptions() -> SuperwallOptions { @@ -451,7 +435,7 @@ extension DependencyContainer: TriggerFactory { // MARK: - Purchase Controller Factory extension DependencyContainer: HasExternalPurchaseControllerFactory { func makeHasExternalPurchaseController() -> Bool { - return storeKitManager.purchaseController.hasExternalPurchaseController + return purchaseController.isInternal == false } } @@ -472,7 +456,7 @@ extension DependencyContainer: ComputedPropertyRequestsFactory { // MARK: - Purchased Transactions Factory extension DependencyContainer: PurchasedTransactionsFactory { func makePurchasingCoordinator() -> PurchasingCoordinator { - return storeKitManager.purchaseController.productPurchaser.coordinator + return productsPurchaser.coordinator } } @@ -485,3 +469,4 @@ extension DependencyContainer: UserAttributesEventFactory { ) } } + diff --git a/Sources/SuperwallKit/Dependencies/FactoryProtocols.swift b/Sources/SuperwallKit/Dependencies/FactoryProtocols.swift index 3b31da9ff..11249ad7d 100644 --- a/Sources/SuperwallKit/Dependencies/FactoryProtocols.swift +++ b/Sources/SuperwallKit/Dependencies/FactoryProtocols.swift @@ -100,14 +100,11 @@ protocol HasExternalPurchaseControllerFactory: AnyObject { } protocol ApiFactory: AnyObject { - // TODO: Think of an alternative way such that we don't need to do this: - // swiftlint:disable implicitly_unwrapped_optional - var api: Api! { get } - var storage: Storage! { get } - var deviceHelper: DeviceHelper! { get } - var configManager: ConfigManager! { get } - var identityManager: IdentityManager! { get } - // swiftlint:enable implicitly_unwrapped_optional + var api: Api { get } + var storage: Storage { get } + var deviceHelper: DeviceHelper { get } + var configManager: ConfigManager { get } + var identityManager: IdentityManager { get } func makeHeaders( fromRequest request: URLRequest, @@ -116,10 +113,6 @@ protocol ApiFactory: AnyObject { ) async -> [String: String] } -protocol ProductPurchaserFactory: AnyObject { - func makeSK1ProductPurchaser() -> ProductPurchaserSK1 -} - protocol StoreTransactionFactory: AnyObject { func makeStoreTransaction(from transaction: SK1Transaction) async -> StoreTransaction diff --git a/Sources/SuperwallKit/Identity/IdentityManager.swift b/Sources/SuperwallKit/Identity/IdentityManager.swift index 06a7a5104..8c3e1b1e5 100644 --- a/Sources/SuperwallKit/Identity/IdentityManager.swift +++ b/Sources/SuperwallKit/Identity/IdentityManager.swift @@ -96,18 +96,24 @@ class IdentityManager { private let queue = DispatchQueue(label: "com.superwall.identitymanager") private let group = DispatchGroup() - private unowned let deviceHelper: DeviceHelper - private unowned let storage: Storage - private unowned let configManager: ConfigManager - - init( - deviceHelper: DeviceHelper, - storage: Storage, - configManager: ConfigManager - ) { - self.deviceHelper = deviceHelper - self.storage = storage - self.configManager = configManager + private var deviceHelper: DeviceHelper { + return factory.deviceHelper + } + + private var storage: Storage { + return factory.storage + } + + private var configManager: ConfigManager { + return factory.configManager + } + + private let factory: DependencyContainer + + init(factory: DependencyContainer) { + self.factory = factory + + let storage = factory.storage self._appUserId = storage.get(AppUserId.self) var extraAttributes: [String: Any] = [:] diff --git a/Sources/SuperwallKit/Models/Paywall/Paywall.swift b/Sources/SuperwallKit/Models/Paywall/Paywall.swift index 1fd48c315..31eb10e9d 100644 --- a/Sources/SuperwallKit/Models/Paywall/Paywall.swift +++ b/Sources/SuperwallKit/Models/Paywall/Paywall.swift @@ -274,7 +274,7 @@ struct Paywall: Decodable { func getInfo( fromEvent: EventData?, - factory: TriggerSessionManagerFactory + factory: DependencyContainer ) -> PaywallInfo { return PaywallInfo( databaseId: databaseId, diff --git a/Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift b/Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift index cf158e77b..6f1ff63ec 100644 --- a/Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift +++ b/Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift @@ -431,18 +431,17 @@ class DeviceHelper { return template.toDictionary() } - private unowned let storage: Storage - private unowned let factory: IdentityInfoFactory & LocaleIdentifierFactory - - init( - api: Api, - storage: Storage, - factory: IdentityInfoFactory & LocaleIdentifierFactory - ) { - self.storage = storage - self.appInstalledAtString = appInstallDate?.isoString ?? "" + private var storage: Storage { + return factory.storage + } + + private var factory: DependencyContainer + + init(factory: DependencyContainer) { self.factory = factory - reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, api.hostDomain) + + self.appInstalledAtString = appInstallDate?.isoString ?? "" + reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, factory.api.hostDomain) self.sdkVersionPadded = Self.makePaddedSdkVersion(using: sdkVersion) } } diff --git a/Sources/SuperwallKit/Paywall/Manager/PaywallManager.swift b/Sources/SuperwallKit/Paywall/Manager/PaywallManager.swift index cf9c552d1..e5470d91c 100644 --- a/Sources/SuperwallKit/Paywall/Manager/PaywallManager.swift +++ b/Sources/SuperwallKit/Paywall/Manager/PaywallManager.swift @@ -9,24 +9,23 @@ import Foundation import UIKit class PaywallManager { + let factory: DependencyContainer + var presentedViewController: PaywallViewController? { return cache.activePaywallViewController } private let queue = DispatchQueue(label: "com.superwall.paywallmanager") - private unowned let paywallRequestManager: PaywallRequestManager - private unowned let factory: ViewControllerFactory & CacheFactory & DeviceHelperFactory + private var paywallRequestManager: PaywallRequestManager { + return factory.paywallRequestManager + } private var cache: PaywallViewControllerCache { return queue.sync { _cache ?? createCache() } } private var _cache: PaywallViewControllerCache? - init( - factory: ViewControllerFactory & CacheFactory & DeviceHelperFactory, - paywallRequestManager: PaywallRequestManager - ) { + init(factory: DependencyContainer) { self.factory = factory - self.paywallRequestManager = paywallRequestManager } private func createCache() -> PaywallViewControllerCache { diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/EvaluateRules.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/EvaluateRules.swift index 63b875d92..20fcba29e 100644 --- a/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/EvaluateRules.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/EvaluateRules.swift @@ -17,11 +17,7 @@ extension Superwall { from request: PresentationRequest ) async throws -> RuleEvaluationOutcome { if let eventData = request.presentationInfo.eventData { - let ruleLogic = RuleLogic( - configManager: dependencyContainer.configManager, - storage: dependencyContainer.storage, - factory: dependencyContainer - ) + let ruleLogic = RuleLogic(factory: dependencyContainer) return await ruleLogic.evaluateRules( forEvent: eventData, triggers: dependencyContainer.configManager.triggersByEventName diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/GetExperiment.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/GetExperiment.swift index 45c67dad9..331cb185a 100644 --- a/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/GetExperiment.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/GetExperiment.swift @@ -69,7 +69,7 @@ extension Superwall { return } let sessionEventsManager = dependencyContainer.sessionEventsManager - await sessionEventsManager?.triggerSession.activateSession( + await sessionEventsManager.triggerSession.activateSession( for: request.presentationInfo, on: request.presenter, triggerResult: rulesOutcome.triggerResult diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/GetPresenter.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/GetPresenter.swift index f827763b7..66bb38e1f 100644 --- a/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/GetPresenter.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/GetPresenter.swift @@ -117,7 +117,7 @@ extension Superwall { triggerResult: InternalTriggerResult ) async -> String? { let sessionEventsManager = dependencyContainer.sessionEventsManager - return await sessionEventsManager?.triggerSession.activateSession( + return await sessionEventsManager.triggerSession.activateSession( for: request.presentationInfo, on: request.presenter, paywall: paywall, diff --git a/Sources/SuperwallKit/Paywall/Presentation/PaywallInfo.swift b/Sources/SuperwallKit/Paywall/Presentation/PaywallInfo.swift index 3de588ab4..74b853d3c 100644 --- a/Sources/SuperwallKit/Paywall/Presentation/PaywallInfo.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/PaywallInfo.swift @@ -112,7 +112,7 @@ public final class PaywallInfo: NSObject { /// Surveys attached to a paywall. public let surveys: [Survey] - private unowned let factory: TriggerSessionManagerFactory + private let factory: DependencyContainer init( databaseId: String, @@ -135,7 +135,7 @@ public final class PaywallInfo: NSObject { paywalljsVersion: String?, isFreeTrialAvailable: Bool, presentationSourceType: String?, - factory: TriggerSessionManagerFactory, + factory: DependencyContainer, featureGatingBehavior: FeatureGatingBehavior, closeReason: PaywallCloseReason, localNotifications: [LocalNotification], diff --git a/Sources/SuperwallKit/Paywall/Presentation/Rule Logic/Expression Evaluator/ExpressionEvaluator.swift b/Sources/SuperwallKit/Paywall/Presentation/Rule Logic/Expression Evaluator/ExpressionEvaluator.swift index ba999ae6c..7d764971a 100644 --- a/Sources/SuperwallKit/Paywall/Presentation/Rule Logic/Expression Evaluator/ExpressionEvaluator.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Rule Logic/Expression Evaluator/ExpressionEvaluator.swift @@ -16,14 +16,12 @@ protocol ExpressionEvaluating { } struct ExpressionEvaluator: ExpressionEvaluating { - private let storage: Storage - private unowned let factory: RuleAttributesFactory - - init( - storage: Storage, - factory: RuleAttributesFactory - ) { - self.storage = storage + private var storage: Storage { + return factory.storage + } + private let factory: DependencyContainer + + init(factory: DependencyContainer) { self.factory = factory } diff --git a/Sources/SuperwallKit/Paywall/Presentation/Rule Logic/RuleLogic.swift b/Sources/SuperwallKit/Paywall/Presentation/Rule Logic/RuleLogic.swift index a607fd864..589a1170b 100644 --- a/Sources/SuperwallKit/Paywall/Presentation/Rule Logic/RuleLogic.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Rule Logic/RuleLogic.swift @@ -25,9 +25,14 @@ enum RuleMatchOutcome { } struct RuleLogic { - unowned let configManager: ConfigManager - unowned let storage: Storage - unowned let factory: RuleAttributesFactory + var configManager: ConfigManager { + return factory.configManager + } + + var storage: Storage { + return factory.storage + } + let factory: DependencyContainer /// Determines the outcome of an event based on given triggers. It also determines /// whether there is an assignment to confirm based on the rule. @@ -132,11 +137,7 @@ struct RuleLogic { for event: EventData, withTrigger trigger: Trigger ) async -> RuleMatchOutcome { - let expressionEvaluator = ExpressionEvaluator( - storage: storage, - factory: factory - ) - + let expressionEvaluator = ExpressionEvaluator(factory: factory) var unmatchedRules: [UnmatchedRule] = [] for rule in trigger.rules { diff --git a/Sources/SuperwallKit/Paywall/Request/Operators/AddPaywallProducts.swift b/Sources/SuperwallKit/Paywall/Request/Operators/AddPaywallProducts.swift index 7091c11f2..d8b3e2325 100644 --- a/Sources/SuperwallKit/Paywall/Request/Operators/AddPaywallProducts.swift +++ b/Sources/SuperwallKit/Paywall/Request/Operators/AddPaywallProducts.swift @@ -47,7 +47,10 @@ extension PaywallRequestManager { fromProducts: result.products, productsById: result.productsById, isFreeTrialAvailableOverride: request.overrides.isFreeTrial, - isFreeTrialAvailable: storeKitManager.isFreeTrialAvailable(for:) + isFreeTrialAvailable: { [weak self] product in + guard let self else { return false } + return await receiptManager.isFreeTrialAvailable(for: product) + } ) paywall.swProducts = outcome.orderedSwProducts paywall.productVariables = outcome.productVariables diff --git a/Sources/SuperwallKit/Paywall/Request/PaywallRequestManager.swift b/Sources/SuperwallKit/Paywall/Request/PaywallRequestManager.swift index 39aec361d..5cfea67e4 100644 --- a/Sources/SuperwallKit/Paywall/Request/PaywallRequestManager.swift +++ b/Sources/SuperwallKit/Paywall/Request/PaywallRequestManager.swift @@ -10,9 +10,17 @@ import Combine /// Actor responsible for handling all paywall requests. actor PaywallRequestManager { - unowned let storeKitManager: StoreKitManager - unowned let network: Network - unowned let factory: Factory + let factory: DependencyContainer + + var storeKitManager: StoreKitManager { + return factory.storeKitManager + } + var receiptManager: ReceiptManager { + return factory.receiptManager + } + var network: Network { + return factory.network + } private var activeTasks: [String: Task] = [:] private var paywallsByHash: [String: Paywall] = [:] @@ -20,13 +28,7 @@ actor PaywallRequestManager { & TriggerSessionManagerFactory & ConfigManagerFactory - init( - storeKitManager: StoreKitManager, - network: Network, - factory: Factory - ) { - self.storeKitManager = storeKitManager - self.network = network + init(factory: DependencyContainer) { self.factory = factory } diff --git a/Sources/SuperwallKit/Paywall/View Controller/PaywallViewController.swift b/Sources/SuperwallKit/Paywall/View Controller/PaywallViewController.swift index ebb477345..7b194acaa 100644 --- a/Sources/SuperwallKit/Paywall/View Controller/PaywallViewController.swift +++ b/Sources/SuperwallKit/Paywall/View Controller/PaywallViewController.swift @@ -141,10 +141,16 @@ public class PaywallViewController: UIViewController, LoadingDelegate { /// paywall presentation. private var unsavedOccurrence: TriggerRuleOccurrence? - private unowned let factory: TriggerSessionManagerFactory & TriggerFactory - private unowned let storage: Storage - private unowned let deviceHelper: DeviceHelper - private unowned let paywallManager: PaywallManager + private let factory: DependencyContainer + private var storage: Storage { + return factory.storage + } + private var deviceHelper: DeviceHelper { + return factory.deviceHelper + } + private var paywallManager: PaywallManager { + return factory.paywallManager + } private weak var cache: PaywallViewControllerCache? // MARK: - View Lifecycle @@ -154,25 +160,22 @@ public class PaywallViewController: UIViewController, LoadingDelegate { eventDelegate: PaywallViewControllerEventDelegate? = nil, delegate: PaywallViewControllerDelegateAdapter? = nil, deviceHelper: DeviceHelper, - factory: TriggerSessionManagerFactory & TriggerFactory, + factory: DependencyContainer, storage: Storage, paywallManager: PaywallManager, webView: SWWebView, cache: PaywallViewControllerCache? ) { + self.factory = factory self.cache = cache self.cacheKey = PaywallCacheLogic.key( identifier: paywall.identifier, locale: deviceHelper.locale ) - self.deviceHelper = deviceHelper self.eventDelegate = eventDelegate self.delegate = delegate - self.factory = factory - self.storage = storage self.paywall = paywall - self.paywallManager = paywallManager self.webView = webView presentationStyle = paywall.presentation.style @@ -225,7 +228,7 @@ public class PaywallViewController: UIViewController, LoadingDelegate { nonisolated private func trackOpen() async { let triggerSessionManager = factory.getTriggerSessionManager() await triggerSessionManager.trackPaywallOpen() - storage.trackPaywallOpen() + await storage.trackPaywallOpen() await webView.messageHandler.handle(.paywallOpen) let trackedEvent = await InternalSuperwallEvent.PaywallOpen(paywallInfo: info) await Superwall.shared.track(trackedEvent) diff --git a/Sources/SuperwallKit/Paywall/View Controller/Web View/Message Handling/PaywallMessageHandler.swift b/Sources/SuperwallKit/Paywall/View Controller/Web View/Message Handling/PaywallMessageHandler.swift index a4005d649..069c9e064 100644 --- a/Sources/SuperwallKit/Paywall/View Controller/Web View/Message Handling/PaywallMessageHandler.swift +++ b/Sources/SuperwallKit/Paywall/View Controller/Web View/Message Handling/PaywallMessageHandler.swift @@ -26,14 +26,12 @@ protocol PaywallMessageHandlerDelegate: AnyObject { @MainActor final class PaywallMessageHandler: WebEventDelegate { weak var delegate: PaywallMessageHandlerDelegate? - private unowned let sessionEventsManager: SessionEventsManager - private let factory: VariablesFactory + private var sessionEventsManager: SessionEventsManager { + return factory.sessionEventsManager + } + private let factory: DependencyContainer - init( - sessionEventsManager: SessionEventsManager, - factory: VariablesFactory - ) { - self.sessionEventsManager = sessionEventsManager + init(factory: DependencyContainer) { self.factory = factory } diff --git a/Sources/SuperwallKit/Paywall/View Controller/Web View/SWWebView.swift b/Sources/SuperwallKit/Paywall/View Controller/Web View/SWWebView.swift index 33f669be4..0f2757d9d 100644 --- a/Sources/SuperwallKit/Paywall/View Controller/Web View/SWWebView.swift +++ b/Sources/SuperwallKit/Paywall/View Controller/Web View/SWWebView.swift @@ -21,7 +21,7 @@ class SWWebView: WKWebView { var didFailToLoad = false private let wkConfig: WKWebViewConfiguration private let isMac: Bool - private unowned let sessionEventsManager: SessionEventsManager + private let sessionEventsManager: SessionEventsManager init( isMac: Bool, diff --git a/Sources/SuperwallKit/Storage/EventsQueue.swift b/Sources/SuperwallKit/Storage/EventsQueue.swift index 275e18860..44b47eec8 100644 --- a/Sources/SuperwallKit/Storage/EventsQueue.swift +++ b/Sources/SuperwallKit/Storage/EventsQueue.swift @@ -15,8 +15,16 @@ actor EventsQueue { private let maxEventCount = 50 private var elements: [JSON] = [] private var timer: Timer? - private unowned let network: Network - private unowned let configManager: ConfigManager + + private let factory: DependencyContainer + + private var network: Network { + return factory.network + } + + private var configManager: ConfigManager { + return factory.configManager + } @MainActor private var resignActiveObserver: AnyCancellable? @@ -26,12 +34,8 @@ actor EventsQueue { timer = nil } - init( - network: Network, - configManager: ConfigManager - ) { - self.network = network - self.configManager = configManager + init(factory: DependencyContainer) { + self.factory = factory Task { [weak self] in await self?.setupTimer() await self?.addObserver() diff --git a/Sources/SuperwallKit/Storage/Storage.swift b/Sources/SuperwallKit/Storage/Storage.swift index 94d14d40a..a08062b14 100644 --- a/Sources/SuperwallKit/Storage/Storage.swift +++ b/Sources/SuperwallKit/Storage/Storage.swift @@ -73,7 +73,7 @@ class Storage { /// The disk cache. private let cache: Cache - private unowned let factory: DeviceHelperFactory & HasExternalPurchaseControllerFactory + private let factory: DeviceHelperFactory & HasExternalPurchaseControllerFactory // MARK: - Configuration diff --git a/Sources/SuperwallKit/StoreKit/AutomaticPurchaseController.swift b/Sources/SuperwallKit/StoreKit/AutomaticPurchaseController.swift new file mode 100644 index 000000000..9d4b8e3f6 --- /dev/null +++ b/Sources/SuperwallKit/StoreKit/AutomaticPurchaseController.swift @@ -0,0 +1,75 @@ +// +// AutomaticPurchaseController.swift +// +// +// Created by Yusuf Tör on 29/08/2023. +// + +import Foundation +import StoreKit + +final class AutomaticPurchaseController { + private let factory: DependencyContainer + + private var productPurchaser: ProductPurchaserSK1 { + return factory.productsPurchaser + } + + private var receiptManager: ReceiptManager { + return factory.receiptManager + } + + init(factory: DependencyContainer) { + self.factory = factory + } + + func syncSubscriptionStatus(withPurchases purchases: Set) async { + let activePurchases = purchases.filter { $0.isActive } + await MainActor.run { + if activePurchases.isEmpty { + Superwall.shared.subscriptionStatus = .inactive + } else { + Superwall.shared.subscriptionStatus = .active + } + } + } +} + +// MARK: - PurchaseController + +extension AutomaticPurchaseController: PurchaseController { + @MainActor + func purchase(product: SKProduct) async -> PurchaseResult { + await productPurchaser.coordinator.beginPurchase( + of: product.productIdentifier + ) + return await productPurchaser.purchase(product: product) + } + + @MainActor + func restorePurchases() async -> RestorationResult { + let result = await productPurchaser.restorePurchases() + + let hasRestored = result == .restored + await receiptManager.refreshReceipt() + if hasRestored { + await receiptManager.loadPurchasedProducts() + } + + return result + } +} + +// MARK: - InternalPurchaseControllable + +extension AutomaticPurchaseController: InternalPurchaseController { + var isInternal: Bool { return true } +} + +// MARK: - ReceiptDelegate + +extension AutomaticPurchaseController: ReceiptDelegate { + func receiptLoaded(purchases: Set) async { + await syncSubscriptionStatus(withPurchases: purchases) + } +} diff --git a/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift b/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift index 80f188cc6..5b16019ae 100644 --- a/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift +++ b/Sources/SuperwallKit/StoreKit/InternalPurchaseController.swift @@ -1,112 +1,18 @@ // -// File.swift -// +// InternalPurchaseController.swift // -// Created by Yusuf Tör on 29/08/2023. +// +// Created by Bryan Dubno on 11/3/23. // import Foundation -import StoreKit - -protocol RestoreDelegate: AnyObject { - func didRestore(result: RestorationResult) async -} - -final class InternalPurchaseController: PurchaseController { - var hasExternalPurchaseController: Bool { - return swiftPurchaseController != nil || objcPurchaseController != nil - } - private var swiftPurchaseController: PurchaseController? - private var objcPurchaseController: PurchaseControllerObjc? - private let factory: ProductPurchaserFactory - lazy var productPurchaser = factory.makeSK1ProductPurchaser() - weak var delegate: RestoreDelegate? - - init( - factory: ProductPurchaserFactory, - swiftPurchaseController: PurchaseController?, - objcPurchaseController: PurchaseControllerObjc? - ) { - self.swiftPurchaseController = swiftPurchaseController - self.objcPurchaseController = objcPurchaseController - self.factory = factory - } -} - -// MARK: - Subscription Status -extension InternalPurchaseController { - func syncSubscriptionStatus(withPurchases purchases: Set) async { - if hasExternalPurchaseController { - return - } - let activePurchases = purchases.filter { $0.isActive } - await MainActor.run { - if activePurchases.isEmpty { - Superwall.shared.subscriptionStatus = .inactive - } else { - Superwall.shared.subscriptionStatus = .active - } - } - } -} -// MARK: - Restoration -extension InternalPurchaseController { - @MainActor - func restorePurchases() async -> RestorationResult { - if let purchaseController = swiftPurchaseController { - return await purchaseController.restorePurchases() - } else if let purchaseController = objcPurchaseController { - return await withCheckedContinuation { continuation in - purchaseController.restorePurchases { result, error in - switch result { - case .restored: - continuation.resume(returning: .restored) - case .failed: - continuation.resume(returning: .failed(error)) - } - } - } - } else { - let result = await productPurchaser.restorePurchases() - await delegate?.didRestore(result: result) - return result - } - } +protocol InternalPurchaseController { + var isInternal: Bool { get } } -// MARK: - Purchasing -extension InternalPurchaseController { - @MainActor - func purchase(product: SKProduct) async -> PurchaseResult { - await productPurchaser.coordinator.beginPurchase( - of: product.productIdentifier - ) - if let purchaseController = swiftPurchaseController { - return await purchaseController.purchase(product: product) - } else if let purchaseController = objcPurchaseController { - return await withCheckedContinuation { continuation in - purchaseController.purchase(product: product) { result, error in - if let error = error { - continuation.resume(returning: .failed(error)) - } else { - switch result { - case .purchased: - continuation.resume(returning: .purchased) - case .restored: - continuation.resume(returning: .restored) - case .pending: - continuation.resume(returning: .pending) - case .cancelled: - continuation.resume(returning: .cancelled) - case .failed: - break - } - } - } - } - } else { - return await productPurchaser.purchase(product: product) - } +extension PurchaseController { + var isInternal: Bool { + return (self as? InternalPurchaseController)?.isInternal ?? false } } diff --git a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ReceiptManager.swift b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ReceiptManager.swift index a819f3843..b529715ca 100644 --- a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ReceiptManager.swift +++ b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ReceiptManager.swift @@ -8,22 +8,30 @@ import Foundation import StoreKit +protocol ReceiptDelegate { + func receiptLoaded(purchases: Set) async +} + actor ReceiptManager: NSObject { + private let factory: DependencyContainer + var purchasedSubscriptionGroupIds: Set? private var purchases: Set = [] private var receiptRefreshCompletion: ((Bool) -> Void)? - private weak var delegate: ProductsFetcherSK1? private let receiptData: () -> Data? - private let purchaseController: InternalPurchaseController + private weak var delegate: ProductsFetcherSK1? { + return factory.productsFetcher + } + private var receiptDelegate: ReceiptDelegate? { + return factory.purchaseController as? ReceiptDelegate + } init( - delegate: ProductsFetcherSK1, - purchaseController: InternalPurchaseController, + factory: DependencyContainer, receiptData: @escaping () -> Data? = ReceiptLogic.getReceiptData ) { - self.delegate = delegate + self.factory = factory self.receiptData = receiptData - self.purchaseController = purchaseController } /// Loads purchased products from the receipt, storing the purchased subscription group identifiers, @@ -34,14 +42,14 @@ actor ReceiptManager: NSObject { let payload = ReceiptLogic.getPayload(using: receiptData), let delegate = delegate else { - await purchaseController.syncSubscriptionStatus(withPurchases: []) + await receiptDelegate?.receiptLoaded(purchases: []) return nil } let purchases = payload.purchases self.purchases = purchases - await purchaseController.syncSubscriptionStatus(withPurchases: purchases) + await receiptDelegate?.receiptLoaded(purchases: purchases) let purchasedProductIds = Set(purchases.map { $0.productIdentifier }) @@ -83,7 +91,10 @@ actor ReceiptManager: NSObject { return !purchasedSubsGroupIds.contains(subsGroupId) } - /// Refreshes the receipt. + /// This refreshes the device receipt. + /// + /// - Warning: This will prompt the user to log in, so only do this on + /// when restoring or after purchasing. func refreshReceipt() async { Logger.debug( logLevel: .debug, diff --git a/Sources/SuperwallKit/StoreKit/PurchaseControllerObjcAdapter.swift b/Sources/SuperwallKit/StoreKit/PurchaseControllerObjcAdapter.swift new file mode 100644 index 000000000..f6907da8d --- /dev/null +++ b/Sources/SuperwallKit/StoreKit/PurchaseControllerObjcAdapter.swift @@ -0,0 +1,53 @@ +// +// PurchaseControllerObjcAdapter.swift +// +// +// Created by Bryan Dubno on 11/1/23. +// + +import Foundation +import StoreKit + +public class PurchaseControllerObjcAdapter: PurchaseController { + private let objcController: PurchaseControllerObjc + + public init(objcController: PurchaseControllerObjc) { + self.objcController = objcController + } + + public func purchase(product: SKProduct) async -> PurchaseResult { + return await withCheckedContinuation { continuation in + objcController.purchase(product: product) { (result, error) in + if let error = error { + continuation.resume(returning: .failed(error)) + } else { + switch result { + case .purchased: + continuation.resume(returning: .purchased) + case .restored: + continuation.resume(returning: .restored) + case .pending: + continuation.resume(returning: .pending) + case .cancelled: + continuation.resume(returning: .cancelled) + case .failed: + break + } + } + } + } + } + + public func restorePurchases() async -> RestorationResult { + return await withCheckedContinuation { continuation in + objcController.restorePurchases { (result, error) in + switch result { + case .restored: + continuation.resume(returning: .restored) + case .failed: + continuation.resume(returning: .failed(error)) + } + } + } + } +} diff --git a/Sources/SuperwallKit/StoreKit/StoreKitManager.swift b/Sources/SuperwallKit/StoreKit/StoreKitManager.swift index 90f36e08b..39c278840 100644 --- a/Sources/SuperwallKit/StoreKit/StoreKitManager.swift +++ b/Sources/SuperwallKit/StoreKit/StoreKitManager.swift @@ -3,16 +3,13 @@ import StoreKit import Combine actor StoreKitManager { - /// Handler purchasing and restoring. - let purchaseController: InternalPurchaseController + private let factory: DependencyContainer /// Retrieves products from storekit. - private let productsFetcher: ProductsFetcherSK1 + private var productsFetcher: ProductsFetcherSK1 { + return factory.productsFetcher + } - private lazy var receiptManager = ReceiptManager( - delegate: productsFetcher, - purchaseController: purchaseController - ) private(set) var productsById: [String: StoreProduct] = [:] private struct ProductProcessingResult { let productIdsToLoad: Set @@ -20,13 +17,8 @@ actor StoreKitManager { let products: [Product] } - init( - purchaseController: InternalPurchaseController, - productsFetcher: ProductsFetcherSK1 = ProductsFetcherSK1() - ) { - self.productsFetcher = productsFetcher - self.purchaseController = purchaseController - purchaseController.delegate = self + init(factory: DependencyContainer) { + self.factory = factory } func getProductVariables(for paywall: Paywall) async -> [ProductVariable] { @@ -130,49 +122,3 @@ actor StoreKitManager { ) } } - -// MARK: - Restoration -extension StoreKitManager: RestoreDelegate { - func didRestore(result: RestorationResult) async { - let hasRestored = result == .restored - await refreshReceipt() - if hasRestored { - await loadPurchasedProducts() - } - } -} - -// MARK: - Receipt API -extension StoreKitManager { - /// This refreshes the device receipt. - /// - /// - Warning: This will prompt the user to log in, so only do this on - /// when restoring or after purchasing. - func refreshReceipt() async { - Logger.debug( - logLevel: .debug, - scope: .storeKitManager, - message: "Refreshing App Store receipt." - ) - await receiptManager.refreshReceipt() - } - - /// Loads the purchased products from the receipt, - func loadPurchasedProducts() async { - Logger.debug( - logLevel: .debug, - scope: .storeKitManager, - message: "Loading purchased products from the App Store receipt." - ) - await receiptManager.loadPurchasedProducts() - } - - /// Determines whether a free trial is available based on the product the user is purchasing. - /// - /// A free trial is available if the user hasn't already purchased within the subscription group of the - /// supplied product. If it isn't a subscription-based product or there are other issues retrieving the products, - /// the outcome will default to whether or not the user has already purchased that product. - func isFreeTrialAvailable(for product: StoreProduct) async -> Bool { - return await receiptManager.isFreeTrialAvailable(for: product) - } -} diff --git a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserSK1.swift b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserSK1.swift index 66df6b61c..f56ec478c 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserSK1.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserSK1.swift @@ -26,22 +26,27 @@ final class ProductPurchaserSK1: NSObject { private let restoration = Restoration() // MARK: Dependencies - private weak var storeKitManager: StoreKitManager? - private weak var sessionEventsManager: SessionEventsManager? - private let factory: StoreTransactionFactory + private var storeKitManager: StoreKitManager { + return factory.storeKitManager + } + + private var receiptManager: ReceiptManager { + return factory.receiptManager + } + + private var sessionEventsManager: SessionEventsManager { + return factory.sessionEventsManager + } + + private let factory: DependencyContainer deinit { SKPaymentQueue.default().remove(self) } - init( - storeKitManager: StoreKitManager, - sessionEventsManager: SessionEventsManager, - factory: StoreTransactionFactory - ) { - self.storeKitManager = storeKitManager - self.sessionEventsManager = sessionEventsManager + init(factory: DependencyContainer) { self.factory = factory + super.init() SKPaymentQueue.default().add(self) } @@ -173,7 +178,8 @@ extension ProductPurchaserSK1: SKPaymentTransactionObserver { // Only continue if using internal purchase controller. The transaction may be // readded to the queue if finishing fails so we need to make sure // we can re-finish the transaction. - if storeKitManager?.purchaseController.hasExternalPurchaseController == true { + let isUsingInternalPurchaseController = !factory.makeHasExternalPurchaseController() + guard isUsingInternalPurchaseController == true else { return } @@ -253,14 +259,14 @@ extension ProductPurchaserSK1: SKPaymentTransactionObserver { return } SKPaymentQueue.default().finishTransaction(transaction) - guard let product = await storeKitManager?.productsById[transaction.payment.productIdentifier] else { + guard let product = await storeKitManager.productsById[transaction.payment.productIdentifier] else { return } guard isPaywallPresented else { return } - await sessionEventsManager?.triggerSession.trackTransactionRestoration( + await sessionEventsManager.triggerSession.trackTransactionRestoration( withId: transaction.transactionIdentifier, product: product ) @@ -286,7 +292,7 @@ extension ProductPurchaserSK1: SKPaymentTransactionObserver { /// Sends the transaction to the backend. private func record(_ transaction: SKPaymentTransaction) async { let storeTransaction = await factory.makeStoreTransaction(from: transaction) - await sessionEventsManager?.enqueue(storeTransaction) + await sessionEventsManager.enqueue(storeTransaction) } /// Loads purchased products in the StoreKitManager if a purchase or restore has occurred. @@ -296,6 +302,6 @@ extension ProductPurchaserSK1: SKPaymentTransactionObserver { ) == nil { return } - await storeKitManager?.loadPurchasedProducts() + await receiptManager.loadPurchasedProducts() } } diff --git a/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift b/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift index 5da16967c..454afd9a6 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift @@ -11,23 +11,25 @@ import UIKit import Combine final class TransactionManager { - private unowned let storeKitManager: StoreKitManager - private unowned let sessionEventsManager: SessionEventsManager - typealias Factories = ProductPurchaserFactory - & OptionsFactory - & TriggerFactory - & StoreTransactionFactory - & DeviceHelperFactory - & PurchasedTransactionsFactory - private let factory: Factories - - init( - storeKitManager: StoreKitManager, - sessionEventsManager: SessionEventsManager, - factory: Factories - ) { - self.storeKitManager = storeKitManager - self.sessionEventsManager = sessionEventsManager + private var storeKitManager: StoreKitManager { + return factory.storeKitManager + } + + private var receiptManager: ReceiptManager { + return factory.receiptManager + } + + private var purchaseController: PurchaseController { + return factory.purchaseController + } + + private var sessionEventsManager: SessionEventsManager { + return factory.sessionEventsManager + } + + private let factory: DependencyContainer + + init(factory: DependencyContainer) { self.factory = factory } @@ -109,7 +111,7 @@ final class TransactionManager { paywallViewController.loadingState = .loadingPurchase - let restorationResult = await storeKitManager.purchaseController.restorePurchases() + let restorationResult = await purchaseController.restorePurchases() let hasRestored = restorationResult == .restored let isUserSubscribed = Superwall.shared.subscriptionStatus == .active @@ -178,7 +180,7 @@ final class TransactionManager { guard let sk1Product = product.sk1Product else { return .failed(PurchaseError.productUnavailable) } - return await storeKitManager.purchaseController.purchase(product: sk1Product) + return await purchaseController.purchase(product: sk1Product) } /// Cancels the transaction timeout when the application resigns active. @@ -271,7 +273,7 @@ final class TransactionManager { await self.sessionEventsManager.enqueue(transaction) } - await storeKitManager.loadPurchasedProducts() + await receiptManager.loadPurchasedProducts() await trackTransactionDidSucceed( transaction, diff --git a/Sources/SuperwallKit/Superwall.swift b/Sources/SuperwallKit/Superwall.swift index fe7a0fd0a..e46f4bf7e 100644 --- a/Sources/SuperwallKit/Superwall.swift +++ b/Sources/SuperwallKit/Superwall.swift @@ -184,14 +184,12 @@ public final class Superwall: NSObject, ObservableObject { private convenience init( apiKey: String, - swiftPurchaseController: PurchaseController? = nil, - objcPurchaseController: PurchaseControllerObjc? = nil, + purchaseController: PurchaseController? = nil, options: SuperwallOptions? = nil, completion: (() -> Void)? ) { let dependencyContainer = DependencyContainer( - swiftPurchaseController: swiftPurchaseController, - objcPurchaseController: objcPurchaseController, + purchaseController: purchaseController, options: options ) self.init(dependencyContainer: dependencyContainer) @@ -288,8 +286,7 @@ public final class Superwall: NSObject, ObservableObject { } superwall = Superwall( apiKey: apiKey, - swiftPurchaseController: purchaseController, - objcPurchaseController: nil, + purchaseController: purchaseController, options: options, completion: completion ) @@ -367,8 +364,7 @@ public final class Superwall: NSObject, ObservableObject { } superwall = Superwall( apiKey: apiKey, - swiftPurchaseController: nil, - objcPurchaseController: purchaseController, + purchaseController: purchaseController.flatMap({ PurchaseControllerObjcAdapter(objcController: $0) }), options: options, completion: completion ) diff --git a/Tests/SuperwallKitTests/Analytics/App Session/AppSessionManagerMock.swift b/Tests/SuperwallKitTests/Analytics/App Session/AppSessionManagerMock.swift index 5ce2ad298..65a815dc4 100644 --- a/Tests/SuperwallKitTests/Analytics/App Session/AppSessionManagerMock.swift +++ b/Tests/SuperwallKitTests/Analytics/App Session/AppSessionManagerMock.swift @@ -20,29 +20,3 @@ class AppManagerDelegateMock: AppManagerDelegate, DeviceHelperFactory, UserAttri func didUpdateAppSession(_ appSession: AppSession) async {} } - -final class AppSessionManagerMock: AppSessionManager { - var internalAppSession: AppSession - override var appSession: AppSession { - return internalAppSession - } - - init( - appSession: AppSession, - identityManager: IdentityManager, - configManager: ConfigManager, - storage: Storage - ) { - internalAppSession = appSession - super.init( - configManager: configManager, - identityManager: identityManager, - storage: storage, - delegate: AppManagerDelegateMock() - ) - } - - override func listenForAppSessionTimeout() { - // Overriding so we don't get ny issues when setting config manually. - } -} diff --git a/Tests/SuperwallKitTests/Analytics/App Session/AppSessionManagerTests.swift b/Tests/SuperwallKitTests/Analytics/App Session/AppSessionManagerTests.swift index 21c93a1d6..df9311529 100644 --- a/Tests/SuperwallKitTests/Analytics/App Session/AppSessionManagerTests.swift +++ b/Tests/SuperwallKitTests/Analytics/App Session/AppSessionManagerTests.swift @@ -12,12 +12,7 @@ import XCTest class AppSessionManagerTests: XCTestCase { lazy var dependencyContainer: DependencyContainer = { let dependencyContainer = DependencyContainer() - appSessionManager = AppSessionManager( - configManager: dependencyContainer.configManager, - identityManager: dependencyContainer.identityManager, - storage: dependencyContainer.storage, - delegate: delegate - ) + appSessionManager = AppSessionManager(factory: dependencyContainer) dependencyContainer.appSessionManager = appSessionManager return dependencyContainer }() diff --git a/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackingLogicTests.swift b/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackingLogicTests.swift index c0f1a1872..2012b43dd 100644 --- a/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackingLogicTests.swift +++ b/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackingLogicTests.swift @@ -296,10 +296,7 @@ final class TrackingLogicTests: XCTestCase { func testDidStartNewSession_canTriggerPaywall_paywallAlreadyPresented() { let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = SWWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, diff --git a/Tests/SuperwallKitTests/Analytics/Session Events/SessionEventsManagerTests.swift b/Tests/SuperwallKitTests/Analytics/Session Events/SessionEventsManagerTests.swift index 9bd63723c..a35775b79 100644 --- a/Tests/SuperwallKitTests/Analytics/Session Events/SessionEventsManagerTests.swift +++ b/Tests/SuperwallKitTests/Analytics/Session Events/SessionEventsManagerTests.swift @@ -13,23 +13,18 @@ import XCTest final class SessionEventsManagerTests: XCTestCase { // MARK: - PostCachedSessionEvents func testPostCachedSessionEvents_noneAvailable() async { + let dependencyContainer = DependencyContainer() + let storage = StorageMock( internalCachedTriggerSessions: [], internalCachedTransactions: [] ) - let dependencyContainer = DependencyContainer() let network = NetworkMock(factory: dependencyContainer) - _ = SessionEventsManager( - queue: SessionEventsQueue( - storage: storage, - network: network, - configManager: dependencyContainer.configManager - ), - storage: storage, - network: network, - configManager: dependencyContainer.configManager, - factory: dependencyContainer - ) + + dependencyContainer.storage = storage + dependencyContainer.network = network + + _ = SessionEventsManager(factory: dependencyContainer) let milliseconds = 200 let nanoseconds = UInt64(milliseconds * 1_000_000) @@ -40,23 +35,18 @@ final class SessionEventsManagerTests: XCTestCase { } func testPostCachedSessionEvents_triggerSessionsOnly() async { - let storage = StorageMock(internalCachedTriggerSessions: [.stub()]) let dependencyContainer = DependencyContainer() - let configManager = dependencyContainer.configManager! - configManager.configState.send(.retrieved(.stub())) + let storage = StorageMock(internalCachedTriggerSessions: [.stub()]) let network = NetworkMock(factory: dependencyContainer) - _ = SessionEventsManager( - queue: SessionEventsQueue( - storage: storage, - network: network, - configManager: configManager - ), - storage: storage, - network: network, - configManager: configManager, - factory: dependencyContainer - ) + + dependencyContainer.storage = storage + dependencyContainer.network = network + + let configManager = dependencyContainer.configManager + configManager.configState.send(.retrieved(.stub())) + + _ = SessionEventsManager(factory: dependencyContainer) let milliseconds = 200 let nanoseconds = UInt64(milliseconds * 1_000_000) @@ -68,26 +58,21 @@ final class SessionEventsManagerTests: XCTestCase { } func testPostCachedSessionEvents_triggerSessionsAndTransactions() async { + let dependencyContainer = DependencyContainer() + let storage = StorageMock( internalCachedTriggerSessions: [.stub()], internalCachedTransactions: [.stub()] ) - let dependencyContainer = DependencyContainer() - let configManager = dependencyContainer.configManager! + let network = NetworkMock(factory: dependencyContainer) + + dependencyContainer.storage = storage + dependencyContainer.network = network + + let configManager = dependencyContainer.configManager configManager.configState.send(.retrieved(.stub())) - let network = NetworkMock(factory: dependencyContainer) - _ = SessionEventsManager( - queue: SessionEventsQueue( - storage: storage, - network: network, - configManager: configManager - ), - storage: storage, - network: network, - configManager: configManager, - factory: dependencyContainer - ) + _ = SessionEventsManager(factory: dependencyContainer) let milliseconds = 200 let nanoseconds = UInt64(milliseconds * 1_000_000) @@ -99,26 +84,22 @@ final class SessionEventsManagerTests: XCTestCase { } func testPostCachedSessionEvents_transactionsOnly() async { + let dependencyContainer = DependencyContainer() + let storage = StorageMock( internalCachedTriggerSessions: [], internalCachedTransactions: [.stub()] - ) - let dependencyContainer = DependencyContainer() - let configManager = dependencyContainer.configManager! - configManager.configState.send(.retrieved(.stub())) + ) let network = NetworkMock(factory: dependencyContainer) - _ = SessionEventsManager( - queue: SessionEventsQueue( - storage: storage, - network: network, - configManager: configManager - ), - storage: storage, - network: network, - configManager: configManager, - factory: dependencyContainer - ) + + dependencyContainer.storage = storage + dependencyContainer.network = network + + let configManager = dependencyContainer.configManager + configManager.configState.send(.retrieved(.stub())) + + _ = SessionEventsManager(factory: dependencyContainer) let milliseconds = 200 let nanoseconds = UInt64(milliseconds * 1_000_000) diff --git a/Tests/SuperwallKitTests/Config/Assignments/AssignmentLogicTests.swift b/Tests/SuperwallKitTests/Config/Assignments/AssignmentLogicTests.swift index 6c3757f7a..541ebd729 100644 --- a/Tests/SuperwallKitTests/Config/Assignments/AssignmentLogicTests.swift +++ b/Tests/SuperwallKitTests/Config/Assignments/AssignmentLogicTests.swift @@ -39,19 +39,18 @@ class AssignmentLogicTests: XCTestCase { ) let triggers = [eventName: trigger] + let storage = StorageMock() + let dependencyContainer = DependencyContainer() + dependencyContainer.storage = storage + let variant = variantOption.toVariant() dependencyContainer.configManager.unconfirmedAssignments = [ rawExperiment.id: variant ] - let storage = StorageMock() // MARK: When - let assignmentLogic = RuleLogic( - configManager: dependencyContainer.configManager, - storage: storage, - factory: dependencyContainer - ) + let assignmentLogic = RuleLogic(factory: dependencyContainer) let outcome = await assignmentLogic.evaluateRules( forEvent: eventData, triggers: triggers @@ -103,17 +102,17 @@ class AssignmentLogicTests: XCTestCase { ) let triggers = [eventName: trigger] + let storage = StorageMock() + let dependencyContainer = DependencyContainer() + dependencyContainer.storage = storage + let variant = variantOption.toVariant() dependencyContainer.configManager.unconfirmedAssignments = [ rawExperiment.id: variant ] - let storage = StorageMock() - let assignmentLogic = RuleLogic( - configManager: dependencyContainer.configManager, - storage: storage, - factory: dependencyContainer - ) + + let assignmentLogic = RuleLogic(factory: dependencyContainer) // MARK: When let outcome = await assignmentLogic.evaluateRules( @@ -168,14 +167,13 @@ class AssignmentLogicTests: XCTestCase { ) let triggers = [eventName: trigger] - let dependencyContainer = DependencyContainer() let variant = variantOption.toVariant() let storage = StorageMock(confirmedAssignments: [rawExperiment.id: variant]) - let assignmentLogic = RuleLogic( - configManager: dependencyContainer.configManager, - storage: storage, - factory: dependencyContainer - ) + + let dependencyContainer = DependencyContainer() + dependencyContainer.storage = storage + + let assignmentLogic = RuleLogic(factory: dependencyContainer) // MARK: When let outcome = await assignmentLogic.evaluateRules( @@ -226,20 +224,19 @@ class AssignmentLogicTests: XCTestCase { ) let triggers = [eventName: trigger] - let dependencyContainer = DependencyContainer() let variant = variantOption.toVariant() let variant2 = variantOption .setting(\.paywallId, to: "123") .toVariant() let storage = StorageMock(confirmedAssignments: [rawExperiment.id: variant]) + + let dependencyContainer = DependencyContainer() + dependencyContainer.storage = storage + dependencyContainer.configManager.unconfirmedAssignments = [ rawExperiment.id: variant2 ] - let assignmentLogic = RuleLogic( - configManager: dependencyContainer.configManager, - storage: storage, - factory: dependencyContainer - ) + let assignmentLogic = RuleLogic(factory: dependencyContainer) // MARK: When let outcome = await assignmentLogic.evaluateRules( @@ -290,17 +287,16 @@ class AssignmentLogicTests: XCTestCase { ) let triggers = [eventName: trigger] - let dependencyContainer = DependencyContainer() let storage = StorageMock() + + let dependencyContainer = DependencyContainer() + dependencyContainer.storage = storage + let variant = variantOption.toVariant() dependencyContainer.configManager.unconfirmedAssignments = [ rawExperiment.id: variant ] - let assignmentLogic = RuleLogic( - configManager: dependencyContainer.configManager, - storage: storage, - factory: dependencyContainer - ) + let assignmentLogic = RuleLogic(factory: dependencyContainer) // MARK: When let outcome = await assignmentLogic.evaluateRules( @@ -346,17 +342,16 @@ class AssignmentLogicTests: XCTestCase { ) let triggers = [eventName: trigger] - let dependencyContainer = DependencyContainer() let storage = StorageMock() + + let dependencyContainer = DependencyContainer() + dependencyContainer.storage = storage + let variant = variantOption.toVariant() dependencyContainer.configManager.unconfirmedAssignments = [ rawExperiment.id: variant ] - let assignmentLogic = RuleLogic( - configManager: dependencyContainer.configManager, - storage: storage, - factory: dependencyContainer - ) + let assignmentLogic = RuleLogic(factory: dependencyContainer) // MARK: When let outcome = await assignmentLogic.evaluateRules( diff --git a/Tests/SuperwallKitTests/Config/Assignments/Expression Evaluator/ExpressionEvaluatorTests.swift b/Tests/SuperwallKitTests/Config/Assignments/Expression Evaluator/ExpressionEvaluatorTests.swift index 4b1d8e720..e995449e9 100644 --- a/Tests/SuperwallKitTests/Config/Assignments/Expression Evaluator/ExpressionEvaluatorTests.swift +++ b/Tests/SuperwallKitTests/Config/Assignments/Expression Evaluator/ExpressionEvaluatorTests.swift @@ -1,6 +1,6 @@ // // File.swift -// +// // // Created by Brian Anglin on 2/21/22. // @@ -16,10 +16,7 @@ final class ExpressionEvaluatorTests: XCTestCase { func test_tryToMatchOccurrence_noMatch() async { let dependencyContainer = DependencyContainer() let storage = StorageMock() - let evaluator = ExpressionEvaluator( - storage: storage, - factory: dependencyContainer - ) + let evaluator = ExpressionEvaluator(factory: dependencyContainer) let rule = TriggerRule.stub() .setting(\.occurrence, to: .stub()) let outcome = await evaluator.tryToMatchOccurrence( @@ -32,10 +29,7 @@ final class ExpressionEvaluatorTests: XCTestCase { func test_tryToMatchOccurrence_noOccurrenceRule() async { let dependencyContainer = DependencyContainer() let storage = StorageMock() - let evaluator = ExpressionEvaluator( - storage: storage, - factory: dependencyContainer - ) + let evaluator = ExpressionEvaluator(factory: dependencyContainer) let rule = TriggerRule.stub() .setting(\.occurrence, to: nil) let outcome = await evaluator.tryToMatchOccurrence( @@ -49,10 +43,7 @@ final class ExpressionEvaluatorTests: XCTestCase { let dependencyContainer = DependencyContainer() let coreDataManagerMock = CoreDataManagerFakeDataMock(internalOccurrenceCount: 1) let storage = StorageMock(coreDataManager: coreDataManagerMock) - let evaluator = ExpressionEvaluator( - storage: storage, - factory: dependencyContainer - ) + let evaluator = ExpressionEvaluator(factory: dependencyContainer) let rule = TriggerRule.stub() .setting(\.occurrence, to: .stub().setting(\.maxCount, to: 1)) @@ -65,13 +56,13 @@ final class ExpressionEvaluatorTests: XCTestCase { } func test_tryToMatchOccurrence_shouldFire_maxCountEqualToCount() async { - let dependencyContainer = DependencyContainer() let coreDataManagerMock = CoreDataManagerFakeDataMock(internalOccurrenceCount: 0) let storage = StorageMock(coreDataManager: coreDataManagerMock) - let evaluator = ExpressionEvaluator( - storage: storage, - factory: dependencyContainer - ) + + let dependencyContainer = DependencyContainer() + dependencyContainer.storage = storage + + let evaluator = ExpressionEvaluator(factory: dependencyContainer) let occurrence: TriggerRuleOccurrence = .stub().setting(\.maxCount, to: 1) let rule = TriggerRule.stub() @@ -88,10 +79,7 @@ final class ExpressionEvaluatorTests: XCTestCase { let dependencyContainer = DependencyContainer() let coreDataManagerMock = CoreDataManagerFakeDataMock(internalOccurrenceCount: 1) let storage = StorageMock(coreDataManager: coreDataManagerMock) - let evaluator = ExpressionEvaluator( - storage: storage, - factory: dependencyContainer - ) + let evaluator = ExpressionEvaluator(factory: dependencyContainer) let occurrence: TriggerRuleOccurrence = .stub().setting(\.maxCount, to: 4) let rule = TriggerRule.stub() @@ -108,10 +96,7 @@ final class ExpressionEvaluatorTests: XCTestCase { func testExpressionMatchesAll() async { let dependencyContainer = DependencyContainer() dependencyContainer.storage.reset() - let evaluator = ExpressionEvaluator( - storage: dependencyContainer.storage, - factory: dependencyContainer - ) + let evaluator = ExpressionEvaluator(factory: dependencyContainer) let rule: TriggerRule = .stub() .setting(\.expression, to: nil) @@ -129,10 +114,7 @@ final class ExpressionEvaluatorTests: XCTestCase { func testExpressionEvaluator_expressionTrue() async { let dependencyContainer = DependencyContainer() dependencyContainer.storage.reset() - let evaluator = ExpressionEvaluator( - storage: dependencyContainer.storage, - factory: dependencyContainer - ) + let evaluator = ExpressionEvaluator(factory: dependencyContainer) dependencyContainer.identityManager.mergeUserAttributes(["a": "b"]) let rule: TriggerRule = .stub() .setting(\.expression, to: "user.a == \"b\"") @@ -147,10 +129,7 @@ final class ExpressionEvaluatorTests: XCTestCase { func testExpressionEvaluator_expression_withOccurrence() async { let dependencyContainer = DependencyContainer() dependencyContainer.storage.reset() - let evaluator = ExpressionEvaluator( - storage: dependencyContainer.storage, - factory: dependencyContainer - ) + let evaluator = ExpressionEvaluator(factory: dependencyContainer) dependencyContainer.identityManager.mergeUserAttributes(["a": "b"]) let occurrence = TriggerRuleOccurrence(key: "a", maxCount: 1, interval: .infinity) let rule: TriggerRule = .stub() @@ -167,10 +146,7 @@ final class ExpressionEvaluatorTests: XCTestCase { func testExpressionEvaluator_expressionParams() async { let dependencyContainer = DependencyContainer() dependencyContainer.storage.reset() - let evaluator = ExpressionEvaluator( - storage: dependencyContainer.storage, - factory: dependencyContainer - ) + let evaluator = ExpressionEvaluator(factory: dependencyContainer) dependencyContainer.identityManager.mergeUserAttributes([:]) let rule: TriggerRule = .stub() .setting(\.expression, to: "params.a == \"b\"") @@ -184,10 +160,7 @@ final class ExpressionEvaluatorTests: XCTestCase { func testExpressionEvaluator_expressionDeviceTrue() async { let dependencyContainer = DependencyContainer() dependencyContainer.storage.reset() - let evaluator = ExpressionEvaluator( - storage: dependencyContainer.storage, - factory: dependencyContainer - ) + let evaluator = ExpressionEvaluator(factory: dependencyContainer) dependencyContainer.identityManager.mergeUserAttributes([:]) let rule: TriggerRule = .stub() .setting(\.expression, to: "device.platform == \"iOS\"") @@ -201,15 +174,12 @@ final class ExpressionEvaluatorTests: XCTestCase { func testExpressionEvaluator_expressionDeviceFalse() async { let dependencyContainer = DependencyContainer() dependencyContainer.storage.reset() - let evaluator = ExpressionEvaluator( - storage: dependencyContainer.storage, - factory: dependencyContainer - ) + let evaluator = ExpressionEvaluator(factory: dependencyContainer) dependencyContainer.identityManager.mergeUserAttributes([:]) let rule: TriggerRule = .stub() .setting(\.expression, to: "device.platform == \"Android\"") let result = await evaluator.evaluateExpression( - fromRule: rule, + fromRule: rule, eventData: EventData(name: "ss", parameters: ["a": "b"], createdAt: Date()) ) XCTAssertEqual(result, .noMatch(source: .expression, experimentId: rule.experiment.id)) @@ -218,10 +188,7 @@ final class ExpressionEvaluatorTests: XCTestCase { func testExpressionEvaluator_expressionFalse() async { let dependencyContainer = DependencyContainer() dependencyContainer.storage.reset() - let evaluator = ExpressionEvaluator( - storage: dependencyContainer.storage, - factory: dependencyContainer - ) + let evaluator = ExpressionEvaluator(factory: dependencyContainer) dependencyContainer.identityManager.mergeUserAttributes([:]) let rule: TriggerRule = .stub() .setting(\.expression, to: "a == \"b\"") @@ -231,29 +198,26 @@ final class ExpressionEvaluatorTests: XCTestCase { ) XCTAssertEqual(result, .noMatch(source: .expression, experimentId: rule.experiment.id)) } -/* - func testExpressionEvaluator_events() { - let triggeredEvents: [String: [EventData]] = [ - "a": [.stub()] - ] - let storage = StorageMock(internalTriggeredEvents: triggeredEvents) - let result = ExpressionEvaluator.evaluateExpression( - fromRule: .stub() - .setting(\.expression, to: "events[\"a\"][\"$count_24h\"] == 1"), - eventData: .stub(), - storage: storage - ) - XCTAssertTrue(result) - }*/ + /* + func testExpressionEvaluator_events() { + let triggeredEvents: [String: [EventData]] = [ + "a": [.stub()] + ] + let storage = StorageMock(internalTriggeredEvents: triggeredEvents) + let result = ExpressionEvaluator.evaluateExpression( + fromRule: .stub() + .setting(\.expression, to: "events[\"a\"][\"$count_24h\"] == 1"), + eventData: .stub(), + storage: storage + ) + XCTAssertTrue(result) + }*/ // MARK: - ExpressionJS func testExpressionEvaluator_expressionJSTrue() async { let dependencyContainer = DependencyContainer() - let evaluator = ExpressionEvaluator( - storage: dependencyContainer.storage, - factory: dependencyContainer - ) + let evaluator = ExpressionEvaluator(factory: dependencyContainer) let rule: TriggerRule = .stub() .setting(\.expressionJs, to: "function superwallEvaluator(){ return true }; superwallEvaluator") let result = await evaluator.evaluateExpression( @@ -264,11 +228,8 @@ final class ExpressionEvaluatorTests: XCTestCase { } func testExpressionEvaluator_expressionJSValues_true() async { - let dependencyContainer = DependencyContainer() - let evaluator = ExpressionEvaluator( - storage: dependencyContainer.storage, - factory: dependencyContainer - ) + let dependencyContainer = DependencyContainer() + let evaluator = ExpressionEvaluator(factory: dependencyContainer) let rule: TriggerRule = .stub() .setting(\.expressionJs, to: "function superwallEvaluator(values) { return values.params.a ==\"b\" }; superwallEvaluator") let result = await evaluator.evaluateExpression( @@ -280,10 +241,7 @@ final class ExpressionEvaluatorTests: XCTestCase { func testExpressionEvaluator_expressionJSValues_false() async { let dependencyContainer = DependencyContainer() - let evaluator = ExpressionEvaluator( - storage: dependencyContainer.storage, - factory: dependencyContainer - ) + let evaluator = ExpressionEvaluator(factory: dependencyContainer) let rule: TriggerRule = .stub() .setting(\.expressionJs, to: "function superwallEvaluator(values) { return values.params.a ==\"b\" }; superwallEvaluator") let result = await evaluator.evaluateExpression( @@ -295,10 +253,7 @@ final class ExpressionEvaluatorTests: XCTestCase { func testExpressionEvaluator_expressionJSNumbers() async { let dependencyContainer = DependencyContainer() - let evaluator = ExpressionEvaluator( - storage: dependencyContainer.storage, - factory: dependencyContainer - ) + let evaluator = ExpressionEvaluator(factory: dependencyContainer) let rule: TriggerRule = .stub() .setting(\.expressionJs, to: "function superwallEvaluator(values) { return 1 == 1 }; superwallEvaluator") let result = await evaluator.evaluateExpression( @@ -307,27 +262,24 @@ final class ExpressionEvaluatorTests: XCTestCase { ) XCTAssertEqual(result, .match(rule: rule)) } -/* - func testExpressionEvaluator_expressionJSValues_events() { - let triggeredEvents: [String: [EventData]] = [ - "a": [.stub()] - ] - let storage = StorageMock(internalTriggeredEvents: triggeredEvents) - let result = ExpressionEvaluator.evaluateExpression( - fromRule: .stub() - .setting(\.expressionJs, to: "function superwallEvaluator(values) { return values.events.a.$count_24h == 1 }; superwallEvaluator"), - eventData: .stub(), - storage: storage - ) - XCTAssertTrue(result) - }*/ + /* + func testExpressionEvaluator_expressionJSValues_events() { + let triggeredEvents: [String: [EventData]] = [ + "a": [.stub()] + ] + let storage = StorageMock(internalTriggeredEvents: triggeredEvents) + let result = ExpressionEvaluator.evaluateExpression( + fromRule: .stub() + .setting(\.expressionJs, to: "function superwallEvaluator(values) { return values.events.a.$count_24h == 1 }; superwallEvaluator"), + eventData: .stub(), + storage: storage + ) + XCTAssertTrue(result) + }*/ func testExpressionEvaluator_expressionJSEmpty() async { let dependencyContainer = DependencyContainer() - let evaluator = ExpressionEvaluator( - storage: dependencyContainer.storage, - factory: dependencyContainer - ) + let evaluator = ExpressionEvaluator(factory: dependencyContainer) let rule: TriggerRule = .stub() .setting(\.expressionJs, to: "") let result = await evaluator.evaluateExpression( diff --git a/Tests/SuperwallKitTests/Config/ConfigLogicTests.swift b/Tests/SuperwallKitTests/Config/ConfigLogicTests.swift index a45349272..f2c3fb19a 100644 --- a/Tests/SuperwallKitTests/Config/ConfigLogicTests.swift +++ b/Tests/SuperwallKitTests/Config/ConfigLogicTests.swift @@ -500,7 +500,6 @@ final class ConfigLogicTests: XCTestCase { func test_getStaticPaywall_deviceLocaleSpecifiedInConfig() { let locale = "en_GB" - let dependencyContainer = DependencyContainer() let response = ConfigLogic.getStaticPaywall( withId: "abc", config: .stub() diff --git a/Tests/SuperwallKitTests/Config/ConfigManagerTests.swift b/Tests/SuperwallKitTests/Config/ConfigManagerTests.swift index 7db3c5530..123302df6 100644 --- a/Tests/SuperwallKitTests/Config/ConfigManagerTests.swift +++ b/Tests/SuperwallKitTests/Config/ConfigManagerTests.swift @@ -21,16 +21,14 @@ final class ConfigManagerTests: XCTestCase { variant: variant ) let dependencyContainer = DependencyContainer() + let network = NetworkMock(factory: dependencyContainer) let storage = StorageMock() - let configManager = ConfigManager( - options: nil, - storeKitManager: dependencyContainer.storeKitManager, - storage: storage, - network: network, - paywallManager: dependencyContainer.paywallManager, - factory: dependencyContainer - ) + let configManager = dependencyContainer.configManager + + dependencyContainer.network = network + dependencyContainer.storage = storage + configManager.confirmAssignment(assignment) let milliseconds = 200 @@ -46,16 +44,13 @@ final class ConfigManagerTests: XCTestCase { func test_loadAssignments_noConfig() async { let dependencyContainer = DependencyContainer() + let network = NetworkMock(factory: dependencyContainer) let storage = StorageMock() - let configManager = ConfigManager( - options: nil, - storeKitManager: dependencyContainer.storeKitManager, - storage: storage, - network: network, - paywallManager: dependencyContainer.paywallManager, - factory: dependencyContainer - ) + let configManager = dependencyContainer.configManager + + dependencyContainer.network = network + dependencyContainer.storage = storage let expectation = expectation(description: "No assignments") expectation.isInverted = true @@ -72,16 +67,14 @@ final class ConfigManagerTests: XCTestCase { func test_loadAssignments_noTriggers() async { let dependencyContainer = DependencyContainer() + let network = NetworkMock(factory: dependencyContainer) let storage = StorageMock() - let configManager = ConfigManager( - options: nil, - storeKitManager: dependencyContainer.storeKitManager, - storage: storage, - network: network, - paywallManager: dependencyContainer.paywallManager, - factory: dependencyContainer - ) + let configManager = dependencyContainer.configManager + + dependencyContainer.network = network + dependencyContainer.storage = storage + configManager.configState.send(.retrieved(.stub() .setting(\.triggers, to: []))) @@ -93,16 +86,13 @@ final class ConfigManagerTests: XCTestCase { func test_loadAssignments_saveAssignmentsFromServer() async { let dependencyContainer = DependencyContainer() + let network = NetworkMock(factory: dependencyContainer) let storage = StorageMock() - let configManager = ConfigManager( - options: nil, - storeKitManager: dependencyContainer.storeKitManager, - storage: storage, - network: network, - paywallManager: dependencyContainer.paywallManager, - factory: dependencyContainer - ) + let configManager = dependencyContainer.configManager + + dependencyContainer.network = network + dependencyContainer.storage = storage let variantId = "variantId" let experimentId = "experimentId" diff --git a/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/ConfirmHoldoutAssignmentTests.swift b/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/ConfirmHoldoutAssignmentTests.swift index 5395c3aff..2f1bab166 100644 --- a/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/ConfirmHoldoutAssignmentTests.swift +++ b/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/ConfirmHoldoutAssignmentTests.swift @@ -14,15 +14,7 @@ final class ConfirmHoldoutAssignmentOperatorTests: XCTestCase { func test_confirmHoldoutAssignment_notHoldout() async { let dependencyContainer = DependencyContainer() - - let configManager = ConfigManagerMock( - options: nil, - storeKitManager: dependencyContainer.storeKitManager, - storage: dependencyContainer.storage, - network: dependencyContainer.network, - paywallManager: dependencyContainer.paywallManager, - factory: dependencyContainer - ) + let configManager = ConfigManagerMock(options: dependencyContainer.options, factory: dependencyContainer) try? await Task.sleep(nanoseconds: 10_000_000) @@ -44,14 +36,7 @@ final class ConfirmHoldoutAssignmentOperatorTests: XCTestCase { func test_confirmHoldoutAssignment_holdout_noConfirmableAssignments() async { let dependencyContainer = DependencyContainer() - let configManager = ConfigManagerMock( - options: nil, - storeKitManager: dependencyContainer.storeKitManager, - storage: dependencyContainer.storage, - network: dependencyContainer.network, - paywallManager: dependencyContainer.paywallManager, - factory: dependencyContainer - ) + let configManager = ConfigManagerMock(options: dependencyContainer.options, factory: dependencyContainer) try? await Task.sleep(nanoseconds: 10_000_000) @@ -72,14 +57,8 @@ final class ConfirmHoldoutAssignmentOperatorTests: XCTestCase { func test_confirmHoldoutAssignment_holdout_hasConfirmableAssignments() async { let dependencyContainer = DependencyContainer() - let configManager = ConfigManagerMock( - options: nil, - storeKitManager: dependencyContainer.storeKitManager, - storage: dependencyContainer.storage, - network: dependencyContainer.network, - paywallManager: dependencyContainer.paywallManager, - factory: dependencyContainer - ) + let configManager = ConfigManagerMock(options: dependencyContainer.options, factory: dependencyContainer) + try? await Task.sleep(nanoseconds: 10_000_000) dependencyContainer.configManager = configManager @@ -101,14 +80,7 @@ final class ConfirmHoldoutAssignmentOperatorTests: XCTestCase { func test_confirmHoldoutAssignment_holdout_getPresentationResult() async { let dependencyContainer = DependencyContainer() - let configManager = ConfigManagerMock( - options: nil, - storeKitManager: dependencyContainer.storeKitManager, - storage: dependencyContainer.storage, - network: dependencyContainer.network, - paywallManager: dependencyContainer.paywallManager, - factory: dependencyContainer - ) + let configManager = ConfigManagerMock(options: dependencyContainer.options, factory: dependencyContainer) try? await Task.sleep(nanoseconds: 10_000_000) dependencyContainer.configManager = configManager @@ -130,14 +102,7 @@ final class ConfirmHoldoutAssignmentOperatorTests: XCTestCase { func test_confirmHoldoutAssignment_holdout_getImplicitPresentationResult() async { let dependencyContainer = DependencyContainer() - let configManager = ConfigManagerMock( - options: nil, - storeKitManager: dependencyContainer.storeKitManager, - storage: dependencyContainer.storage, - network: dependencyContainer.network, - paywallManager: dependencyContainer.paywallManager, - factory: dependencyContainer - ) + let configManager = ConfigManagerMock(options: dependencyContainer.options, factory: dependencyContainer) try? await Task.sleep(nanoseconds: 10_000_000) dependencyContainer.configManager = configManager diff --git a/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/ConfirmPaywallAssignmentOperatorTests.swift b/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/ConfirmPaywallAssignmentOperatorTests.swift index 3b456361f..767ba065a 100644 --- a/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/ConfirmPaywallAssignmentOperatorTests.swift +++ b/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/ConfirmPaywallAssignmentOperatorTests.swift @@ -15,14 +15,7 @@ final class ConfirmPaywallAssignmentOperatorTests: XCTestCase { @MainActor func test_confirmPaywallAssignment_debuggerLaunched() async { let dependencyContainer = DependencyContainer() - let configManager = ConfigManagerMock( - options: nil, - storeKitManager: dependencyContainer.storeKitManager, - storage: dependencyContainer.storage, - network: dependencyContainer.network, - paywallManager: dependencyContainer.paywallManager, - factory: dependencyContainer - ) + let configManager = ConfigManagerMock(options: dependencyContainer.options, factory: dependencyContainer) try? await Task.sleep(nanoseconds: 10_000_000) dependencyContainer.configManager = configManager @@ -39,14 +32,7 @@ final class ConfirmPaywallAssignmentOperatorTests: XCTestCase { @MainActor func test_confirmPaywallAssignment_noAssignment() async { let dependencyContainer = DependencyContainer() - let configManager = ConfigManagerMock( - options: nil, - storeKitManager: dependencyContainer.storeKitManager, - storage: dependencyContainer.storage, - network: dependencyContainer.network, - paywallManager: dependencyContainer.paywallManager, - factory: dependencyContainer - ) + let configManager = ConfigManagerMock(options: dependencyContainer.options, factory: dependencyContainer) try? await Task.sleep(nanoseconds: 10_000_000) dependencyContainer.configManager = configManager @@ -64,14 +50,7 @@ final class ConfirmPaywallAssignmentOperatorTests: XCTestCase { @MainActor func test_confirmPaywallAssignment_confirmAssignment() async { let dependencyContainer = DependencyContainer() - let configManager = ConfigManagerMock( - options: nil, - storeKitManager: dependencyContainer.storeKitManager, - storage: dependencyContainer.storage, - network: dependencyContainer.network, - paywallManager: dependencyContainer.paywallManager, - factory: dependencyContainer - ) + let configManager = ConfigManagerMock(options: dependencyContainer.options, factory: dependencyContainer) try? await Task.sleep(nanoseconds: 10_000_000) dependencyContainer.configManager = configManager @@ -94,14 +73,7 @@ final class ConfirmPaywallAssignmentOperatorTests: XCTestCase { @MainActor func test_confirmPaywallAssignment_getPresentationResult() async { let dependencyContainer = DependencyContainer() - let configManager = ConfigManagerMock( - options: nil, - storeKitManager: dependencyContainer.storeKitManager, - storage: dependencyContainer.storage, - network: dependencyContainer.network, - paywallManager: dependencyContainer.paywallManager, - factory: dependencyContainer - ) + let configManager = ConfigManagerMock(options: dependencyContainer.options, factory: dependencyContainer) try? await Task.sleep(nanoseconds: 10_000_000) dependencyContainer.configManager = configManager @@ -124,14 +96,7 @@ final class ConfirmPaywallAssignmentOperatorTests: XCTestCase { @MainActor func test_confirmPaywallAssignment_getImplicitPresentationResult() async { let dependencyContainer = DependencyContainer() - let configManager = ConfigManagerMock( - options: nil, - storeKitManager: dependencyContainer.storeKitManager, - storage: dependencyContainer.storage, - network: dependencyContainer.network, - paywallManager: dependencyContainer.paywallManager, - factory: dependencyContainer - ) + let configManager = ConfigManagerMock(options: dependencyContainer.options, factory: dependencyContainer) try? await Task.sleep(nanoseconds: 10_000_000) dependencyContainer.configManager = configManager diff --git a/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/GetPaywallVcOperatorTests.swift b/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/GetPaywallVcOperatorTests.swift index b61c22d3c..ac1aecbbc 100644 --- a/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/GetPaywallVcOperatorTests.swift +++ b/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/GetPaywallVcOperatorTests.swift @@ -39,10 +39,7 @@ final class GetPaywallVcOperatorTests: XCTestCase { .store(in: &cancellables) let dependencyContainer = DependencyContainer() - let paywallManager = PaywallManagerMock( - factory: dependencyContainer, - paywallRequestManager: dependencyContainer.paywallRequestManager - ) + let paywallManager = PaywallManagerMock(factory: dependencyContainer) paywallManager.getPaywallError = PresentationPipelineError.noPaywallViewController let publisher = CurrentValueSubject(SubscriptionStatus.active) @@ -92,10 +89,7 @@ final class GetPaywallVcOperatorTests: XCTestCase { .store(in: &cancellables) let dependencyContainer = DependencyContainer() - let paywallManager = PaywallManagerMock( - factory: dependencyContainer, - paywallRequestManager: dependencyContainer.paywallRequestManager - ) + let paywallManager = PaywallManagerMock(factory: dependencyContainer) paywallManager.getPaywallError = PresentationPipelineError.userIsSubscribed let publisher = CurrentValueSubject(SubscriptionStatus.inactive) @@ -135,10 +129,7 @@ final class GetPaywallVcOperatorTests: XCTestCase { .store(in: &cancellables) let dependencyContainer = DependencyContainer() - let paywallManager = PaywallManagerMock( - factory: dependencyContainer, - paywallRequestManager: dependencyContainer.paywallRequestManager - ) + let paywallManager = PaywallManagerMock(factory: dependencyContainer) paywallManager.getPaywallVc = dependencyContainer.makePaywallViewController(for: .stub(), withCache: nil, delegate: nil) dependencyContainer.paywallManager = paywallManager diff --git a/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/PresentPaywallOperatorTests.swift b/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/PresentPaywallOperatorTests.swift index 284d6f775..8b30dcd2a 100644 --- a/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/PresentPaywallOperatorTests.swift +++ b/Tests/SuperwallKitTests/Paywall/Presentation/Internal Presentation/Operators/PresentPaywallOperatorTests.swift @@ -30,10 +30,7 @@ final class PresentPaywallOperatorTests: XCTestCase { .store(in: &cancellables) let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = SWWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, @@ -95,10 +92,7 @@ final class PresentPaywallOperatorTests: XCTestCase { let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = SWWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, diff --git a/Tests/SuperwallKitTests/Paywall/View Controller/SurveyManagerTests.swift b/Tests/SuperwallKitTests/Paywall/View Controller/SurveyManagerTests.swift index 71321f494..d7d35d89e 100644 --- a/Tests/SuperwallKitTests/Paywall/View Controller/SurveyManagerTests.swift +++ b/Tests/SuperwallKitTests/Paywall/View Controller/SurveyManagerTests.swift @@ -19,10 +19,7 @@ final class SurveyManagerTests: XCTestCase { let expectation = expectation(description: "called completion block") let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = SWWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, @@ -60,10 +57,7 @@ final class SurveyManagerTests: XCTestCase { let expectation = expectation(description: "called completion block") let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = SWWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, @@ -102,10 +96,7 @@ final class SurveyManagerTests: XCTestCase { expectation.isInverted = true let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = SWWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, @@ -143,10 +134,7 @@ final class SurveyManagerTests: XCTestCase { let expectation = expectation(description: "called completion block") let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = SWWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, @@ -184,10 +172,7 @@ final class SurveyManagerTests: XCTestCase { let expectation = expectation(description: "called completion block") let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = SWWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, @@ -225,10 +210,7 @@ final class SurveyManagerTests: XCTestCase { let expectation = expectation(description: "called completion block") let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = SWWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, @@ -272,10 +254,7 @@ final class SurveyManagerTests: XCTestCase { let expectation = expectation(description: "called completion block") let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = SWWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, @@ -322,10 +301,7 @@ final class SurveyManagerTests: XCTestCase { let expectation = expectation(description: "called completion block") let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = SWWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, @@ -370,10 +346,7 @@ final class SurveyManagerTests: XCTestCase { expectation.isInverted = true let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = SWWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, @@ -422,10 +395,7 @@ final class SurveyManagerTests: XCTestCase { expectation.isInverted = true let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = SWWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, diff --git a/Tests/SuperwallKitTests/Paywall/View Controller/Web View/Message Handling/PaywallMessageHandlerTests.swift b/Tests/SuperwallKitTests/Paywall/View Controller/Web View/Message Handling/PaywallMessageHandlerTests.swift index 6c7f3e74d..b0791350b 100644 --- a/Tests/SuperwallKitTests/Paywall/View Controller/Web View/Message Handling/PaywallMessageHandlerTests.swift +++ b/Tests/SuperwallKitTests/Paywall/View Controller/Web View/Message Handling/PaywallMessageHandlerTests.swift @@ -12,10 +12,7 @@ final class PaywallMessageHandlerTests: XCTestCase { @MainActor func test_handleTemplateParams() async { let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = FakeWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, @@ -36,10 +33,7 @@ final class PaywallMessageHandlerTests: XCTestCase { @MainActor func test_onReady() async { let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = FakeWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, @@ -61,10 +55,7 @@ final class PaywallMessageHandlerTests: XCTestCase { @MainActor func test_close() { let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = FakeWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, @@ -83,10 +74,7 @@ final class PaywallMessageHandlerTests: XCTestCase { @MainActor func test_openUrl() { let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = FakeWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, @@ -107,10 +95,7 @@ final class PaywallMessageHandlerTests: XCTestCase { @MainActor func test_openUrlInSafari() { let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = FakeWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, @@ -131,10 +116,7 @@ final class PaywallMessageHandlerTests: XCTestCase { @MainActor func test_openDeepLink() { let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = FakeWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, @@ -155,10 +137,7 @@ final class PaywallMessageHandlerTests: XCTestCase { @MainActor func test_restore() { let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = FakeWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, @@ -177,10 +156,7 @@ final class PaywallMessageHandlerTests: XCTestCase { @MainActor func test_purchaseProduct() { let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = FakeWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, @@ -200,10 +176,7 @@ final class PaywallMessageHandlerTests: XCTestCase { @MainActor func test_custom() { let dependencyContainer = DependencyContainer() - let messageHandler = PaywallMessageHandler( - sessionEventsManager: dependencyContainer.sessionEventsManager, - factory: dependencyContainer - ) + let messageHandler = PaywallMessageHandler(factory: dependencyContainer) let webView = FakeWebView( isMac: false, sessionEventsManager: dependencyContainer.sessionEventsManager, diff --git a/Tests/SuperwallKitTests/Storage/StorageTests.swift b/Tests/SuperwallKitTests/Storage/StorageTests.swift index 74390c928..2e91bbe49 100644 --- a/Tests/SuperwallKitTests/Storage/StorageTests.swift +++ b/Tests/SuperwallKitTests/Storage/StorageTests.swift @@ -12,16 +12,9 @@ import XCTest class StorageTests: XCTestCase { func test_saveConfirmedAssignments() { let dependencyContainer = DependencyContainer() - let storage = Storage(factory: dependencyContainer) - let network = NetworkMock(factory: dependencyContainer) - let configManager = ConfigManager( - options: nil, - storeKitManager: dependencyContainer.storeKitManager, - storage: storage, - network: network, - paywallManager: dependencyContainer.paywallManager, - factory: dependencyContainer - ) + let storage = dependencyContainer.storage + let network = dependencyContainer.network + let configManager = dependencyContainer.configManager let assignments: [Experiment.ID: Experiment.Variant] = [ "123": .init(id: "1", type: .treatment, paywallId: "23") diff --git a/Tests/SuperwallKitTests/StoreKit/Products/Receipt Manager/ReceiptManagerTests.swift b/Tests/SuperwallKitTests/StoreKit/Products/Receipt Manager/ReceiptManagerTests.swift index 4c04c49e6..22f93154c 100644 --- a/Tests/SuperwallKitTests/StoreKit/Products/Receipt Manager/ReceiptManagerTests.swift +++ b/Tests/SuperwallKitTests/StoreKit/Products/Receipt Manager/ReceiptManagerTests.swift @@ -10,26 +10,21 @@ import XCTest @testable import SuperwallKit class ReceiptManagerTests: XCTestCase { - let dependencyContainer = DependencyContainer() - lazy var purchaseController = InternalPurchaseController( - factory: dependencyContainer, - swiftPurchaseController: nil, - objcPurchaseController: nil - ) - func test_loadPurchasedProducts_nilProducts() async { let product = MockSkProduct(subscriptionGroupIdentifier: "abc") let productsFetcher = ProductsFetcherSK1Mock( productCompletionResult: .success([StoreProduct(sk1Product: product)]) ) + + let dependencyContainer = DependencyContainer() + dependencyContainer.productsFetcher = productsFetcher + let getReceiptData: () -> Data = { return MockReceiptData.newReceipt } - let receiptManager = ReceiptManager( - delegate: productsFetcher, - purchaseController: purchaseController, - receiptData: getReceiptData - ) + let receiptManager = ReceiptManager(factory: dependencyContainer, receiptData: getReceiptData) + + dependencyContainer.receiptManager = receiptManager _ = await receiptManager.loadPurchasedProducts() let purchasedSubscriptionGroupIds = await receiptManager.purchasedSubscriptionGroupIds @@ -40,14 +35,16 @@ class ReceiptManagerTests: XCTestCase { let productsFetcher = ProductsFetcherSK1Mock( productCompletionResult: .failure(TestError("error")) ) + + let dependencyContainer = DependencyContainer() + dependencyContainer.productsFetcher = productsFetcher + let getReceiptData: () -> Data = { return MockReceiptData.newReceipt } - let receiptManager = ReceiptManager( - delegate: productsFetcher, - purchaseController: purchaseController, - receiptData: getReceiptData - ) + let receiptManager = ReceiptManager(factory: dependencyContainer, receiptData: getReceiptData) + + dependencyContainer.receiptManager = receiptManager _ = await receiptManager.loadPurchasedProducts() let purchasedSubscriptionGroupIds = await receiptManager.purchasedSubscriptionGroupIds diff --git a/Tests/SuperwallKitTests/StoreKit/StoreKitManagerTests.swift b/Tests/SuperwallKitTests/StoreKit/StoreKitManagerTests.swift index 6aaef24c3..56145fb5e 100644 --- a/Tests/SuperwallKitTests/StoreKit/StoreKitManagerTests.swift +++ b/Tests/SuperwallKitTests/StoreKit/StoreKitManagerTests.swift @@ -11,16 +11,9 @@ import XCTest import StoreKit class StoreKitManagerTests: XCTestCase { - let dependencyContainer = DependencyContainer() - lazy var purchaseController = InternalPurchaseController( - factory: dependencyContainer, - swiftPurchaseController: nil, - objcPurchaseController: nil - ) - func test_getProducts_primaryProduct() async { let dependencyContainer = DependencyContainer() - let manager = dependencyContainer.storeKitManager! + let manager = dependencyContainer.storeKitManager let primary = MockSkProduct(productIdentifier: "abc") let substituteProducts = PaywallProducts( @@ -39,7 +32,7 @@ class StoreKitManagerTests: XCTestCase { func test_getProducts_primaryAndTertiaryProduct() async { let dependencyContainer = DependencyContainer() - let manager = dependencyContainer.storeKitManager! + let manager = dependencyContainer.storeKitManager let primary = MockSkProduct(productIdentifier: "abc") let tertiary = MockSkProduct(productIdentifier: "def") @@ -64,7 +57,7 @@ class StoreKitManagerTests: XCTestCase { func test_getProducts_primarySecondaryTertiaryProduct() async { let dependencyContainer = DependencyContainer() - let manager = dependencyContainer.storeKitManager! + let manager = dependencyContainer.storeKitManager let primary = MockSkProduct(productIdentifier: "abc") let secondary = MockSkProduct(productIdentifier: "def") @@ -96,11 +89,11 @@ class StoreKitManagerTests: XCTestCase { func test_getProducts_substitutePrimaryProduct_oneResponseProduct() async { let productsResult: Result, Error> = .success([]) let productsFetcher = ProductsFetcherSK1Mock(productCompletionResult: productsResult) - let manager = StoreKitManager( - purchaseController: purchaseController, - productsFetcher: productsFetcher - ) + let dependencyContainer = DependencyContainer() + dependencyContainer.productsFetcher = productsFetcher + + let manager = dependencyContainer.storeKitManager let primary = MockSkProduct(productIdentifier: "abc") let substituteProducts = PaywallProducts( primary: StoreProduct(sk1Product: primary) @@ -118,15 +111,18 @@ class StoreKitManagerTests: XCTestCase { } func test_getProducts_substitutePrimaryProduct_twoResponseProducts() async { + + let responseProduct2 = MockSkProduct(productIdentifier: "2") let productsResult: Result, Error> = .success([ StoreProduct(sk1Product: responseProduct2) ]) let productsFetcher = ProductsFetcherSK1Mock(productCompletionResult: productsResult) - let manager = StoreKitManager( - purchaseController: purchaseController, - productsFetcher: productsFetcher - ) + + let dependencyContainer = DependencyContainer() + dependencyContainer.productsFetcher = productsFetcher + + let manager = dependencyContainer.storeKitManager let primary = MockSkProduct(productIdentifier: "abc") let substituteProducts = PaywallProducts(