Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class InAppPurchasesRemoteTests: XCTestCase {

// Then
let identifiers = try XCTUnwrap(result?.get())
XCTAssert(identifiers.count == 2)
XCTAssert(identifiers.count == 1)
}

func test_purchase_product_returns_created_order() throws {
Expand Down
3 changes: 1 addition & 2 deletions Networking/NetworkingTests/Responses/iap-products.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
[
"woocommerce_entry_monthly",
"woocommerce_entry_yearly"
"debug.woocommerce.ecommerce.monthly"
]
54 changes: 54 additions & 0 deletions WooCommerce/Resources/WooCommerce.storekit
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"identifier" : "28D2E099",
"nonRenewingSubscriptions" : [

],
"products" : [

],
"settings" : {
"_applicationInternalID" : "1389130815",
"_developerTeamID" : "PZYM8XX95Q",
"_lastSynchronizedDate" : 689685986.85126996
Copy link
Member Author

Choose a reason for hiding this comment

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

I'm having second thoughts about this. I added the StoreKit configuration with sync enabled, but if we're only going to use this for testing, maybe I should convert to local and ensure it only has the debug product? I'm not sure we want the whole product catalog synced to a public repo.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, if it's only for unit testing I would convert it to local and only add the debug product, no need to have the whole product catalog even if I don't see huge security issues with that.

},
"subscriptionGroups" : [
{
"id" : "21032762",
"localizations" : [

],
"name" : "test_subscription_group",
"subscriptions" : [
{
"adHocOffers" : [

],
"codeOffers" : [

],
"displayPrice" : "69.99",
"familyShareable" : false,
"groupNumber" : 1,
"internalID" : "1650562345",
"introductoryOffer" : null,
"localizations" : [
{
"description" : "1 Month of Debug Woo",
"displayName" : "Debug Monthly",
"locale" : "en_US"
}
],
"productID" : "debug.woocommerce.ecommerce.monthly",
"recurringSubscriptionPeriod" : "P1M",
"referenceName" : "Debug Monthly",
"subscriptionGroupID" : "21032762",
"type" : "RecurringSubscription"
}
]
}
],
"version" : {
"major" : 2,
"minor" : 0
}
}
8 changes: 8 additions & 0 deletions WooCommerce/WooCommerce.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1827,8 +1827,10 @@
E16715CD2666543000326230 /* CardPresentModalSuccessWithoutEmailTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E16715CC2666543000326230 /* CardPresentModalSuccessWithoutEmailTests.swift */; };
E17E3BF9266917C10009D977 /* CardPresentModalScanningFailedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E17E3BF8266917C10009D977 /* CardPresentModalScanningFailedTests.swift */; };
E17E3BFB266917E20009D977 /* CardPresentModalBluetoothRequiredTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E17E3BFA266917E20009D977 /* CardPresentModalBluetoothRequiredTests.swift */; };
E181CDCC291BB2E1002DA3C6 /* InAppPurchaseStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E181CDCB291BB2E1002DA3C6 /* InAppPurchaseStoreTests.swift */; };
E1906E9A26C4126300CA6819 /* InPersonPaymentsMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1906E9926C4126300CA6819 /* InPersonPaymentsMenuViewController.swift */; };
E1ABAEF728479E0300F40BB2 /* InPersonPaymentsSelectPluginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1ABAEF628479E0300F40BB2 /* InPersonPaymentsSelectPluginView.swift */; };
E1B0839B291BC5E3001D99C8 /* WooCommerce.storekit in Resources */ = {isa = PBXBuildFile; fileRef = E1B0839A291BC5DD001D99C8 /* WooCommerce.storekit */; };
E1BAAEA026BBECEF00F2C037 /* ButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BAAE9F26BBECEF00F2C037 /* ButtonStyles.swift */; };
E1BE703A265E6F47006CA4D9 /* CardPresentModalScanningFailed.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BE7039265E6F47006CA4D9 /* CardPresentModalScanningFailed.swift */; };
E1C47209267A1ECC00D06DA1 /* CrashLoggingStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C47208267A1ECC00D06DA1 /* CrashLoggingStack.swift */; };
Expand Down Expand Up @@ -3797,8 +3799,10 @@
E16715CC2666543000326230 /* CardPresentModalSuccessWithoutEmailTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalSuccessWithoutEmailTests.swift; sourceTree = "<group>"; };
E17E3BF8266917C10009D977 /* CardPresentModalScanningFailedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalScanningFailedTests.swift; sourceTree = "<group>"; };
E17E3BFA266917E20009D977 /* CardPresentModalBluetoothRequiredTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalBluetoothRequiredTests.swift; sourceTree = "<group>"; };
E181CDCB291BB2E1002DA3C6 /* InAppPurchaseStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseStoreTests.swift; sourceTree = "<group>"; };
E1906E9926C4126300CA6819 /* InPersonPaymentsMenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPersonPaymentsMenuViewController.swift; sourceTree = "<group>"; };
E1ABAEF628479E0300F40BB2 /* InPersonPaymentsSelectPluginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPersonPaymentsSelectPluginView.swift; sourceTree = "<group>"; };
E1B0839A291BC5DD001D99C8 /* WooCommerce.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = WooCommerce.storekit; sourceTree = "<group>"; };
E1BAAE9F26BBECEF00F2C037 /* ButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonStyles.swift; sourceTree = "<group>"; };
E1BE7039265E6F47006CA4D9 /* CardPresentModalScanningFailed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalScanningFailed.swift; sourceTree = "<group>"; };
E1C47208267A1ECC00D06DA1 /* CrashLoggingStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashLoggingStack.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6989,6 +6993,7 @@
B573B19D219DC2690081C78C /* Localizable.strings */,
3F587028281B9C19004F7556 /* InfoPlist.strings */,
B56DB3D82049BFAA00D4AA8E /* Info.plist */,
E1B0839A291BC5DD001D99C8 /* WooCommerce.storekit */,
B56C721921B5F65E00E5E85B /* Woo-Debug.entitlements */,
03180BE72763AA9000B938A8 /* Woo-Debug-macOS.entitlements */,
B56C721A21B5F65E00E5E85B /* Woo-Release.entitlements */,
Expand Down Expand Up @@ -7225,6 +7230,7 @@
isa = PBXGroup;
children = (
B5DBF3C220E1484400B53AED /* StoresManagerTests.swift */,
E181CDCB291BB2E1002DA3C6 /* InAppPurchaseStoreTests.swift */,
);
path = Yosemite;
sourceTree = "<group>";
Expand Down Expand Up @@ -9318,6 +9324,7 @@
9379E1A5225536AD006A6BE4 /* TestAssets.xcassets in Resources */,
9379E1A32255365F006A6BE4 /* TestingMode.storyboard in Resources */,
B5F571A921BEECA50010D1B8 /* Responses in Resources */,
E1B0839B291BC5E3001D99C8 /* WooCommerce.storekit in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -10851,6 +10858,7 @@
0271125D2887D4E900FCD13C /* LoggedOutAppSettingsTests.swift in Sources */,
74F3015A2200EC0800931B9E /* NSDecimalNumberWooTests.swift in Sources */,
D85136CD231E15B800DD0539 /* MockReviews.swift in Sources */,
E181CDCC291BB2E1002DA3C6 /* InAppPurchaseStoreTests.swift in Sources */,
2655905B27863D1300BB8457 /* MockCollectOrderPaymentUseCase.swift in Sources */,
D8053BCE231F98DA00CE60C2 /* ReviewAgeTests.swift in Sources */,
A650BE862578E76600C655E0 /* MockStorageManager+Sample.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ final class MockNetworkMonitor: NetworkMonitoring {
}
}

struct MockNetwork: NetworkMonitorable {
private struct MockNetwork: NetworkMonitorable {
let status: NWPath.Status
private let currentInterface: NWInterface.InterfaceType

Expand Down
210 changes: 210 additions & 0 deletions WooCommerce/WooCommerceTests/Yosemite/InAppPurchaseStoreTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import XCTest
import TestKit
import StoreKitTest

@testable import Yosemite
@testable import Networking

final class InAppPurchaseStoreTests: XCTestCase {

/// Mock Network: Allows us to inject predefined responses!
///
private var network: MockNetwork!

/// Mock Storage: InMemory
///
private var storageManager: MockStorageManager!

private var storeKitSession = try! SKTestSession(configurationFileNamed: "WooCommerce")

/// Testing SiteID
///
private let sampleSiteID: Int64 = 123

/// Testing Product ID
/// Should match the product ID in WooCommerce.storekit
///
private let sampleProductID: String = "debug.woocommerce.ecommerce.monthly"

/// Testing Order ID
/// Should match the order ID in iap-order-create.json
///
private let sampleOrderID: Int64 = 12345

var store: InAppPurchaseStore!


override func setUp() {
network = MockNetwork(useResponseQueue: true)
storageManager = MockStorageManager()
store = InAppPurchaseStore(dispatcher: Dispatcher(), storageManager: storageManager, network: network)
storeKitSession.disableDialogs = true
}

override func tearDown() {
storeKitSession.resetToDefaultState()
storeKitSession.clearTransactions()
}

func test_iap_supported_in_us() throws {
// Given
storeKitSession.storefront = "USA"

// When
let result = waitFor { promise in
let action = InAppPurchaseAction.inAppPurchasesAreSupported { result in
promise(result)
}
self.store.onAction(action)
}

// Then
XCTAssertTrue(result)
}

func test_iap_supported_in_canada() throws {
// Given
storeKitSession.storefront = "CAN"

// When
let result = waitFor { promise in
let action = InAppPurchaseAction.inAppPurchasesAreSupported { result in
promise(result)
}
self.store.onAction(action)
}

// Then
XCTAssertFalse(result)
}

func test_load_products_loads_products_response() throws {
// Given
network.simulateResponse(requestUrlSuffix: "iap/products", filename: "iap-products")

// When
let result = waitFor { promise in
let action = InAppPurchaseAction.loadProducts { result in
promise(result)
}
self.store.onAction(action)
}

// Then
let products = try XCTUnwrap(result.get())
XCTAssertFalse(products.isEmpty)
XCTAssertEqual(products.first?.id, sampleProductID)
}

func test_load_products_fails_if_iap_unsupported() throws {
// Given
storeKitSession.storefront = "CAN"
network.simulateResponse(requestUrlSuffix: "iap/products", filename: "iap-products")

// When
let result = waitFor { promise in
let action = InAppPurchaseAction.loadProducts { result in
promise(result)
}
self.store.onAction(action)
}

// Then
XCTAssert(result.isFailure)
}

func test_purchase_product_completes_purchase() throws {
// Given
network.simulateResponse(requestUrlSuffix: "iap/orders", filename: "iap-order-create")

// When
let result = waitFor { promise in
let action = InAppPurchaseAction.purchaseProduct(siteID: self.sampleSiteID, productID: self.sampleProductID) { result in
promise(result)
}
self.store.onAction(action)
}

// Then
let purchaseResult = try XCTUnwrap(result.get())
guard case let .success(verificationResult) = purchaseResult,
case let .verified(transaction) = verificationResult else {
return XCTFail()
}
XCTAssertEqual(transaction.productID, sampleProductID)
XCTAssertNotNil(transaction.appAccountToken)
}

@available(iOS 16.0, *)
func test_purchase_product_ensure_xcode_environment() throws {
// Given
network.simulateResponse(requestUrlSuffix: "iap/orders", filename: "iap-order-create")

// When
let result = waitFor { promise in
let action = InAppPurchaseAction.purchaseProduct(siteID: self.sampleSiteID, productID: self.sampleProductID) { result in
promise(result)
}
self.store.onAction(action)
}

// Then
let purchaseResult = try XCTUnwrap(result.get())
guard case let .success(verificationResult) = purchaseResult,
case let .verified(transaction) = verificationResult else {
return XCTFail()
}
XCTAssertEqual(transaction.environment, .xcode)
}

func test_purchase_product_handles_api_errors() throws {
// Given
network.simulateResponse(requestUrlSuffix: "iap/orders", filename: "error-wp-rest-forbidden")

// When
let result = waitFor { promise in
let action = InAppPurchaseAction.purchaseProduct(siteID: self.sampleSiteID, productID: self.sampleProductID) { result in
promise(result)
}
self.store.onAction(action)
}

// Then
XCTAssert(result.isFailure)
let error = try XCTUnwrap(result.failure)
XCTAssert(error is WordPressApiError)
}

func test_user_is_entitled_to_product_returns_false_when_not_entitled() throws {
// Given

// When
let result = waitFor { promise in
let action = InAppPurchaseAction.userIsEntitledToProduct(productID: self.sampleProductID) { result in
promise(result)
}
self.store.onAction(action)
}

// Then
let isEntitled = try XCTUnwrap(result.get())
XCTAssertFalse(isEntitled)
}

func test_user_is_entitled_to_product_returns_true_when_entitled() throws {
// Given
try storeKitSession.buyProduct(productIdentifier: sampleProductID)

// When
let result = waitFor { promise in
let action = InAppPurchaseAction.userIsEntitledToProduct(productID: self.sampleProductID) { result in
promise(result)
}
self.store.onAction(action)
}

// Then
let isEntitled = try XCTUnwrap(result.get())
XCTAssertTrue(isEntitled)
}
}