From a834469f96bfd4aa3eb81c18f8c14a27e8e9041b Mon Sep 17 00:00:00 2001 From: Louise Hsu Date: Mon, 26 Feb 2024 15:51:44 -0800 Subject: [PATCH 01/10] initial commit --- .../darwin/Classes/InAppPurchasePlugin.m | 7 +- .../ios/Runner.xcodeproj/project.pbxproj | 4 +- .../RunnerTests/InAppPurchasePluginTests.m | 201 ++++++++++++++++++ .../example/shared/RunnerTests/Stubs.h | 4 + .../example/shared/RunnerTests/Stubs.m | 19 +- 5 files changed, 230 insertions(+), 5 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m index 0a8b162bf1a..06f54c5eedd 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m @@ -117,7 +117,7 @@ - (void)startProductRequestProductIdentifiers:(NSArray *)productIden FlutterError *_Nullable))completion { SKProductsRequest *request = [self getProductRequestWithIdentifiers:[NSSet setWithArray:productIdentifiers]]; - FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request]; + FIAPRequestHandler *handler = [self getHandler:request]; [self.requestHandlers addObject:handler]; __weak typeof(self) weakSelf = self; @@ -282,6 +282,7 @@ - (void)refreshReceiptReceiptProperties:(nullable NSDictionary *)receiptProperti message:error.localizedDescription details:error.description]; completion(requestError); + return; } completion(nil); [weakSelf.requestHandlers removeObject:handler]; @@ -396,4 +397,8 @@ - (SKProduct *)getProduct:(NSString *)productID { - (SKReceiptRefreshRequest *)getRefreshReceiptRequest:(NSDictionary *)properties { return [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:properties]; } + +- (FIAPRequestHandler *)getHandler:(SKRequest *)request { + return [[FIAPRequestHandler alloc] initWithRequest:request]; +} @end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj index 06e0b3ac947..26c4fbb65cc 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj @@ -579,7 +579,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = S8QB4VV633; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -606,7 +606,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = S8QB4VV633; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m index 1c0f734bc01..799831b5543 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m @@ -108,6 +108,129 @@ - (void)testGetProductResponse { [self waitForExpectations:@[ expectation ] timeout:5]; } +- (void)testFinishTransactionSucceeds { + NSDictionary *args = @{ + @"transactionIdentifier" : @"567", + @"productIdentifier" : @"unique_identifier", + }; + + NSDictionary *transactionMap = @{ + @"transactionIdentifier" : @"567", + @"transactionState" : @(SKPaymentTransactionStatePurchasing), + @"payment" : [NSNull null], + @"error" : [FIAObjectTranslator getMapFromNSError:[NSError errorWithDomain:@"test_stub" + code:123 + userInfo:@{}]], + @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), + }; + + SKPaymentTransactionStub *paymentTransaction = + [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; + NSArray *array = [NSArray arrayWithObjects:paymentTransaction,nil]; + + FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); + OCMStub([mockHandler getUnfinishedTransactions]).andReturn(array); +// OCMStub([mockHandler finishTransaction:[OCMArg any]]).andReturn(array); + + self.plugin.paymentQueueHandler = mockHandler; + + FlutterError *error; + [self.plugin finishTransactionFinishMap:args error:&error]; + + XCTAssertNil(error); +} + +- (void)testFinishTransactionSucceedsWithNilTransaction { + NSDictionary *args = @{ + @"transactionIdentifier" : [NSNull null], + @"productIdentifier" : @"unique_identifier", + }; + + NSDictionary *paymentMap = @{ + @"productIdentifier" : @"123", + @"requestData" : @"abcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefgh", + @"quantity" : @(2), + @"applicationUsername" : @"app user name", + @"simulatesAskToBuyInSandbox" : @(NO) + }; + + SKMutablePayment *payment = [FIAObjectTranslator getSKMutablePaymentFromMap:paymentMap]; + + NSDictionary *transactionMap = @{ + @"transactionState" : @(SKPaymentTransactionStatePurchasing), + @"payment" :paymentMap, + @"error" : [FIAObjectTranslator getMapFromNSError:[NSError errorWithDomain:@"test_stub" + code:123 + userInfo:@{}]], + @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), + }; + + SKPaymentTransactionStub *paymentTransaction = + [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; + + FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); + OCMStub([mockHandler getUnfinishedTransactions]).andReturn(@[paymentTransaction]); + + self.plugin.paymentQueueHandler = mockHandler; + + FlutterError *error; + [self.plugin finishTransactionFinishMap:args error:&error]; + + XCTAssertNil(error); +} + +- (void)testGetProductResponseWithRequestError { + NSArray *argument = @[ @"123" ]; + + id mockHandler = OCMClassMock([FIAPRequestHandler class]); + OCMStub([mockHandler alloc]).andReturn(mockHandler); + OCMStub([mockHandler initWithRequest:OCMOCK_ANY]).andReturn(mockHandler); + + FlutterError *error = [NSError errorWithDomain:@"errorDomain" + code:0 + userInfo:@{ + NSLocalizedDescriptionKey : @"description" + }]; + + OCMStub([mockHandler + startProductRequestWithCompletionHandler: + ([OCMArg invokeBlockWithArgs:[NSNull null], + error, + nil])]); + + [self.plugin + startProductRequestProductIdentifiers:argument + completion:^(SKProductsResponseMessage *_Nullable response, + FlutterError *_Nullable startProductRequestError) { + XCTAssertNotNil(error); + XCTAssertNotNil(startProductRequestError); + XCTAssertEqualObjects(startProductRequestError.code, @"storekit_getproductrequest_platform_error"); + }]; + [mockHandler stopMocking]; +} + +- (void)testGetProductResponseWithNoResponse { + NSArray *argument = @[ @"123" ]; + + id mockHandler = OCMClassMock([FIAPRequestHandler class]); + OCMStub([mockHandler alloc]).andReturn(mockHandler); + OCMStub([mockHandler initWithRequest:OCMOCK_ANY]).andReturn(mockHandler); + OCMStub([mockHandler + startProductRequestWithCompletionHandler: + ([OCMArg invokeBlockWithArgs:[NSNull null], + [NSNull null], + nil])]); + + [self.plugin + startProductRequestProductIdentifiers:argument + completion:^(SKProductsResponseMessage *_Nullable response, + FlutterError *_Nullable startProductRequestError) { + XCTAssertNotNil(startProductRequestError); + XCTAssertEqualObjects(startProductRequestError.code, @"storekit_platform_no_response"); + }]; + [mockHandler stopMocking]; +} + - (void)testAddPaymentShouldReturnFlutterErrorWhenPaymentFails { NSDictionary *argument = @{ @"productIdentifier" : @"123", @@ -132,6 +255,27 @@ - (void)testAddPaymentShouldReturnFlutterErrorWhenPaymentFails { XCTAssertEqualObjects(argument, error.details); } +- (void)testAddPaymentShouldReturnFlutterErrorWhenInvalidProduct { + NSDictionary *argument = @{ + // stubbed function will return nil for an empty productIdentifier + @"productIdentifier" : @"", + @"quantity" : @(1), + @"simulatesAskToBuyInSandbox" : @YES, + }; + + FlutterError *error; + + [self.plugin addPaymentPaymentMap:argument error:&error]; + + XCTAssertEqualObjects(@"storekit_invalid_payment_object", error.code); + XCTAssertEqualObjects( + @"You have requested a payment for an invalid product. Either the " + @"`productIdentifier` of the payment is not valid or the product has not been " + @"fetched before adding the payment to the payment queue.", + error.message); + XCTAssertEqualObjects(argument, error.details); +} + - (void)testAddPaymentSuccessWithoutPaymentDiscount { NSDictionary *argument = @{ @"productIdentifier" : @"123", @@ -323,6 +467,53 @@ - (void)testRefreshReceiptRequest { [self waitForExpectations:@[ expectation ] timeout:5]; } +- (void)testRefreshReceiptRequestWithParams { + NSDictionary *properties = @{ + @"isExpired" : @NO, + @"isRevoked" : @NO, + @"isVolumePurchase" : @NO, + }; + + XCTestExpectation *expectation = + [self expectationWithDescription:@"completion handler successfully called"]; + [self.plugin refreshReceiptReceiptProperties:properties + completion:^(FlutterError *_Nullable error) { + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; +} + +- (void)testRefreshReceiptRequestWithError { + NSDictionary *properties = @{ + @"isExpired" : @NO, + @"isRevoked" : @NO, + @"isVolumePurchase" : @NO, + }; + + id mockHandler = OCMClassMock([FIAPRequestHandler class]); + OCMStub([mockHandler alloc]).andReturn(mockHandler); + OCMStub([mockHandler initWithRequest:OCMOCK_ANY]).andReturn(mockHandler); + + FlutterError *recieptError = [NSError errorWithDomain:@"errorDomain" + code:0 + userInfo:@{ + NSLocalizedDescriptionKey : @"description" + }]; + + OCMStub([mockHandler + startProductRequestWithCompletionHandler: + ([OCMArg invokeBlockWithArgs:[NSNull null], + recieptError, + nil])]); + + [self.plugin refreshReceiptReceiptProperties:properties + completion:^(FlutterError *_Nullable error) { + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.code, @"storekit_refreshreceiptrequest_platform_error"); + }]; + [mockHandler stopMocking]; +} + /// presentCodeRedemptionSheetWithError:error is only available on iOS #if TARGET_OS_IOS - (void)testPresentCodeRedemptionSheet { @@ -382,6 +573,16 @@ - (void)testStartObservingPaymentQueue { OCMVerify(times(1), [mockHandler startObservingPaymentQueue]); } +- (void)testStartObservingPaymentQueue2 { + // FIAPRequestHandler *mockHandler = OCMClassMock(FIAPRequestHandler.class); + // + // OCMStub(mockHandler.storefront).andReturn(nil); + // + // [self.plugin startObservingPaymentQueueWithError:&error]; + // + // OCMVerify(times(1), [mockHandler startObservingPaymentQueue]); +} + - (void)testStopObservingPaymentQueue { FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]); self.plugin.paymentQueueHandler = mockHandler; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h index d4e8df3eba7..2ef8e23181a 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h @@ -23,6 +23,7 @@ API_AVAILABLE(ios(11.2), macos(10.13.2)) @end @interface SKProductRequestStub : SKProductsRequest +@property(assign, nonatomic) BOOL returnError; - (instancetype)initWithProductIdentifiers:(NSSet *)productIdentifiers; - (instancetype)initWithFailureError:(NSError *)error; @end @@ -34,6 +35,9 @@ API_AVAILABLE(ios(11.2), macos(10.13.2)) @interface InAppPurchasePluginStub : InAppPurchasePlugin @end +@interface SKRequestStub : SKRequest +@end + @interface SKPaymentQueueStub : SKPaymentQueue @property(assign, nonatomic) SKPaymentTransactionState testState; @property(strong, nonatomic, nullable) id observer; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m index 38081bb18f9..c667382ded1 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m @@ -111,8 +111,14 @@ - (void)start { for (NSString *identifier in self.identifers) { [productArray addObject:@{@"productIdentifier" : identifier}]; } - SKProductsResponseStub *response = - [[SKProductsResponseStub alloc] initWithMap:@{@"products" : productArray}]; + SKProductsResponseStub *response; + if (self.returnError) { + response = nil; + } else { + response = + [[SKProductsResponseStub alloc] initWithMap:@{@"products" : productArray}]; + } + if (self.error) { [self.delegate request:self didFailWithError:self.error]; } else { @@ -141,6 +147,8 @@ - (instancetype)initWithMap:(NSDictionary *)map { @interface InAppPurchasePluginStub () +@property(strong, nonatomic) NSMutableSet *requestHandlers; + @end @implementation InAppPurchasePluginStub @@ -150,6 +158,9 @@ - (SKProductRequestStub *)getProductRequestWithIdentifiers:(NSSet *)identifiers } - (SKProduct *)getProduct:(NSString *)productID { + if ([productID isEqualToString:@""]) { + return nil; + } return [[SKProductStub alloc] initWithProductID:productID]; } @@ -157,6 +168,10 @@ - (SKReceiptRefreshRequestStub *)getRefreshReceiptRequest:(NSDictionary *)proper return [[SKReceiptRefreshRequestStub alloc] initWithReceiptProperties:properties]; } +- (FIAPRequestHandler *)getHandler:(SKRequest *)request { + return [[FIAPRequestHandler alloc] initWithRequest:request]; +} + @end @interface SKPaymentQueueStub () From 8a9faf5a311133cdda24a188493c641259a0d627 Mon Sep 17 00:00:00 2001 From: Louise Hsu Date: Tue, 27 Feb 2024 14:22:26 -0800 Subject: [PATCH 02/10] more tests --- .../shared/RunnerTests/InAppPurchasePluginTests.m | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m index 799831b5543..9fc139b8482 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m @@ -154,8 +154,6 @@ - (void)testFinishTransactionSucceedsWithNilTransaction { @"simulatesAskToBuyInSandbox" : @(NO) }; - SKMutablePayment *payment = [FIAObjectTranslator getSKMutablePaymentFromMap:paymentMap]; - NSDictionary *transactionMap = @{ @"transactionState" : @(SKPaymentTransactionStatePurchasing), @"payment" :paymentMap, @@ -573,16 +571,6 @@ - (void)testStartObservingPaymentQueue { OCMVerify(times(1), [mockHandler startObservingPaymentQueue]); } -- (void)testStartObservingPaymentQueue2 { - // FIAPRequestHandler *mockHandler = OCMClassMock(FIAPRequestHandler.class); - // - // OCMStub(mockHandler.storefront).andReturn(nil); - // - // [self.plugin startObservingPaymentQueueWithError:&error]; - // - // OCMVerify(times(1), [mockHandler startObservingPaymentQueue]); -} - - (void)testStopObservingPaymentQueue { FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]); self.plugin.paymentQueueHandler = mockHandler; From f5e19e2a8f670d6cc01fbdab26a3544a91ab41a0 Mon Sep 17 00:00:00 2001 From: Louise Hsu Date: Tue, 27 Feb 2024 16:37:54 -0800 Subject: [PATCH 03/10] ugh --- .../darwin/Classes/InAppPurchasePlugin.m | 6 +--- .../RunnerTests/InAppPurchasePluginTests.m | 33 ++++++++++++++++++- .../example/shared/RunnerTests/Stubs.m | 7 ---- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m index 06f54c5eedd..428f4d061e1 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m @@ -117,7 +117,7 @@ - (void)startProductRequestProductIdentifiers:(NSArray *)productIden FlutterError *_Nullable))completion { SKProductsRequest *request = [self getProductRequestWithIdentifiers:[NSSet setWithArray:productIdentifiers]]; - FIAPRequestHandler *handler = [self getHandler:request]; + FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request]; [self.requestHandlers addObject:handler]; __weak typeof(self) weakSelf = self; @@ -397,8 +397,4 @@ - (SKProduct *)getProduct:(NSString *)productID { - (SKReceiptRefreshRequest *)getRefreshReceiptRequest:(NSDictionary *)properties { return [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:properties]; } - -- (FIAPRequestHandler *)getHandler:(SKRequest *)request { - return [[FIAPRequestHandler alloc] initWithRequest:request]; -} @end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m index 9fc139b8482..0d01b2e35e0 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m @@ -9,6 +9,13 @@ @import in_app_purchase_storekit; +@interface InAppPurchasePlugin () + +@property(strong, nonatomic) FlutterMethodChannel *transactionObserverCallbackChannel; +- (void)handleTransactionsUpdated:(NSArray *)transactions; + +@end + @interface InAppPurchasePluginTest : XCTestCase @property(strong, nonatomic) FIAPReceiptManagerStub *receiptManagerStub; @@ -130,7 +137,6 @@ - (void)testFinishTransactionSucceeds { FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); OCMStub([mockHandler getUnfinishedTransactions]).andReturn(array); -// OCMStub([mockHandler finishTransaction:[OCMArg any]]).andReturn(array); self.plugin.paymentQueueHandler = mockHandler; @@ -630,6 +636,31 @@ - (void)testRemovePaymentQueueDelegate { } } +- (void)testObserverMethods { + NSDictionary *transactionMap = @{ + @"transactionIdentifier" : @"567", + @"transactionState" : @(SKPaymentTransactionStatePurchasing), + @"payment" : [NSNull null], + @"error" : [FIAObjectTranslator getMapFromNSError:[NSError errorWithDomain:@"test_stub" + code:123 + userInfo:@{}]], + @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), + }; + + InAppPurchasePlugin *iapPlugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; + FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); + iapPlugin.transactionObserverCallbackChannel = mockChannel; + OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); + + SKPaymentTransactionStub *paymentTransaction = + [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; + NSArray *array = [NSArray arrayWithObjects:paymentTransaction,nil]; + + [_plugin handleTransactionsUpdated:array]; + OCMVerify(times(1), [mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); + +} + #if TARGET_OS_IOS - (void)testShowPriceConsentIfNeeded { FIAPaymentQueueHandler *mockQueueHandler = OCMClassMock(FIAPaymentQueueHandler.class); diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m index c667382ded1..75660a102c0 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m @@ -146,9 +146,6 @@ - (instancetype)initWithMap:(NSDictionary *)map { @end @interface InAppPurchasePluginStub () - -@property(strong, nonatomic) NSMutableSet *requestHandlers; - @end @implementation InAppPurchasePluginStub @@ -168,10 +165,6 @@ - (SKReceiptRefreshRequestStub *)getRefreshReceiptRequest:(NSDictionary *)proper return [[SKReceiptRefreshRequestStub alloc] initWithReceiptProperties:properties]; } -- (FIAPRequestHandler *)getHandler:(SKRequest *)request { - return [[FIAPRequestHandler alloc] initWithRequest:request]; -} - @end @interface SKPaymentQueueStub () From b3d6706a76b6927193f9965b6926b33cd9d1e634 Mon Sep 17 00:00:00 2001 From: Louise Hsu Date: Thu, 29 Feb 2024 13:26:58 -0800 Subject: [PATCH 04/10] expose private methods in seperate head file, more tests --- .../darwin/Classes/FIAPRequestHandler.m | 1 + .../Classes/InAppPurchasePlugin+TestOnly.h | 26 ++++ .../darwin/Classes/InAppPurchasePlugin.m | 14 +- .../ios/Runner.xcodeproj/project.pbxproj | 2 + .../InAppPurchasePlugin+TestOnly.h | 25 ++++ .../RunnerTests/InAppPurchasePluginTests.m | 125 +++++++++++++++--- 6 files changed, 163 insertions(+), 30 deletions(-) create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePlugin+TestOnly.h diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m index 8767265d854..bf8fa4e4f41 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m @@ -5,6 +5,7 @@ #import "FIAPRequestHandler.h" #import + #pragma mark - Main Handler @interface FIAPRequestHandler () diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h new file mode 100644 index 00000000000..ac31f0346c7 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h @@ -0,0 +1,26 @@ +// 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 "InAppPurchasePlugin.h" +#import "FIAPRequestHandler.h" +#import + +@interface InAppPurchasePlugin () + +// Holding strong references to FIAPRequestHandlers. Remove the handlers from the set after +// the request is finished. +@property(strong, nonatomic, readonly) NSMutableSet *requestHandlers; +@property(strong, nonatomic) FlutterMethodChannel *transactionObserverCallbackChannel; + +// Transaction observer methods +- (void)handleTransactionsUpdated:(NSArray *)transactions; +- (void)handleTransactionsRemoved:(NSArray *)transactions; +- (void)handleTransactionRestoreFailed:(NSError *)error; +- (void)restoreCompletedTransactionsFinished; +- (BOOL)shouldAddStorePayment:(SKPayment *)payment product:(SKProduct *)product; + +// Dependency Injection +- (FIAPRequestHandler *)getHandler:(SKRequest *)request; + +@end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m index 428f4d061e1..8e7b12eb808 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m @@ -3,6 +3,7 @@ // found in the LICENSE file. #import "InAppPurchasePlugin.h" +#import "InAppPurchasePlugin+TestOnly.h" #import #import "FIAObjectTranslator.h" #import "FIAPPaymentQueueDelegate.h" @@ -12,17 +13,10 @@ @interface InAppPurchasePlugin () -// Holding strong references to FIAPRequestHandlers. Remove the handlers from the set after -// the request is finished. -@property(strong, nonatomic, readonly) NSMutableSet *requestHandlers; - // After querying the product, the available products will be saved in the map to be used // for purchase. @property(strong, nonatomic, readonly) NSMutableDictionary *productsCache; -// Callback channel to dart used for when a function from the transaction observer is triggered. -@property(strong, nonatomic, readonly) FlutterMethodChannel *transactionObserverCallbackChannel; - // Callback channel to dart used for when a function from the payment queue delegate is triggered. @property(strong, nonatomic, readonly) FlutterMethodChannel *paymentQueueDelegateCallbackChannel; @property(strong, nonatomic, readonly) NSObject *registrar; @@ -117,7 +111,7 @@ - (void)startProductRequestProductIdentifiers:(NSArray *)productIden FlutterError *_Nullable))completion { SKProductsRequest *request = [self getProductRequestWithIdentifiers:[NSSet setWithArray:productIdentifiers]]; - FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request]; + FIAPRequestHandler *handler = [self getHandler:request]; [self.requestHandlers addObject:handler]; __weak typeof(self) weakSelf = self; @@ -394,6 +388,10 @@ - (SKProduct *)getProduct:(NSString *)productID { return [self.productsCache objectForKey:productID]; } +- (FIAPRequestHandler *)getHandler:(SKRequest *)request{ + return [[FIAPRequestHandler alloc] initWithRequest:request]; +} + - (SKReceiptRefreshRequest *)getRefreshReceiptRequest:(NSDictionary *)properties { return [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:properties]; } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj index 26c4fbb65cc..10fb1a4df7d 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj @@ -78,6 +78,7 @@ A59001A621E69658004A3E5E /* InAppPurchasePluginTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InAppPurchasePluginTests.m; sourceTree = ""; }; A59001A821E69658004A3E5E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E4F9651425A612301059769C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + F2BAFAE42B8FDA9D00FC27DF /* InAppPurchasePlugin+TestOnly.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "InAppPurchasePlugin+TestOnly.h"; path = "../../shared/RunnerTests/InAppPurchasePlugin+TestOnly.h"; sourceTree = ""; }; F67646F72681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIAPPaymentQueueDeleteTests.m; sourceTree = ""; }; F6995BDC27CF73000050EA78 /* FIATransactionCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIATransactionCacheTests.m; sourceTree = ""; }; F6E5D5F926131C4800C68BED /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Configuration.storekit; sourceTree = ""; }; @@ -184,6 +185,7 @@ A59001A521E69658004A3E5E /* RunnerTests */ = { isa = PBXGroup; children = ( + F2BAFAE42B8FDA9D00FC27DF /* InAppPurchasePlugin+TestOnly.h */, A59001A821E69658004A3E5E /* Info.plist */, 6896B34A21EEB4B800D37AEF /* Stubs.h */, 6896B34B21EEB4B800D37AEF /* Stubs.m */, diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePlugin+TestOnly.h b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePlugin+TestOnly.h new file mode 100644 index 00000000000..e4259fa94a0 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePlugin+TestOnly.h @@ -0,0 +1,25 @@ +// 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 "InAppPurchasePlugin.h" +#import "FIAPRequestHandler.h" +#import + +@interface InAppPurchasePlugin () + +// Holding strong references to FIAPRequestHandlers. Remove the handlers from the set after +// the request is finished. +@property(strong, nonatomic, readonly) NSMutableSet *requestHandlers; +@property(strong, nonatomic) FlutterMethodChannel *transactionObserverCallbackChannel; + +// Transaction observer methods +- (void)handleTransactionsUpdated:(NSArray *)transactions; +- (void)handleTransactionsRemoved:(NSArray *)transactions; +- (void)handleTransactionRestoreFailed:(NSError *)error; +- (void)restoreCompletedTransactionsFinished; +- (BOOL)shouldAddStorePayment:(SKPayment *)payment product:(SKProduct *)product; + +// Dependency Injection +- (FIAPRequestHandler *)getHandler:(SKRequest *)request; +@end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m index 0d01b2e35e0..7ed6d1766c0 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m @@ -5,17 +5,12 @@ #import #import #import "FIAPaymentQueueHandler.h" +#import "InAppPurchasePlugin+TestOnly.h" + #import "Stubs.h" @import in_app_purchase_storekit; -@interface InAppPurchasePlugin () - -@property(strong, nonatomic) FlutterMethodChannel *transactionObserverCallbackChannel; -- (void)handleTransactionsUpdated:(NSArray *)transactions; - -@end - @interface InAppPurchasePluginTest : XCTestCase @property(strong, nonatomic) FIAPReceiptManagerStub *receiptManagerStub; @@ -186,9 +181,11 @@ - (void)testFinishTransactionSucceedsWithNilTransaction { - (void)testGetProductResponseWithRequestError { NSArray *argument = @[ @"123" ]; + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; + id mockPlugin = OCMPartialMock(plugin); id mockHandler = OCMClassMock([FIAPRequestHandler class]); - OCMStub([mockHandler alloc]).andReturn(mockHandler); - OCMStub([mockHandler initWithRequest:OCMOCK_ANY]).andReturn(mockHandler); + + OCMStub([mockPlugin getHandler:[OCMArg any]]).andReturn(mockHandler); FlutterError *error = [NSError errorWithDomain:@"errorDomain" code:0 @@ -202,7 +199,7 @@ - (void)testGetProductResponseWithRequestError { error, nil])]); - [self.plugin + [mockPlugin startProductRequestProductIdentifiers:argument completion:^(SKProductsResponseMessage *_Nullable response, FlutterError *_Nullable startProductRequestError) { @@ -216,16 +213,17 @@ - (void)testGetProductResponseWithRequestError { - (void)testGetProductResponseWithNoResponse { NSArray *argument = @[ @"123" ]; + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; + id mockPlugin = OCMPartialMock(plugin); id mockHandler = OCMClassMock([FIAPRequestHandler class]); - OCMStub([mockHandler alloc]).andReturn(mockHandler); - OCMStub([mockHandler initWithRequest:OCMOCK_ANY]).andReturn(mockHandler); + OCMStub([mockHandler startProductRequestWithCompletionHandler: ([OCMArg invokeBlockWithArgs:[NSNull null], [NSNull null], nil])]); - [self.plugin + [mockPlugin startProductRequestProductIdentifiers:argument completion:^(SKProductsResponseMessage *_Nullable response, FlutterError *_Nullable startProductRequestError) { @@ -494,9 +492,9 @@ - (void)testRefreshReceiptRequestWithError { @"isVolumePurchase" : @NO, }; + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; + id mockPlugin = OCMPartialMock(plugin); id mockHandler = OCMClassMock([FIAPRequestHandler class]); - OCMStub([mockHandler alloc]).andReturn(mockHandler); - OCMStub([mockHandler initWithRequest:OCMOCK_ANY]).andReturn(mockHandler); FlutterError *recieptError = [NSError errorWithDomain:@"errorDomain" code:0 @@ -510,7 +508,7 @@ - (void)testRefreshReceiptRequestWithError { recieptError, nil])]); - [self.plugin refreshReceiptReceiptProperties:properties + [mockPlugin refreshReceiptReceiptProperties:properties completion:^(FlutterError *_Nullable error) { XCTAssertNotNil(error); XCTAssertEqualObjects(error.code, @"storekit_refreshreceiptrequest_platform_error"); @@ -636,7 +634,7 @@ - (void)testRemovePaymentQueueDelegate { } } -- (void)testObserverMethods { +- (void)testHandleTransactionsUpdated { NSDictionary *transactionMap = @{ @"transactionIdentifier" : @"567", @"transactionState" : @(SKPaymentTransactionStatePurchasing), @@ -647,18 +645,101 @@ - (void)testObserverMethods { @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), }; - InAppPurchasePlugin *iapPlugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); - iapPlugin.transactionObserverCallbackChannel = mockChannel; + plugin.transactionObserverCallbackChannel = mockChannel; OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); SKPaymentTransactionStub *paymentTransaction = - [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; + [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; + NSArray *array = [NSArray arrayWithObjects:paymentTransaction,nil]; + NSMutableArray *maps = [NSMutableArray new]; + [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransaction]]; + + [plugin handleTransactionsUpdated:array]; + OCMVerify(times(1), [mockChannel invokeMethod:@"updatedTransactions" arguments:[OCMArg any]]); +} + +- (void)testHandleTransactionsRemoved { + NSDictionary *transactionMap = @{ + @"transactionIdentifier" : @"567", + @"transactionState" : @(SKPaymentTransactionStatePurchasing), + @"payment" : [NSNull null], + @"error" : [FIAObjectTranslator getMapFromNSError:[NSError errorWithDomain:@"test_stub" + code:123 + userInfo:@{}]], + @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), + }; + + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; + FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); + plugin.transactionObserverCallbackChannel = mockChannel; + OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); + + SKPaymentTransactionStub *paymentTransaction = + [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; NSArray *array = [NSArray arrayWithObjects:paymentTransaction,nil]; + NSMutableArray *maps = [NSMutableArray new]; + [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransaction]]; - [_plugin handleTransactionsUpdated:array]; - OCMVerify(times(1), [mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); + [plugin handleTransactionsRemoved:array]; + OCMVerify(times(1), [mockChannel invokeMethod:@"removedTransactions" arguments:maps]); +} + +- (void)testHandleTransactionRestoreFailed { + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; + FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); + plugin.transactionObserverCallbackChannel = mockChannel; + OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); + + FlutterError *error; + [plugin handleTransactionRestoreFailed:error]; + OCMVerify(times(1), [mockChannel invokeMethod:@"restoreCompletedTransactionsFailed" arguments:[FIAObjectTranslator getMapFromNSError:error]]); +} + +- (void)testRestoreCompletedTransactionsFinished { + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; + FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); + plugin.transactionObserverCallbackChannel = mockChannel; + OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); + + [plugin restoreCompletedTransactionsFinished]; + OCMVerify(times(1), [mockChannel invokeMethod:@"paymentQueueRestoreCompletedTransactionsFinished" arguments:nil]); +} + +- (void)testShouldAddStorePayment { + NSDictionary *paymentMap = @{ + @"productIdentifier" : @"123", + @"requestData" : @"abcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefgh", + @"quantity" : @(2), + @"applicationUsername" : @"app user name", + @"simulatesAskToBuyInSandbox" : @(NO) + }; + + NSDictionary *productMap = @{ + @"price" : @"1", + @"priceLocale" : [FIAObjectTranslator getMapFromNSLocale:NSLocale.systemLocale], + @"productIdentifier" : @"123", + @"localizedTitle" : @"title", + @"localizedDescription" : @"des", + }; + + SKMutablePayment *payment = [FIAObjectTranslator getSKMutablePaymentFromMap:paymentMap]; + SKProductStub *product = [[SKProductStub alloc] initWithMap:productMap]; + + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; + FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); + plugin.transactionObserverCallbackChannel = mockChannel; + OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); + + NSDictionary *args = @{ + @"payment" : [FIAObjectTranslator getMapFromSKPayment:payment], + @"product" : [FIAObjectTranslator getMapFromSKProduct:product] + }; + BOOL result = [plugin shouldAddStorePayment:payment product:product]; + XCTAssertEqual(result, NO); + OCMVerify(times(1), [mockChannel invokeMethod:@"shouldAddStorePayment" arguments:args]); } #if TARGET_OS_IOS From 6e4c13a17058a746d8c98ec5d81920719eeaab4a Mon Sep 17 00:00:00 2001 From: Louise Hsu Date: Thu, 29 Feb 2024 15:15:19 -0800 Subject: [PATCH 05/10] formating, remove redundant file, fix test flakiness --- .../darwin/Classes/FIAPRequestHandler.m | 1 - .../Classes/InAppPurchasePlugin+TestOnly.h | 6 +- .../darwin/Classes/InAppPurchasePlugin.m | 4 +- .../ios/Runner.xcodeproj/project.pbxproj | 2 - .../InAppPurchasePlugin+TestOnly.h | 25 ------ .../RunnerTests/InAppPurchasePluginTests.m | 90 ++++++++++--------- .../example/shared/RunnerTests/Stubs.m | 3 +- 7 files changed, 53 insertions(+), 78 deletions(-) delete mode 100644 packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePlugin+TestOnly.h diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m index bf8fa4e4f41..8767265d854 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m @@ -5,7 +5,6 @@ #import "FIAPRequestHandler.h" #import - #pragma mark - Main Handler @interface FIAPRequestHandler () diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h index ac31f0346c7..26b67d6d906 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h @@ -2,15 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "InAppPurchasePlugin.h" -#import "FIAPRequestHandler.h" #import +#import "FIAPRequestHandler.h" +#import "InAppPurchasePlugin.h" @interface InAppPurchasePlugin () // Holding strong references to FIAPRequestHandlers. Remove the handlers from the set after // the request is finished. @property(strong, nonatomic, readonly) NSMutableSet *requestHandlers; + +// Callback channel to dart used for when a function from the transaction observer is triggered. @property(strong, nonatomic) FlutterMethodChannel *transactionObserverCallbackChannel; // Transaction observer methods diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m index 8e7b12eb808..24b10fedad1 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m @@ -3,13 +3,13 @@ // found in the LICENSE file. #import "InAppPurchasePlugin.h" -#import "InAppPurchasePlugin+TestOnly.h" #import #import "FIAObjectTranslator.h" #import "FIAPPaymentQueueDelegate.h" #import "FIAPReceiptManager.h" #import "FIAPRequestHandler.h" #import "FIAPaymentQueueHandler.h" +#import "InAppPurchasePlugin+TestOnly.h" @interface InAppPurchasePlugin () @@ -388,7 +388,7 @@ - (SKProduct *)getProduct:(NSString *)productID { return [self.productsCache objectForKey:productID]; } -- (FIAPRequestHandler *)getHandler:(SKRequest *)request{ +- (FIAPRequestHandler *)getHandler:(SKRequest *)request { return [[FIAPRequestHandler alloc] initWithRequest:request]; } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj index 10fb1a4df7d..26c4fbb65cc 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj @@ -78,7 +78,6 @@ A59001A621E69658004A3E5E /* InAppPurchasePluginTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InAppPurchasePluginTests.m; sourceTree = ""; }; A59001A821E69658004A3E5E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E4F9651425A612301059769C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - F2BAFAE42B8FDA9D00FC27DF /* InAppPurchasePlugin+TestOnly.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "InAppPurchasePlugin+TestOnly.h"; path = "../../shared/RunnerTests/InAppPurchasePlugin+TestOnly.h"; sourceTree = ""; }; F67646F72681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIAPPaymentQueueDeleteTests.m; sourceTree = ""; }; F6995BDC27CF73000050EA78 /* FIATransactionCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIATransactionCacheTests.m; sourceTree = ""; }; F6E5D5F926131C4800C68BED /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Configuration.storekit; sourceTree = ""; }; @@ -185,7 +184,6 @@ A59001A521E69658004A3E5E /* RunnerTests */ = { isa = PBXGroup; children = ( - F2BAFAE42B8FDA9D00FC27DF /* InAppPurchasePlugin+TestOnly.h */, A59001A821E69658004A3E5E /* Info.plist */, 6896B34A21EEB4B800D37AEF /* Stubs.h */, 6896B34B21EEB4B800D37AEF /* Stubs.m */, diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePlugin+TestOnly.h b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePlugin+TestOnly.h deleted file mode 100644 index e4259fa94a0..00000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePlugin+TestOnly.h +++ /dev/null @@ -1,25 +0,0 @@ -// 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 "InAppPurchasePlugin.h" -#import "FIAPRequestHandler.h" -#import - -@interface InAppPurchasePlugin () - -// Holding strong references to FIAPRequestHandlers. Remove the handlers from the set after -// the request is finished. -@property(strong, nonatomic, readonly) NSMutableSet *requestHandlers; -@property(strong, nonatomic) FlutterMethodChannel *transactionObserverCallbackChannel; - -// Transaction observer methods -- (void)handleTransactionsUpdated:(NSArray *)transactions; -- (void)handleTransactionsRemoved:(NSArray *)transactions; -- (void)handleTransactionRestoreFailed:(NSError *)error; -- (void)restoreCompletedTransactionsFinished; -- (BOOL)shouldAddStorePayment:(SKPayment *)payment product:(SKProduct *)product; - -// Dependency Injection -- (FIAPRequestHandler *)getHandler:(SKRequest *)request; -@end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m index 7ed6d1766c0..98d7a6e4259 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m @@ -128,7 +128,7 @@ - (void)testFinishTransactionSucceeds { SKPaymentTransactionStub *paymentTransaction = [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; - NSArray *array = [NSArray arrayWithObjects:paymentTransaction,nil]; + NSArray *array = [NSArray arrayWithObjects:paymentTransaction, nil]; FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); OCMStub([mockHandler getUnfinishedTransactions]).andReturn(array); @@ -157,7 +157,7 @@ - (void)testFinishTransactionSucceedsWithNilTransaction { NSDictionary *transactionMap = @{ @"transactionState" : @(SKPaymentTransactionStatePurchasing), - @"payment" :paymentMap, + @"payment" : paymentMap, @"error" : [FIAObjectTranslator getMapFromNSError:[NSError errorWithDomain:@"test_stub" code:123 userInfo:@{}]], @@ -168,7 +168,7 @@ - (void)testFinishTransactionSucceedsWithNilTransaction { [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); - OCMStub([mockHandler getUnfinishedTransactions]).andReturn(@[paymentTransaction]); + OCMStub([mockHandler getUnfinishedTransactions]).andReturn(@[ paymentTransaction ]); self.plugin.paymentQueueHandler = mockHandler; @@ -189,25 +189,22 @@ - (void)testGetProductResponseWithRequestError { FlutterError *error = [NSError errorWithDomain:@"errorDomain" code:0 - userInfo:@{ - NSLocalizedDescriptionKey : @"description" - }]; + userInfo:@{NSLocalizedDescriptionKey : @"description"}]; OCMStub([mockHandler - startProductRequestWithCompletionHandler: - ([OCMArg invokeBlockWithArgs:[NSNull null], - error, - nil])]); + startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], error, + nil])]); [mockPlugin startProductRequestProductIdentifiers:argument completion:^(SKProductsResponseMessage *_Nullable response, FlutterError *_Nullable startProductRequestError) { - XCTAssertNotNil(error); - XCTAssertNotNil(startProductRequestError); - XCTAssertEqualObjects(startProductRequestError.code, @"storekit_getproductrequest_platform_error"); - }]; - [mockHandler stopMocking]; + XCTAssertNotNil(error); + XCTAssertNotNil(startProductRequestError); + XCTAssertEqualObjects( + startProductRequestError.code, + @"storekit_getproductrequest_platform_error"); + }]; } - (void)testGetProductResponseWithNoResponse { @@ -217,20 +214,25 @@ - (void)testGetProductResponseWithNoResponse { id mockPlugin = OCMPartialMock(plugin); id mockHandler = OCMClassMock([FIAPRequestHandler class]); + OCMStub([mockPlugin getHandler:[OCMArg any]]).andReturn(mockHandler); + + FlutterError *error = [NSError errorWithDomain:@"errorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey : @"description"}]; + OCMStub([mockHandler - startProductRequestWithCompletionHandler: - ([OCMArg invokeBlockWithArgs:[NSNull null], - [NSNull null], - nil])]); + startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], + [NSNull null], nil])]); [mockPlugin startProductRequestProductIdentifiers:argument completion:^(SKProductsResponseMessage *_Nullable response, FlutterError *_Nullable startProductRequestError) { - XCTAssertNotNil(startProductRequestError); - XCTAssertEqualObjects(startProductRequestError.code, @"storekit_platform_no_response"); - }]; - [mockHandler stopMocking]; + XCTAssertNotNil(error); + XCTAssertNotNil(startProductRequestError); + XCTAssertEqualObjects(startProductRequestError.code, + @"storekit_platform_no_response"); + }]; } - (void)testAddPaymentShouldReturnFlutterErrorWhenPaymentFails { @@ -496,24 +498,22 @@ - (void)testRefreshReceiptRequestWithError { id mockPlugin = OCMPartialMock(plugin); id mockHandler = OCMClassMock([FIAPRequestHandler class]); - FlutterError *recieptError = [NSError errorWithDomain:@"errorDomain" - code:0 - userInfo:@{ - NSLocalizedDescriptionKey : @"description" - }]; + FlutterError *recieptError = + [NSError errorWithDomain:@"errorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey : @"description"}]; OCMStub([mockHandler - startProductRequestWithCompletionHandler: - ([OCMArg invokeBlockWithArgs:[NSNull null], - recieptError, - nil])]); + startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], + recieptError, nil])]); - [mockPlugin refreshReceiptReceiptProperties:properties - completion:^(FlutterError *_Nullable error) { - XCTAssertNotNil(error); - XCTAssertEqualObjects(error.code, @"storekit_refreshreceiptrequest_platform_error"); - }]; - [mockHandler stopMocking]; + [mockPlugin + refreshReceiptReceiptProperties:properties + completion:^(FlutterError *_Nullable error) { + XCTAssertNotNil(error); + XCTAssertEqualObjects( + error.code, @"storekit_refreshreceiptrequest_platform_error"); + }]; } /// presentCodeRedemptionSheetWithError:error is only available on iOS @@ -651,8 +651,8 @@ - (void)testHandleTransactionsUpdated { OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); SKPaymentTransactionStub *paymentTransaction = - [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; - NSArray *array = [NSArray arrayWithObjects:paymentTransaction,nil]; + [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; + NSArray *array = [NSArray arrayWithObjects:paymentTransaction, nil]; NSMutableArray *maps = [NSMutableArray new]; [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransaction]]; @@ -677,8 +677,8 @@ - (void)testHandleTransactionsRemoved { OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); SKPaymentTransactionStub *paymentTransaction = - [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; - NSArray *array = [NSArray arrayWithObjects:paymentTransaction,nil]; + [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; + NSArray *array = [NSArray arrayWithObjects:paymentTransaction, nil]; NSMutableArray *maps = [NSMutableArray new]; [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransaction]]; @@ -694,7 +694,8 @@ - (void)testHandleTransactionRestoreFailed { FlutterError *error; [plugin handleTransactionRestoreFailed:error]; - OCMVerify(times(1), [mockChannel invokeMethod:@"restoreCompletedTransactionsFailed" arguments:[FIAObjectTranslator getMapFromNSError:error]]); + OCMVerify(times(1), [mockChannel invokeMethod:@"restoreCompletedTransactionsFailed" + arguments:[FIAObjectTranslator getMapFromNSError:error]]); } - (void)testRestoreCompletedTransactionsFinished { @@ -704,7 +705,8 @@ - (void)testRestoreCompletedTransactionsFinished { OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); [plugin restoreCompletedTransactionsFinished]; - OCMVerify(times(1), [mockChannel invokeMethod:@"paymentQueueRestoreCompletedTransactionsFinished" arguments:nil]); + OCMVerify(times(1), [mockChannel invokeMethod:@"paymentQueueRestoreCompletedTransactionsFinished" + arguments:nil]); } - (void)testShouldAddStorePayment { diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m index 75660a102c0..b4dba710f02 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m @@ -115,8 +115,7 @@ - (void)start { if (self.returnError) { response = nil; } else { - response = - [[SKProductsResponseStub alloc] initWithMap:@{@"products" : productArray}]; + response = [[SKProductsResponseStub alloc] initWithMap:@{@"products" : productArray}]; } if (self.error) { From d2a5f6862d35229af42657db848569ca0c19cdc3 Mon Sep 17 00:00:00 2001 From: Louise Hsu Date: Mon, 4 Mar 2024 14:52:50 -0800 Subject: [PATCH 06/10] update tests --- .../Classes/InAppPurchasePlugin+TestOnly.h | 10 ++- .../darwin/Classes/InAppPurchasePlugin.m | 17 +++-- .../RunnerTests/InAppPurchasePluginTests.m | 62 ++++++++++--------- 3 files changed, 52 insertions(+), 37 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h index 26b67d6d906..0f3caa6b190 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h @@ -15,6 +15,13 @@ // Callback channel to dart used for when a function from the transaction observer is triggered. @property(strong, nonatomic) FlutterMethodChannel *transactionObserverCallbackChannel; +// Callback channel to dart used for when a function from the transaction observer is triggered. +@property(strong, nonatomic) FIAPRequestHandler * (^handlerFactory)(SKRequest *); + +// Convenience nitializer with dependancy injection +- (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager + handlerFactory:(FIAPRequestHandler * (^)(SKRequest *))handlerFactory; + // Transaction observer methods - (void)handleTransactionsUpdated:(NSArray *)transactions; - (void)handleTransactionsRemoved:(NSArray *)transactions; @@ -22,7 +29,4 @@ - (void)restoreCompletedTransactionsFinished; - (BOOL)shouldAddStorePayment:(SKPayment *)payment product:(SKProduct *)product; -// Dependency Injection -- (FIAPRequestHandler *)getHandler:(SKRequest *)request; - @end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m index 24b10fedad1..6d1ccb0fdfb 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m @@ -46,6 +46,16 @@ - (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager { _receiptManager = receiptManager; _requestHandlers = [NSMutableSet new]; _productsCache = [NSMutableDictionary new]; + _handlerFactory = ^FIAPRequestHandler *(SKRequest *request) { + return [[FIAPRequestHandler alloc] initWithRequest:request]; + }; + return self; +} + +- (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager + handlerFactory:(FIAPRequestHandler * (^)(SKRequest *))handlerFactory { + self = [self initWithReceiptManager:receiptManager]; + _handlerFactory = handlerFactory; return self; } @@ -111,7 +121,7 @@ - (void)startProductRequestProductIdentifiers:(NSArray *)productIden FlutterError *_Nullable))completion { SKProductsRequest *request = [self getProductRequestWithIdentifiers:[NSSet setWithArray:productIdentifiers]]; - FIAPRequestHandler *handler = [self getHandler:request]; + FIAPRequestHandler *handler = self.handlerFactory(request); [self.requestHandlers addObject:handler]; __weak typeof(self) weakSelf = self; @@ -388,11 +398,8 @@ - (SKProduct *)getProduct:(NSString *)productID { return [self.productsCache objectForKey:productID]; } -- (FIAPRequestHandler *)getHandler:(SKRequest *)request { - return [[FIAPRequestHandler alloc] initWithRequest:request]; -} - - (SKReceiptRefreshRequest *)getRefreshReceiptRequest:(NSDictionary *)properties { return [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:properties]; } + @end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m index 98d7a6e4259..3afab9206d9 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m @@ -181,21 +181,22 @@ - (void)testFinishTransactionSucceedsWithNilTransaction { - (void)testGetProductResponseWithRequestError { NSArray *argument = @[ @"123" ]; - InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; - id mockPlugin = OCMPartialMock(plugin); id mockHandler = OCMClassMock([FIAPRequestHandler class]); + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] + initWithReceiptManager:nil + handlerFactory:^FIAPRequestHandler *(SKRequest *request) { + return mockHandler; + }]; - OCMStub([mockPlugin getHandler:[OCMArg any]]).andReturn(mockHandler); - - FlutterError *error = [NSError errorWithDomain:@"errorDomain" - code:0 - userInfo:@{NSLocalizedDescriptionKey : @"description"}]; + NSError *error = [NSError errorWithDomain:@"errorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey : @"description"}]; OCMStub([mockHandler startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], error, nil])]); - [mockPlugin + [plugin startProductRequestProductIdentifiers:argument completion:^(SKProductsResponseMessage *_Nullable response, FlutterError *_Nullable startProductRequestError) { @@ -210,21 +211,23 @@ - (void)testGetProductResponseWithRequestError { - (void)testGetProductResponseWithNoResponse { NSArray *argument = @[ @"123" ]; - InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; - id mockPlugin = OCMPartialMock(plugin); id mockHandler = OCMClassMock([FIAPRequestHandler class]); - OCMStub([mockPlugin getHandler:[OCMArg any]]).andReturn(mockHandler); + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] + initWithReceiptManager:nil + handlerFactory:^FIAPRequestHandler *(SKRequest *request) { + return mockHandler; + }]; - FlutterError *error = [NSError errorWithDomain:@"errorDomain" - code:0 - userInfo:@{NSLocalizedDescriptionKey : @"description"}]; + NSError *error = [NSError errorWithDomain:@"errorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey : @"description"}]; OCMStub([mockHandler startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], [NSNull null], nil])]); - [mockPlugin + [plugin startProductRequestProductIdentifiers:argument completion:^(SKProductsResponseMessage *_Nullable response, FlutterError *_Nullable startProductRequestError) { @@ -494,26 +497,27 @@ - (void)testRefreshReceiptRequestWithError { @"isVolumePurchase" : @NO, }; - InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; - id mockPlugin = OCMPartialMock(plugin); id mockHandler = OCMClassMock([FIAPRequestHandler class]); + InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] + initWithReceiptManager:nil + handlerFactory:^FIAPRequestHandler *(SKRequest *request) { + return mockHandler; + }]; - FlutterError *recieptError = - [NSError errorWithDomain:@"errorDomain" - code:0 - userInfo:@{NSLocalizedDescriptionKey : @"description"}]; + NSError *recieptError = [NSError errorWithDomain:@"errorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey : @"description"}]; OCMStub([mockHandler startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], recieptError, nil])]); - [mockPlugin - refreshReceiptReceiptProperties:properties - completion:^(FlutterError *_Nullable error) { - XCTAssertNotNil(error); - XCTAssertEqualObjects( - error.code, @"storekit_refreshreceiptrequest_platform_error"); - }]; + [plugin refreshReceiptReceiptProperties:properties + completion:^(FlutterError *_Nullable error) { + XCTAssertNotNil(error); + XCTAssertEqualObjects( + error.code, @"storekit_refreshreceiptrequest_platform_error"); + }]; } /// presentCodeRedemptionSheetWithError:error is only available on iOS @@ -692,7 +696,7 @@ - (void)testHandleTransactionRestoreFailed { plugin.transactionObserverCallbackChannel = mockChannel; OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); - FlutterError *error; + NSError *error; [plugin handleTransactionRestoreFailed:error]; OCMVerify(times(1), [mockChannel invokeMethod:@"restoreCompletedTransactionsFailed" arguments:[FIAObjectTranslator getMapFromNSError:error]]); From f80f221a75abbf3cb336f32066c285a9bc4cc878 Mon Sep 17 00:00:00 2001 From: Louise Hsu Date: Mon, 4 Mar 2024 15:16:04 -0800 Subject: [PATCH 07/10] update version --- .../in_app_purchase/in_app_purchase_storekit/CHANGELOG.md | 4 ++++ .../in_app_purchase/in_app_purchase_storekit/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md index 309510b3af5..1e6151ddaf7 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.13 + +* Added new native tests for more complete test coverage. + ## 0.3.12 * Converts `refreshReceipt()`, `startObservingPaymentQueue()`, `stopObservingPaymentQueue()`, diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml index ee7422b1000..852bd127fe1 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase_storekit description: An implementation for the iOS and macOS platforms of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework. repository: https://github.com/flutter/packages/tree/main/packages/in_app_purchase/in_app_purchase_storekit issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.3.12 +version: 0.3.13 environment: sdk: ^3.2.3 From 35f12a79f032dcbf29508cca16361e5a7cbbb004 Mon Sep 17 00:00:00 2001 From: Louise Hsu Date: Mon, 4 Mar 2024 15:21:21 -0800 Subject: [PATCH 08/10] random white space removal + remove dev team --- .../darwin/Classes/InAppPurchasePlugin.m | 1 - .../example/ios/Runner.xcodeproj/project.pbxproj | 4 ++-- .../example/shared/RunnerTests/InAppPurchasePluginTests.m | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m index 6d1ccb0fdfb..1db88b24c15 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m @@ -401,5 +401,4 @@ - (SKProduct *)getProduct:(NSString *)productID { - (SKReceiptRefreshRequest *)getRefreshReceiptRequest:(NSDictionary *)properties { return [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:properties]; } - @end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj index 26c4fbb65cc..06e0b3ac947 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj @@ -579,7 +579,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = S8QB4VV633; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -606,7 +606,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = S8QB4VV633; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m index 3afab9206d9..1d884caf313 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m @@ -6,7 +6,6 @@ #import #import "FIAPaymentQueueHandler.h" #import "InAppPurchasePlugin+TestOnly.h" - #import "Stubs.h" @import in_app_purchase_storekit; From aa35c6c65e20390ca03eb023292fba6739f91f0a Mon Sep 17 00:00:00 2001 From: Louise Hsu Date: Mon, 4 Mar 2024 15:22:43 -0800 Subject: [PATCH 09/10] =?UTF-8?q?spelled=20"initializer=20wrong"=20?= =?UTF-8?q?=F0=9F=98=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../darwin/Classes/InAppPurchasePlugin+TestOnly.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h index 0f3caa6b190..cbed4c8862f 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h @@ -18,7 +18,7 @@ // Callback channel to dart used for when a function from the transaction observer is triggered. @property(strong, nonatomic) FIAPRequestHandler * (^handlerFactory)(SKRequest *); -// Convenience nitializer with dependancy injection +// Convenience initializer with dependancy injection - (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager handlerFactory:(FIAPRequestHandler * (^)(SKRequest *))handlerFactory; From f0fbdbc87ff499783f3a6649e27f751a0a386adb Mon Sep 17 00:00:00 2001 From: Louise Hsu Date: Mon, 4 Mar 2024 16:33:36 -0800 Subject: [PATCH 10/10] pr comements! --- .../darwin/Classes/InAppPurchasePlugin+TestOnly.h | 2 +- .../darwin/Classes/InAppPurchasePlugin.m | 4 ++-- .../shared/RunnerTests/InAppPurchasePluginTests.m | 14 +++++++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h index cbed4c8862f..ca090b6b992 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h @@ -16,7 +16,7 @@ @property(strong, nonatomic) FlutterMethodChannel *transactionObserverCallbackChannel; // Callback channel to dart used for when a function from the transaction observer is triggered. -@property(strong, nonatomic) FIAPRequestHandler * (^handlerFactory)(SKRequest *); +@property(copy, nonatomic) FIAPRequestHandler * (^handlerFactory)(SKRequest *); // Convenience initializer with dependancy injection - (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m index 1db88b24c15..2e2abaaed35 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m @@ -55,7 +55,7 @@ - (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager { - (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager handlerFactory:(FIAPRequestHandler * (^)(SKRequest *))handlerFactory { self = [self initWithReceiptManager:receiptManager]; - _handlerFactory = handlerFactory; + _handlerFactory = [handlerFactory copy]; return self; } @@ -275,7 +275,7 @@ - (void)refreshReceiptReceiptProperties:(nullable NSDictionary *)receiptProperti request = [self getRefreshReceiptRequest:nil]; } - FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request]; + FIAPRequestHandler *handler = self.handlerFactory(request); [self.requestHandlers addObject:handler]; __weak typeof(self) weakSelf = self; [handler startProductRequestWithCompletionHandler:^(SKProductsResponse *_Nullable response, diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m index 1d884caf313..4f6d78d7f28 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m @@ -127,7 +127,7 @@ - (void)testFinishTransactionSucceeds { SKPaymentTransactionStub *paymentTransaction = [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; - NSArray *array = [NSArray arrayWithObjects:paymentTransaction, nil]; + NSArray *array = @[ paymentTransaction ]; FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); OCMStub([mockHandler getUnfinishedTransactions]).andReturn(array); @@ -179,6 +179,8 @@ - (void)testFinishTransactionSucceedsWithNilTransaction { - (void)testGetProductResponseWithRequestError { NSArray *argument = @[ @"123" ]; + XCTestExpectation *expectation = + [self expectationWithDescription:@"completion handler successfully called"]; id mockHandler = OCMClassMock([FIAPRequestHandler class]); InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] @@ -199,16 +201,20 @@ - (void)testGetProductResponseWithRequestError { startProductRequestProductIdentifiers:argument completion:^(SKProductsResponseMessage *_Nullable response, FlutterError *_Nullable startProductRequestError) { + [expectation fulfill]; XCTAssertNotNil(error); XCTAssertNotNil(startProductRequestError); XCTAssertEqualObjects( startProductRequestError.code, @"storekit_getproductrequest_platform_error"); }]; + [self waitForExpectations:@[ expectation ] timeout:5]; } - (void)testGetProductResponseWithNoResponse { NSArray *argument = @[ @"123" ]; + XCTestExpectation *expectation = + [self expectationWithDescription:@"completion handler successfully called"]; id mockHandler = OCMClassMock([FIAPRequestHandler class]); @@ -230,11 +236,13 @@ - (void)testGetProductResponseWithNoResponse { startProductRequestProductIdentifiers:argument completion:^(SKProductsResponseMessage *_Nullable response, FlutterError *_Nullable startProductRequestError) { + [expectation fulfill]; XCTAssertNotNil(error); XCTAssertNotNil(startProductRequestError); XCTAssertEqualObjects(startProductRequestError.code, @"storekit_platform_no_response"); }]; + [self waitForExpectations:@[ expectation ] timeout:5]; } - (void)testAddPaymentShouldReturnFlutterErrorWhenPaymentFails { @@ -495,6 +503,8 @@ - (void)testRefreshReceiptRequestWithError { @"isRevoked" : @NO, @"isVolumePurchase" : @NO, }; + XCTestExpectation *expectation = + [self expectationWithDescription:@"completion handler successfully called"]; id mockHandler = OCMClassMock([FIAPRequestHandler class]); InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] @@ -516,7 +526,9 @@ - (void)testRefreshReceiptRequestWithError { XCTAssertNotNil(error); XCTAssertEqualObjects( error.code, @"storekit_refreshreceiptrequest_platform_error"); + [expectation fulfill]; }]; + [self waitForExpectations:@[ expectation ] timeout:5]; } /// presentCodeRedemptionSheetWithError:error is only available on iOS