Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Offline Entitlements: integration tests #2501

Merged
merged 12 commits into from
May 19, 2023

Conversation

NachoSoto
Copy link
Contributor

@NachoSoto NachoSoto commented May 17, 2023

Example tests:

func testOfferingsAreCachedInMemory() async throws {
    let onlineOfferings = try await Purchases.shared.offerings()
    expect(onlineOfferings.all).toNot(beEmpty())

    self.serverDown()

    let offlineOfferings = try await Purchases.shared.offerings()
    expect(offlineOfferings) === onlineOfferings
}

func testOfferingsAreCachedOnDisk() async throws {
    let onlineOfferings = try await Purchases.shared.offerings()
    expect(onlineOfferings.all).toNot(beEmpty())

    self.serverDown()
    await self.resetPurchases()

    let offlineOfferings = try await Purchases.shared.offerings()
    expect(offlineOfferings.response) == onlineOfferings.response

    let offering = try XCTUnwrap(offlineOfferings.current)
    expect(offering.availablePackages.count) == onlineOfferings.current?.availablePackages.count
    expect(offering.monthly?.storeProduct.productIdentifier) == "com.revenuecat.monthly_4.99.1_week_intro"
}

@NachoSoto NachoSoto added the test label May 17, 2023
@NachoSoto NachoSoto requested a review from a team May 17, 2023 19:00
@NachoSoto NachoSoto force-pushed the nacho/sdk-2991-write-flow-to-use-locally-computed branch 2 times, most recently from 8e823a7 to 860d17b Compare May 17, 2023 19:32
@NachoSoto NachoSoto force-pushed the offline-entitlements-integration-tests branch from 8417156 to c83d3d0 Compare May 17, 2023 19:33
@NachoSoto NachoSoto force-pushed the nacho/sdk-2991-write-flow-to-use-locally-computed branch from 860d17b to 6cf0d99 Compare May 17, 2023 20:43
@NachoSoto NachoSoto force-pushed the offline-entitlements-integration-tests branch 5 times, most recently from 31dd833 to 0f54435 Compare May 17, 2023 22:55
@NachoSoto NachoSoto force-pushed the nacho/sdk-2991-write-flow-to-use-locally-computed branch from 6cf0d99 to 6cd570e Compare May 17, 2023 23:04
@NachoSoto NachoSoto force-pushed the offline-entitlements-integration-tests branch from 0f54435 to f052941 Compare May 17, 2023 23:17
@NachoSoto NachoSoto force-pushed the nacho/sdk-2991-write-flow-to-use-locally-computed branch from 0edce74 to 3719956 Compare May 17, 2023 23:27
@NachoSoto NachoSoto force-pushed the offline-entitlements-integration-tests branch from f052941 to 58f528c Compare May 17, 2023 23:28
@NachoSoto NachoSoto marked this pull request as ready for review May 17, 2023 23:35
@@ -100,3 +97,16 @@ import Foundation
}

extension DangerousSettings: Sendable {}

/// Dangerous settings not exposed outside of the SDK.
internal protocol InternalDangerousSettingsType: Sendable {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows having a custom type in integration tests so we can swap the return value, without having to make the actual DangerousSettings.Internal struct mutable.

@@ -13,14 +13,11 @@ import Foundation
*/
@objc(RCDangerousSettings) public final class DangerousSettings: NSObject {

/// Dangerous settings not exposed outside of the SDK.
internal struct InternalSettings {
internal struct Internal: InternalDangerousSettingsType {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed this from DangerousSettings.InternalSettings to DangerousSettings.Internal.

@NachoSoto NachoSoto force-pushed the offline-entitlements-integration-tests branch from 58f528c to 6fcca74 Compare May 17, 2023 23:41
Comment on lines 173 to +183
private var dangerousSettings: DangerousSettings {
return .init(autoSyncPurchases: true,
internalSettings: .init(enableReceiptFetchRetry: true))
internalSettings: self)
}

}

extension BaseBackendIntegrationTests: InternalDangerousSettingsType {

var enableReceiptFetchRetry: Bool { return true }
var forceServerErrors: Bool { return false }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think composition over inheritance is the way to go here.

It feels unnatural for the test class itself, which is fairly large and complex, to conform to this protocol and being what gets "dependency-injected" into the DangerousSettings initializer.

Why not have BaseBackendIntegrationTests instances hold a member variable of a new type, say a MutableInternalDangerousSettings class, that conforms to this protocol?

The serverUp() and serverDown() methods can then just mutate forceServerErrors that variable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think composition over inheritance is the way to go

Normally I agree, but I fail to see how that would improve this particular case. That would semantically be the same.
This set up is much simpler this way. The way these tests are set up is such that the tests themselves become the configuration, which allows us to override flags like this.

What benefit does your suggestion provide?

Copy link
Contributor

@tonidero tonidero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really love these tests! I think I should be able to copy part of it for the Android tests 👍. Though we don't support coroutines there yet so they won't look nearly as nice 😬

@NachoSoto NachoSoto force-pushed the offline-entitlements-integration-tests branch from ee1d6bc to 82bda68 Compare May 18, 2023 17:19
@NachoSoto
Copy link
Contributor Author

NachoSoto commented May 18, 2023

Though we don't support coroutines there yet so they won't look nearly as nice

@tonidero migrating tests to async (#1332) was huge, it allowed us to iterate, maintain, and write new integration tests much faster. Before it was callback hell.
Might be worth investing on that in Android.

@NachoSoto NachoSoto force-pushed the nacho/sdk-2991-write-flow-to-use-locally-computed branch from 14d252b to 777318f Compare May 18, 2023 17:40
@NachoSoto NachoSoto force-pushed the offline-entitlements-integration-tests branch 2 times, most recently from d6d52c7 to a44e3d1 Compare May 18, 2023 17:55
@codecov
Copy link

codecov bot commented May 18, 2023

Codecov Report

Merging #2501 (d76b395) into nacho/sdk-2991-write-flow-to-use-locally-computed (8459cb7) will increase coverage by 0.05%.
The diff coverage is 63.15%.

❗ Current head d76b395 differs from pull request most recent head 09ad41b. Consider uploading reports for the commit 09ad41b to get more accurate results

@@                                  Coverage Diff                                  @@
##           nacho/sdk-2991-write-flow-to-use-locally-computed    #2501      +/-   ##
=====================================================================================
+ Coverage                                              87.83%   87.89%   +0.05%     
=====================================================================================
  Files                                                    198      198              
  Lines                                                  13583    13617      +34     
=====================================================================================
+ Hits                                                   11931    11968      +37     
+ Misses                                                  1652     1649       -3     
Impacted Files Coverage Δ
Sources/Misc/SystemInfo.swift 98.27% <ø> (-0.02%) ⬇️
...ources/Purchasing/StoreKit1/StoreKit1Wrapper.swift 84.21% <51.72%> (-7.66%) ⬇️
Sources/Logging/Strings/NetworkStrings.swift 100.00% <100.00%> (ø)
Sources/Misc/DangerousSettings.swift 100.00% <100.00%> (ø)
Sources/Networking/HTTPClient/HTTPClient.swift 98.33% <100.00%> (+<0.01%) ⬆️

... and 6 files with indirect coverage changes

@NachoSoto NachoSoto force-pushed the offline-entitlements-integration-tests branch from 3bd13cb to f72a183 Compare May 18, 2023 23:50
@NachoSoto NachoSoto force-pushed the nacho/sdk-2991-write-flow-to-use-locally-computed branch from 6115e26 to 8459cb7 Compare May 18, 2023 23:52
@NachoSoto NachoSoto force-pushed the offline-entitlements-integration-tests branch from f72a183 to d76b395 Compare May 18, 2023 23:52
Copy link
Contributor

@tonidero tonidero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! This brought a great point in favor of supporting coroutines in Android 🙏

Sources/Purchasing/StoreKit1/StoreKit1Wrapper.swift Outdated Show resolved Hide resolved
}

@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
func testPurchaseAgainAfterServerRecovers() async throws {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe apple/Google shouldn't allow you to purchase the same subscription a second time right? So not sure if this is a valid scenario.

Copy link
Contributor Author

@NachoSoto NachoSoto May 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can call the method multiple times, it will just reuse the transaction if it's not finished. It's not very important, but I was thinking "what if for some reason the purchase fails the first time while the server is down, will the SDK get in a weird state"?
So that's why I wrote this.

}

@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
func testReopeningAppWithOfflineEntitlementsDoesNotReturnStaleCache() async throws {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a test case for foregrounding the app sends unsynced purchases?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's done in testPurchaseWhileServerIsDownPostsReceiptAfterServerComesBack.

Base automatically changed from nacho/sdk-2991-write-flow-to-use-locally-computed to main May 19, 2023 17:27
NachoSoto added a commit that referenced this pull request May 19, 2023
…er is down (#2368)

### Changes:
- Created `PurchasedProductsFetcherType` to be able to mock and hold
fetchers, even on older devices
- Created `ProductEntitlementMappingFetcher` to abstract injecting
`ProductEntitlementMapping`
- Added logic to `CustomerInfoResponseHandler` to compute offline
`CustomerInfo` after a server error (see #2367). **This is used by all
`CustomerInfo` requests as well as `PostReceiptDataOperation`**.
- Wrote comprehensive `CustomerInfoResponseHandler` tests

### TODO:
- [x] Don't cache offline computed `CustomerInfo`: #2378
- [x] Integration tests: #2501.
@NachoSoto NachoSoto force-pushed the offline-entitlements-integration-tests branch from 09ad41b to fd03123 Compare May 19, 2023 17:29
@NachoSoto NachoSoto enabled auto-merge (squash) May 19, 2023 17:31
@NachoSoto NachoSoto merged commit e9e6d88 into main May 19, 2023
@NachoSoto NachoSoto deleted the offline-entitlements-integration-tests branch May 19, 2023 17:54
This was referenced May 31, 2023
@NachoSoto NachoSoto mentioned this pull request May 31, 2023
NachoSoto added a commit that referenced this pull request Jun 1, 2023
**This is an automatic release.**

### New Features
* `Offline Entitlements`: use offline-computed `CustomerInfo` when
server is down (#2368) via NachoSoto (@NachoSoto)

### Bugfixes
* `AppleReceipt.debugDescription`: don't pretty-print JSON (#2564) via
NachoSoto (@NachoSoto)
* `SK2StoreProduct`: fix crash on iOS 12 (#2565) via NachoSoto
(@NachoSoto)
* `GetCustomerInfo` posts receipts if there are pending transactions
(#2533) via NachoSoto (@NachoSoto)
### Performance Improvements
* `PurchasedProductsFetcher`: cache current entitlements (#2507) via
NachoSoto (@NachoSoto)
* Performance: new check to ensure serialization / deserialization is
done from background thread (#2496) via NachoSoto (@NachoSoto)
### Dependency Updates
* Bump fastlane from 2.212.2 to 2.213.0 (#2544) via dependabot[bot]
(@dependabot[bot])
### Other Changes
* `CustomerInfoManager`: post all unfinished transactions (#2563) via
NachoSoto (@NachoSoto)
* `PostReceiptOperation`: added ability to also post `AdServices` token
(#2566) via NachoSoto (@NachoSoto)
* `Offline Entitlements`: improved computation log (#2562) via NachoSoto
(@NachoSoto)
* Added `TransactionPoster` tests (#2557) via NachoSoto (@NachoSoto)
* Refactored `TransactionPoster`: removed 2 dependencies and abstracted
parameters (#2542) via NachoSoto (@NachoSoto)
* `CustomerInfoManagerTests`: wait for `getAndCacheCustomerInfo` to
finish (#2555) via NachoSoto (@NachoSoto)
* `StoreTransaction`: implemented `description` (#2556) via NachoSoto
(@NachoSoto)
* `Backend.ResponseHandler` is now `@Sendable` (#2541) via NachoSoto
(@NachoSoto)
* Extracted `TransactionPoster` from `PurchasesOrchestrator` (#2540) via
NachoSoto (@NachoSoto)
* `enableAdServicesAttributionTokenCollection`: added integration test
(#2551) via NachoSoto (@NachoSoto)
* `AttributionPoster`: replaced hardcoded strings with constants (#2548)
via NachoSoto (@NachoSoto)
* `DefaultDecodable`: moved to `Misc/Codable/DefaultDecodable.swift`
(#2528) via NachoSoto (@NachoSoto)
* `CircleCI`: specify device to run `backend_integration_tests` (#2547)
via NachoSoto (@NachoSoto)
* Created `StoreKit2TransactionFetcher` (#2539) via NachoSoto
(@NachoSoto)
* Fix load shedder integration tests (#2546) via Josh Holtz
(@joshdholtz)
* Fix doc on `Offering.getMetadataValue` (#2545) via Josh Holtz
(@joshdholtz)
* Extracted and tested `AsyncSequence.extractValues` (#2538) via
NachoSoto (@NachoSoto)
* `Offline Entitlements`: don't compute offline `CustomerInfo` when
purchasing a consumable products (#2522) via NachoSoto (@NachoSoto)
* `OfflineEntitlementsManager`: disable offline `CustomerInfo` in
observer mode (#2520) via NachoSoto (@NachoSoto)
* `BasePurchasesTests`: fixed leak detection (#2534) via NachoSoto
(@NachoSoto)
* `PurchaseTesterSwiftUI`: added `ProxyView` to `iOS` (#2531) via
NachoSoto (@NachoSoto)
* `PurchasedProductsFetcher`: removed `AppStore.sync` call (#2521) via
NachoSoto (@NachoSoto)
* `PurchaseTesterSwiftUI`: added new window on Mac to manage proxy
(#2518) via NachoSoto (@NachoSoto)
* `PurchasedProductsFetcher`: added log if fetching purchased products
is slow (#2515) via NachoSoto (@NachoSoto)
* `Offline Entitlements`: disable for custom entitlements mode (#2509)
via NachoSoto (@NachoSoto)
* `Offline Entitlements`: fixed iOS 12 tests (#2514) via NachoSoto
(@NachoSoto)
* `PurchasedProductsFetcher`: don't throw errors if purchased products
were found (#2506) via NachoSoto (@NachoSoto)
* `Offline Entitlements`: allow creating offline `CustomerInfo` with
empty `ProductEntitlementMapping` (#2504) via NachoSoto (@NachoSoto)
* `Offline Entitlements`: integration tests (#2501) via NachoSoto
(@NachoSoto)
* `CustomerInfoManager`: don't cache offline `CustomerInfo` (#2378) via
NachoSoto (@NachoSoto)
* `DangerousSettings`: debug-only `forceServerErrors` (#2486) via
NachoSoto (@NachoSoto)
* `CocoapodsInstallation`: fixed `Xcode 14.3.0` issue (#2489) via
NachoSoto (@NachoSoto)
* `CarthageInstallation`: removed workaround (#2488) via NachoSoto
(@NachoSoto)

---------

Co-authored-by: NachoSoto <ignaciosoto90@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants