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

StoreKitObserverModeIntegrationTests: added test for posting renewals #2590

Merged
merged 5 commits into from
Jun 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Sources/Logging/Strings/PurchaseStrings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ enum PurchaseStrings {
case begin_refund_customer_info_error(entitlementID: String?)
case missing_cached_customer_info
case sk2_transactions_update_received_transaction(productID: String)
case transaction_poster_handling_transaction(productID: String)
case sk1_purchase_too_slow
case sk2_purchase_too_slow
case payment_queue_wrapper_delegate_call_sk1_enabled
Expand Down Expand Up @@ -285,6 +286,9 @@ extension PurchaseStrings: CustomStringConvertible {
case let .sk2_transactions_update_received_transaction(productID):
return "StoreKit.Transaction.updates: received transaction for product '\(productID)'"

case let .transaction_poster_handling_transaction(productID):
return "TransactionPoster: handling transaction for product '\(productID)'"

case .sk1_purchase_too_slow:
return "StoreKit 1 purchase took longer than expected"

Expand Down
4 changes: 4 additions & 0 deletions Sources/Purchasing/Purchases/TransactionPoster.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ final class TransactionPoster: TransactionPosterType {
func handlePurchasedTransaction(_ transaction: StoreTransactionType,
data: PurchasedTransactionData,
completion: @escaping CustomerAPI.CustomerInfoResponseHandler) {
Logger.debug(Strings.purchase.transaction_poster_handling_transaction(
productID: transaction.productIdentifier
))

self.receiptFetcher.receiptData(
refreshPolicy: self.refreshRequestPolicy(forProductIdentifier: transaction.productIdentifier)
) { receiptData, receiptURL in
Expand Down
15 changes: 12 additions & 3 deletions Tests/BackendIntegrationTests/BaseStoreKitIntegrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -315,11 +315,20 @@ extension BaseStoreKitIntegrationTests {
/// Purchases a product directly with StoreKit.
@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
@discardableResult
func purchaseProductFromStoreKit() async throws -> Product.PurchaseResult {
let products = try await StoreKit.Product.products(for: [Self.monthlyNoIntroProductID])
func purchaseProductFromStoreKit(
productIdentifier: String = BaseStoreKitIntegrationTests.monthlyNoIntroProductID,
finishTransaction: Bool = false
) async throws -> Product.PurchaseResult {
let products = try await StoreKit.Product.products(for: [productIdentifier])
let product = try XCTUnwrap(products.onlyElement)

return try await product.purchase()
let result = try await product.purchase()

if finishTransaction {
await result.verificationResult?.underlyingTransaction.finish()
}

return result
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,25 @@ class StoreKit2ObserverModeIntegrationTests: StoreKit1ObserverModeIntegrationTes
)
}

@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
func testRenewalsPostReceipt() async throws {
self.testSession.timeRate = .realTime

let productID = Self.monthlyNoIntroProductID

try await self.purchaseProductFromStoreKit(productIdentifier: productID, finishTransaction: true)

let logger = TestLogHandler()

try self.testSession.forceRenewalOfSubscription(productIdentifier: productID)

try await logger.verifyMessageIsEventuallyLogged(
Strings.network.operation_state(PostReceiptDataOperation.self, state: "Finished").description,
timeout: .seconds(3),
pollInterval: .milliseconds(100)
)
}

}

class StoreKit1ObserverModeIntegrationTests: BaseStoreKitObserverModeIntegrationTests {
Expand All @@ -72,6 +91,30 @@ class StoreKit1ObserverModeIntegrationTests: BaseStoreKitObserverModeIntegration
try await self.verifyEntitlementWentThrough(info)
}

@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
func testSK2RenewalsPostReceiptOnlyOnceWhenSK1IsEnabled() async throws {
try XCTSkipIf(Self.storeKit2Setting.isEnabledAndAvailable, "Test only for SK1")

// `StoreKit2TransactionListener` is always enabled even in SK1 mode.
// This test ensures that we don't end up posting receipts multiple times when renewals come through.

self.testSession.timeRate = .realTime

let productID = Self.monthlyNoIntroProductID

try await self.purchaseProductFromStoreKit(productIdentifier: productID, finishTransaction: true)

let logger = TestLogHandler()

try? self.testSession.forceRenewalOfSubscription(productIdentifier: productID)

try await logger.verifyMessageIsEventuallyLogged(
"Network operation 'PostReceiptDataOperation' found with the same cache key",
timeout: .seconds(4),
pollInterval: .milliseconds(100)
)
}

}

@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
Expand Down