package com.stripe.wrap.pay.activity; import android.content.Intent; import android.support.annotation.NonNull; import com.google.android.gms.common.api.BooleanResult; import com.google.android.gms.common.api.CommonStatusCodes; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.Status; import com.google.android.gms.wallet.Cart; import com.google.android.gms.wallet.IsReadyToPayRequest; import com.google.android.gms.wallet.WalletConstants; import com.google.android.gms.wallet.fragment.SupportWalletFragment; import com.google.android.gms.wallet.fragment.WalletFragmentMode; import com.google.android.gms.wallet.fragment.WalletFragmentOptions; import com.stripe.android.exception.StripeException; import com.stripe.android.model.Token; import com.stripe.android.net.StripeApiHandler; import com.stripe.android.net.StripeResponse; import com.stripe.android.net.TokenParser; import com.stripe.wrap.pay.AndroidPayConfiguration; import com.stripe.wrap.pay.BuildConfig; import com.stripe.wrap.pay.testharness.TestAndroidPayActivity; import com.stripe.wrap.pay.utils.CartContentException; import com.stripe.wrap.pay.utils.CartManager; import org.json.JSONException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.android.controller.ActivityController; import org.robolectric.annotation.Config; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; /** * Test class for {@link StripeAndroidPayActivity}. Note that we have to test against SDK 22 * because of a <a href="https://github.com/robolectric/robolectric/issues/1932">known issue</a> in * Robolectric. */ @RunWith(RobolectricTestRunner.class) @Config(constants = BuildConfig.class, sdk = 22) public class StripeAndroidPayActivityTest { private static final String FUNCTIONAL_SOURCE_PUBLISHABLE_KEY = "pk_test_vOo1umqsYxSrP5UXfOeL3ecm"; private static final String RAW_TOKEN = "{\n" + " \"id\": \"tok_189fi32eZvKYlo2Ct0KZvU5Y\",\n" + " \"object\": \"token\",\n" + " \"card\": {\n" + " \"id\": \"card_189fi32eZvKYlo2CHK8NPRME\",\n" + " \"object\": \"card\",\n" + " \"address_city\": null,\n" + " \"address_country\": null,\n" + " \"address_line1\": null,\n" + " \"address_line1_check\": null,\n" + " \"address_line2\": null,\n" + " \"address_state\": null,\n" + " \"address_zip\": null,\n" + " \"address_zip_check\": null,\n" + " \"brand\": \"Visa\",\n" + " \"country\": \"US\",\n" + " \"cvc_check\": null,\n" + " \"dynamic_last4\": null,\n" + " \"exp_month\": 8,\n" + " \"exp_year\": 2017,\n" + " \"funding\": \"credit\",\n" + " \"last4\": \"4242\",\n" + " \"metadata\": {\n" + " },\n" + " \"name\": null,\n" + " \"tokenization_method\": null\n" + " },\n" + " \"client_ip\": null,\n" + " \"created\": 1462905355,\n" + " \"livemode\": false,\n" + " \"type\": \"card\",\n" + " \"used\": false\n" + "}"; @Mock TestAndroidPayActivity.AndroidPayAvailabilityChooser mAndroidPayAvailabilityChooser; @Mock GoogleApiClient mGoogleApiClient; @Mock TestAndroidPayActivity.GoogleApiClientMockBuilder mGoogleApiClientMockBuilder; @Mock TestAndroidPayActivity.StripeAndroidPayActivityListener mListener; private ActivityController<TestAndroidPayActivity> mActivityController; private CartManager mCartManager; private Cart mCart; @Before public void setup() { MockitoAnnotations.initMocks(this); when(mGoogleApiClientMockBuilder.getMockGoogleApiClient()).thenReturn(mGoogleApiClient); AndroidPayConfiguration.init(FUNCTIONAL_SOURCE_PUBLISHABLE_KEY, "USD"); mCartManager = new CartManager(); mCartManager.addLineItem("First item", 100L); mCartManager.addLineItem("Second item", 200L); when(mAndroidPayAvailabilityChooser.doesAndroidPayCheckSucceed()).thenReturn( new BooleanResult(new Status(CommonStatusCodes.SUCCESS), true)); try { mCart = mCartManager.buildCart(); Intent intent = new Intent(RuntimeEnvironment.application, TestAndroidPayActivity.class) .putExtra(StripeAndroidPayActivity.EXTRA_CART, mCart); mActivityController = Robolectric.buildActivity(TestAndroidPayActivity.class, intent); mActivityController.get().setStripeAppCompatActivityListener(mListener); mActivityController.get().setGoogleApiClientMockBuilder(mGoogleApiClientMockBuilder); mActivityController.get() .setAndroidPayAvailabilityChooser(mAndroidPayAvailabilityChooser); mActivityController.get().setTheme( android.support.v7.appcompat.R.style.Theme_AppCompat); } catch (CartContentException unexpected) { fail("Error setting up tests"); } } @Test public void onCreate_whenGooglePlaySucceedsAndIsAvailable_listenerHitsExpectedMethods() { mActivityController.create().start(); ArgumentCaptor<WalletFragmentOptions> walletFragmentOptionsCaptor = ArgumentCaptor.forClass(WalletFragmentOptions.class); verify(mListener).onBeforeAndroidPayAvailable(); verify(mListener).onAfterAndroidPayCheckComplete(); verify(mListener).getWalletEnvironment(WalletConstants.ENVIRONMENT_TEST); verify(mListener).getWalletFragmentOptions(walletFragmentOptionsCaptor.capture()); verify(mListener).getWalletTheme(WalletConstants.THEME_LIGHT); verify(mListener).addBuyButtonWalletFragment(any(SupportWalletFragment.class)); verify(mListener).verifyAndPrepareAndroidPayControls(any(IsReadyToPayRequest.class)); verifyNoMoreInteractions(mListener); WalletFragmentOptions optionsUsed = walletFragmentOptionsCaptor.getValue(); assertEquals(WalletFragmentMode.BUY_BUTTON, optionsUsed.getMode()); verify(mGoogleApiClient).connect(); } @Test public void onCreate_whenGooglePlaySucceedsAndIsNotAvailable_listenerHitsExpectedMethods() { reset(mAndroidPayAvailabilityChooser); when(mAndroidPayAvailabilityChooser.doesAndroidPayCheckSucceed()).thenReturn( new BooleanResult(new Status(CommonStatusCodes.SUCCESS), false)); mActivityController.create().start(); verify(mListener).onBeforeAndroidPayAvailable(); verify(mListener).verifyAndPrepareAndroidPayControls(any(IsReadyToPayRequest.class)); verify(mListener).onAfterAndroidPayCheckComplete(); verify(mListener).onAndroidPayNotAvailable(); verifyNoMoreInteractions(mListener); verify(mGoogleApiClient).connect(); } @Test public void onCreate_whenGooglePlayFails_listenerHitsExpectedMethods() { reset(mAndroidPayAvailabilityChooser); when(mAndroidPayAvailabilityChooser.doesAndroidPayCheckSucceed()).thenReturn( new BooleanResult(new Status(CommonStatusCodes.ERROR), false)); mActivityController.create().start(); verify(mListener).onBeforeAndroidPayAvailable(); verify(mListener).verifyAndPrepareAndroidPayControls(any(IsReadyToPayRequest.class)); verify(mListener).onAfterAndroidPayCheckComplete(); verify(mListener).onAndroidPayNotAvailable(); verifyNoMoreInteractions(mListener); verify(mGoogleApiClient).connect(); } @Test public void onStop_disconnectsGoogleApiClient() { mActivityController.create().start(); reset(mListener); reset(mGoogleApiClient); mActivityController.stop(); verify(mGoogleApiClient).disconnect(); verifyNoMoreInteractions(mGoogleApiClient); verifyZeroInteractions(mListener); } @Test public void getWalletOptions_whenUsingBuyButtonMode_returnsExpectedResult() { mActivityController.create().start(); WalletFragmentOptions options = mActivityController.get() .accessWalletFragmentOptions(WalletFragmentMode.BUY_BUTTON); assertEquals(WalletFragmentMode.BUY_BUTTON, options.getMode()); assertEquals(WalletConstants.ENVIRONMENT_TEST, options.getEnvironment()); assertEquals(WalletConstants.THEME_LIGHT, options.getTheme()); } @Test public void getWalletOptions_whenUsingSettingsMode_returnsExpectedResult() { mActivityController.create().start(); WalletFragmentOptions options = mActivityController.get() .accessWalletFragmentOptions(WalletFragmentMode.SELECTION_DETAILS); assertEquals(WalletFragmentMode.SELECTION_DETAILS, options.getMode()); assertEquals(WalletConstants.ENVIRONMENT_TEST, options.getEnvironment()); assertEquals(WalletConstants.THEME_LIGHT, options.getTheme()); } @Test public void logApiCallOnNewThread_whenPaymentMethodIsToken_logsTokenCreation() { mActivityController.create().start(); StripeApiHandler.LoggingResponseListener listener = mock(StripeApiHandler.LoggingResponseListener.class); when(listener.shouldLogTest()).thenReturn(true); ArgumentCaptor<StripeResponse> responseArgumentCaptor = ArgumentCaptor.forClass(StripeResponse.class); StripeAndroidPayActivity stripeActivity = mActivityController.get(); // For testing purposes, we work on the main thread. stripeActivity.setExecutor(new Executor() { @Override public void execute(@NonNull Runnable command) { command.run(); } }); try { Token token = TokenParser.parseToken(RAW_TOKEN); stripeActivity.logApiCallOnNewThread(token, listener); verify(listener).onLoggingResponse(responseArgumentCaptor.capture()); StripeResponse response = responseArgumentCaptor.getValue(); assertEquals(200, response.getResponseCode()); verify(listener, never()).onStripeException(any(StripeException.class)); } catch (JSONException unexpected) { fail("Test data parsing failure"); } } }