Skip to content

Commit

Permalink
CustomerInfoManager: don't cache offline CustomerInfo (#2378)
Browse files Browse the repository at this point in the history
This is meant to be a temporary `CustomerInfo` while the backend is
offline.
See also #2368.
  • Loading branch information
NachoSoto committed May 17, 2023
1 parent 8b778b5 commit 64287a5
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 10 deletions.
41 changes: 35 additions & 6 deletions Sources/Identity/CustomerInfoManager.swift
Expand Up @@ -181,13 +181,18 @@ class CustomerInfoManager {
}

func cache(customerInfo: CustomerInfo, appUserID: String) {
do {
let jsonData = try JSONEncoder.default.encode(customerInfo)
self.withData { $0.deviceCache.cache(customerInfo: jsonData, appUserID: appUserID) }
self.sendUpdateIfChanged(customerInfo: customerInfo)
} catch {
Logger.error(Strings.customerInfo.error_encoding_customerinfo(error))
if customerInfo.shouldCache {
do {
let jsonData = try JSONEncoder.default.encode(customerInfo)
self.withData { $0.deviceCache.cache(customerInfo: jsonData, appUserID: appUserID) }
} catch {
Logger.error(Strings.customerInfo.error_encoding_customerinfo(error))
}
} else {
Logger.debug(Strings.customerInfo.not_caching_offline_customer_info)
}

self.sendUpdateIfChanged(customerInfo: customerInfo)
}

func clearCustomerInfoCache(forAppUserID appUserID: String) {
Expand Down Expand Up @@ -313,4 +318,28 @@ private extension CustomerInfoManager {
func modifyData<Result>(_ action: (inout Data) -> Result) -> Result {
return self.data.modify(action)
}

}

private extension CustomerInfo {

var shouldCache: Bool {
guard #available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) else {
return true
}

return self.entitlements.verification.shouldCache
}

}

private extension VerificationResult {

var shouldCache: Bool {
switch self {
case .failed, .verified, .notRequested: return true
case .verifiedOnDevice: return false
}
}

}
3 changes: 3 additions & 0 deletions Sources/Logging/Strings/CustomerInfoStrings.swift
Expand Up @@ -23,6 +23,7 @@ enum CustomerInfoStrings {
case checking_intro_eligibility_locally_from_receipt(AppleReceipt)
case invalidating_customerinfo_cache
case no_cached_customerinfo
case not_caching_offline_customer_info
case customerinfo_stale_updating_in_background
case customerinfo_stale_updating_in_foreground
case customerinfo_updated_from_network
Expand Down Expand Up @@ -51,6 +52,8 @@ extension CustomerInfoStrings: CustomStringConvertible {
return "Invalidating CustomerInfo cache."
case .no_cached_customerinfo:
return "No cached CustomerInfo, fetching from network."
case .not_caching_offline_customer_info:
return "CustomerInfo was computed offline. Won't be stored in cache."
case .customerinfo_stale_updating_in_background:
return "CustomerInfo cache is stale, updating from network in background."
case .customerinfo_stale_updating_in_foreground:
Expand Down
Expand Up @@ -84,7 +84,7 @@ extension CustomerInfo {
}

/// Purchases are verified with StoreKit 2.
private static let verification: VerificationResult = .verified
private static let verification: VerificationResult = .verifiedOnDevice

static let defaultManagementURL = URL(string: "https://apps.apple.com/account/subscriptions")!

Expand Down
Expand Up @@ -251,7 +251,7 @@ private extension CustomerInfoOfflineEntitlementsStoreKitTest {
expect(entitlement.periodType) == periodType
expect(entitlement.store) == .appStore
expect(entitlement.unsubscribeDetectedAt).to(beNil())
expect(entitlement.verification) == .verified
expect(entitlement.verification) == .verifiedOnDevice
}

}
59 changes: 57 additions & 2 deletions Tests/UnitTests/Identity/CustomerInfoManagerTests.swift
Expand Up @@ -388,12 +388,67 @@ class CustomerInfoManagerTests: BaseCustomerInfoManagerTests {
expect(self.mockDeviceCache.cacheCustomerInfoCount) == 1
}

func testCachePurchaserSendsToDelegateIfChanged() {
self.customerInfoManager.cache(customerInfo: mockCustomerInfo, appUserID: "myUser")
func testCachesCustomerInfoWithVerifiedEntitlements() {
let appUserID = "myUser"
let info = self.mockCustomerInfo.copy(with: .verified)

self.customerInfoManager.cache(customerInfo: info, appUserID: appUserID)

expect(self.customerInfoManager.cachedCustomerInfo(appUserID: appUserID)) == info
expect(self.mockDeviceCache.cacheCustomerInfoCount) == 1
}

func testCachesCustomerInfoWithEntitlementVerificationNotRequested() {
let appUserID = "myUser"
let info = self.mockCustomerInfo.copy(with: .notRequested)

self.customerInfoManager.cache(customerInfo: info, appUserID: appUserID)

expect(self.customerInfoManager.cachedCustomerInfo(appUserID: appUserID)) == info
expect(self.mockDeviceCache.cacheCustomerInfoCount) == 1
}

func testCachesCustomerInfoWithFailedVerification() {
let appUserID = "myUser"
let info = self.mockCustomerInfo.copy(with: .failed)

self.customerInfoManager.cache(customerInfo: info, appUserID: appUserID)

expect(self.customerInfoManager.cachedCustomerInfo(appUserID: appUserID)) == info
expect(self.mockDeviceCache.cacheCustomerInfoCount) == 1
}

func testDoesNotCacheCustomerInfoWithLocalEntitlements() throws {
// Entitlement verification not available prior
try AvailabilityChecks.iOS13APIAvailableOrSkipTest()

let logger = TestLogHandler()

let appUserID = "myUser"
let info = self.mockCustomerInfo.copy(with: .verifiedOnDevice)

self.customerInfoManager.cache(customerInfo: info, appUserID: appUserID)

expect(self.customerInfoManager.cachedCustomerInfo(appUserID: appUserID)).to(beNil())
expect(self.mockDeviceCache.cacheCustomerInfoCount) == 0

logger.verifyMessageWasLogged(Strings.customerInfo.not_caching_offline_customer_info, level: .debug)
}

func testCacheCustomerInfoSendsToDelegateIfChanged() {
self.customerInfoManager.cache(customerInfo: self.mockCustomerInfo, appUserID: "myUser")
expect(self.customerInfoManagerChangesCallCount).toEventually(equal(1))
expect(self.customerInfoManagerLastCustomerInfo) == self.mockCustomerInfo
}

func testCacheCustomerInfoSendsToDelegateWhenComputedOnDevice() {
let info = self.mockCustomerInfo.copy(with: .verifiedOnDevice)

self.customerInfoManager.cache(customerInfo: info, appUserID: "myUser")
expect(self.customerInfoManagerChangesCallCount).toEventually(equal(1))
expect(self.customerInfoManagerLastCustomerInfo) == info
}

func testClearCustomerInfoCacheClearsCorrectly() {
let appUserID = "myUser"
customerInfoManager.clearCustomerInfoCache(forAppUserID: appUserID)
Expand Down

0 comments on commit 64287a5

Please sign in to comment.