2022-12-21 07:35:38 -08:00
|
|
|
//
|
|
|
|
|
// 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
|
|
|
|
|
//
|
|
|
|
|
// StoreKitObserverModeIntegrationTests.swift
|
|
|
|
|
//
|
|
|
|
|
// Created by Nacho Soto on 12/15/22.
|
|
|
|
|
|
|
|
|
|
import Foundation
|
2024-07-01 12:37:21 -07:00
|
|
|
import Nimble
|
2022-12-21 07:35:38 -08:00
|
|
|
@testable import RevenueCat
|
|
|
|
|
import StoreKit
|
|
|
|
|
import StoreKitTest
|
|
|
|
|
import XCTest
|
|
|
|
|
|
|
|
|
|
// swiftlint:disable type_name
|
|
|
|
|
|
|
|
|
|
class BaseStoreKitObserverModeIntegrationTests: BaseStoreKitIntegrationTests {
|
|
|
|
|
|
|
|
|
|
override class var observerMode: Bool { return true }
|
|
|
|
|
|
2023-08-07 15:42:11 -07:00
|
|
|
var manager: ObserverModeManager!
|
|
|
|
|
|
|
|
|
|
final override func configureTestSession() async throws {
|
|
|
|
|
try await super.configureTestSession()
|
|
|
|
|
|
|
|
|
|
self.manager = .init()
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-21 07:35:38 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class StoreKit2ObserverModeIntegrationTests: StoreKit1ObserverModeIntegrationTests {
|
|
|
|
|
|
2024-07-01 12:37:21 -07:00
|
|
|
override class var storeKitVersion: StoreKitVersion { .storeKit2 }
|
2022-12-21 07:35:38 -08:00
|
|
|
|
|
|
|
|
override func setUp() async throws {
|
|
|
|
|
try await super.setUp()
|
|
|
|
|
|
2024-07-01 12:37:21 -07:00
|
|
|
try AvailabilityChecks.iOS16APIAvailableOrSkipTest()
|
2022-12-21 07:35:38 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
|
2024-07-01 12:37:21 -07:00
|
|
|
func testObservingTransactionUnlocksEntitlement() async throws {
|
|
|
|
|
await self.deleteAllTransactions(session: self.testSession)
|
|
|
|
|
|
2023-08-07 15:42:11 -07:00
|
|
|
let result = try await self.manager.purchaseProductFromStoreKit2()
|
2023-06-06 15:15:38 -07:00
|
|
|
let transaction = try XCTUnwrap(result.verificationResult?.underlyingTransaction)
|
|
|
|
|
try self.testSession.disableAutoRenewForTransaction(identifier: UInt(transaction.id))
|
|
|
|
|
|
2024-07-01 12:37:21 -07:00
|
|
|
try await simulateAppDidBecomeActive()
|
2022-12-21 07:35:38 -08:00
|
|
|
|
2024-07-01 12:37:21 -07:00
|
|
|
try await self.verifyReceiptIsEventuallyPosted()
|
|
|
|
|
let customerInfo = try XCTUnwrap(self.purchasesDelegate.customerInfo)
|
|
|
|
|
try await self.verifyEntitlementWentThrough(customerInfo)
|
2022-12-21 07:35:38 -08:00
|
|
|
}
|
|
|
|
|
|
2023-06-14 13:18:23 -07:00
|
|
|
@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
|
|
|
|
|
func testRenewalsPostReceipt() async throws {
|
2024-06-21 08:58:48 +02:00
|
|
|
// forceRenewalOfSubscription doesn't work well, so we use this instead
|
|
|
|
|
setShortestTestSessionTimeRate(self.testSession)
|
2023-06-14 13:18:23 -07:00
|
|
|
|
2024-06-21 08:58:48 +02:00
|
|
|
let productID = Self.group3MonthlyNoTrialProductID
|
2023-06-14 13:18:23 -07:00
|
|
|
|
2023-08-07 15:42:11 -07:00
|
|
|
try await self.manager.purchaseProductFromStoreKit2(productIdentifier: productID)
|
2023-06-14 13:18:23 -07:00
|
|
|
|
2024-06-21 08:58:48 +02:00
|
|
|
// swiftlint:disable:next force_try
|
|
|
|
|
try! await Task.sleep(nanoseconds: 3 * 1_000_000_000)
|
2023-06-14 13:18:23 -07:00
|
|
|
|
2023-09-13 07:48:40 -07:00
|
|
|
try await self.verifyReceiptIsEventuallyPosted()
|
2023-06-14 13:18:23 -07:00
|
|
|
}
|
|
|
|
|
|
2024-07-01 12:37:21 -07:00
|
|
|
/// Simulates the app becoming active by broadcasting the SystemInfo.applicationDidBecomeActiveNotification.
|
|
|
|
|
/// This is necessary because our backend integration test app initiates the purchase
|
|
|
|
|
/// flow without any user input and therefore the purchase dialogs never appear.
|
|
|
|
|
/// Without the dialogs, the SDK's trigger to detect purchases in observer mode is never activated.
|
|
|
|
|
private func simulateAppDidBecomeActive() async throws {
|
|
|
|
|
NotificationCenter.default.post(name: SystemInfo.applicationDidBecomeActiveNotification!, object: nil)
|
|
|
|
|
}
|
2023-12-13 17:00:37 +01:00
|
|
|
}
|
|
|
|
|
|
2022-12-21 07:35:38 -08:00
|
|
|
class StoreKit1ObserverModeIntegrationTests: BaseStoreKitObserverModeIntegrationTests {
|
|
|
|
|
|
2024-07-01 12:37:21 -07:00
|
|
|
override class var storeKitVersion: StoreKitVersion { .storeKit1 }
|
2023-06-13 11:17:00 -07:00
|
|
|
|
2022-12-21 07:35:38 -08:00
|
|
|
func testPurchaseOutsideTheAppPostsReceipt() async throws {
|
|
|
|
|
try self.testSession.buyProduct(productIdentifier: Self.monthlyNoIntroProductID)
|
|
|
|
|
|
2023-12-13 17:00:37 +01:00
|
|
|
// In JWS mode, transaction takes a bit longer to be processed after `buyProduct`
|
|
|
|
|
// We need to wait so `restorePurchases` actually posts it.
|
|
|
|
|
try await self.waitUntilUnfinishedTransactions { $0 == 1 }
|
|
|
|
|
|
2023-07-24 13:20:00 +02:00
|
|
|
let info = try await self.purchases.restorePurchases()
|
2022-12-21 07:35:38 -08:00
|
|
|
try await self.verifyEntitlementWentThrough(info)
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-20 15:28:21 -07:00
|
|
|
func testPurchaseOutsideTheAppUpdatesCustomerInfoDelegate() async throws {
|
|
|
|
|
try self.testSession.buyProduct(productIdentifier: Self.monthlyNoIntroProductID)
|
|
|
|
|
|
|
|
|
|
try await asyncWait(
|
2023-08-11 15:39:44 -07:00
|
|
|
description: "Delegate should be notified",
|
2023-06-20 15:28:21 -07:00
|
|
|
timeout: .seconds(4),
|
2023-08-11 15:39:44 -07:00
|
|
|
pollInterval: .milliseconds(100)
|
|
|
|
|
) {
|
2024-03-05 05:30:49 -06:00
|
|
|
await self.purchasesDelegate.customerInfo?.entitlements.active.isEmpty == false
|
2023-08-11 15:39:44 -07:00
|
|
|
}
|
2023-06-20 15:28:21 -07:00
|
|
|
|
|
|
|
|
let customerInfo = try XCTUnwrap(self.purchasesDelegate.customerInfo)
|
|
|
|
|
try await self.verifyEntitlementWentThrough(customerInfo)
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-21 07:35:38 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
|
|
|
|
|
class StoreKit2ObserverModeWithExistingPurchasesTests: StoreKit1ObserverModeWithExistingPurchasesTests {
|
|
|
|
|
|
2024-07-01 12:37:21 -07:00
|
|
|
override class var storeKitVersion: StoreKitVersion { .storeKit2 }
|
2022-12-21 07:35:38 -08:00
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Purchases a product before configuring `Purchases` to verify behavior upon initialization in observer mode.
|
|
|
|
|
@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
|
|
|
|
|
class StoreKit1ObserverModeWithExistingPurchasesTests: BaseStoreKitObserverModeIntegrationTests {
|
|
|
|
|
|
2024-07-01 12:37:21 -07:00
|
|
|
override class var storeKitVersion: StoreKitVersion { .storeKit1 }
|
2023-06-13 11:17:00 -07:00
|
|
|
|
2022-12-21 07:35:38 -08:00
|
|
|
// MARK: - Transactions observation
|
|
|
|
|
|
|
|
|
|
private static var transactionsObservation: Task<Void, Never>?
|
|
|
|
|
|
|
|
|
|
override class func setUp() {
|
2023-07-13 05:31:21 -07:00
|
|
|
super.setUp()
|
|
|
|
|
|
2022-12-21 07:35:38 -08:00
|
|
|
Self.transactionsObservation?.cancel()
|
2024-03-05 05:30:49 -06:00
|
|
|
Self.transactionsObservation = Task {
|
|
|
|
|
// Silence warning in tests:
|
|
|
|
|
// "Making a purchase without listening for transaction updates risks missing successful purchases.
|
|
|
|
|
for await _ in Transaction.updates {}
|
|
|
|
|
}
|
2022-12-21 07:35:38 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override class func tearDown() {
|
|
|
|
|
Self.transactionsObservation?.cancel()
|
|
|
|
|
Self.transactionsObservation = nil
|
2023-07-13 05:31:21 -07:00
|
|
|
|
|
|
|
|
super.tearDown()
|
2022-12-21 07:35:38 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override func setUp() async throws {
|
2023-08-07 15:42:11 -07:00
|
|
|
// Not calling `super.setUp` so each test can
|
|
|
|
|
// do something else before initializing SDK.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testDoesNotSyncExistingSK1Purchases() async throws {
|
2022-12-21 07:35:38 -08:00
|
|
|
// 1. Create `SKTestSession`
|
2023-08-07 15:42:11 -07:00
|
|
|
try await self.configureTestSession()
|
2022-12-21 07:35:38 -08:00
|
|
|
|
|
|
|
|
// 2. Purchase product directly from StoreKit
|
2023-08-07 15:42:11 -07:00
|
|
|
try await self.manager.purchaseProductFromStoreKit1()
|
2022-12-21 07:35:38 -08:00
|
|
|
|
|
|
|
|
// 3. Configure SDK
|
|
|
|
|
try await super.setUp()
|
2023-08-07 15:42:11 -07:00
|
|
|
|
|
|
|
|
// 4. Sync customer info
|
|
|
|
|
let info = try await self.purchases.customerInfo(fetchPolicy: .fetchCurrent)
|
|
|
|
|
self.assertNoPurchases(info)
|
2022-12-21 07:35:38 -08:00
|
|
|
}
|
|
|
|
|
|
2023-08-07 15:42:11 -07:00
|
|
|
func testDoesNotSyncExistingSK2Purchases() async throws {
|
|
|
|
|
// 1. Create `SKTestSession`
|
|
|
|
|
try await self.configureTestSession()
|
2022-12-21 07:35:38 -08:00
|
|
|
|
2023-08-07 15:42:11 -07:00
|
|
|
// 2. Purchase product directly from StoreKit
|
|
|
|
|
try await self.manager.purchaseProductFromStoreKit2()
|
|
|
|
|
|
|
|
|
|
// 3. Configure SDK
|
|
|
|
|
try await super.setUp()
|
|
|
|
|
|
|
|
|
|
// 4. Sync customer info
|
2023-07-24 13:20:00 +02:00
|
|
|
let info = try await self.purchases.customerInfo(fetchPolicy: .fetchCurrent)
|
2022-12-21 07:35:38 -08:00
|
|
|
self.assertNoPurchases(info)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
2024-07-01 12:37:21 -07:00
|
|
|
|
|
|
|
|
class StoreKit2NotEnabledObserverModeIntegrationTests: BaseStoreKitObserverModeIntegrationTests {
|
|
|
|
|
|
|
|
|
|
override class var storeKitVersion: StoreKitVersion { .storeKit1 }
|
|
|
|
|
|
|
|
|
|
@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
|
|
|
|
|
func testObservingTransactionThrowsIfStoreKit2NotEnabled() async throws {
|
|
|
|
|
let manager = ObserverModeManager()
|
|
|
|
|
let result = try await manager.purchaseProductFromStoreKit2()
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
_ = try await Purchases.shared.recordPurchase(result)
|
|
|
|
|
fail("Expected error")
|
|
|
|
|
} catch {
|
|
|
|
|
expect(error).to(matchError(ErrorCode.configurationError))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|