Skip to content

Commit

Permalink
Further refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
NachoSoto committed May 18, 2023
1 parent f1754ce commit 8459cb7
Show file tree
Hide file tree
Showing 17 changed files with 79 additions and 46 deletions.
6 changes: 6 additions & 0 deletions RevenueCat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@
42F1DF385E3C1F9903A07FBF /* ProductsFetcherSK1.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFB3CBAA73855779FE828CE2 /* ProductsFetcherSK1.swift */; };
4F69EB092A14406E00ED6D4B /* Matchers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F69EB082A14406E00ED6D4B /* Matchers.swift */; };
4F69EB0A2A14406E00ED6D4B /* Matchers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F69EB082A14406E00ED6D4B /* Matchers.swift */; };
4F8A58172A16EE3500EF97AD /* MockOfflineCustomerInfoCreator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F8A58162A16EE3500EF97AD /* MockOfflineCustomerInfoCreator.swift */; };
4F8A58182A16EE3500EF97AD /* MockOfflineCustomerInfoCreator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F8A58162A16EE3500EF97AD /* MockOfflineCustomerInfoCreator.swift */; };
4FA4C8DA2A168956007D2803 /* OfflineCustomerInfoCreator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA4C8D92A168956007D2803 /* OfflineCustomerInfoCreator.swift */; };
4FA4C9732A16D3AC007D2803 /* MockBackendConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA4C9722A16D3AC007D2803 /* MockBackendConfiguration.swift */; };
4FA4C9742A16D3AC007D2803 /* MockBackendConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA4C9722A16D3AC007D2803 /* MockBackendConfiguration.swift */; };
Expand Down Expand Up @@ -870,6 +872,7 @@
37E35F783903362B65FB7AF3 /* MockProductsRequestFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockProductsRequestFactory.swift; sourceTree = "<group>"; };
37E35FDA0A44EA03EA12DAA2 /* DateFormatter+ExtensionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DateFormatter+ExtensionsTests.swift"; sourceTree = "<group>"; };
4F69EB082A14406E00ED6D4B /* Matchers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Matchers.swift; sourceTree = "<group>"; };
4F8A58162A16EE3500EF97AD /* MockOfflineCustomerInfoCreator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockOfflineCustomerInfoCreator.swift; sourceTree = "<group>"; };
4FA4C8D92A168956007D2803 /* OfflineCustomerInfoCreator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfflineCustomerInfoCreator.swift; sourceTree = "<group>"; };
4FA4C9722A16D3AC007D2803 /* MockBackendConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBackendConfiguration.swift; sourceTree = "<group>"; };
4FA696A329FC43C600D228B1 /* ReceiptParserTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ReceiptParserTests-Info.plist"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1691,6 +1694,7 @@
578DAA492948EF4F001700FD /* TestClock.swift */,
5791FBD1299184EF00F1FEDA /* MockAsyncSequence.swift */,
57CB2A7B29CCC91800C91439 /* MockProductEntitlementMappingFetcher.swift */,
4F8A58162A16EE3500EF97AD /* MockOfflineCustomerInfoCreator.swift */,
);
path = Mocks;
sourceTree = "<group>";
Expand Down Expand Up @@ -2906,6 +2910,7 @@
57488B2B29CA803F0000EE7E /* MockSandboxEnvironmentDetector.swift in Sources */,
F55FFA5D27634E1900995146 /* MockTransactionsManager.swift in Sources */,
4FA4C9742A16D3AC007D2803 /* MockBackendConfiguration.swift in Sources */,
4F8A58182A16EE3500EF97AD /* MockOfflineCustomerInfoCreator.swift in Sources */,
57554CC2282AE1E3009A7E58 /* TestCase.swift in Sources */,
2D90F8B826FD20AA009B9142 /* MockReceiptFetcher.swift in Sources */,
2D90F8B626FD2099009B9142 /* MockSubscriberAttributesManager.swift in Sources */,
Expand Down Expand Up @@ -3167,6 +3172,7 @@
4FA4C9732A16D3AC007D2803 /* MockBackendConfiguration.swift in Sources */,
5766C620282DA3D50067D886 /* GetIntroEligibilityDecodingTests.swift in Sources */,
57C381E2279627B7009E3940 /* MockStoreProductDiscount.swift in Sources */,
4F8A58172A16EE3500EF97AD /* MockOfflineCustomerInfoCreator.swift in Sources */,
2DDF41E824F6F61B005BC22D /* MockSKProductDiscount.swift in Sources */,
579189B728F4747700BF4963 /* EitherTests.swift in Sources */,
5753EE00294B8C0C00CBAB54 /* AttributionFetcherTests.swift in Sources */,
Expand Down
7 changes: 5 additions & 2 deletions Sources/Logging/Strings/OfflineEntitlementsStrings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ import StoreKit

// swiftlint:disable identifier_name

@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
enum OfflineEntitlementsStrings {

case offline_entitlements_not_available

case product_entitlement_mapping_stale_updating
case product_entitlement_mapping_updated_from_network
case product_entitlement_mapping_fetching_error(BackendError)
Expand All @@ -31,11 +32,13 @@ enum OfflineEntitlementsStrings {

}

@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
extension OfflineEntitlementsStrings: CustomStringConvertible {

var description: String {
switch self {
case .offline_entitlements_not_available:
return "OS version does not support offline entitlements."

case .product_entitlement_mapping_stale_updating:
return "ProductEntitlementMapping cache is stale, updating from network."

Expand Down
1 change: 0 additions & 1 deletion Sources/Logging/Strings/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ enum Strings {
static let identity = IdentityStrings.self
static let network = NetworkStrings.self
static let offering = OfferingStrings.self
@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
static let offlineEntitlements = OfflineEntitlementsStrings.self
static let purchase = PurchaseStrings.self
static let receipt = ReceiptStrings.self
Expand Down
10 changes: 2 additions & 8 deletions Sources/Networking/Backend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ class Backend {
eTagManager: ETagManager,
operationDispatcher: OperationDispatcher,
attributionFetcher: AttributionFetcher,
productEntitlementMappingFetcher: ProductEntitlementMappingFetcher,
purchasedProductsFetcher: PurchasedProductsFetcherType = Backend.createDefaultProductFetcher(),
offlineCustomerInfoCreator: OfflineCustomerInfoCreator?,
dateProvider: DateProvider = DateProvider()
) {
let httpClient = HTTPClient(apiKey: apiKey,
Expand All @@ -42,8 +41,7 @@ class Backend {
operationDispatcher: operationDispatcher,
operationQueue: QueueProvider.createBackendQueue(),
systemInfo: systemInfo,
productEntitlementMappingFetcher: productEntitlementMappingFetcher,
purchasedProductsFetcher: purchasedProductsFetcher,
offlineCustomerInfoCreator: offlineCustomerInfoCreator,
dateProvider: dateProvider)
self.init(backendConfig: config, attributionFetcher: attributionFetcher)
}
Expand Down Expand Up @@ -191,8 +189,4 @@ extension Backend {

}

static func createDefaultProductFetcher() -> PurchasedProductsFetcherType {
return PurchasedProductsFetcher()
}

}
15 changes: 3 additions & 12 deletions Sources/Networking/BackendConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,18 @@ class BackendConfiguration {
let operationQueue: OperationQueue
let dateProvider: DateProvider
let systemInfo: SystemInfo

private let productEntitlementMappingFetcher: ProductEntitlementMappingFetcher
private let purchasedProductsFetcher: PurchasedProductsFetcherType
let offlineCustomerInfoCreator: OfflineCustomerInfoCreator?

init(httpClient: HTTPClient,
operationDispatcher: OperationDispatcher,
operationQueue: OperationQueue,
systemInfo: SystemInfo,
productEntitlementMappingFetcher: ProductEntitlementMappingFetcher,
purchasedProductsFetcher: PurchasedProductsFetcherType,
offlineCustomerInfoCreator: OfflineCustomerInfoCreator?,
dateProvider: DateProvider = DateProvider()) {
self.httpClient = httpClient
self.operationDispatcher = operationDispatcher
self.operationQueue = operationQueue
self.productEntitlementMappingFetcher = productEntitlementMappingFetcher
self.purchasedProductsFetcher = purchasedProductsFetcher
self.offlineCustomerInfoCreator = offlineCustomerInfoCreator
self.dateProvider = dateProvider
self.systemInfo = systemInfo
}
Expand All @@ -45,11 +41,6 @@ class BackendConfiguration {
self.httpClient.clearCaches()
}

func createOfflineCustomerInfoCreator() -> OfflineCustomerInfoCreator {
return .init(purchasedProductsFetcher: self.purchasedProductsFetcher,
productEntitlementMapping: self.productEntitlementMappingFetcher.productEntitlementMapping)
}

}

extension BackendConfiguration: NetworkConfiguration {}
Expand Down
4 changes: 2 additions & 2 deletions Sources/Networking/CustomerAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ final class CustomerAPI {
configuration: config,
customerInfoCallbackCache: self.customerInfoCallbackCache,
offlineCreator: allowComputingOffline
? self.backendConfig.createOfflineCustomerInfoCreator()
? self.backendConfig.offlineCustomerInfoCreator
: nil
)

Expand Down Expand Up @@ -123,7 +123,7 @@ final class CustomerAPI {
configuration: config,
postData: postData,
customerInfoCallbackCache: self.customerInfoCallbackCache,
offlineCustomerInfoCreator: self.backendConfig.createOfflineCustomerInfoCreator()
offlineCustomerInfoCreator: self.backendConfig.offlineCustomerInfoCreator
)

let callbackObject = CustomerInfoCallback(cacheKey: factory.cacheKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class CustomerInfoResponseHandler {
private let offlineCreator: OfflineCustomerInfoCreator?
private let userID: String

/// - Parameter offlineCreator: can be `nil` if offline ``CustomerInfo`` shouldn't be computed.
/// - Parameter offlineCreator: can be `nil` if offline ``CustomerInfo`` shouldn't or can't be computed.
init(offlineCreator: OfflineCustomerInfoCreator?, userID: String) {
self.offlineCreator = offlineCreator
self.userID = userID
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ final class PostReceiptDataOperation: CacheableNetworkOperation {
configuration: UserSpecificConfiguration,
postData: PostData,
customerInfoCallbackCache: CallbackCache<CustomerInfoCallback>,
offlineCustomerInfoCreator: OfflineCustomerInfoCreator
offlineCustomerInfoCreator: OfflineCustomerInfoCreator?
) -> CacheableNetworkOperationFactory<PostReceiptDataOperation> {
return Self.createFactory(
configuration: configuration,
Expand Down
29 changes: 22 additions & 7 deletions Sources/OfflineEntitlements/OfflineCustomerInfoCreator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,35 @@
import Foundation

/// Holds the necessary dependencies to create a `CustomerInfo` while offline.
final class OfflineCustomerInfoCreator {
class OfflineCustomerInfoCreator {

typealias Creator = @Sendable ([PurchasedSK2Product],
ProductEntitlementMapping,
String) -> CustomerInfo

private let purchasedProductsFetcher: PurchasedProductsFetcherType
private let productEntitlementMapping: ProductEntitlementMapping?
private let productEntitlementMappingFetcher: ProductEntitlementMappingFetcher
private let creator: Creator

static func createDefault(
productEntitlementMappingFetcher: ProductEntitlementMappingFetcher
) -> OfflineCustomerInfoCreator? {
if #available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *) {
return .init(
purchasedProductsFetcher: PurchasedProductsFetcher(),
productEntitlementMappingFetcher: productEntitlementMappingFetcher
)
} else {
Logger.debug(Strings.offlineEntitlements.offline_entitlements_not_available)
return nil
}
}

convenience init(purchasedProductsFetcher: PurchasedProductsFetcherType,
productEntitlementMapping: ProductEntitlementMapping?) {
productEntitlementMappingFetcher: ProductEntitlementMappingFetcher) {
self.init(
purchasedProductsFetcher: purchasedProductsFetcher,
productEntitlementMapping: productEntitlementMapping,
productEntitlementMappingFetcher: productEntitlementMappingFetcher,
creator: { products, mapping, userID in
CustomerInfo(from: products, mapping: mapping, userID: userID)
}
Expand All @@ -37,19 +51,20 @@ final class OfflineCustomerInfoCreator {

init(
purchasedProductsFetcher: PurchasedProductsFetcherType,
productEntitlementMapping: ProductEntitlementMapping?,
productEntitlementMappingFetcher: ProductEntitlementMappingFetcher,
creator: @escaping Creator
) {
self.purchasedProductsFetcher = purchasedProductsFetcher
self.productEntitlementMapping = productEntitlementMapping
self.productEntitlementMappingFetcher = productEntitlementMappingFetcher
self.creator = creator
}

@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
func create(for userID: String) async throws -> CustomerInfo {
Logger.info(Strings.offlineEntitlements.computing_offline_customer_info)

guard let mapping = self.productEntitlementMapping, !mapping.entitlementsByProduct.isEmpty else {
guard let mapping = self.productEntitlementMappingFetcher.productEntitlementMapping,
!mapping.entitlementsByProduct.isEmpty else {
Logger.warn(Strings.offlineEntitlements.computing_offline_customer_info_with_no_entitlement_mapping)
throw Error.noEntitlementMappingAvailable
}
Expand Down
3 changes: 1 addition & 2 deletions Sources/OfflineEntitlements/PurchasedProductsFetcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ protocol PurchasedProductsFetcherType {
}

/// A type that can fetch purchased products from StoreKit 2.
@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
class PurchasedProductsFetcher: PurchasedProductsFetcherType {

private let sandboxDetector: SandboxEnvironmentDetector
Expand All @@ -30,7 +31,6 @@ class PurchasedProductsFetcher: PurchasedProductsFetcherType {
self.sandboxDetector = sandboxDetector
}

@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
func fetchPurchasedProducts() async throws -> [PurchasedSK2Product] {
var result: [PurchasedSK2Product] = []

Expand All @@ -52,7 +52,6 @@ class PurchasedProductsFetcher: PurchasedProductsFetcherType {
return result
}

@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
private static func forceSyncToEnsureAllTransactionsAreAccountedFor() async throws {
try await AppStore.sync()
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Purchasing/Purchases/Purchases.swift
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ public typealias StartPurchaseBlock = (@escaping PurchaseCompletedBlock) -> Void
eTagManager: eTagManager,
operationDispatcher: operationDispatcher,
attributionFetcher: attributionFetcher,
productEntitlementMappingFetcher: deviceCache)
offlineCustomerInfoCreator: .createDefault(productEntitlementMappingFetcher: deviceCache))

let paymentQueueWrapper: EitherPaymentQueueWrapper = systemInfo.storeKit2Setting.shouldOnlyUseStoreKit2
? .right(.init())
Expand Down
3 changes: 1 addition & 2 deletions Tests/UnitTests/Mocks/MockBackendConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ class MockBackendConfiguration: BackendConfiguration {
operationDispatcher: MockOperationDispatcher(),
operationQueue: Backend.QueueProvider.createBackendQueue(),
systemInfo: systemInfo,
productEntitlementMappingFetcher: MockProductEntitlementMappingFetcher(),
purchasedProductsFetcher: MockPurchasedProductsFetcher(),
offlineCustomerInfoCreator: MockOfflineCustomerInfoCreator(),
dateProvider: MockDateProvider(stubbedNow: MockBackend.referenceDate)
)
}
Expand Down
26 changes: 26 additions & 0 deletions Tests/UnitTests/Mocks/MockOfflineCustomerInfoCreator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// Copyright RevenueCat Inc. All Rights Reserved.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://opensource.org/licenses/MIT
//
// MockOfflineCustomerInfoCreator.swift
//
// Created by Nacho Soto on 5/18/23.

@testable import RevenueCat

class MockOfflineCustomerInfoCreator: OfflineCustomerInfoCreator {

init() {
super.init(
purchasedProductsFetcher: MockPurchasedProductsFetcher(),
productEntitlementMappingFetcher: MockProductEntitlementMappingFetcher(),
creator: { CustomerInfo(from: $0, mapping: $1, userID: $2) }
)
}

}
3 changes: 1 addition & 2 deletions Tests/UnitTests/Networking/Backend/BaseBackendTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ class BaseBackendTests: TestCase {
operationDispatcher: self.operationDispatcher,
operationQueue: MockBackend.QueueProvider.createBackendQueue(),
systemInfo: self.systemInfo,
productEntitlementMappingFetcher: self.mockProductEntitlementMappingFetcher,
purchasedProductsFetcher: self.mockPurchasedProductsFetcher,
offlineCustomerInfoCreator: MockOfflineCustomerInfoCreator(),
dateProvider: MockDateProvider(stubbedNow: MockBackend.referenceDate)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,11 +256,15 @@ class OfflineCustomerInfoResponseHandlerTests: BaseCustomerInfoResponseHandlerTe
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *)
private extension BaseCustomerInfoResponseHandlerTests {

private struct MappingFetcher: ProductEntitlementMappingFetcher {
let productEntitlementMapping: ProductEntitlementMapping?
}

private func create(_ mapping: ProductEntitlementMapping?) -> CustomerInfoResponseHandler {
return .init(
offlineCreator: .init(
purchasedProductsFetcher: self.fetcher,
productEntitlementMapping: mapping,
productEntitlementMappingFetcher: MappingFetcher(productEntitlementMapping: mapping),
creator: self.factory.create
),
userID: self.userID
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ class BasePurchasesTests: TestCase {
operationDispatcher: self.mockOperationDispatcher,
operationQueue: MockBackend.QueueProvider.createBackendQueue(),
systemInfo: self.systemInfo,
productEntitlementMappingFetcher: self.mockProductEntitlementMappingFetcher,
purchasedProductsFetcher: self.mockPurchasedProductsFetcher,
offlineCustomerInfoCreator: MockOfflineCustomerInfoCreator(),
dateProvider: MockDateProvider(stubbedNow: MockBackend.referenceDate))
self.backend = MockBackend(backendConfig: config, attributionFetcher: self.attributionFetcher)
self.subscriberAttributesManager = MockSubscriberAttributesManager(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ class BackendSubscriberAttributesTests: TestCase {
operationDispatcher: MockOperationDispatcher(),
operationQueue: MockBackend.QueueProvider.createBackendQueue(),
systemInfo: self.systemInfo,
productEntitlementMappingFetcher: MockProductEntitlementMappingFetcher(),
purchasedProductsFetcher: MockPurchasedProductsFetcher(),
offlineCustomerInfoCreator: MockOfflineCustomerInfoCreator(),
dateProvider: self.dateProvider)

self.backend = Backend(backendConfig: config, attributionFetcher: attributionFetcher)
Expand Down

0 comments on commit 8459cb7

Please sign in to comment.