| // 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 <XCTest/XCTest.h> |
| #import "Stubs.h" |
| |
| @import in_app_purchase_storekit; |
| |
| @interface TranslatorTest : XCTestCase |
| |
| @property(strong, nonatomic) NSDictionary *periodMap; |
| @property(strong, nonatomic) NSMutableDictionary *discountMap; |
| @property(strong, nonatomic) NSMutableDictionary *discountMissingIdentifierMap; |
| @property(strong, nonatomic) NSMutableDictionary *productMap; |
| @property(strong, nonatomic) NSDictionary *productResponseMap; |
| @property(strong, nonatomic) NSDictionary *paymentMap; |
| @property(copy, nonatomic) NSDictionary *paymentDiscountMap; |
| @property(strong, nonatomic) NSDictionary *transactionMap; |
| @property(strong, nonatomic) NSDictionary *errorMap; |
| @property(strong, nonatomic) NSDictionary *localeMap; |
| @property(strong, nonatomic) NSDictionary *storefrontMap; |
| @property(strong, nonatomic) NSDictionary *storefrontAndPaymentTransactionMap; |
| |
| @end |
| |
| @implementation TranslatorTest |
| |
| - (void)setUp { |
| self.periodMap = @{@"numberOfUnits" : @(0), @"unit" : @(0)}; |
| |
| self.discountMap = [[NSMutableDictionary alloc] initWithDictionary:@{ |
| @"price" : @"1", |
| @"priceLocale" : [FIAObjectTranslator getMapFromNSLocale:NSLocale.systemLocale], |
| @"numberOfPeriods" : @1, |
| @"subscriptionPeriod" : self.periodMap, |
| @"paymentMode" : @1, |
| }]; |
| if (@available(iOS 12.2, *)) { |
| self.discountMap[@"identifier"] = @"test offer id"; |
| self.discountMap[@"type"] = @(SKProductDiscountTypeIntroductory); |
| } |
| self.discountMissingIdentifierMap = [[NSMutableDictionary alloc] initWithDictionary:@{ |
| @"price" : @"1", |
| @"priceLocale" : [FIAObjectTranslator getMapFromNSLocale:NSLocale.systemLocale], |
| @"numberOfPeriods" : @1, |
| @"subscriptionPeriod" : self.periodMap, |
| @"paymentMode" : @1, |
| @"identifier" : [NSNull null], |
| @"type" : @0, |
| }]; |
| |
| self.productMap = [[NSMutableDictionary alloc] initWithDictionary:@{ |
| @"price" : @"1", |
| @"priceLocale" : [FIAObjectTranslator getMapFromNSLocale:NSLocale.systemLocale], |
| @"productIdentifier" : @"123", |
| @"localizedTitle" : @"title", |
| @"localizedDescription" : @"des", |
| }]; |
| if (@available(iOS 11.2, *)) { |
| self.productMap[@"subscriptionPeriod"] = self.periodMap; |
| self.productMap[@"introductoryPrice"] = self.discountMap; |
| } |
| if (@available(iOS 12.2, *)) { |
| self.productMap[@"discounts"] = @[ self.discountMap ]; |
| } |
| |
| if (@available(iOS 12.0, *)) { |
| self.productMap[@"subscriptionGroupIdentifier"] = @"com.group"; |
| } |
| |
| self.productResponseMap = |
| @{@"products" : @[ self.productMap ], @"invalidProductIdentifiers" : @[]}; |
| self.paymentMap = @{ |
| @"productIdentifier" : @"123", |
| @"requestData" : @"abcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefgh", |
| @"quantity" : @(2), |
| @"applicationUsername" : @"app user name", |
| @"simulatesAskToBuyInSandbox" : @(NO) |
| }; |
| self.paymentDiscountMap = @{ |
| @"identifier" : @"payment_discount_identifier", |
| @"keyIdentifier" : @"payment_discount_key_identifier", |
| @"nonce" : @"d18981e0-9003-4365-98a2-4b90e3b62c52", |
| @"signature" : @"this is a encrypted signature", |
| @"timestamp" : @([NSDate date].timeIntervalSince1970), |
| }; |
| NSDictionary *originalTransactionMap = @{ |
| @"transactionIdentifier" : @"567", |
| @"transactionState" : @(SKPaymentTransactionStatePurchasing), |
| @"payment" : [NSNull null], |
| @"error" : [FIAObjectTranslator getMapFromNSError:[NSError errorWithDomain:@"test_stub" |
| code:123 |
| userInfo:@{}]], |
| @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), |
| @"originalTransaction" : [NSNull null], |
| }; |
| self.transactionMap = @{ |
| @"transactionIdentifier" : @"567", |
| @"transactionState" : @(SKPaymentTransactionStatePurchasing), |
| @"payment" : [NSNull null], |
| @"error" : [FIAObjectTranslator getMapFromNSError:[NSError errorWithDomain:@"test_stub" |
| code:123 |
| userInfo:@{}]], |
| @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), |
| @"originalTransaction" : originalTransactionMap, |
| }; |
| self.errorMap = @{ |
| @"code" : @(123), |
| @"domain" : @"test_domain", |
| @"userInfo" : @{ |
| @"key" : @"value", |
| } |
| }; |
| self.storefrontMap = @{ |
| @"countryCode" : @"USA", |
| @"identifier" : @"unique_identifier", |
| }; |
| |
| self.storefrontAndPaymentTransactionMap = @{ |
| @"storefront" : self.storefrontMap, |
| @"transaction" : self.transactionMap, |
| }; |
| } |
| |
| - (void)testSKProductSubscriptionPeriodStubToMap { |
| if (@available(iOS 11.2, *)) { |
| SKProductSubscriptionPeriodStub *period = |
| [[SKProductSubscriptionPeriodStub alloc] initWithMap:self.periodMap]; |
| NSDictionary *map = [FIAObjectTranslator getMapFromSKProductSubscriptionPeriod:period]; |
| XCTAssertEqualObjects(map, self.periodMap); |
| } |
| } |
| |
| - (void)testSKProductDiscountStubToMap { |
| if (@available(iOS 11.2, *)) { |
| SKProductDiscountStub *discount = [[SKProductDiscountStub alloc] initWithMap:self.discountMap]; |
| NSDictionary *map = [FIAObjectTranslator getMapFromSKProductDiscount:discount]; |
| XCTAssertEqualObjects(map, self.discountMap); |
| } |
| } |
| |
| - (void)testProductToMap { |
| SKProductStub *product = [[SKProductStub alloc] initWithMap:self.productMap]; |
| NSDictionary *map = [FIAObjectTranslator getMapFromSKProduct:product]; |
| XCTAssertEqualObjects(map, self.productMap); |
| } |
| |
| - (void)testProductResponseToMap { |
| SKProductsResponseStub *response = |
| [[SKProductsResponseStub alloc] initWithMap:self.productResponseMap]; |
| NSDictionary *map = [FIAObjectTranslator getMapFromSKProductsResponse:response]; |
| XCTAssertEqualObjects(map, self.productResponseMap); |
| } |
| |
| - (void)testPaymentToMap { |
| SKMutablePayment *payment = [FIAObjectTranslator getSKMutablePaymentFromMap:self.paymentMap]; |
| NSDictionary *map = [FIAObjectTranslator getMapFromSKPayment:payment]; |
| XCTAssertEqualObjects(map, self.paymentMap); |
| } |
| |
| - (void)testPaymentTransactionToMap { |
| // payment is not KVC, cannot test payment field. |
| SKPaymentTransactionStub *paymentTransaction = |
| [[SKPaymentTransactionStub alloc] initWithMap:self.transactionMap]; |
| NSDictionary *map = [FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransaction]; |
| XCTAssertEqualObjects(map, self.transactionMap); |
| } |
| |
| - (void)testError { |
| NSErrorStub *error = [[NSErrorStub alloc] initWithMap:self.errorMap]; |
| NSDictionary *map = [FIAObjectTranslator getMapFromNSError:error]; |
| XCTAssertEqualObjects(map, self.errorMap); |
| } |
| |
| - (void)testErrorWithNSNumberAsUserInfo { |
| NSError *error = [NSError errorWithDomain:SKErrorDomain code:3 userInfo:@{@"key" : @42}]; |
| NSDictionary *expectedMap = |
| @{@"domain" : SKErrorDomain, @"code" : @3, @"userInfo" : @{@"key" : @42}}; |
| NSDictionary *map = [FIAObjectTranslator getMapFromNSError:error]; |
| XCTAssertEqualObjects(expectedMap, map); |
| } |
| |
| - (void)testErrorWithMultipleUnderlyingErrors { |
| NSError *underlyingErrorOne = [NSError errorWithDomain:SKErrorDomain code:2 userInfo:nil]; |
| NSError *underlyingErrorTwo = [NSError errorWithDomain:SKErrorDomain code:1 userInfo:nil]; |
| NSError *mainError = [NSError |
| errorWithDomain:SKErrorDomain |
| code:3 |
| userInfo:@{@"underlyingErrors" : @[ underlyingErrorOne, underlyingErrorTwo ]}]; |
| NSDictionary *expectedMap = @{ |
| @"domain" : SKErrorDomain, |
| @"code" : @3, |
| @"userInfo" : @{ |
| @"underlyingErrors" : @[ |
| @{@"domain" : SKErrorDomain, @"code" : @2, @"userInfo" : @{}}, |
| @{@"domain" : SKErrorDomain, @"code" : @1, @"userInfo" : @{}} |
| ] |
| } |
| }; |
| NSDictionary *map = [FIAObjectTranslator getMapFromNSError:mainError]; |
| XCTAssertEqualObjects(expectedMap, map); |
| } |
| |
| - (void)testErrorWithUnsupportedUserInfo { |
| NSError *error = [NSError errorWithDomain:SKErrorDomain |
| code:3 |
| userInfo:@{@"user_info" : [[NSObject alloc] init]}]; |
| NSDictionary *expectedMap = @{ |
| @"domain" : SKErrorDomain, |
| @"code" : @3, |
| @"userInfo" : @{ |
| @"user_info" : [NSString |
| stringWithFormat: |
| @"Unable to encode native userInfo object of type %@ to map. Please submit an " |
| @"issue at https://github.com/flutter/flutter/issues/new with the title " |
| @"\"[in_app_purchase_storekit] Unable to encode userInfo of type %@\" and add " |
| @"reproduction steps and the error details in the description field.", |
| [NSObject class], [NSObject class]] |
| } |
| }; |
| NSDictionary *map = [FIAObjectTranslator getMapFromNSError:error]; |
| XCTAssertEqualObjects(expectedMap, map); |
| } |
| |
| - (void)testLocaleToMap { |
| NSLocale *system = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; |
| NSDictionary *map = [FIAObjectTranslator getMapFromNSLocale:system]; |
| XCTAssertEqualObjects(map[@"currencySymbol"], system.currencySymbol); |
| XCTAssertEqualObjects(map[@"countryCode"], system.countryCode); |
| } |
| |
| - (void)testSKStorefrontToMap { |
| if (@available(iOS 13.0, *)) { |
| SKStorefront *storefront = [[SKStorefrontStub alloc] initWithMap:self.storefrontMap]; |
| NSDictionary *map = [FIAObjectTranslator getMapFromSKStorefront:storefront]; |
| XCTAssertEqualObjects(map, self.storefrontMap); |
| } |
| } |
| |
| - (void)testSKStorefrontAndSKPaymentTransactionToMap { |
| if (@available(iOS 13.0, *)) { |
| SKStorefront *storefront = [[SKStorefrontStub alloc] initWithMap:self.storefrontMap]; |
| SKPaymentTransaction *transaction = |
| [[SKPaymentTransactionStub alloc] initWithMap:self.transactionMap]; |
| NSDictionary *map = [FIAObjectTranslator getMapFromSKStorefront:storefront |
| andSKPaymentTransaction:transaction]; |
| XCTAssertEqualObjects(map, self.storefrontAndPaymentTransactionMap); |
| } |
| } |
| |
| - (void)testSKPaymentDiscountFromMap { |
| if (@available(iOS 12.2, *)) { |
| NSString *error = nil; |
| SKPaymentDiscount *paymentDiscount = |
| [FIAObjectTranslator getSKPaymentDiscountFromMap:self.paymentDiscountMap withError:&error]; |
| |
| XCTAssertEqual(paymentDiscount.identifier, self.paymentDiscountMap[@"identifier"]); |
| XCTAssertEqual(paymentDiscount.keyIdentifier, self.paymentDiscountMap[@"keyIdentifier"]); |
| XCTAssertEqualObjects(paymentDiscount.nonce, |
| [[NSUUID alloc] initWithUUIDString:self.paymentDiscountMap[@"nonce"]]); |
| XCTAssertEqual(paymentDiscount.signature, self.paymentDiscountMap[@"signature"]); |
| XCTAssertEqual(paymentDiscount.timestamp, self.paymentDiscountMap[@"timestamp"]); |
| } |
| } |
| |
| - (void)testSKPaymentDiscountFromMapMissingIdentifier { |
| if (@available(iOS 12.2, *)) { |
| NSArray *invalidValues = @[ [NSNull null], @(1), @"" ]; |
| |
| for (id value in invalidValues) { |
| NSDictionary *discountMap = @{ |
| @"identifier" : value, |
| @"keyIdentifier" : @"payment_discount_key_identifier", |
| @"nonce" : @"d18981e0-9003-4365-98a2-4b90e3b62c52", |
| @"signature" : @"this is a encrypted signature", |
| @"timestamp" : @([NSDate date].timeIntervalSince1970), |
| }; |
| |
| NSString *error = nil; |
| [FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error]; |
| |
| XCTAssertNotNil(error); |
| XCTAssertEqualObjects( |
| error, @"When specifying a payment discount the 'identifier' field is mandatory."); |
| } |
| } |
| } |
| |
| - (void)testGetMapFromSKProductDiscountMissingIdentifier { |
| if (@available(iOS 12.2, *)) { |
| SKProductDiscountStub *discount = |
| [[SKProductDiscountStub alloc] initWithMap:self.discountMissingIdentifierMap]; |
| NSDictionary *map = [FIAObjectTranslator getMapFromSKProductDiscount:discount]; |
| XCTAssertEqualObjects(map, self.discountMissingIdentifierMap); |
| } |
| } |
| |
| - (void)testSKPaymentDiscountFromMapMissingKeyIdentifier { |
| if (@available(iOS 12.2, *)) { |
| NSArray *invalidValues = @[ [NSNull null], @(1), @"" ]; |
| |
| for (id value in invalidValues) { |
| NSDictionary *discountMap = @{ |
| @"identifier" : @"payment_discount_identifier", |
| @"keyIdentifier" : value, |
| @"nonce" : @"d18981e0-9003-4365-98a2-4b90e3b62c52", |
| @"signature" : @"this is a encrypted signature", |
| @"timestamp" : @([NSDate date].timeIntervalSince1970), |
| }; |
| |
| NSString *error = nil; |
| [FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error]; |
| |
| XCTAssertNotNil(error); |
| XCTAssertEqualObjects( |
| error, @"When specifying a payment discount the 'keyIdentifier' field is mandatory."); |
| } |
| } |
| } |
| |
| - (void)testSKPaymentDiscountFromMapMissingNonce { |
| if (@available(iOS 12.2, *)) { |
| NSArray *invalidValues = @[ [NSNull null], @(1), @"" ]; |
| |
| for (id value in invalidValues) { |
| NSDictionary *discountMap = @{ |
| @"identifier" : @"payment_discount_identifier", |
| @"keyIdentifier" : @"payment_discount_key_identifier", |
| @"nonce" : value, |
| @"signature" : @"this is a encrypted signature", |
| @"timestamp" : @([NSDate date].timeIntervalSince1970), |
| }; |
| |
| NSString *error = nil; |
| [FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error]; |
| |
| XCTAssertNotNil(error); |
| XCTAssertEqualObjects(error, |
| @"When specifying a payment discount the 'nonce' field is mandatory."); |
| } |
| } |
| } |
| |
| - (void)testSKPaymentDiscountFromMapMissingSignature { |
| if (@available(iOS 12.2, *)) { |
| NSArray *invalidValues = @[ [NSNull null], @(1), @"" ]; |
| |
| for (id value in invalidValues) { |
| NSDictionary *discountMap = @{ |
| @"identifier" : @"payment_discount_identifier", |
| @"keyIdentifier" : @"payment_discount_key_identifier", |
| @"nonce" : @"d18981e0-9003-4365-98a2-4b90e3b62c52", |
| @"signature" : value, |
| @"timestamp" : @([NSDate date].timeIntervalSince1970), |
| }; |
| |
| NSString *error = nil; |
| [FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error]; |
| |
| XCTAssertNotNil(error); |
| XCTAssertEqualObjects( |
| error, @"When specifying a payment discount the 'signature' field is mandatory."); |
| } |
| } |
| } |
| |
| - (void)testSKPaymentDiscountFromMapMissingTimestamp { |
| if (@available(iOS 12.2, *)) { |
| NSArray *invalidValues = @[ [NSNull null], @"", @(-1) ]; |
| |
| for (id value in invalidValues) { |
| NSDictionary *discountMap = @{ |
| @"identifier" : @"payment_discount_identifier", |
| @"keyIdentifier" : @"payment_discount_key_identifier", |
| @"nonce" : @"d18981e0-9003-4365-98a2-4b90e3b62c52", |
| @"signature" : @"this is a encrypted signature", |
| @"timestamp" : value, |
| }; |
| |
| NSString *error = nil; |
| [FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error]; |
| |
| XCTAssertNotNil(error); |
| XCTAssertEqualObjects( |
| error, @"When specifying a payment discount the 'timestamp' field is mandatory."); |
| } |
| } |
| } |
| |
| - (void)testSKPaymentDiscountFromMapOverflowingTimestamp { |
| if (@available(iOS 12.2, *)) { |
| NSDictionary *discountMap = @{ |
| @"identifier" : @"payment_discount_identifier", |
| @"keyIdentifier" : @"payment_discount_key_identifier", |
| @"nonce" : @"d18981e0-9003-4365-98a2-4b90e3b62c52", |
| @"signature" : @"this is a encrypted signature", |
| @"timestamp" : @1665044583595, // timestamp 2022 Oct |
| }; |
| NSString *error = nil; |
| SKPaymentDiscount *paymentDiscount = |
| [FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error]; |
| XCTAssertNil(error); |
| XCTAssertNotNil(paymentDiscount); |
| XCTAssertEqual(paymentDiscount.identifier, discountMap[@"identifier"]); |
| XCTAssertEqual(paymentDiscount.keyIdentifier, discountMap[@"keyIdentifier"]); |
| XCTAssertEqualObjects(paymentDiscount.nonce, |
| [[NSUUID alloc] initWithUUIDString:discountMap[@"nonce"]]); |
| XCTAssertEqual(paymentDiscount.signature, discountMap[@"signature"]); |
| XCTAssertEqual(paymentDiscount.timestamp, discountMap[@"timestamp"]); |
| } |
| } |
| |
| @end |