chromium/components/payments/content/android/junit/src/org/chromium/components/payments/PaymentRequestServiceTest.java

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.components.payments;

import android.graphics.drawable.Drawable;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.quality.Strictness;
import org.robolectric.annotation.Config;

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.JniMocker;
import org.chromium.components.payments.test_support.DefaultPaymentFeatureConfig;
import org.chromium.components.payments.test_support.PaymentRequestServiceBuilder;
import org.chromium.content.browser.webcontents.WebContentsImpl;
import org.chromium.content.browser.webcontents.WebContentsImplJni;
import org.chromium.content_public.browser.NavigationController;
import org.chromium.content_public.browser.WebContents;
import org.chromium.mojo.system.MojoException;
import org.chromium.payments.mojom.PayerDetail;
import org.chromium.payments.mojom.PaymentAddress;
import org.chromium.payments.mojom.PaymentDetails;
import org.chromium.payments.mojom.PaymentErrorReason;
import org.chromium.payments.mojom.PaymentMethodData;
import org.chromium.payments.mojom.PaymentOptions;
import org.chromium.payments.mojom.PaymentRequestClient;
import org.chromium.payments.mojom.PaymentResponse;
import org.chromium.url.mojom.Url;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/** A test for PaymentRequestService. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
@DisableFeatures(PaymentFeatureList.WEB_PAYMENTS_EXPERIMENTAL_FEATURES)
public class PaymentRequestServiceTest implements PaymentRequestClient {
    private static final int NATIVE_WEB_CONTENTS_ANDROID = 1;
    private static final int NO_PAYMENT_ERROR = PaymentErrorReason.MIN_VALUE;
    private final BrowserPaymentRequest mBrowserPaymentRequest;
    private List<PaymentApp> mNotifiedPendingApps;

    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.WARN);
    @Rule public JniMocker mJniMocker = new JniMocker();

    @Mock private NavigationController mNavigationController;
    @Mock private WebContentsImpl.Natives mWebContentsJniMock;
    @Mock private PaymentRequestWebContentsData.Natives mWebContentsDataJniMock;

    private boolean mIsOnCloseListenerInvoked;
    private String mSentMethodName;
    private String mSentStringifiedDetails;
    private PaymentAddress mSentAddress;
    private String mSentShippingOptionId;
    private PayerDetail mSentPayerDetail;
    private PaymentResponse mSentPaymentResponse;
    private int mSentErrorReason;
    private String mSentErrorMessage;
    private boolean mOnCompleteCalled;
    private boolean mIsAbortedSuccessfully;
    private int mSentCanMakePayment;
    private int mSentHasEnrolledInstrument;
    private boolean mWarnNoFaviconCalled;
    private boolean mIsClientClosed;
    private MojoException mConnectionError;
    private boolean mWaitForUpdatedDetailsDefaultValue;
    private boolean mIsUserGestureShow;
    private PaymentAppService mPaymentAppService;
    private PaymentAppFactoryDelegate mPaymentAppFactoryDelegate;
    private JourneyLogger mJourneyLogger;
    private PaymentRequestWebContentsData mPaymentRequestWebContentsData;

    public PaymentRequestServiceTest() {
        MockitoAnnotations.initMocks(this);
        mJniMocker.mock(WebContentsImplJni.TEST_HOOKS, mWebContentsJniMock);
        WebContentsImpl webContentsImpl =
                Mockito.spy(
                        WebContentsImpl.create(NATIVE_WEB_CONTENTS_ANDROID, mNavigationController));
        // We don't mock the WebContentsObserverProxy, so mock the observer behaviour.
        Mockito.doNothing().when(webContentsImpl).addObserver(Mockito.any());
        webContentsImpl.initializeForTesting();
        mPaymentRequestWebContentsData = new PaymentRequestWebContentsData(webContentsImpl);
        PaymentRequestWebContentsData.setInstanceForTesting(mPaymentRequestWebContentsData);

        mJniMocker.mock(PaymentRequestWebContentsDataJni.TEST_HOOKS, mWebContentsDataJniMock);
        Mockito.doNothing().when(mWebContentsDataJniMock).recordActivationlessShow(Mockito.any());
        Mockito.doReturn(false).when(mWebContentsDataJniMock).hadActivationlessShow(Mockito.any());

        mPaymentAppService = Mockito.mock(PaymentAppService.class);
        Mockito.doAnswer(
                        (args) -> {
                            mPaymentAppFactoryDelegate = args.getArgument(0);
                            return null;
                        })
                .when(mPaymentAppService)
                .create(Mockito.any());

        mBrowserPaymentRequest = Mockito.mock(BrowserPaymentRequest.class);
        Mockito.doReturn(true).when(mBrowserPaymentRequest).hasAvailableApps();
        Mockito.doReturn(false)
                .when(mBrowserPaymentRequest)
                .disconnectIfExtraValidationFails(
                        Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
        Mockito.doReturn(true)
                .when(mBrowserPaymentRequest)
                .patchPaymentResponseIfNeeded(Mockito.any());
        Mockito.doReturn(null)
                .when(mBrowserPaymentRequest)
                .showOrSkipAppSelector(Mockito.anyBoolean(), Mockito.any(), Mockito.anyBoolean());
        Mockito.doReturn(true).when(mBrowserPaymentRequest).onPaymentAppCreated(Mockito.any());
        Mockito.doReturn(true)
                .when(mBrowserPaymentRequest)
                .parseAndValidateDetailsFurtherIfNeeded(Mockito.any());
        Mockito.doAnswer(
                        (args) -> {
                            List<PaymentApp> pendingApps = args.getArgument(0);
                            mNotifiedPendingApps = new ArrayList<>(pendingApps);
                            return null;
                        })
                .when(mBrowserPaymentRequest)
                .notifyPaymentUiOfPendingApps(Mockito.any());

        PaymentApp app =
                new PaymentApp("appId", "appLabel", "appSublabel", Mockito.mock(Drawable.class)) {
                    @Override
                    public Set<String> getInstrumentMethodNames() {
                        Set<String> names = new HashSet<>();
                        names.add("https://www.chromium.org");
                        return names;
                    }

                    @Override
                    public void dismissInstrument() {}
                };
        Mockito.doReturn(app).when(mBrowserPaymentRequest).getSelectedPaymentApp();
        List<PaymentApp> apps = new ArrayList();
        apps.add(app);
        Mockito.doReturn(apps).when(mBrowserPaymentRequest).getPaymentApps();

        mJourneyLogger = Mockito.mock(JourneyLogger.class);
    }

    @Before
    public void setUp() {
        PaymentRequestService.resetShowingPaymentRequestForTest();
        DefaultPaymentFeatureConfig.setDefaultFlagConfigurationForTesting();
    }

    @After
    public void tearDown() {
        PaymentRequestService.resetShowingPaymentRequestForTest();
    }

    @Override
    public void onPaymentMethodChange(String methodName, String stringifiedDetails) {
        mSentMethodName = methodName;
        mSentStringifiedDetails = stringifiedDetails;
    }

    @Override
    public void onShippingAddressChange(PaymentAddress address) {
        mSentAddress = address;
    }

    @Override
    public void onShippingOptionChange(String shippingOptionId) {
        mSentShippingOptionId = shippingOptionId;
    }

    @Override
    public void onPayerDetailChange(PayerDetail detail) {
        mSentPayerDetail = detail;
    }

    @Override
    public void onPaymentResponse(PaymentResponse response) {
        mSentPaymentResponse = response;
    }

    @Override
    public void onError(int error, String errorMessage) {
        mSentErrorReason = error;
        mSentErrorMessage = errorMessage;
    }

    @Override
    public void onComplete() {
        mOnCompleteCalled = true;
    }

    @Override
    public void onAbort(boolean abortedSuccessfully) {
        mIsAbortedSuccessfully = abortedSuccessfully;
    }

    @Override
    public void onCanMakePayment(int result) {
        mSentCanMakePayment = result;
    }

    @Override
    public void onHasEnrolledInstrument(int result) {
        mSentHasEnrolledInstrument = result;
    }

    @Override
    public void warnNoFavicon() {
        mWarnNoFaviconCalled = true;
    }

    @Override
    public void allowConnectToSource(
            Url url,
            Url urlBeforeRedirects,
            boolean didFollowRedirect,
            AllowConnectToSource_Response callback) {
        callback.call(/* allow= */ true);
    }

    @Override
    public void close() {
        mIsClientClosed = true;
    }

    @Override
    public void onConnectionError(MojoException e) {
        mConnectionError = e;
    }

    private void assertNoError() {
        assertErrorAndReason(null, NO_PAYMENT_ERROR);
    }

    private void assertErrorAndReason(String errorMessage, int errorReason) {
        Assert.assertEquals(errorMessage, mSentErrorMessage);
        Assert.assertEquals(errorReason, mSentErrorReason);
    }

    private void assertClosed(boolean isClosed) {
        Assert.assertEquals(mIsClientClosed, isClosed);
        Assert.assertEquals(mIsOnCloseListenerInvoked, isClosed);
    }

    private void show(PaymentRequestService service) {
        service.show(mWaitForUpdatedDetailsDefaultValue, mIsUserGestureShow);
    }

    private void updateWith(PaymentRequestService service) {
        service.updateWith(getDefaultPaymentDetailsUpdate());
    }

    private PaymentRequestServiceBuilder defaultBuilder() {
        return PaymentRequestServiceBuilder.defaultBuilder(
                () -> mIsOnCloseListenerInvoked = true,
                /* client= */ this,
                mPaymentAppService,
                mBrowserPaymentRequest,
                mJourneyLogger);
    }

    private PaymentApp createDefaultPaymentApp() {
        PaymentApp app = Mockito.mock(PaymentApp.class);
        Mockito.doReturn(true).when(app).hasEnrolledInstrument();
        return app;
    }

    private void queryPaymentApps() {
        mPaymentAppFactoryDelegate.onCanMakePaymentCalculated(true);
        mPaymentAppFactoryDelegate.onPaymentAppCreated(createDefaultPaymentApp());
        mPaymentAppFactoryDelegate.onDoneCreatingPaymentApps(null);
    }

    private PaymentDetails getDefaultPaymentDetailsUpdate() {
        return new PaymentDetails();
    }

    private void verifyShowAppSelector(int times) {
        Mockito.verify(mBrowserPaymentRequest, Mockito.times(times))
                .showOrSkipAppSelector(Mockito.anyBoolean(), Mockito.any(), Mockito.anyBoolean());
    }

    private void verifyContinuedShowWithUpdatedDetails(int times) {
        Mockito.verify(mBrowserPaymentRequest, Mockito.times(times))
                .continueShowWithUpdatedDetails(Mockito.any(), Mockito.anyBoolean());
    }

    private void resetErrorMessageAndCloseState() {
        mSentErrorReason = NO_PAYMENT_ERROR;
        mSentErrorMessage = null;
        mIsClientClosed = false;
        mIsOnCloseListenerInvoked = false;
    }

    @Test
    @Feature({"Payments"})
    public void testNullFrameOriginFailsCreation() {
        Assert.assertNull(defaultBuilder().setRenderFrameHostLastCommittedOrigin(null).build());
        // Not asserting error because no frame to receive the error message and error reason.
    }

    @Test
    @Feature({"Payments"})
    public void testNullFrameUrlFailsCreation() {
        Assert.assertNull(defaultBuilder().setRenderFrameHostLastCommittedURL(null).build());
        // Not asserting error because no frame to receive the error message and error reason.
    }

    @Test
    @Feature({"Payments"})
    public void testNullWebContentsFailsCreation() {
        Assert.assertNull(defaultBuilder().setWebContents(null).build());
        // Not asserting error because no WebContents to receive the error message and error reason.
    }

    @Test
    @Feature({"Payments"})
    public void testDestroyedWebContentsFailsCreation() {
        WebContents webContents = Mockito.mock(WebContents.class);
        Mockito.doReturn(true).when(webContents).isDestroyed();
        Assert.assertNull(defaultBuilder().setWebContents(webContents).build());
        // Not asserting error because no WebContents to receive the error message and error reason.
    }

    @Test
    @Feature({"Payments"})
    public void testNullClientFailsCreation() {
        Assert.assertNull(defaultBuilder().setPaymentRequestClient(null).build());
        // Not asserting error because no client to receive the error message and error reason.
    }

    @Test
    @Feature({"Payments"})
    public void testInsecureOriginFailsCreation() {
        Assert.assertNull(defaultBuilder().setOriginSecure(false).build());
        assertErrorAndReason(
                ErrorStrings.NOT_IN_A_SECURE_ORIGIN, PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
    }

    @Test
    @Feature({"Payments"})
    public void testNullMethodDataFailsCreation() {
        Assert.assertNull(defaultBuilder().setMethodData(null).build());
        assertErrorAndReason(
                ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA,
                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
    }

    @Test
    @Feature({"Payments"})
    public void testNullDetailsFailsCreation() {
        Assert.assertNull(defaultBuilder().setPaymentDetailsInit(null).build());
        assertErrorAndReason(
                ErrorStrings.INVALID_PAYMENT_DETAILS,
                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
    }

    @Test
    @Feature({"Payments"})
    public void testDetailsWithoutIdFailsCreation() {
        Assert.assertNull(defaultBuilder().setPaymentDetailsInitId(null).build());
        assertErrorAndReason(
                ErrorStrings.INVALID_PAYMENT_DETAILS,
                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
    }

    @Test
    @Feature({"Payments"})
    public void testDetailsWithoutTotalFailsCreation() {
        Assert.assertNull(defaultBuilder().setPaymentDetailsInitTotal(null).build());
        assertErrorAndReason(
                ErrorStrings.INVALID_PAYMENT_DETAILS,
                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
    }

    @Test
    @Feature({"Payments"})
    public void testNullDetailsFailsUpdateWith() {
        PaymentRequestService service = defaultBuilder().build();
        service.show(false, mIsUserGestureShow);
        assertNoError();
        service.updateWith(null);
        assertErrorAndReason(
                ErrorStrings.INVALID_PAYMENT_DETAILS,
                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
        Mockito.verify(mBrowserPaymentRequest, Mockito.never())
                .onPaymentDetailsUpdated(Mockito.any(), Mockito.anyBoolean());
    }

    @Test
    @Feature({"Payments"})
    public void testDetailsWithIdFailsUpdateWith() {
        PaymentRequestService service = defaultBuilder().build();
        service.show(false, mIsUserGestureShow);
        PaymentDetails details = getDefaultPaymentDetailsUpdate();
        details.id = "testId";
        assertNoError();
        service.updateWith(details);
        assertErrorAndReason(
                ErrorStrings.INVALID_PAYMENT_DETAILS,
                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
        Mockito.verify(mBrowserPaymentRequest, Mockito.never())
                .onPaymentDetailsUpdated(Mockito.any(), Mockito.anyBoolean());
    }

    @Test
    @Feature({"Payments"})
    public void testOnPaymentDetailsUpdatedIsInvoked() {
        PaymentRequestService service = defaultBuilder().build();
        service.show(false, mIsUserGestureShow);
        updateWith(service);
        assertNoError();
        Mockito.verify(mBrowserPaymentRequest, Mockito.times(1))
                .onPaymentDetailsUpdated(Mockito.any(), Mockito.anyBoolean());
    }

    @Test
    @Feature({"Payments"})
    public void testNullDetailsFailsContinueShow() {
        PaymentRequestService service = defaultBuilder().build();
        service.show(true, mIsUserGestureShow);
        assertNoError();
        service.updateWith(null);
        assertErrorAndReason(ErrorStrings.INVALID_PAYMENT_DETAILS, PaymentErrorReason.USER_CANCEL);
        verifyContinuedShowWithUpdatedDetails(0);
    }

    @Test
    @Feature({"Payments"})
    public void testDetailsWithIdFailsContinueShow() {
        PaymentRequestService service = defaultBuilder().build();
        service.show(true, mIsUserGestureShow);
        assertNoError();
        PaymentDetails details = getDefaultPaymentDetailsUpdate();
        details.id = "testId";
        service.updateWith(details);
        assertErrorAndReason(ErrorStrings.INVALID_PAYMENT_DETAILS, PaymentErrorReason.USER_CANCEL);
        verifyContinuedShowWithUpdatedDetails(0);
    }

    @Test
    @Feature({"Payments"})
    public void testContinueShowIsInvoked() {
        PaymentRequestService service = defaultBuilder().build();
        service.show(true, mIsUserGestureShow);
        updateWith(service);
        assertNoError();
        verifyContinuedShowWithUpdatedDetails(1);
    }

    @Test
    @Feature({"Payments"})
    public void testCallUpdateWithBeforeShowFailsPayment() {
        PaymentRequestService service = defaultBuilder().build();
        assertNoError();

        updateWith(service);
        assertErrorAndReason(
                ErrorStrings.CANNOT_UPDATE_WITHOUT_SHOW, PaymentErrorReason.USER_CANCEL);
    }

    @Test
    @Feature({"Payments"})
    public void testCallUpdateWithWithoutRequestingAnyInfoFailsPayment() {
        PaymentRequestService service = defaultBuilder().setOptions(new PaymentOptions()).build();
        assertNoError();

        show(service);
        assertNoError();

        updateWith(service);
        assertErrorAndReason(ErrorStrings.INVALID_STATE, PaymentErrorReason.USER_CANCEL);
    }

    @Test
    @Feature({"Payments"})
    public void testCallOnPaymentDetailsNotUpdatedBeforeShowFailsPayment() {
        PaymentRequestService service = defaultBuilder().build();
        assertNoError();

        service.onPaymentDetailsNotUpdated();
        assertErrorAndReason(
                ErrorStrings.CANNOT_UPDATE_WITHOUT_SHOW, PaymentErrorReason.USER_CANCEL);
    }

    @Test
    @Feature({"Payments"})
    public void testOnConnectionErrorFailsPayment() {
        PaymentRequestService service = defaultBuilder().build();
        Assert.assertFalse(mIsOnCloseListenerInvoked);

        service.onConnectionError(null);
        Assert.assertTrue(mIsOnCloseListenerInvoked);
    }

    @Test
    @Feature({"Payments"})
    public void testNullOptionsFailsCreation() {
        Assert.assertNull(defaultBuilder().setOptions(null).build());
        assertErrorAndReason(
                ErrorStrings.INVALID_PAYMENT_OPTIONS,
                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
    }

    @Test
    @Feature({"Payments"})
    public void testSslErrorFailsCreation() {
        Assert.assertNull(
                defaultBuilder().setInvalidSslCertificateErrorMessage("StubbedError").build());
        assertErrorAndReason(
                "StubbedError", PaymentErrorReason.NOT_SUPPORTED_FOR_INVALID_ORIGIN_OR_SSL);
    }

    @Test
    @Feature({"Payments"})
    public void testDisallowedOriginFailsCreation() {
        Assert.assertNull(defaultBuilder().setOriginAllowedToUseWebPaymentApis(false).build());
        assertErrorAndReason(
                ErrorStrings.PROHIBITED_ORIGIN,
                PaymentErrorReason.NOT_SUPPORTED_FOR_INVALID_ORIGIN_OR_SSL);
    }

    @Test
    @Feature({"Payments"})
    public void testMethodDataNullElementFailsCreation() {
        PaymentMethodData[] methodData = new PaymentMethodData[1];
        Assert.assertNull(defaultBuilder().setMethodData(methodData).build());
        assertErrorAndReason(
                ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA,
                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
    }

    @Test
    @Feature({"Payments"})
    public void testEmptyMethodNameFailsCreation() {
        PaymentMethodData[] methodData = new PaymentMethodData[1];
        methodData[0] = new PaymentMethodData();
        methodData[0].supportedMethod = "";
        Assert.assertNull(defaultBuilder().setMethodData(methodData).build());
        assertErrorAndReason(
                ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA,
                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
    }

    @Test
    @Feature({"Payments"})
    public void testNullMethodNameFailsCreation() {
        PaymentMethodData[] methodData = new PaymentMethodData[1];
        methodData[0] = new PaymentMethodData();
        methodData[0].supportedMethod = null;
        Assert.assertNull(defaultBuilder().setMethodData(methodData).build());
        assertErrorAndReason(
                ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA,
                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
    }

    @Test
    @Feature({"Payments"})
    public void testInvalidDetailsFailsCreation() {
        Assert.assertNull(defaultBuilder().setIsPaymentDetailsValid(false).build());
        assertErrorAndReason(
                ErrorStrings.INVALID_PAYMENT_DETAILS,
                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
    }

    @Test
    @Feature({"Payments"})
    public void testNullRawTotalFailsCreation() {
        PaymentRequestSpec spec = Mockito.mock(PaymentRequestSpec.class);
        Mockito.doReturn(null).when(spec).getRawTotal();
        Assert.assertNull(defaultBuilder().setPaymentRequestSpec(spec).build());
        assertErrorAndReason(
                ErrorStrings.TOTAL_REQUIRED, PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
    }

    @Test
    @Feature({"Payments"})
    public void testTwoShowsFailService() {
        PaymentRequestService service = defaultBuilder().build();
        show(service);
        assertErrorAndReason(null, NO_PAYMENT_ERROR);
        assertClosed(false);
        show(service);
        assertErrorAndReason(ErrorStrings.CANNOT_SHOW_TWICE, PaymentErrorReason.USER_CANCEL);
        assertClosed(true);
    }

    @Test
    @Feature({"Payments"})
    public void testDefaultParamsMakeCreationSuccess() {
        Assert.assertNull(mPaymentAppFactoryDelegate);
        PaymentRequestService service = defaultBuilder().build();
        Assert.assertNotNull(service);
        Mockito.verify(mBrowserPaymentRequest, Mockito.times(1)).onSpecValidated(Mockito.notNull());
        assertNoError();
        Assert.assertNotNull(mPaymentAppFactoryDelegate);
    }

    @Test
    @Feature({"Payments"})
    public void testCanNotMakePaymentFailsPayment() {
        PaymentRequestService service = defaultBuilder().build();
        show(service);
        mPaymentAppFactoryDelegate.onCanMakePaymentCalculated(false);
        mPaymentAppFactoryDelegate.onPaymentAppCreated(createDefaultPaymentApp());
        mPaymentAppFactoryDelegate.onDoneCreatingPaymentApps(null);
        assertErrorAndReason(ErrorStrings.USER_CANCELLED, PaymentErrorReason.USER_CANCEL);
        assertClosed(true);
    }

    @Test
    @Feature({"Payments"})
    public void testNoPaymentAppFailsPayment() {
        PaymentRequestService service = defaultBuilder().build();
        show(service);
        mPaymentAppFactoryDelegate.onDoneCreatingPaymentApps(null);
        assertErrorAndReason(ErrorStrings.USER_CANCELLED, PaymentErrorReason.USER_CANCEL);
        assertClosed(true);
    }

    @Test
    @Feature({"Payments"})
    public void testAppSelectorIsTriggeredOnShownAndAppsQueried() {
        PaymentRequestService service = defaultBuilder().build();
        verifyShowAppSelector(0);
        show(service);
        verifyShowAppSelector(0);
        queryPaymentApps();
        verifyShowAppSelector(1);
    }

    @Test
    @Feature({"Payments"})
    public void testUiIsNotifiedOfPendingAppsOnShownAndAppsQueried() {
        PaymentRequestService service = defaultBuilder().build();
        show(service);
        Mockito.verify(mBrowserPaymentRequest, Mockito.never())
                .notifyPaymentUiOfPendingApps(Mockito.any());
        Assert.assertNull(mNotifiedPendingApps);
        queryPaymentApps();
        Mockito.verify(mBrowserPaymentRequest, Mockito.times(1))
                .notifyPaymentUiOfPendingApps(Mockito.any());
        Assert.assertEquals(1, mNotifiedPendingApps.size());
    }

    @Test
    @Feature({"Payments"})
    public void testAppSelectorIsNotTriggeredOnAppsQueriedOnly() {
        PaymentRequestService service = defaultBuilder().build();
        queryPaymentApps();
        verifyShowAppSelector(0);
        show(service);
        verifyShowAppSelector(1);
    }

    @Test
    @Feature({"Payments"})
    public void testInvokeUiSkipMethodOnShownAndAppsQueried() {
        PaymentRequestService service = defaultBuilder().build();
        show(service);
        Mockito.verify(mBrowserPaymentRequest, Mockito.never())
                .onShowCalledAndAppsQueriedAndDetailsFinalized();
        queryPaymentApps();
        Mockito.verify(mBrowserPaymentRequest, Mockito.times(1))
                .onShowCalledAndAppsQueriedAndDetailsFinalized();
    }

    @Test
    @Feature({"Payments"})
    public void testWaitingForUpdatedDetailsDeterUiSkipMethod() {
        PaymentRequestService service = defaultBuilder().build();
        service.show(true, mIsUserGestureShow);
        Mockito.verify(mBrowserPaymentRequest, Mockito.never())
                .onShowCalledAndAppsQueriedAndDetailsFinalized();
        queryPaymentApps();
        Mockito.verify(mBrowserPaymentRequest, Mockito.never())
                .onShowCalledAndAppsQueriedAndDetailsFinalized();
        updateWith(service);
        Mockito.verify(mBrowserPaymentRequest, Mockito.times(1))
                .onShowCalledAndAppsQueriedAndDetailsFinalized();
    }

    @Test
    @Feature({"Payments"})
    public void testQueryFinishCanTriggerUiSkipped() {
        PaymentRequestService service = defaultBuilder().build();
        service.show(true, mIsUserGestureShow);
        Mockito.verify(mBrowserPaymentRequest, Mockito.never())
                .onShowCalledAndAppsQueriedAndDetailsFinalized();
        updateWith(service);
        Mockito.verify(mBrowserPaymentRequest, Mockito.never())
                .onShowCalledAndAppsQueriedAndDetailsFinalized();
        queryPaymentApps();
        Mockito.verify(mBrowserPaymentRequest, Mockito.times(1))
                .onShowCalledAndAppsQueriedAndDetailsFinalized();
    }

    @Test
    @Feature({"Payments"})
    public void testCloseTeardownResources() {
        PaymentRequestService service = defaultBuilder().build();
        Mockito.verify(mBrowserPaymentRequest, Mockito.never()).close();
        assertClosed(false);
        service.close();
        Mockito.verify(mBrowserPaymentRequest, Mockito.times(1)).close();
        assertClosed(true);
    }

    @Test
    @Feature({"Payments"})
    public void testOnlyOneServiceCanBeShownGlobally() {
        PaymentRequestService service1 = defaultBuilder().build();
        show(service1);
        assertNoError();
        PaymentRequestService service2 = defaultBuilder().build();
        show(service2);
        assertErrorAndReason(ErrorStrings.ANOTHER_UI_SHOWING, PaymentErrorReason.ALREADY_SHOWING);
    }

    @Test
    @Feature({"Payments"})
    public void testSpcCanOnlyBeRequestedAlone_success() {
        Assert.assertNotNull(defaultBuilder().setOnlySpcMethodWithoutPaymentOptions().build());
    }

    @Test
    @Feature({"Payments"})
    public void testSpcCanOnlyBeRequestedAlone_failedForHavingOptions() {
        PaymentOptions options = new PaymentOptions();
        options.requestShipping = true;
        Assert.assertNull(
                defaultBuilder()
                        .setOnlySpcMethodWithoutPaymentOptions()
                        .setOptions(options)
                        .build());
        assertErrorAndReason(
                ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA,
                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
    }

    @Test
    @Feature({"Payments"})
    public void testSpcCanOnlyBeRequestedAlone_failedForNullPayeeNameAndOrigin() {
        Assert.assertNull(
                defaultBuilder()
                        .setPayeeName(null)
                        .setPayeeOrigin(null)
                        .setOnlySpcMethodWithoutPaymentOptions()
                        .build());
        assertErrorAndReason(
                ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA,
                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
    }

    @Test
    @Feature({"Payments"})
    public void testSpcCanOnlyBeRequestedAlone_allowsNullPayeeOrigin() {
        // If a valid payeeName is passed, then payeeOrigin is not needed.
        Assert.assertNotNull(
                defaultBuilder()
                        .setOnlySpcMethodWithoutPaymentOptions()
                        .setPayeeName("Merchant Shop")
                        .setPayeeOrigin(null)
                        .build());
    }

    @Test
    @Feature({"Payments"})
    public void testSpcCanOnlyBeRequestedAlone_failedForEmptyPayeeName() {
        Assert.assertNull(
                defaultBuilder().setPayeeName("").setOnlySpcMethodWithoutPaymentOptions().build());
        assertErrorAndReason(
                ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA,
                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
    }

    @Test
    @Feature({"Payments"})
    public void testSpcCanOnlyBeRequestedAlone_failedForHttpPayeeOrigin() {
        org.chromium.url.internal.mojom.Origin payeeOrigin =
                new org.chromium.url.internal.mojom.Origin();
        payeeOrigin.scheme = "http";
        payeeOrigin.host = "www.example.test";
        payeeOrigin.port = 443;
        Assert.assertNull(
                defaultBuilder()
                        .setPayeeOrigin(payeeOrigin)
                        .setOnlySpcMethodWithoutPaymentOptions()
                        .build());
        assertErrorAndReason(
                ErrorStrings.INVALID_PAYMENT_METHODS_OR_DATA,
                PaymentErrorReason.INVALID_DATA_FROM_RENDERER);
    }

    @Test
    @Feature({"Payments"})
    public void testActivationlessShow() {
        mJniMocker.mock(PaymentRequestWebContentsDataJni.TEST_HOOKS, mWebContentsDataJniMock);
        // The first show() with no user gesture is allowed.
        mIsUserGestureShow = false;
        PaymentRequestService service = defaultBuilder().setOptions(new PaymentOptions()).build();
        show(service);
        assertNoError();
        assertClosed(false);
        service.close();
        assertClosed(true);

        Mockito.verify(mWebContentsDataJniMock, Mockito.times(1))
                .recordActivationlessShow(Mockito.any());
        Mockito.doReturn(true).when(mWebContentsDataJniMock).hadActivationlessShow(Mockito.any());

        // A second show() with no user gesture is not allowed.
        service = defaultBuilder().setOptions(new PaymentOptions()).build();
        show(service);
        assertErrorAndReason(
                ErrorStrings.CANNOT_SHOW_WITHOUT_USER_ACTIVATION,
                PaymentErrorReason.USER_ACTIVATION_REQUIRED);
        assertClosed(true);
        resetErrorMessageAndCloseState();

        // A following show() with a user gesture is allowed.
        mIsUserGestureShow = true;
        service = defaultBuilder().setOptions(new PaymentOptions()).build();
        show(service);
        assertNoError();
        assertClosed(false);
        service.close();
        assertClosed(true);

        Mockito.verify(mWebContentsDataJniMock, Mockito.times(1))
                .recordActivationlessShow(Mockito.any());
    }
}