diff --git a/Sources/Logging/Strings/PurchaseStrings.swift b/Sources/Logging/Strings/PurchaseStrings.swift index 61b6cfc6cd..694f3332a8 100644 --- a/Sources/Logging/Strings/PurchaseStrings.swift +++ b/Sources/Logging/Strings/PurchaseStrings.swift @@ -23,6 +23,8 @@ enum PurchaseStrings { case storekit1_wrapper_deinit(StoreKit1Wrapper) case device_cache_init(DeviceCache) case device_cache_deinit(DeviceCache) + case purchases_orchestrator_init(PurchasesOrchestrator) + case purchases_orchestrator_deinit(PurchasesOrchestrator) case updating_all_caches case cannot_purchase_product_appstore_configuration_error case entitlements_revoked_syncing_purchases(productIdentifiers: [String]) @@ -92,6 +94,12 @@ extension PurchaseStrings: CustomStringConvertible { case let .device_cache_deinit(instance): return "DeviceCache.deinit: \(Strings.objectDescription(instance))" + case let .purchases_orchestrator_init(instance): + return "PurchasesOrchestrator.init: \(Strings.objectDescription(instance))" + + case let .purchases_orchestrator_deinit(instance): + return "PurchasesOrchestrator.deinit: \(Strings.objectDescription(instance))" + case .updating_all_caches: return "Updating all caches" diff --git a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift index 852463486f..6df45adfd8 100644 --- a/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift +++ b/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift @@ -166,6 +166,12 @@ final class PurchasesOrchestrator { self.offeringsManager = offeringsManager self.manageSubscriptionsHelper = manageSubscriptionsHelper self.beginRefundRequestHelper = beginRefundRequestHelper + + Logger.verbose(Strings.purchase.purchases_orchestrator_init(self)) + } + + deinit { + Logger.verbose(Strings.purchase.purchases_orchestrator_deinit(self)) } func restorePurchases(completion: (@Sendable (Result) -> Void)?) { diff --git a/Tests/UnitTests/Purchasing/Purchases/BasePurchasesTests.swift b/Tests/UnitTests/Purchasing/Purchases/BasePurchasesTests.swift index c6f8d35193..39db5c6637 100644 --- a/Tests/UnitTests/Purchasing/Purchases/BasePurchasesTests.swift +++ b/Tests/UnitTests/Purchasing/Purchases/BasePurchasesTests.swift @@ -101,54 +101,20 @@ class BasePurchasesTests: TestCase { // this level it should be moved to `StoreKitUnitTests`, which runs serially. Purchases.logLevel = .verbose - // See `addTeardownBlock` docs: - // - These run *before* `tearDown`. - // - They run in LIFO order. self.addTeardownBlock { - expect { [weak purchases = self.purchases] in purchases } - .toEventually(beNil(), description: "Purchases has leaked") - } - self.addTeardownBlock { - expect { [weak orchestrator = self.purchasesOrchestrator] in orchestrator } - .toEventually(beNil(), description: "PurchasesOrchestrator has leaked") - } - self.addTeardownBlock { - expect { [weak deviceCache = self.deviceCache] in deviceCache } - .toEventually(beNil(), description: "DeviceCache has leaked: \(self)") - } + weak var purchases = self.purchases + weak var orchestrator = self.purchasesOrchestrator + weak var deviceCache = self.deviceCache - self.addTeardownBlock { Purchases.clearSingleton() + self.clearReferences() - self.mockOperationDispatcher = nil - self.paymentQueueWrapper = nil - self.requestFetcher = nil - self.receiptFetcher = nil - self.mockProductsManager = nil - self.mockIntroEligibilityCalculator = nil - self.mockTransactionsManager = nil - self.backend = nil - self.attributionFetcher = nil - self.purchasesDelegate.makeDeferredPurchase = nil - self.purchasesDelegate = nil - self.storeKit1Wrapper.delegate = nil - self.storeKit1Wrapper = nil - self.systemInfo = nil - self.notificationCenter = nil - self.subscriberAttributesManager = nil - self.trialOrIntroPriceEligibilityChecker = nil - self.attributionPoster = nil - self.attribution = nil - self.customerInfoManager = nil - self.identityManager = nil - self.mockOfferingsManager = nil - self.mockOfflineEntitlementsManager = nil - self.mockPurchasedProductsFetcher = nil - self.mockManageSubsHelper = nil - self.mockBeginRefundRequestHelper = nil - self.purchasesOrchestrator = nil - self.deviceCache = nil - self.purchases = nil + expect(purchases) + .toEventually(beNil(), description: "Purchases has leaked") + expect(orchestrator) + .toEventually(beNil(), description: "PurchasesOrchestrator has leaked") + expect(deviceCache) + .toEventually(beNil(), description: "DeviceCache has leaked: \(self)") } } @@ -498,3 +464,39 @@ extension OfferingsResponse { ) } + +private extension BasePurchasesTests { + + func clearReferences() { + self.mockOperationDispatcher = nil + self.paymentQueueWrapper = nil + self.requestFetcher = nil + self.receiptFetcher = nil + self.mockProductsManager = nil + self.mockIntroEligibilityCalculator = nil + self.mockTransactionsManager = nil + self.backend = nil + self.attributionFetcher = nil + self.purchasesDelegate.makeDeferredPurchase = nil + self.purchasesDelegate = nil + self.storeKit1Wrapper.delegate = nil + self.storeKit1Wrapper = nil + self.systemInfo = nil + self.notificationCenter = nil + self.subscriberAttributesManager = nil + self.trialOrIntroPriceEligibilityChecker = nil + self.attributionPoster = nil + self.attribution = nil + self.customerInfoManager = nil + self.identityManager = nil + self.mockOfferingsManager = nil + self.mockOfflineEntitlementsManager = nil + self.mockPurchasedProductsFetcher = nil + self.mockManageSubsHelper = nil + self.mockBeginRefundRequestHelper = nil + self.purchasesOrchestrator = nil + self.deviceCache = nil + self.purchases = nil + } + +} diff --git a/Tests/UnitTests/Purchasing/Purchases/PurchasesConfiguringTests.swift b/Tests/UnitTests/Purchasing/Purchases/PurchasesConfiguringTests.swift index 5d2539577e..dfaca4458b 100644 --- a/Tests/UnitTests/Purchasing/Purchases/PurchasesConfiguringTests.swift +++ b/Tests/UnitTests/Purchasing/Purchases/PurchasesConfiguringTests.swift @@ -110,7 +110,7 @@ class PurchasesConfiguringTests: BasePurchasesTests { let purchases = Purchases.configure( with: .init(withAPIKey: "") // This test requires no previously stored user - .with(userDefaults: .init(suiteName: UUID().uuidString)!) + .with(userDefaults: .emptyNewUserDefaults()) .with(appUserID: "") ) expect(purchases.appUserID).toNot(beEmpty()) @@ -120,7 +120,7 @@ class PurchasesConfiguringTests: BasePurchasesTests { func testUserIdOverridesPreviouslyConfiguredUser() { // This test requires no previously stored user - let userDefaults: UserDefaults = .init(suiteName: UUID().uuidString)! + let userDefaults: UserDefaults = .emptyNewUserDefaults() let newUserID = Self.appUserID + "_new" @@ -141,7 +141,7 @@ class PurchasesConfiguringTests: BasePurchasesTests { func testNilUserIdIsIgnoredIfPreviousUserExists() { // This test requires no previously stored user - let userDefaults: UserDefaults = .init(suiteName: UUID().uuidString)! + let userDefaults: UserDefaults = .emptyNewUserDefaults() _ = Purchases.configure( with: .init(withAPIKey: "") @@ -408,13 +408,15 @@ class PurchasesConfiguringTests: BasePurchasesTests { } func testConfigureWithCustomEntitlementComputationFatalErrorIfNoAppUserID() throws { - self.systemInfo = MockSystemInfo(finishTransactions: true, - customEntitlementsComputation: true) - let expectedMessage = Strings.configure.custom_entitlements_computation_enabled_but_no_app_user_id.description expectFatalError(expectedMessage: expectedMessage) { - self.setupAnonPurchases() + _ = Purchases(apiKey: "", + appUserID: nil, + userDefaults: .emptyNewUserDefaults(), + observerMode: false, + responseVerificationMode: .default, + dangerousSettings: .init(customEntitlementComputation: true)) } } @@ -498,3 +500,11 @@ class PurchasesConfiguringTests: BasePurchasesTests { private static let customUserDefaults: UserDefaults = .init(suiteName: "com.revenuecat.testing_user_defaults")! } + +private extension UserDefaults { + + static func emptyNewUserDefaults() -> Self { + return .init(suiteName: UUID().uuidString)! + } + +}