blob: 2f8d5857c8d85c128a8dd9a87531042d1c84b1af [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import "Stubs.h"
@import in_app_purchase_storekit;
@interface PaymentQueueTest : XCTestCase
@property(strong, nonatomic) NSDictionary *periodMap;
@property(strong, nonatomic) NSDictionary *discountMap;
@property(strong, nonatomic) NSDictionary *productMap;
@property(strong, nonatomic) NSDictionary *productResponseMap;
@end
@implementation PaymentQueueTest
- (void)setUp {
self.periodMap = @{@"numberOfUnits" : @(0), @"unit" : @(0)};
self.discountMap = @{
@"price" : @1.0,
@"currencyCode" : @"USD",
@"numberOfPeriods" : @1,
@"subscriptionPeriod" : self.periodMap,
@"paymentMode" : @1
};
self.productMap = @{
@"price" : @1.0,
@"currencyCode" : @"USD",
@"productIdentifier" : @"123",
@"localizedTitle" : @"title",
@"localizedDescription" : @"des",
@"subscriptionPeriod" : self.periodMap,
@"introductoryPrice" : self.discountMap,
@"subscriptionGroupIdentifier" : @"com.group"
};
self.productResponseMap =
@{@"products" : @[ self.productMap ], @"invalidProductIdentifiers" : [NSNull null]};
}
- (void)testTransactionPurchased {
XCTestExpectation *expectation =
[self expectationWithDescription:@"expect to get purchased transcation."];
SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init];
queue.testState = SKPaymentTransactionStatePurchased;
__block SKPaymentTransactionStub *tran;
FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
SKPaymentTransaction *transaction = transactions[0];
tran = (SKPaymentTransactionStub *)transaction;
[expectation fulfill];
}
transactionRemoved:nil
restoreTransactionFailed:nil
restoreCompletedTransactionsFinished:nil
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
return YES;
}
updatedDownloads:nil
transactionCache:OCMClassMock(FIATransactionCache.class)];
SKPayment *payment =
[SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
[handler startObservingPaymentQueue];
[handler addPayment:payment];
[self waitForExpectations:@[ expectation ] timeout:5];
XCTAssertEqual(tran.transactionState, SKPaymentTransactionStatePurchased);
XCTAssertEqual(tran.transactionIdentifier, @"fakeID");
}
- (void)testTransactionFailed {
XCTestExpectation *expectation =
[self expectationWithDescription:@"expect to get failed transcation."];
SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init];
queue.testState = SKPaymentTransactionStateFailed;
__block SKPaymentTransactionStub *tran;
FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
SKPaymentTransaction *transaction = transactions[0];
tran = (SKPaymentTransactionStub *)transaction;
[expectation fulfill];
}
transactionRemoved:nil
restoreTransactionFailed:nil
restoreCompletedTransactionsFinished:nil
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
return YES;
}
updatedDownloads:nil
transactionCache:OCMClassMock(FIATransactionCache.class)];
SKPayment *payment =
[SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
[handler startObservingPaymentQueue];
[handler addPayment:payment];
[self waitForExpectations:@[ expectation ] timeout:5];
XCTAssertEqual(tran.transactionState, SKPaymentTransactionStateFailed);
XCTAssertEqual(tran.transactionIdentifier, nil);
}
- (void)testTransactionRestored {
XCTestExpectation *expectation =
[self expectationWithDescription:@"expect to get restored transcation."];
SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init];
queue.testState = SKPaymentTransactionStateRestored;
__block SKPaymentTransactionStub *tran;
FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
SKPaymentTransaction *transaction = transactions[0];
tran = (SKPaymentTransactionStub *)transaction;
[expectation fulfill];
}
transactionRemoved:nil
restoreTransactionFailed:nil
restoreCompletedTransactionsFinished:nil
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
return YES;
}
updatedDownloads:nil
transactionCache:OCMClassMock(FIATransactionCache.class)];
SKPayment *payment =
[SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
[handler startObservingPaymentQueue];
[handler addPayment:payment];
[self waitForExpectations:@[ expectation ] timeout:5];
XCTAssertEqual(tran.transactionState, SKPaymentTransactionStateRestored);
XCTAssertEqual(tran.transactionIdentifier, @"fakeID");
}
- (void)testTransactionPurchasing {
XCTestExpectation *expectation =
[self expectationWithDescription:@"expect to get purchasing transcation."];
SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init];
queue.testState = SKPaymentTransactionStatePurchasing;
__block SKPaymentTransactionStub *tran;
FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
SKPaymentTransaction *transaction = transactions[0];
tran = (SKPaymentTransactionStub *)transaction;
[expectation fulfill];
}
transactionRemoved:nil
restoreTransactionFailed:nil
restoreCompletedTransactionsFinished:nil
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
return YES;
}
updatedDownloads:nil
transactionCache:OCMClassMock(FIATransactionCache.class)];
SKPayment *payment =
[SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
[handler startObservingPaymentQueue];
[handler addPayment:payment];
[self waitForExpectations:@[ expectation ] timeout:5];
XCTAssertEqual(tran.transactionState, SKPaymentTransactionStatePurchasing);
XCTAssertEqual(tran.transactionIdentifier, nil);
}
- (void)testTransactionDeferred {
XCTestExpectation *expectation =
[self expectationWithDescription:@"expect to get deffered transcation."];
SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init];
queue.testState = SKPaymentTransactionStateDeferred;
__block SKPaymentTransactionStub *tran;
FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
SKPaymentTransaction *transaction = transactions[0];
tran = (SKPaymentTransactionStub *)transaction;
[expectation fulfill];
}
transactionRemoved:nil
restoreTransactionFailed:nil
restoreCompletedTransactionsFinished:nil
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
return YES;
}
updatedDownloads:nil
transactionCache:OCMClassMock(FIATransactionCache.class)];
SKPayment *payment =
[SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
[handler startObservingPaymentQueue];
[handler addPayment:payment];
[self waitForExpectations:@[ expectation ] timeout:5];
XCTAssertEqual(tran.transactionState, SKPaymentTransactionStateDeferred);
XCTAssertEqual(tran.transactionIdentifier, nil);
}
- (void)testFinishTransaction {
XCTestExpectation *expectation =
[self expectationWithDescription:@"handler.transactions should be empty."];
SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init];
queue.testState = SKPaymentTransactionStateDeferred;
__block FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
XCTAssertEqual(transactions.count, 1);
SKPaymentTransaction *transaction = transactions[0];
[handler finishTransaction:transaction];
}
transactionRemoved:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
XCTAssertEqual(transactions.count, 1);
[expectation fulfill];
}
restoreTransactionFailed:nil
restoreCompletedTransactionsFinished:nil
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
return YES;
}
updatedDownloads:nil
transactionCache:OCMClassMock(FIATransactionCache.class)];
SKPayment *payment =
[SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
[handler startObservingPaymentQueue];
[handler addPayment:payment];
[self waitForExpectations:@[ expectation ] timeout:5];
}
- (void)testStartObservingPaymentQueueShouldNotProcessTransactionsWhenCacheIsEmpty {
FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class);
FIAPaymentQueueHandler *handler =
[[FIAPaymentQueueHandler alloc] initWithQueue:[[SKPaymentQueueStub alloc] init]
transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
XCTFail("transactionsUpdated callback should not be called when cache is empty.");
}
transactionRemoved:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
XCTFail("transactionRemoved callback should not be called when cache is empty.");
}
restoreTransactionFailed:nil
restoreCompletedTransactionsFinished:nil
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
return YES;
}
updatedDownloads:^(NSArray<SKDownload *> *_Nonnull downloads) {
XCTFail("updatedDownloads callback should not be called when cache is empty.");
}
transactionCache:mockCache];
[handler startObservingPaymentQueue];
OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]);
OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]);
OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]);
}
- (void)
testStartObservingPaymentQueueShouldNotProcessTransactionsWhenCacheContainsEmptyTransactionArrays {
FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class);
FIAPaymentQueueHandler *handler =
[[FIAPaymentQueueHandler alloc] initWithQueue:[[SKPaymentQueueStub alloc] init]
transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
XCTFail("transactionsUpdated callback should not be called when cache is empty.");
}
transactionRemoved:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
XCTFail("transactionRemoved callback should not be called when cache is empty.");
}
restoreTransactionFailed:nil
restoreCompletedTransactionsFinished:nil
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
return YES;
}
updatedDownloads:^(NSArray<SKDownload *> *_Nonnull downloads) {
XCTFail("updatedDownloads callback should not be called when cache is empty.");
}
transactionCache:mockCache];
OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]).andReturn(@[]);
OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]).andReturn(@[]);
OCMStub([mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]).andReturn(@[]);
[handler startObservingPaymentQueue];
OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]);
OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]);
OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]);
}
- (void)testStartObservingPaymentQueueShouldProcessTransactionsForItemsInCache {
XCTestExpectation *updateTransactionsExpectation =
[self expectationWithDescription:
@"transactionsUpdated callback should be called with one transaction."];
XCTestExpectation *removeTransactionsExpectation =
[self expectationWithDescription:
@"transactionsRemoved callback should be called with one transaction."];
XCTestExpectation *updateDownloadsExpectation =
[self expectationWithDescription:
@"downloadsUpdated callback should be called with one transaction."];
SKPaymentTransaction *mockTransaction = OCMClassMock(SKPaymentTransaction.class);
SKDownload *mockDownload = OCMClassMock(SKDownload.class);
FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class);
FIAPaymentQueueHandler *handler =
[[FIAPaymentQueueHandler alloc] initWithQueue:[[SKPaymentQueueStub alloc] init]
transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
XCTAssertEqualObjects(transactions, @[ mockTransaction ]);
[updateTransactionsExpectation fulfill];
}
transactionRemoved:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
XCTAssertEqualObjects(transactions, @[ mockTransaction ]);
[removeTransactionsExpectation fulfill];
}
restoreTransactionFailed:nil
restoreCompletedTransactionsFinished:nil
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
return YES;
}
updatedDownloads:^(NSArray<SKDownload *> *_Nonnull downloads) {
XCTAssertEqualObjects(downloads, @[ mockDownload ]);
[updateDownloadsExpectation fulfill];
}
transactionCache:mockCache];
OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]).andReturn(@[
mockTransaction
]);
OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]).andReturn(@[
mockDownload
]);
OCMStub([mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]).andReturn(@[
mockTransaction
]);
[handler startObservingPaymentQueue];
[self waitForExpectations:@[
updateTransactionsExpectation, removeTransactionsExpectation, updateDownloadsExpectation
]
timeout:5];
OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]);
OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]);
OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]);
OCMVerify(times(1), [mockCache clear]);
}
- (void)testTransactionsShouldBeCachedWhenNotObserving {
SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init];
FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class);
FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
XCTFail("transactionsUpdated callback should not be called when cache is empty.");
}
transactionRemoved:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
XCTFail("transactionRemoved callback should not be called when cache is empty.");
}
restoreTransactionFailed:nil
restoreCompletedTransactionsFinished:nil
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
return YES;
}
updatedDownloads:^(NSArray<SKDownload *> *_Nonnull downloads) {
XCTFail("updatedDownloads callback should not be called when cache is empty.");
}
transactionCache:mockCache];
SKPayment *payment =
[SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]];
[handler addPayment:payment];
OCMVerify(times(1), [mockCache addObjects:[OCMArg any]
forKey:TransactionCacheKeyUpdatedTransactions]);
OCMVerify(never(), [mockCache addObjects:[OCMArg any]
forKey:TransactionCacheKeyUpdatedDownloads]);
OCMVerify(never(), [mockCache addObjects:[OCMArg any]
forKey:TransactionCacheKeyRemovedTransactions]);
}
- (void)testTransactionsShouldNotBeCachedWhenObserving {
XCTestExpectation *updateTransactionsExpectation =
[self expectationWithDescription:
@"transactionsUpdated callback should be called with one transaction."];
XCTestExpectation *removeTransactionsExpectation =
[self expectationWithDescription:
@"transactionsRemoved callback should be called with one transaction."];
XCTestExpectation *updateDownloadsExpectation =
[self expectationWithDescription:
@"downloadsUpdated callback should be called with one transaction."];
SKPaymentTransaction *mockTransaction = OCMClassMock(SKPaymentTransaction.class);
SKDownload *mockDownload = OCMClassMock(SKDownload.class);
SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init];
queue.testState = SKPaymentTransactionStatePurchased;
FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class);
FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue
transactionsUpdated:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
XCTAssertEqualObjects(transactions, @[ mockTransaction ]);
[updateTransactionsExpectation fulfill];
}
transactionRemoved:^(NSArray<SKPaymentTransaction *> *_Nonnull transactions) {
XCTAssertEqualObjects(transactions, @[ mockTransaction ]);
[removeTransactionsExpectation fulfill];
}
restoreTransactionFailed:nil
restoreCompletedTransactionsFinished:nil
shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) {
return YES;
}
updatedDownloads:^(NSArray<SKDownload *> *_Nonnull downloads) {
XCTAssertEqualObjects(downloads, @[ mockDownload ]);
[updateDownloadsExpectation fulfill];
}
transactionCache:mockCache];
[handler startObservingPaymentQueue];
[handler paymentQueue:queue updatedTransactions:@[ mockTransaction ]];
[handler paymentQueue:queue removedTransactions:@[ mockTransaction ]];
[handler paymentQueue:queue updatedDownloads:@[ mockDownload ]];
[self waitForExpectations:@[
updateTransactionsExpectation, removeTransactionsExpectation, updateDownloadsExpectation
]
timeout:5];
OCMVerify(never(), [mockCache addObjects:[OCMArg any]
forKey:TransactionCacheKeyUpdatedTransactions]);
OCMVerify(never(), [mockCache addObjects:[OCMArg any]
forKey:TransactionCacheKeyUpdatedDownloads]);
OCMVerify(never(), [mockCache addObjects:[OCMArg any]
forKey:TransactionCacheKeyRemovedTransactions]);
}
@end