mirror of
https://github.com/RevenueCat/purchases-ios
synced 2026-11-11 22:55:55 +05:45
This is a follow up to #717. In order to be able to reproduce #2338, which seems to be an issue only on `watchOS`, it's important that we now run tests on `watchOS` as well. We probably won't be able to fully replicate that issue since we don't run tests on actual devices, so they'll still run on 64 bits. But this way we might be able to prevent issues that are only reproducible on `watchOS` in the future. ## Changes: - Added CircleCI job - Added lane - Simplified `UnitTestsHostApp` definition using `SwiftUI` - Increased _test only_ `watchOS` deployment target to 7.0 (this was necessary to be able to have a common `@main` for the host app) - Increased _test only_ `tvOS` deployment target to `14.0` (same as above, plus we only run tvOS tests on the latest version anyway. We test backwards compatibility through iOS already) - Fixed test compilation on `watchOS` - Disabled `HTTPClient` tests on `watchOS` (see https://github.com/AliSoftware/OHHTTPStubs/issues/287) - `StoreKitConfigTestCase` is only available from `watchOS 7.0` since that's when `SKTestSession` was introduced - Added `watchOS` support to `UnitTests` target - Added `watchOS` support to `ReceiptParserTests` target - Fixed `isFamilySharable` `watchOS` version checks for version 8.0 - Created new `CI-RevenueCat` Test Plan to only run unit tests and not StoreKit tests on `watchOS`
133 lines
4.0 KiB
Swift
133 lines
4.0 KiB
Swift
//
|
|
// 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
|
|
//
|
|
// StoreKitConfigTestCase+Extensions.swift
|
|
//
|
|
// Created by Nacho Soto on 11/17/22.
|
|
|
|
import Nimble
|
|
@testable import RevenueCat
|
|
@preconcurrency import StoreKit
|
|
import XCTest
|
|
|
|
@available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *)
|
|
extension StoreKitConfigTestCase {
|
|
|
|
@MainActor
|
|
@discardableResult
|
|
func simulateAnyPurchase() async throws -> SK2Product {
|
|
let product = try await fetchSk2Product()
|
|
_ = try await product.purchase()
|
|
|
|
return product
|
|
}
|
|
|
|
@MainActor
|
|
func createTransactionWithPurchase() async throws -> Transaction {
|
|
let product = try await self.simulateAnyPurchase()
|
|
|
|
let transaction = try await XCTAsyncUnwrap(await product.latestTransaction)
|
|
|
|
switch transaction {
|
|
case let .verified(transaction):
|
|
return transaction
|
|
default:
|
|
XCTFail("Invalid transaction: \(transaction)")
|
|
fatalError("Unreachable")
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
func fetchSk2Product(_ productID: String = StoreKitConfigTestCase.productID) async throws -> SK2Product {
|
|
let products: [SK2Product] = try await StoreKit.Product.products(for: [productID])
|
|
return try XCTUnwrap(products.first)
|
|
}
|
|
|
|
@MainActor
|
|
@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
|
|
func fetchSk2StoreProduct(_ productID: String = StoreKitConfigTestCase.productID) async throws -> SK2StoreProduct {
|
|
return SK2StoreProduct(sk2Product: try await self.fetchSk2Product(productID))
|
|
}
|
|
|
|
}
|
|
|
|
@available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *)
|
|
extension StoreKitConfigTestCase {
|
|
|
|
/// Updates `SKTestSession.storefront` and waits for `Storefront.current` to reflect the change
|
|
/// This is necessary because the change is aynchronous within `StoreKit`, and otherwise code that depends
|
|
/// on the change might not see it in time, resulting in race conditions and flaky tests.
|
|
func changeStorefront(
|
|
_ new: String,
|
|
file: FileString = #fileID,
|
|
line: UInt = #line
|
|
) async {
|
|
self.testSession.storefront = new
|
|
|
|
await asyncWait(
|
|
until: { await Storefront.currentStorefront?.countryCode == new },
|
|
timeout: .seconds(1),
|
|
pollInterval: .milliseconds(100),
|
|
description: "Storefront change not detected"
|
|
)
|
|
}
|
|
|
|
}
|
|
|
|
@available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *)
|
|
extension StoreKitConfigTestCase {
|
|
|
|
static let productID = "com.revenuecat.monthly_4.99.1_week_intro"
|
|
static let lifetimeProductID = "lifetime"
|
|
|
|
}
|
|
|
|
@available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *)
|
|
fileprivate extension StoreKitConfigTestCase {
|
|
|
|
enum Error: Swift.Error {
|
|
|
|
case noProductsFound
|
|
case multipleProductsFound
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *)
|
|
extension ProductsFetcherSK1 {
|
|
|
|
func product(withIdentifier identifier: String) async throws -> StoreProduct {
|
|
let products = try await self.products(withIdentifiers: Set([identifier]))
|
|
|
|
switch products.count {
|
|
case 0: throw StoreKitConfigTestCase.Error.noProductsFound
|
|
case 1: return StoreProduct.from(product: products.first!)
|
|
default: throw StoreKitConfigTestCase.Error.multipleProductsFound
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
@MainActor
|
|
@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
|
|
extension ProductsFetcherSK2 {
|
|
|
|
func product(withIdentifier identifier: String) async throws -> StoreProduct {
|
|
let products = try await self.products(identifiers: Set([identifier]))
|
|
|
|
switch products.count {
|
|
case 0: throw StoreKitConfigTestCase.Error.noProductsFound
|
|
case 1: return StoreProduct.from(product: products.first!)
|
|
default: throw StoreKitConfigTestCase.Error.multipleProductsFound
|
|
}
|
|
}
|
|
|
|
}
|