blob: 0cf01b0bbfd6d0ac13f3ef70cc0c136c1c49ff62 [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 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:in_app_purchase_storekit/src/channel.dart';
import 'package:in_app_purchase_storekit/store_kit_wrappers.dart';
import 'sk_test_stub_objects.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final FakeStoreKitPlatform fakeStoreKitPlatform = FakeStoreKitPlatform();
setUpAll(() {
_ambiguate(TestDefaultBinaryMessengerBinding.instance)!
.defaultBinaryMessenger
.setMockMethodCallHandler(
SystemChannels.platform, fakeStoreKitPlatform.onMethodCall);
});
setUp(() {});
tearDown(() {
fakeStoreKitPlatform.testReturnNull = false;
fakeStoreKitPlatform.queueIsActive = null;
fakeStoreKitPlatform.getReceiptFailTest = false;
});
group('sk_request_maker', () {
test('get products method channel', () async {
final SkProductResponseWrapper productResponseWrapper =
await SKRequestMaker().startProductRequest(<String>['xxx']);
expect(
productResponseWrapper.products,
isNotEmpty,
);
expect(
productResponseWrapper.products.first.priceLocale.currencySymbol,
r'$',
);
expect(
productResponseWrapper.products.first.priceLocale.currencySymbol,
isNot('A'),
);
expect(
productResponseWrapper.products.first.priceLocale.currencyCode,
'USD',
);
expect(
productResponseWrapper.products.first.priceLocale.countryCode,
'US',
);
expect(
productResponseWrapper.invalidProductIdentifiers,
isNotEmpty,
);
expect(
fakeStoreKitPlatform.startProductRequestParam,
<String>['xxx'],
);
});
test('get products method channel should throw exception', () async {
fakeStoreKitPlatform.getProductRequestFailTest = true;
expect(
SKRequestMaker().startProductRequest(<String>['xxx']),
throwsException,
);
fakeStoreKitPlatform.getProductRequestFailTest = false;
});
test('refreshed receipt', () async {
final int receiptCountBefore = fakeStoreKitPlatform.refreshReceipt;
await SKRequestMaker().startRefreshReceiptRequest(
receiptProperties: <String, dynamic>{'isExpired': true});
expect(fakeStoreKitPlatform.refreshReceipt, receiptCountBefore + 1);
expect(fakeStoreKitPlatform.refreshReceiptParam,
<String, dynamic>{'isExpired': true});
});
test('should get null receipt if any exceptions are raised', () async {
fakeStoreKitPlatform.getReceiptFailTest = true;
expect(() async => SKReceiptManager.retrieveReceiptData(),
throwsA(const TypeMatcher<PlatformException>()));
});
});
group('sk_receipt_manager', () {
test('should get receipt (faking it by returning a `receipt data` string)',
() async {
final String receiptData = await SKReceiptManager.retrieveReceiptData();
expect(receiptData, 'receipt data');
});
});
group('sk_payment_queue', () {
test('canMakePayment should return true', () async {
expect(await SKPaymentQueueWrapper.canMakePayments(), true);
});
test('canMakePayment returns false if method channel returns null',
() async {
fakeStoreKitPlatform.testReturnNull = true;
expect(await SKPaymentQueueWrapper.canMakePayments(), false);
});
test('transactions should return a valid list of transactions', () async {
expect(await SKPaymentQueueWrapper().transactions(), isNotEmpty);
});
test(
'throws if observer is not set for payment queue before adding payment',
() async {
expect(SKPaymentQueueWrapper().addPayment(dummyPayment),
throwsAssertionError);
});
test('should add payment to the payment queue', () async {
final SKPaymentQueueWrapper queue = SKPaymentQueueWrapper();
final TestPaymentTransactionObserver observer =
TestPaymentTransactionObserver();
queue.setTransactionObserver(observer);
await queue.addPayment(dummyPayment);
expect(fakeStoreKitPlatform.payments.first, equals(dummyPayment));
});
test('should finish transaction', () async {
final SKPaymentQueueWrapper queue = SKPaymentQueueWrapper();
final TestPaymentTransactionObserver observer =
TestPaymentTransactionObserver();
queue.setTransactionObserver(observer);
await queue.finishTransaction(dummyTransaction);
expect(fakeStoreKitPlatform.transactionsFinished.first,
equals(dummyTransaction.toFinishMap()));
});
test('should restore transaction', () async {
final SKPaymentQueueWrapper queue = SKPaymentQueueWrapper();
final TestPaymentTransactionObserver observer =
TestPaymentTransactionObserver();
queue.setTransactionObserver(observer);
await queue.restoreTransactions(applicationUserName: 'aUserID');
expect(fakeStoreKitPlatform.applicationNameHasTransactionRestored,
'aUserID');
});
test('startObservingTransactionQueue should call methodChannel', () async {
expect(fakeStoreKitPlatform.queueIsActive, isNot(true));
await SKPaymentQueueWrapper().startObservingTransactionQueue();
expect(fakeStoreKitPlatform.queueIsActive, true);
});
test('stopObservingTransactionQueue should call methodChannel', () async {
expect(fakeStoreKitPlatform.queueIsActive, isNot(false));
await SKPaymentQueueWrapper().stopObservingTransactionQueue();
expect(fakeStoreKitPlatform.queueIsActive, false);
});
test('setDelegate should call methodChannel', () async {
expect(fakeStoreKitPlatform.isPaymentQueueDelegateRegistered, false);
await SKPaymentQueueWrapper().setDelegate(TestPaymentQueueDelegate());
expect(fakeStoreKitPlatform.isPaymentQueueDelegateRegistered, true);
await SKPaymentQueueWrapper().setDelegate(null);
expect(fakeStoreKitPlatform.isPaymentQueueDelegateRegistered, false);
});
test('showPriceConsentIfNeeded should call methodChannel', () async {
expect(fakeStoreKitPlatform.showPriceConsentIfNeeded, false);
await SKPaymentQueueWrapper().showPriceConsentIfNeeded();
expect(fakeStoreKitPlatform.showPriceConsentIfNeeded, true);
});
});
group('Code Redemption Sheet', () {
test('presentCodeRedemptionSheet should not throw', () async {
expect(fakeStoreKitPlatform.presentCodeRedemption, false);
await SKPaymentQueueWrapper().presentCodeRedemptionSheet();
expect(fakeStoreKitPlatform.presentCodeRedemption, true);
fakeStoreKitPlatform.presentCodeRedemption = false;
});
});
}
class FakeStoreKitPlatform {
FakeStoreKitPlatform() {
_ambiguate(TestDefaultBinaryMessengerBinding.instance)!
.defaultBinaryMessenger
.setMockMethodCallHandler(channel, onMethodCall);
}
// get product request
List<dynamic> startProductRequestParam = <dynamic>[];
bool getProductRequestFailTest = false;
bool testReturnNull = false;
// get receipt request
bool getReceiptFailTest = false;
// refresh receipt request
int refreshReceipt = 0;
late Map<String, dynamic> refreshReceiptParam;
// payment queue
List<SKPaymentWrapper> payments = <SKPaymentWrapper>[];
List<Map<String, String>> transactionsFinished = <Map<String, String>>[];
String applicationNameHasTransactionRestored = '';
// present Code Redemption
bool presentCodeRedemption = false;
// show price consent sheet
bool showPriceConsentIfNeeded = false;
// indicate if the payment queue delegate is registered
bool isPaymentQueueDelegateRegistered = false;
// Listen to purchase updates
bool? queueIsActive;
Future<dynamic> onMethodCall(MethodCall call) {
switch (call.method) {
// request makers
case '-[InAppPurchasePlugin startProductRequest:result:]':
startProductRequestParam = call.arguments as List<dynamic>;
if (getProductRequestFailTest) {
return Future<dynamic>.value();
}
return Future<Map<String, dynamic>>.value(
buildProductResponseMap(dummyProductResponseWrapper));
case '-[InAppPurchasePlugin refreshReceipt:result:]':
refreshReceipt++;
refreshReceiptParam = Map.castFrom<dynamic, dynamic, String, dynamic>(
call.arguments as Map<dynamic, dynamic>);
return Future<void>.sync(() {});
// receipt manager
case '-[InAppPurchasePlugin retrieveReceiptData:result:]':
if (getReceiptFailTest) {
throw Exception('some arbitrary error');
}
return Future<String>.value('receipt data');
// payment queue
case '-[SKPaymentQueue canMakePayments:]':
if (testReturnNull) {
return Future<dynamic>.value();
}
return Future<bool>.value(true);
case '-[SKPaymentQueue transactions]':
return Future<List<dynamic>>.value(
<dynamic>[buildTransactionMap(dummyTransaction)]);
case '-[InAppPurchasePlugin addPayment:result:]':
payments.add(SKPaymentWrapper.fromJson(Map<String, dynamic>.from(
call.arguments as Map<dynamic, dynamic>)));
return Future<void>.sync(() {});
case '-[InAppPurchasePlugin finishTransaction:result:]':
transactionsFinished.add(
Map<String, String>.from(call.arguments as Map<dynamic, dynamic>));
return Future<void>.sync(() {});
case '-[InAppPurchasePlugin restoreTransactions:result:]':
applicationNameHasTransactionRestored = call.arguments as String;
return Future<void>.sync(() {});
case '-[InAppPurchasePlugin presentCodeRedemptionSheet:result:]':
presentCodeRedemption = true;
return Future<void>.sync(() {});
case '-[SKPaymentQueue startObservingTransactionQueue]':
queueIsActive = true;
return Future<void>.sync(() {});
case '-[SKPaymentQueue stopObservingTransactionQueue]':
queueIsActive = false;
return Future<void>.sync(() {});
case '-[SKPaymentQueue registerDelegate]':
isPaymentQueueDelegateRegistered = true;
return Future<void>.sync(() {});
case '-[SKPaymentQueue removeDelegate]':
isPaymentQueueDelegateRegistered = false;
return Future<void>.sync(() {});
case '-[SKPaymentQueue showPriceConsentIfNeeded]':
showPriceConsentIfNeeded = true;
return Future<void>.sync(() {});
}
return Future<dynamic>.error('method not mocked');
}
}
class TestPaymentQueueDelegate extends SKPaymentQueueDelegateWrapper {}
class TestPaymentTransactionObserver extends SKTransactionObserverWrapper {
@override
void updatedTransactions(
{required List<SKPaymentTransactionWrapper> transactions}) {}
@override
void removedTransactions(
{required List<SKPaymentTransactionWrapper> transactions}) {}
@override
void restoreCompletedTransactionsFailed({required SKError error}) {}
@override
void paymentQueueRestoreCompletedTransactionsFinished() {}
@override
bool shouldAddStorePayment(
{required SKPaymentWrapper payment, required SKProductWrapper product}) {
return true;
}
}
/// This allows a value of type T or T? to be treated as a value of type T?.
///
/// We use this so that APIs that have become non-nullable can still be used
/// with `!` and `?` on the stable branch.
T? _ambiguate<T>(T? value) => value;