mirrored_repositories/awesome-flutter/examples/plugins/packages/in_app_purchase/in_app_purchase_android
// 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 'dart:async';

import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart' as widgets;
import 'package:flutter_test/flutter_test.dart';
import 'package:in_app_purchase_android/billing_client_wrappers.dart';
import 'package:in_app_purchase_android/in_app_purchase_android.dart';
import 'package:in_app_purchase_android/src/billing_client_wrappers/enum_converters.dart';
import 'package:in_app_purchase_android/src/channel.dart';
import 'package:in_app_purchase_android/src/in_app_purchase_android_platform_addition.dart';
import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart';

import 'billing_client_wrappers/purchase_wrapper_test.dart';
import 'billing_client_wrappers/sku_details_wrapper_test.dart';
import 'stub_in_app_purchase_platform.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform();
  late InAppPurchaseAndroidPlatform iapAndroidPlatform;
  const String startConnectionCall =
      'BillingClient#startConnection(BillingClientStateListener)';
  const String endConnectionCall = 'BillingClient#endConnection()';

  setUpAll(() {
    channel.setMockMethodCallHandler(stubPlatform.fakeMethodCallHandler);
  });

  setUp(() {
    widgets.WidgetsFlutterBinding.ensureInitialized();

    const String debugMessage = 'dummy message';
    final BillingResponse responseCode = BillingResponse.ok;
    final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
        responseCode: responseCode, debugMessage: debugMessage);
    stubPlatform.addResponse(
        name: startConnectionCall,
        value: buildBillingResultMap(expectedBillingResult));
    stubPlatform.addResponse(name: endConnectionCall, value: null);

    InAppPurchaseAndroidPlatformAddition.enablePendingPurchases();
    InAppPurchaseAndroidPlatform.registerPlatform();
    iapAndroidPlatform =
        InAppPurchasePlatform.instance as InAppPurchaseAndroidPlatform;
  });

  tearDown(() {
    stubPlatform.reset();
  });

  group('connection management', () {
    test('connects on initialization', () {
      //await iapAndroidPlatform.isAvailable();
      expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(1));
    });
  });

  group('isAvailable', () {
    test('true', () async {
      stubPlatform.addResponse(name: 'BillingClient#isReady()', value: true);
      expect(await iapAndroidPlatform.isAvailable(), isTrue);
    });

    test('false', () async {
      stubPlatform.addResponse(name: 'BillingClient#isReady()', value: false);
      expect(await iapAndroidPlatform.isAvailable(), isFalse);
    });
  });

  group('querySkuDetails', () {
    final String queryMethodName =
        'BillingClient#querySkuDetailsAsync(SkuDetailsParams, SkuDetailsResponseListener)';

    test('handles empty skuDetails', () async {
      const String debugMessage = 'dummy message';
      final BillingResponse responseCode = BillingResponse.ok;
      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
          responseCode: responseCode, debugMessage: debugMessage);
      stubPlatform.addResponse(name: queryMethodName, value: <String, dynamic>{
        'billingResult': buildBillingResultMap(expectedBillingResult),
        'skuDetailsList': [],
      });

      final ProductDetailsResponse response =
          await iapAndroidPlatform.queryProductDetails(<String>[''].toSet());
      expect(response.productDetails, isEmpty);
    });

    test('should get correct product details', () async {
      const String debugMessage = 'dummy message';
      final BillingResponse responseCode = BillingResponse.ok;
      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
          responseCode: responseCode, debugMessage: debugMessage);
      stubPlatform.addResponse(name: queryMethodName, value: <String, dynamic>{
        'billingResult': buildBillingResultMap(expectedBillingResult),
        'skuDetailsList': <Map<String, dynamic>>[buildSkuMap(dummySkuDetails)]
      });
      // Since queryProductDetails makes 2 platform method calls (one for each SkuType), the result will contain 2 dummyWrapper instead
      // of 1.
      final ProductDetailsResponse response = await iapAndroidPlatform
          .queryProductDetails(<String>['valid'].toSet());
      expect(response.productDetails.first.title, dummySkuDetails.title);
      expect(response.productDetails.first.description,
          dummySkuDetails.description);
      expect(response.productDetails.first.price, dummySkuDetails.price);
    });

    test('should get the correct notFoundIDs', () async {
      const String debugMessage = 'dummy message';
      final BillingResponse responseCode = BillingResponse.ok;
      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
          responseCode: responseCode, debugMessage: debugMessage);
      stubPlatform.addResponse(name: queryMethodName, value: <String, dynamic>{
        'billingResult': buildBillingResultMap(expectedBillingResult),
        'skuDetailsList': <Map<String, dynamic>>[buildSkuMap(dummySkuDetails)]
      });
      // Since queryProductDetails makes 2 platform method calls (one for each SkuType), the result will contain 2 dummyWrapper instead
      // of 1.
      final ProductDetailsResponse response = await iapAndroidPlatform
          .queryProductDetails(<String>['invalid'].toSet());
      expect(response.notFoundIDs.first, 'invalid');
    });

    test(
        'should have error stored in the response when platform exception is thrown',
        () async {
      final BillingResponse responseCode = BillingResponse.ok;
      stubPlatform.addResponse(
          name: queryMethodName,
          value: <String, dynamic>{
            'responseCode': BillingResponseConverter().toJson(responseCode),
            'skuDetailsList': <Map<String, dynamic>>[
              buildSkuMap(dummySkuDetails)
            ]
          },
          additionalStepBeforeReturn: (_) {
            throw PlatformException(
              code: 'error_code',
              message: 'error_message',
              details: {'info': 'error_info'},
            );
          });
      // Since queryProductDetails makes 2 platform method calls (one for each SkuType), the result will contain 2 dummyWrapper instead
      // of 1.
      final ProductDetailsResponse response = await iapAndroidPlatform
          .queryProductDetails(<String>['invalid'].toSet());
      expect(response.notFoundIDs, ['invalid']);
      expect(response.productDetails, isEmpty);
      expect(response.error, isNotNull);
      expect(response.error!.source, kIAPSource);
      expect(response.error!.code, 'error_code');
      expect(response.error!.message, 'error_message');
      expect(response.error!.details, {'info': 'error_info'});
    });
  });

  group('restorePurchases', () {
    const String queryMethodName = 'BillingClient#queryPurchases(String)';
    test('handles error', () async {
      const String debugMessage = 'dummy message';
      final BillingResponse responseCode = BillingResponse.developerError;
      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
          responseCode: responseCode, debugMessage: debugMessage);

      stubPlatform.addResponse(name: queryMethodName, value: <dynamic, dynamic>{
        'billingResult': buildBillingResultMap(expectedBillingResult),
        'responseCode': BillingResponseConverter().toJson(responseCode),
        'purchasesList': <Map<String, dynamic>>[]
      });

      expect(
        iapAndroidPlatform.restorePurchases(),
        throwsA(
          isA<InAppPurchaseException>()
              .having((e) => e.source, 'source', kIAPSource)
              .having((e) => e.code, 'code', kRestoredPurchaseErrorCode)
              .having((e) => e.message, 'message', responseCode.toString()),
        ),
      );
    });

    test('should store platform exception in the response', () async {
      const String debugMessage = 'dummy message';

      final BillingResponse responseCode = BillingResponse.developerError;
      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
          responseCode: responseCode, debugMessage: debugMessage);
      stubPlatform.addResponse(
          name: queryMethodName,
          value: <dynamic, dynamic>{
            'responseCode': BillingResponseConverter().toJson(responseCode),
            'billingResult': buildBillingResultMap(expectedBillingResult),
            'purchasesList': <Map<String, dynamic>>[]
          },
          additionalStepBeforeReturn: (_) {
            throw PlatformException(
              code: 'error_code',
              message: 'error_message',
              details: {'info': 'error_info'},
            );
          });

      expect(
        iapAndroidPlatform.restorePurchases(),
        throwsA(
          isA<PlatformException>()
              .having((e) => e.code, 'code', 'error_code')
              .having((e) => e.message, 'message', 'error_message')
              .having((e) => e.details, 'details', {'info': 'error_info'}),
        ),
      );
    });

    test('returns SkuDetailsResponseWrapper', () async {
      Completer completer = Completer();
      Stream<List<PurchaseDetails>> stream = iapAndroidPlatform.purchaseStream;

      late StreamSubscription subscription;
      subscription = stream.listen((purchaseDetailsList) {
        if (purchaseDetailsList.first.status == PurchaseStatus.restored) {
          completer.complete(purchaseDetailsList);
          subscription.cancel();
        }
      });

      const String debugMessage = 'dummy message';
      final BillingResponse responseCode = BillingResponse.ok;
      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
          responseCode: responseCode, debugMessage: debugMessage);

      stubPlatform.addResponse(name: queryMethodName, value: <String, dynamic>{
        'billingResult': buildBillingResultMap(expectedBillingResult),
        'responseCode': BillingResponseConverter().toJson(responseCode),
        'purchasesList': <Map<String, dynamic>>[
          buildPurchaseMap(dummyPurchase),
        ]
      });

      // Since queryPastPurchases makes 2 platform method calls (one for each
      // SkuType), the result will contain 2 dummyPurchase instances instead
      // of 1.
      await iapAndroidPlatform.restorePurchases();
      final List<PurchaseDetails> restoredPurchases = await completer.future;

      expect(restoredPurchases.length, 2);
      restoredPurchases.forEach((element) {
        GooglePlayPurchaseDetails purchase =
            element as GooglePlayPurchaseDetails;

        expect(purchase.productID, dummyPurchase.sku);
        expect(purchase.purchaseID, dummyPurchase.orderId);
        expect(purchase.verificationData.localVerificationData,
            dummyPurchase.originalJson);
        expect(purchase.verificationData.serverVerificationData,
            dummyPurchase.purchaseToken);
        expect(purchase.verificationData.source, kIAPSource);
        expect(purchase.transactionDate, dummyPurchase.purchaseTime.toString());
        expect(purchase.billingClientPurchase, dummyPurchase);
        expect(purchase.status, PurchaseStatus.restored);
      });
    });
  });

  group('make payment', () {
    final String launchMethodName =
        'BillingClient#launchBillingFlow(Activity, BillingFlowParams)';
    const String consumeMethodName =
        'BillingClient#consumeAsync(String, ConsumeResponseListener)';

    test('buy non consumable, serializes and deserializes data', () async {
      final SkuDetailsWrapper skuDetails = dummySkuDetails;
      final String accountId = "hashedAccountId";
      const String debugMessage = 'dummy message';
      final BillingResponse sentCode = BillingResponse.ok;
      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
          responseCode: sentCode, debugMessage: debugMessage);

      stubPlatform.addResponse(
          name: launchMethodName,
          value: buildBillingResultMap(expectedBillingResult),
          additionalStepBeforeReturn: (_) {
            // Mock java update purchase callback.
            MethodCall call = MethodCall(kOnPurchasesUpdated, {
              'billingResult': buildBillingResultMap(expectedBillingResult),
              'responseCode': BillingResponseConverter().toJson(sentCode),
              'purchasesList': [
                {
                  'orderId': 'orderID1',
                  'sku': skuDetails.sku,
                  'isAutoRenewing': false,
                  'packageName': "package",
                  'purchaseTime': 1231231231,
                  'purchaseToken': "token",
                  'signature': 'sign',
                  'originalJson': 'json',
                  'developerPayload': 'dummy payload',
                  'isAcknowledged': true,
                  'purchaseState': 1,
                }
              ]
            });
            iapAndroidPlatform.billingClient.callHandler(call);
          });
      Completer completer = Completer();
      PurchaseDetails purchaseDetails;
      Stream purchaseStream = iapAndroidPlatform.purchaseStream;
      late StreamSubscription subscription;
      subscription = purchaseStream.listen((_) {
        purchaseDetails = _.first;
        completer.complete(purchaseDetails);
        subscription.cancel();
      }, onDone: () {});
      final GooglePlayPurchaseParam purchaseParam = GooglePlayPurchaseParam(
          productDetails: GooglePlayProductDetails.fromSkuDetails(skuDetails),
          applicationUserName: accountId);
      final bool launchResult = await iapAndroidPlatform.buyNonConsumable(
          purchaseParam: purchaseParam);

      PurchaseDetails result = await completer.future;
      expect(launchResult, isTrue);
      expect(result.purchaseID, 'orderID1');
      expect(result.status, PurchaseStatus.purchased);
      expect(result.productID, dummySkuDetails.sku);
    });

    test('handles an error with an empty purchases list', () async {
      final SkuDetailsWrapper skuDetails = dummySkuDetails;
      final String accountId = "hashedAccountId";
      const String debugMessage = 'dummy message';
      final BillingResponse sentCode = BillingResponse.error;
      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
          responseCode: sentCode, debugMessage: debugMessage);

      stubPlatform.addResponse(
          name: launchMethodName,
          value: buildBillingResultMap(expectedBillingResult),
          additionalStepBeforeReturn: (_) {
            // Mock java update purchase callback.
            MethodCall call = MethodCall(kOnPurchasesUpdated, {
              'billingResult': buildBillingResultMap(expectedBillingResult),
              'responseCode': BillingResponseConverter().toJson(sentCode),
              'purchasesList': []
            });
            iapAndroidPlatform.billingClient.callHandler(call);
          });
      Completer completer = Completer();
      PurchaseDetails purchaseDetails;
      Stream purchaseStream = iapAndroidPlatform.purchaseStream;
      late StreamSubscription subscription;
      subscription = purchaseStream.listen((_) {
        purchaseDetails = _.first;
        completer.complete(purchaseDetails);
        subscription.cancel();
      }, onDone: () {});
      final GooglePlayPurchaseParam purchaseParam = GooglePlayPurchaseParam(
          productDetails: GooglePlayProductDetails.fromSkuDetails(skuDetails),
          applicationUserName: accountId);
      await iapAndroidPlatform.buyNonConsumable(purchaseParam: purchaseParam);
      PurchaseDetails result = await completer.future;

      expect(result.error, isNotNull);
      expect(result.error!.source, kIAPSource);
      expect(result.status, PurchaseStatus.error);
      expect(result.purchaseID, isEmpty);
    });

    test('buy consumable with auto consume, serializes and deserializes data',
        () async {
      final SkuDetailsWrapper skuDetails = dummySkuDetails;
      final String accountId = "hashedAccountId";
      const String debugMessage = 'dummy message';
      final BillingResponse sentCode = BillingResponse.ok;
      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
          responseCode: sentCode, debugMessage: debugMessage);

      stubPlatform.addResponse(
          name: launchMethodName,
          value: buildBillingResultMap(expectedBillingResult),
          additionalStepBeforeReturn: (_) {
            // Mock java update purchase callback.
            MethodCall call = MethodCall(kOnPurchasesUpdated, {
              'billingResult': buildBillingResultMap(expectedBillingResult),
              'responseCode': BillingResponseConverter().toJson(sentCode),
              'purchasesList': [
                {
                  'orderId': 'orderID1',
                  'sku': skuDetails.sku,
                  'isAutoRenewing': false,
                  'packageName': "package",
                  'purchaseTime': 1231231231,
                  'purchaseToken': "token",
                  'signature': 'sign',
                  'originalJson': 'json',
                  'developerPayload': 'dummy payload',
                  'isAcknowledged': true,
                  'purchaseState': 1,
                }
              ]
            });
            iapAndroidPlatform.billingClient.callHandler(call);
          });
      Completer consumeCompleter = Completer();
      // adding call back for consume purchase
      final BillingResponse expectedCode = BillingResponse.ok;
      final BillingResultWrapper expectedBillingResultForConsume =
          BillingResultWrapper(
              responseCode: expectedCode, debugMessage: debugMessage);
      stubPlatform.addResponse(
          name: consumeMethodName,
          value: buildBillingResultMap(expectedBillingResultForConsume),
          additionalStepBeforeReturn: (dynamic args) {
            String purchaseToken = args['purchaseToken'];
            consumeCompleter.complete((purchaseToken));
          });

      Completer completer = Completer();
      PurchaseDetails purchaseDetails;
      Stream purchaseStream = iapAndroidPlatform.purchaseStream;
      late StreamSubscription subscription;
      subscription = purchaseStream.listen((_) {
        purchaseDetails = _.first;
        completer.complete(purchaseDetails);
        subscription.cancel();
      }, onDone: () {});
      final GooglePlayPurchaseParam purchaseParam = GooglePlayPurchaseParam(
          productDetails: GooglePlayProductDetails.fromSkuDetails(skuDetails),
          applicationUserName: accountId);
      final bool launchResult =
          await iapAndroidPlatform.buyConsumable(purchaseParam: purchaseParam);

      // Verify that the result has succeeded
      GooglePlayPurchaseDetails result = await completer.future;
      expect(launchResult, isTrue);
      expect(result.billingClientPurchase, isNotNull);
      expect(result.billingClientPurchase.purchaseToken,
          await consumeCompleter.future);
      expect(result.status, PurchaseStatus.purchased);
      expect(result.error, isNull);
    });

    test('buyNonConsumable propagates failures to launch the billing flow',
        () async {
      const String debugMessage = 'dummy message';
      final BillingResponse sentCode = BillingResponse.error;
      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
          responseCode: sentCode, debugMessage: debugMessage);
      stubPlatform.addResponse(
          name: launchMethodName,
          value: buildBillingResultMap(expectedBillingResult));

      final bool result = await iapAndroidPlatform.buyNonConsumable(
          purchaseParam: GooglePlayPurchaseParam(
              productDetails:
                  GooglePlayProductDetails.fromSkuDetails(dummySkuDetails)));

      // Verify that the failure has been converted and returned
      expect(result, isFalse);
    });

    test('buyConsumable propagates failures to launch the billing flow',
        () async {
      const String debugMessage = 'dummy message';
      final BillingResponse sentCode = BillingResponse.developerError;
      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
          responseCode: sentCode, debugMessage: debugMessage);
      stubPlatform.addResponse(
        name: launchMethodName,
        value: buildBillingResultMap(expectedBillingResult),
      );

      final bool result = await iapAndroidPlatform.buyConsumable(
          purchaseParam: GooglePlayPurchaseParam(
              productDetails:
                  GooglePlayProductDetails.fromSkuDetails(dummySkuDetails)));

      // Verify that the failure has been converted and returned
      expect(result, isFalse);
    });

    test('adds consumption failures to PurchaseDetails objects', () async {
      final SkuDetailsWrapper skuDetails = dummySkuDetails;
      final String accountId = "hashedAccountId";
      const String debugMessage = 'dummy message';
      final BillingResponse sentCode = BillingResponse.ok;
      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
          responseCode: sentCode, debugMessage: debugMessage);
      stubPlatform.addResponse(
          name: launchMethodName,
          value: buildBillingResultMap(expectedBillingResult),
          additionalStepBeforeReturn: (_) {
            // Mock java update purchase callback.
            MethodCall call = MethodCall(kOnPurchasesUpdated, {
              'billingResult': buildBillingResultMap(expectedBillingResult),
              'responseCode': BillingResponseConverter().toJson(sentCode),
              'purchasesList': [
                {
                  'orderId': 'orderID1',
                  'sku': skuDetails.sku,
                  'isAutoRenewing': false,
                  'packageName': "package",
                  'purchaseTime': 1231231231,
                  'purchaseToken': "token",
                  'signature': 'sign',
                  'originalJson': 'json',
                  'developerPayload': 'dummy payload',
                  'isAcknowledged': true,
                  'purchaseState': 1,
                }
              ]
            });
            iapAndroidPlatform.billingClient.callHandler(call);
          });
      Completer consumeCompleter = Completer();
      // adding call back for consume purchase
      final BillingResponse expectedCode = BillingResponse.error;
      final BillingResultWrapper expectedBillingResultForConsume =
          BillingResultWrapper(
              responseCode: expectedCode, debugMessage: debugMessage);
      stubPlatform.addResponse(
          name: consumeMethodName,
          value: buildBillingResultMap(expectedBillingResultForConsume),
          additionalStepBeforeReturn: (dynamic args) {
            String purchaseToken = args['purchaseToken'];
            consumeCompleter.complete(purchaseToken);
          });

      Completer completer = Completer();
      PurchaseDetails purchaseDetails;
      Stream purchaseStream = iapAndroidPlatform.purchaseStream;
      late StreamSubscription subscription;
      subscription = purchaseStream.listen((_) {
        purchaseDetails = _.first;
        completer.complete(purchaseDetails);
        subscription.cancel();
      }, onDone: () {});
      final GooglePlayPurchaseParam purchaseParam = GooglePlayPurchaseParam(
          productDetails: GooglePlayProductDetails.fromSkuDetails(skuDetails),
          applicationUserName: accountId);
      await iapAndroidPlatform.buyConsumable(purchaseParam: purchaseParam);

      // Verify that the result has an error for the failed consumption
      GooglePlayPurchaseDetails result = await completer.future;
      expect(result.billingClientPurchase, isNotNull);
      expect(result.billingClientPurchase.purchaseToken,
          await consumeCompleter.future);
      expect(result.status, PurchaseStatus.error);
      expect(result.error, isNotNull);
      expect(result.error!.code, kConsumptionFailedErrorCode);
    });

    test(
        'buy consumable without auto consume, consume api should not receive calls',
        () async {
      final SkuDetailsWrapper skuDetails = dummySkuDetails;
      final String accountId = "hashedAccountId";
      const String debugMessage = 'dummy message';
      final BillingResponse sentCode = BillingResponse.developerError;
      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
          responseCode: sentCode, debugMessage: debugMessage);

      stubPlatform.addResponse(
          name: launchMethodName,
          value: buildBillingResultMap(expectedBillingResult),
          additionalStepBeforeReturn: (_) {
            // Mock java update purchase callback.
            MethodCall call = MethodCall(kOnPurchasesUpdated, {
              'billingResult': buildBillingResultMap(expectedBillingResult),
              'responseCode': BillingResponseConverter().toJson(sentCode),
              'purchasesList': [
                {
                  'orderId': 'orderID1',
                  'sku': skuDetails.sku,
                  'isAutoRenewing': false,
                  'packageName': "package",
                  'purchaseTime': 1231231231,
                  'purchaseToken': "token",
                  'signature': 'sign',
                  'originalJson': 'json',
                  'developerPayload': 'dummy payload',
                  'isAcknowledged': true,
                  'purchaseState': 1,
                }
              ]
            });
            iapAndroidPlatform.billingClient.callHandler(call);
          });
      Completer consumeCompleter = Completer();
      // adding call back for consume purchase
      final BillingResponse expectedCode = BillingResponse.ok;
      final BillingResultWrapper expectedBillingResultForConsume =
          BillingResultWrapper(
              responseCode: expectedCode, debugMessage: debugMessage);
      stubPlatform.addResponse(
          name: consumeMethodName,
          value: buildBillingResultMap(expectedBillingResultForConsume),
          additionalStepBeforeReturn: (dynamic args) {
            String purchaseToken = args['purchaseToken'];
            consumeCompleter.complete((purchaseToken));
          });

      Stream purchaseStream = iapAndroidPlatform.purchaseStream;
      late StreamSubscription subscription;
      subscription = purchaseStream.listen((_) {
        consumeCompleter.complete(null);
        subscription.cancel();
      }, onDone: () {});
      final GooglePlayPurchaseParam purchaseParam = GooglePlayPurchaseParam(
          productDetails: GooglePlayProductDetails.fromSkuDetails(skuDetails),
          applicationUserName: accountId);
      await iapAndroidPlatform.buyConsumable(
          purchaseParam: purchaseParam, autoConsume: false);
      expect(null, await consumeCompleter.future);
    });
  });

  group('complete purchase', () {
    const String completeMethodName =
        'BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)';
    test('complete purchase success', () async {
      final BillingResponse expectedCode = BillingResponse.ok;
      const String debugMessage = 'dummy message';
      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
          responseCode: expectedCode, debugMessage: debugMessage);
      stubPlatform.addResponse(
        name: completeMethodName,
        value: buildBillingResultMap(expectedBillingResult),
      );
      PurchaseDetails purchaseDetails =
          GooglePlayPurchaseDetails.fromPurchase(dummyUnacknowledgedPurchase);
      Completer completer = Completer();
      purchaseDetails.status = PurchaseStatus.purchased;
      if (purchaseDetails.pendingCompletePurchase) {
        final BillingResultWrapper billingResultWrapper =
            await iapAndroidPlatform.completePurchase(purchaseDetails);
        expect(billingResultWrapper, equals(expectedBillingResult));
        completer.complete(billingResultWrapper);
      }
      expect(await completer.future, equals(expectedBillingResult));
    });
  });
}
