diff --git a/stripe/src/main/java/com/stripe/android/CustomerSession.java b/stripe/src/main/java/com/stripe/android/CustomerSession.java index c56e0ef5b7c..f39102555c8 100644 --- a/stripe/src/main/java/com/stripe/android/CustomerSession.java +++ b/stripe/src/main/java/com/stripe/android/CustomerSession.java @@ -12,9 +12,6 @@ import android.support.annotation.VisibleForTesting; import android.support.v4.content.LocalBroadcastManager; -import com.stripe.android.exception.APIConnectionException; -import com.stripe.android.exception.APIException; -import com.stripe.android.exception.InvalidRequestException; import com.stripe.android.exception.StripeException; import com.stripe.android.model.Customer; import com.stripe.android.model.ShippingInformation; @@ -27,11 +24,9 @@ import java.util.Calendar; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -93,10 +88,8 @@ public class CustomerSession @NonNull private final Handler mUiThreadHandler; @NonNull private final Set mProductUsageTokens; @Nullable private final Calendar mProxyNowCalendar; - @Nullable private final StripeApiProxy mStripeApiProxy; // A queue of Runnables for doing customer updates - @NonNull private final BlockingQueue mNetworkQueue = new LinkedBlockingQueue<>(); @NonNull private final ThreadPoolExecutor mThreadPoolExecutor; @NonNull private final StripeApiHandler mApiHandler; @@ -107,7 +100,7 @@ public class CustomerSession * {@link CustomerEphemeralKey EphemeralKeys} as needed */ public static void initCustomerSession(@NonNull EphemeralKeyProvider keyProvider) { - initCustomerSession(keyProvider, null, null); + setInstance(new CustomerSession(keyProvider)); } /** @@ -125,6 +118,11 @@ public static CustomerSession getInstance() { return mInstance; } + @VisibleForTesting + static void setInstance(@Nullable CustomerSession customerSession) { + mInstance = customerSession; + } + /** * End the singleton instance of a {@link CustomerSession}. * Calls to {@link CustomerSession#getInstance()} will throw an {@link IllegalStateException} @@ -135,14 +133,6 @@ public static void endCustomerSession() { clearInstance(); } - @VisibleForTesting - static void initCustomerSession( - @NonNull EphemeralKeyProvider keyProvider, - @Nullable StripeApiProxy stripeApiProxy, - @Nullable Calendar proxyNowCalendar) { - mInstance = new CustomerSession(keyProvider, stripeApiProxy, proxyNowCalendar); - } - @VisibleForTesting static void clearInstance() { if (mInstance != null) { @@ -150,7 +140,7 @@ static void clearInstance() { mInstance.mSourceRetrievalListenerRefs.clear(); } cancelCallbacks(); - mInstance = null; + setInstance(null); } /** @@ -168,22 +158,27 @@ public static void cancelCallbacks() { mInstance.mThreadPoolExecutor.shutdownNow(); } - private CustomerSession( + private CustomerSession(@NonNull EphemeralKeyProvider keyProvider) { + this(keyProvider, null, createThreadPoolExecutor(), new StripeApiHandler()); + } + + @VisibleForTesting + CustomerSession( @NonNull EphemeralKeyProvider keyProvider, - @Nullable StripeApiProxy stripeApiProxy, - @Nullable Calendar proxyNowCalendar) { - mThreadPoolExecutor = createThreadPoolExecutor(); - mUiThreadHandler = createMainThreadHandler(); - mStripeApiProxy = stripeApiProxy; + @Nullable Calendar proxyNowCalendar, + @NonNull ThreadPoolExecutor threadPoolExecutor, + @NonNull StripeApiHandler apiHandler) { + mThreadPoolExecutor = threadPoolExecutor; mProxyNowCalendar = proxyNowCalendar; mProductUsageTokens = new HashSet<>(); + mApiHandler = apiHandler; + mUiThreadHandler = createMainThreadHandler(); mEphemeralKeyManager = new EphemeralKeyManager<>( keyProvider, this, KEY_REFRESH_BUFFER_IN_SECONDS, proxyNowCalendar, CustomerEphemeralKey.class); - mApiHandler = new StripeApiHandler(); } @RestrictTo(RestrictTo.Scope.LIBRARY) @@ -202,8 +197,9 @@ public void addProductUsageTokenIfValid(@Nullable String token) { * customer, either from the cache or from the server */ public void retrieveCurrentCustomer(@NonNull CustomerRetrievalListener listener) { - if (canUseCachedCustomer()) { - listener.onCustomerRetrieved(getCachedCustomer()); + final Customer cachedCustomer = getCachedCustomer(); + if (cachedCustomer != null) { + listener.onCustomerRetrieved(cachedCustomer); } else { mCustomer = null; @@ -490,12 +486,6 @@ public void run() { } private void executeRunnable(@NonNull Runnable runnable) { - // In automation, run on the main thread. - if (mStripeApiProxy != null) { - runnable.run(); - return; - } - mThreadPoolExecutor.execute(runnable); } @@ -664,13 +654,13 @@ private void handleRetrievalError(@Nullable String operationId, } @NonNull - private ThreadPoolExecutor createThreadPoolExecutor() { + private static ThreadPoolExecutor createThreadPoolExecutor() { return new ThreadPoolExecutor( THREAD_POOL_SIZE, THREAD_POOL_SIZE, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, - mNetworkQueue); + new LinkedBlockingQueue()); } @NonNull @@ -684,26 +674,15 @@ private Source addCustomerSourceWithKey( @NonNull CustomerEphemeralKey key, @NonNull String sourceId, @NonNull @Source.SourceType String sourceType) throws StripeException { - if (mStripeApiProxy != null) { - return mStripeApiProxy.addCustomerSourceWithKey( - context, - key.getCustomerId(), - PaymentConfiguration.getInstance().getPublishableKey(), - new ArrayList<>(mProductUsageTokens), - sourceId, - sourceType, - key.getSecret()); - } else { - return mApiHandler.addCustomerSource( - context, - key.getCustomerId(), - PaymentConfiguration.getInstance().getPublishableKey(), - new ArrayList<>(mProductUsageTokens), - sourceId, - sourceType, - key.getSecret(), - null); - } + return mApiHandler.addCustomerSource( + context, + key.getCustomerId(), + PaymentConfiguration.getInstance().getPublishableKey(), + new ArrayList<>(mProductUsageTokens), + sourceId, + sourceType, + key.getSecret(), + null); } @Nullable @@ -711,24 +690,14 @@ private Source deleteCustomerSourceWithKey( @NonNull Context context, @NonNull CustomerEphemeralKey key, @NonNull String sourceId) throws StripeException { - if (mStripeApiProxy != null) { - return mStripeApiProxy.deleteCustomerSourceWithKey( - context, - key.getCustomerId(), - PaymentConfiguration.getInstance().getPublishableKey(), - new ArrayList<>(mProductUsageTokens), - sourceId, - key.getSecret()); - } else { - return mApiHandler.deleteCustomerSource( - context, - key.getCustomerId(), - PaymentConfiguration.getInstance().getPublishableKey(), - new ArrayList<>(mProductUsageTokens), - sourceId, - key.getSecret(), - null); - } + return mApiHandler.deleteCustomerSource( + context, + key.getCustomerId(), + PaymentConfiguration.getInstance().getPublishableKey(), + new ArrayList<>(mProductUsageTokens), + sourceId, + key.getSecret(), + null); } @Nullable @@ -736,24 +705,14 @@ private Customer setCustomerShippingInfoWithKey( @NonNull Context context, @NonNull CustomerEphemeralKey key, @NonNull ShippingInformation shippingInformation) throws StripeException { - if (mStripeApiProxy != null) { - return mStripeApiProxy.setCustomerShippingInfoWithKey( - context, - key.getCustomerId(), - PaymentConfiguration.getInstance().getPublishableKey(), - new ArrayList<>(mProductUsageTokens), - shippingInformation, - key.getSecret()); - } else { - return mApiHandler.setCustomerShippingInfo( - context, - key.getCustomerId(), - PaymentConfiguration.getInstance().getPublishableKey(), - new ArrayList<>(mProductUsageTokens), - shippingInformation, - key.getSecret(), - null); - } + return mApiHandler.setCustomerShippingInfo( + context, + key.getCustomerId(), + PaymentConfiguration.getInstance().getPublishableKey(), + new ArrayList<>(mProductUsageTokens), + shippingInformation, + key.getSecret(), + null); } @Nullable @@ -762,26 +721,15 @@ private Customer setCustomerSourceDefaultWithKey( @NonNull CustomerEphemeralKey key, @NonNull String sourceId, @NonNull @Source.SourceType String sourceType) throws StripeException { - if (mStripeApiProxy != null) { - return mStripeApiProxy.setDefaultCustomerSourceWithKey( - context, - key.getCustomerId(), - PaymentConfiguration.getInstance().getPublishableKey(), - new ArrayList<>(mProductUsageTokens), - sourceId, - sourceType, - key.getSecret()); - } else { - return mApiHandler.setDefaultCustomerSource( - context, - key.getCustomerId(), - PaymentConfiguration.getInstance().getPublishableKey(), - new ArrayList<>(mProductUsageTokens), - sourceId, - sourceType, - key.getSecret(), - null); - } + return mApiHandler.setDefaultCustomerSource( + context, + key.getCustomerId(), + PaymentConfiguration.getInstance().getPublishableKey(), + new ArrayList<>(mProductUsageTokens), + sourceId, + sourceType, + key.getSecret(), + null); } /** @@ -796,11 +744,7 @@ private Customer setCustomerSourceDefaultWithKey( @Nullable private Customer retrieveCustomerWithKey(@NonNull CustomerEphemeralKey key) throws StripeException { - if (mStripeApiProxy != null) { - return mStripeApiProxy.retrieveCustomerWithKey(key.getCustomerId(), key.getSecret()); - } else { - return mApiHandler.retrieveCustomer(key.getCustomerId(), key.getSecret()); - } + return mApiHandler.retrieveCustomer(key.getCustomerId(), key.getSecret()); } private void sendErrorIntent(@NonNull StripeException exception) { @@ -841,51 +785,6 @@ void onError(int errorCode, @Nullable String errorMessage, @Nullable StripeError stripeError); } - interface StripeApiProxy { - @Nullable Customer retrieveCustomerWithKey(@NonNull String customerId, - @NonNull String secret) - throws InvalidRequestException, APIConnectionException, APIException; - - @Nullable Source addCustomerSourceWithKey( - @Nullable Context context, - @NonNull String customerId, - @NonNull String publicKey, - @NonNull List productUsageTokens, - @NonNull String sourceId, - @NonNull String sourceType, - @NonNull String secret) - throws InvalidRequestException, APIConnectionException, APIException; - - @Nullable Source deleteCustomerSourceWithKey( - @Nullable Context context, - @NonNull String customerId, - @NonNull String publicKey, - @NonNull List productUsageTokens, - @NonNull String sourceId, - @NonNull String secret) - throws InvalidRequestException, APIConnectionException, APIException; - - @Nullable Customer setDefaultCustomerSourceWithKey( - @Nullable Context context, - @NonNull String customerId, - @NonNull String publicKey, - @NonNull List productUsageTokens, - @NonNull String sourceId, - @NonNull String sourceType, - @NonNull String secret) - throws InvalidRequestException, APIConnectionException, APIException; - - - @Nullable Customer setCustomerShippingInfoWithKey( - @Nullable Context context, - @NonNull String customerId, - @NonNull String publicKey, - @NonNull List productUsageTokens, - @NonNull ShippingInformation shippingInformation, - @NonNull String secret) - throws InvalidRequestException, APIConnectionException, APIException; - } - private static class CustomerMessage { @Nullable private final String operationId; @Nullable private final Customer customer; diff --git a/stripe/src/test/java/com/stripe/android/CustomerSessionTest.java b/stripe/src/test/java/com/stripe/android/CustomerSessionTest.java index f154b56fb04..73e81935d3a 100644 --- a/stripe/src/test/java/com/stripe/android/CustomerSessionTest.java +++ b/stripe/src/test/java/com/stripe/android/CustomerSessionTest.java @@ -5,14 +5,16 @@ import android.content.Intent; import android.content.IntentFilter; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.content.LocalBroadcastManager; import androidx.test.core.app.ApplicationProvider; import com.stripe.android.exception.APIConnectionException; import com.stripe.android.exception.APIException; +import com.stripe.android.exception.AuthenticationException; +import com.stripe.android.exception.CardException; import com.stripe.android.exception.InvalidRequestException; -import com.stripe.android.exception.StripeException; import com.stripe.android.model.Customer; import com.stripe.android.model.ShippingInformation; import com.stripe.android.model.Source; @@ -31,6 +33,8 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; @@ -39,6 +43,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import static com.stripe.android.PaymentSession.PAYMENT_SESSION_CONFIG; @@ -51,6 +56,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -146,9 +152,14 @@ public class CustomerSessionTest { " }\n" + "}"; + private static final Customer FIRST_CUSTOMER = + Customer.fromString(FIRST_TEST_CUSTOMER_OBJECT); + private static final Customer SECOND_CUSTOMER = + Customer.fromString(SECOND_TEST_CUSTOMER_OBJECT); @Mock private BroadcastReceiver mBroadcastReceiver; - @Mock private CustomerSession.StripeApiProxy mStripeApiProxy; + @Mock private StripeApiHandler mApiHandler; + @Mock private ThreadPoolExecutor mThreadPoolExecutor; @Captor private ArgumentCaptor> mListArgumentCaptor; @Captor private ArgumentCaptor mSourceArgumentCaptor; @@ -157,8 +168,6 @@ public class CustomerSessionTest { private TestEphemeralKeyProvider mEphemeralKeyProvider; - private Customer mFirstCustomer; - private Customer mSecondCustomer; private Source mAddedSource; private final Context mContext = ApplicationProvider.getApplicationContext(); @@ -173,7 +182,9 @@ private CustomerEphemeralKey getCustomerEphemeralKey(@NonNull String key) { } @Before - public void setup() throws APIException, APIConnectionException, InvalidRequestException { + public void setup() + throws APIException, APIConnectionException, InvalidRequestException, + AuthenticationException, CardException { MockitoAnnotations.initMocks(this); PaymentConfiguration.init("pk_test_abc123"); @@ -181,44 +192,53 @@ public void setup() throws APIException, APIConnectionException, InvalidRequestE mBroadcastReceiver, new IntentFilter(CustomerSession.ACTION_API_EXCEPTION)); - mFirstCustomer = Customer.fromString(FIRST_TEST_CUSTOMER_OBJECT); - assertNotNull(mFirstCustomer); - mSecondCustomer = Customer.fromString(SECOND_TEST_CUSTOMER_OBJECT); - assertNotNull(mSecondCustomer); + assertNotNull(FIRST_CUSTOMER); + assertNotNull(SECOND_CUSTOMER); mEphemeralKeyProvider = new TestEphemeralKeyProvider(); mAddedSource = Source.fromString(CardInputTestActivity.EXAMPLE_JSON_CARD_SOURCE); assertNotNull(mAddedSource); - when(mStripeApiProxy.retrieveCustomerWithKey(anyString(), anyString())) - .thenReturn(mFirstCustomer, mSecondCustomer); - when(mStripeApiProxy.addCustomerSourceWithKey( + when(mApiHandler.retrieveCustomer(anyString(), anyString())) + .thenReturn(FIRST_CUSTOMER, SECOND_CUSTOMER); + when(mApiHandler.addCustomerSource( eq(mContext), anyString(), anyString(), ArgumentMatchers.anyList(), anyString(), anyString(), - anyString())) + anyString(), + ArgumentMatchers.isNull())) .thenReturn(mAddedSource); - when(mStripeApiProxy.deleteCustomerSourceWithKey( + when(mApiHandler.deleteCustomerSource( eq(mContext), anyString(), anyString(), ArgumentMatchers.anyList(), anyString(), - anyString())) + anyString(), + ArgumentMatchers.isNull())) .thenReturn(Source.fromString(CardInputTestActivity.EXAMPLE_JSON_CARD_SOURCE)); - when(mStripeApiProxy.setDefaultCustomerSourceWithKey( + when(mApiHandler.setDefaultCustomerSource( eq(mContext), anyString(), anyString(), ArgumentMatchers.anyList(), anyString(), anyString(), - anyString())) - .thenReturn(mSecondCustomer); + anyString(), + ArgumentMatchers.isNull())) + .thenReturn(SECOND_CUSTOMER); + + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + invocation.getArgument(0).run(); + return null; + } + }).when(mThreadPoolExecutor).execute(any(Runnable.class)); } @After @@ -237,91 +257,79 @@ public void getInstance_withoutInitializing_throwsException() { @Test public void addProductUsageTokenIfValid_whenValid_addsExpectedTokens() { - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - null); - CustomerSession.getInstance().addProductUsageTokenIfValid("AddSourceActivity"); + final CustomerSession customerSession = createCustomerSession(null); + customerSession.addProductUsageTokenIfValid("AddSourceActivity"); List expectedTokens = new ArrayList<>(); expectedTokens.add("AddSourceActivity"); - List actualTokens = - new ArrayList<>(CustomerSession.getInstance().getProductUsageTokens()); + JsonTestUtils.assertListEquals(expectedTokens, + new ArrayList<>(customerSession.getProductUsageTokens())); - JsonTestUtils.assertListEquals(expectedTokens, actualTokens); - - CustomerSession.getInstance().addProductUsageTokenIfValid("PaymentMethodsActivity"); + customerSession.addProductUsageTokenIfValid("PaymentMethodsActivity"); expectedTokens.add("PaymentMethodsActivity"); - actualTokens = new ArrayList<>(CustomerSession.getInstance().getProductUsageTokens()); - JsonTestUtils.assertListEquals(expectedTokens, actualTokens); + JsonTestUtils.assertListEquals(expectedTokens, + new ArrayList<>(customerSession.getProductUsageTokens())); } @Test public void addProductUsageTokenIfValid_whenNotValid_addsNoTokens() { - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - null); - CustomerSession.getInstance().addProductUsageTokenIfValid("SomeUnknownActivity"); + final CustomerSession customerSession = createCustomerSession(null); + customerSession.addProductUsageTokenIfValid("SomeUnknownActivity"); JsonTestUtils.assertListEquals(Collections.EMPTY_LIST, - new ArrayList<>(CustomerSession.getInstance().getProductUsageTokens())); + new ArrayList<>(customerSession.getProductUsageTokens())); } @Test - public void create_withoutInvokingFunctions_fetchesKeyAndCustomer() { + public void create_withoutInvokingFunctions_fetchesKeyAndCustomer() + throws CardException, APIException, InvalidRequestException, AuthenticationException, + APIConnectionException { final CustomerEphemeralKey firstKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); assertNotNull(firstKey); mEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); - CustomerSession.initCustomerSession(mEphemeralKeyProvider, mStripeApiProxy, null); - final CustomerSession session = CustomerSession.getInstance(); + final CustomerSession customerSession = createCustomerSession(null); - try { - verify(mStripeApiProxy).retrieveCustomerWithKey( - firstKey.getCustomerId(), firstKey.getSecret()); - assertNotNull(session.getCustomer()); - assertEquals(mFirstCustomer.getId(), session.getCustomer().getId()); - } catch (StripeException unexpected) { - fail(unexpected.getMessage()); - } + verify(mApiHandler).retrieveCustomer(firstKey.getCustomerId(), firstKey.getSecret()); + assertNotNull(customerSession.getCustomer()); + assertNotNull(FIRST_CUSTOMER); + assertEquals(FIRST_CUSTOMER.getId(), customerSession.getCustomer().getId()); } @Test - public void setCustomerShippingInfo_withValidInfo_callsWithExpectedArgs(){ - CustomerEphemeralKey firstKey = Objects.requireNonNull( + public void setCustomerShippingInfo_withValidInfo_callsWithExpectedArgs() + throws CardException, APIException, InvalidRequestException, AuthenticationException, + APIConnectionException { + final CustomerEphemeralKey firstKey = Objects.requireNonNull( getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW)); mEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); Calendar proxyCalendar = Calendar.getInstance(); - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - proxyCalendar); - CustomerSession session = CustomerSession.getInstance(); - session.addProductUsageTokenIfValid("PaymentMethodsActivity"); + final CustomerSession customerSession = createCustomerSession(proxyCalendar); + customerSession.addProductUsageTokenIfValid("PaymentMethodsActivity"); Customer customerWithShippingInfo = Objects .requireNonNull(Customer.fromString(FIRST_TEST_CUSTOMER_OBJECT_WITH_SHIPPING_INFO)); ShippingInformation shippingInformation = Objects.requireNonNull(customerWithShippingInfo .getShippingInformation()); - session.setCustomerShippingInformation(mContext, shippingInformation); - try { - verify(mStripeApiProxy).setCustomerShippingInfoWithKey( - eq(mContext), - eq(mFirstCustomer.getId()), - eq("pk_test_abc123"), - mListArgumentCaptor.capture(), - eq(shippingInformation), - eq(firstKey.getSecret())); - List productUsage = mListArgumentCaptor.getValue(); - assertTrue(productUsage.contains("PaymentMethodsActivity")); - } catch (StripeException unexpected) { - fail(unexpected.getMessage()); - } + customerSession.setCustomerShippingInformation(mContext, shippingInformation); + + assertNotNull(FIRST_CUSTOMER); + assertNotNull(FIRST_CUSTOMER.getId()); + verify(mApiHandler).setCustomerShippingInfo( + eq(mContext), + eq(FIRST_CUSTOMER.getId()), + eq("pk_test_abc123"), + mListArgumentCaptor.capture(), + eq(shippingInformation), + eq(firstKey.getSecret()), + ArgumentMatchers.isNull()); + assertTrue(mListArgumentCaptor.getValue().contains("PaymentMethodsActivity")); } @Test - public void retrieveCustomer_withExpiredCache_updatesCustomer() { + public void retrieveCustomer_withExpiredCache_updatesCustomer() + throws CardException, APIException, InvalidRequestException, + AuthenticationException, APIConnectionException { CustomerEphemeralKey firstKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); assertNotNull(firstKey); @@ -335,14 +343,11 @@ public void retrieveCustomer_withExpiredCache_updatesCustomer() { assertTrue(proxyCalendar.getTimeInMillis() > 0); mEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - proxyCalendar); - final CustomerSession session = CustomerSession.getInstance(); - assertEquals(firstKey.getCustomerId(), mFirstCustomer.getId()); - - long firstCustomerCacheTime = session.getCustomerCacheTime(); + final CustomerSession customerSession = createCustomerSession(proxyCalendar); + assertNotNull(FIRST_CUSTOMER); + assertEquals(firstKey.getCustomerId(), FIRST_CUSTOMER.getId()); + + long firstCustomerCacheTime = customerSession.getCustomerCacheTime(); assertEquals(firstExpiryTimeInMillis - 100L, firstCustomerCacheTime); long timeForCustomerToExpire = TimeUnit.MINUTES.toMillis(2); @@ -357,29 +362,26 @@ public void retrieveCustomer_withExpiredCache_updatesCustomer() { // because the first one was expired. CustomerSession.CustomerRetrievalListener mockListener = mock(CustomerSession.CustomerRetrievalListener.class); - session.retrieveCurrentCustomer(mockListener); + customerSession.retrieveCurrentCustomer(mockListener); verify(mockListener).onCustomerRetrieved(mCustomerArgumentCaptor.capture()); final Customer capturedCustomer = mCustomerArgumentCaptor.getValue(); assertNotNull(capturedCustomer); - assertEquals(mSecondCustomer.getId(), capturedCustomer.getId()); - assertNotNull(session.getCustomer()); + assertNotNull(SECOND_CUSTOMER); + assertEquals(SECOND_CUSTOMER.getId(), capturedCustomer.getId()); + assertNotNull(customerSession.getCustomer()); // Make sure the value is cached. - assertEquals(mSecondCustomer.getId(), session.getCustomer().getId()); + assertEquals(SECOND_CUSTOMER.getId(), customerSession.getCustomer().getId()); - try { - verify(mStripeApiProxy).retrieveCustomerWithKey( - firstKey.getCustomerId(), firstKey.getSecret()); - verify(mStripeApiProxy).retrieveCustomerWithKey( - secondKey.getCustomerId(), secondKey.getSecret()); - } catch (StripeException unexpected) { - fail(unexpected.getMessage()); - } + verify(mApiHandler).retrieveCustomer(firstKey.getCustomerId(), firstKey.getSecret()); + verify(mApiHandler).retrieveCustomer(secondKey.getCustomerId(), secondKey.getSecret()); } @Test - public void retrieveCustomer_withUnExpiredCache_returnsCustomerWithoutHittingApi() { - CustomerEphemeralKey firstKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); + public void retrieveCustomer_withUnExpiredCache_returnsCustomerWithoutHittingApi() + throws CardException, APIException, InvalidRequestException, AuthenticationException, + APIConnectionException { + final CustomerEphemeralKey firstKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); assertNotNull(firstKey); Calendar proxyCalendar = Calendar.getInstance(); @@ -391,25 +393,17 @@ public void retrieveCustomer_withUnExpiredCache_returnsCustomerWithoutHittingApi assertTrue(proxyCalendar.getTimeInMillis() > 0); mEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - proxyCalendar); - CustomerSession session = CustomerSession.getInstance(); + final CustomerSession customerSession = createCustomerSession(proxyCalendar); // Make sure we're in a good state and that we have the expected customer - assertNotNull(session.getCustomer()); - assertEquals(firstKey.getCustomerId(), mFirstCustomer.getId()); - assertEquals(firstKey.getCustomerId(), session.getCustomer().getId()); + assertNotNull(customerSession.getCustomer()); + assertNotNull(FIRST_CUSTOMER); + assertEquals(firstKey.getCustomerId(), FIRST_CUSTOMER.getId()); + assertEquals(firstKey.getCustomerId(), customerSession.getCustomer().getId()); - try { - verify(mStripeApiProxy).retrieveCustomerWithKey( - firstKey.getCustomerId(), firstKey.getSecret()); - } catch (StripeException unexpected) { - fail(unexpected.getMessage()); - } + verify(mApiHandler).retrieveCustomer(firstKey.getCustomerId(), firstKey.getSecret()); - long firstCustomerCacheTime = session.getCustomerCacheTime(); + long firstCustomerCacheTime = customerSession.getCustomerCacheTime(); long shortIntervalInMilliseconds = 10L; proxyCalendar.setTimeInMillis(firstCustomerCacheTime + shortIntervalInMilliseconds); @@ -420,21 +414,23 @@ public void retrieveCustomer_withUnExpiredCache_returnsCustomerWithoutHittingApi // because the first one was expired. CustomerSession.CustomerRetrievalListener mockListener = mock(CustomerSession.CustomerRetrievalListener.class); - session.retrieveCurrentCustomer(mockListener); + customerSession.retrieveCurrentCustomer(mockListener); verify(mockListener).onCustomerRetrieved(mCustomerArgumentCaptor.capture()); Customer capturedCustomer = mCustomerArgumentCaptor.getValue(); assertNotNull(capturedCustomer); - assertEquals(mFirstCustomer.getId(), capturedCustomer.getId()); - assertNotNull(session.getCustomer()); + assertEquals(FIRST_CUSTOMER.getId(), capturedCustomer.getId()); + assertNotNull(customerSession.getCustomer()); // Make sure the value is cached. - assertEquals(mFirstCustomer.getId(), session.getCustomer().getId()); - verifyNoMoreInteractions(mStripeApiProxy); + assertEquals(FIRST_CUSTOMER.getId(), customerSession.getCustomer().getId()); + verifyNoMoreInteractions(mApiHandler); } @Test - public void addSourceToCustomer_withUnExpiredCustomer_returnsAddedSourceAndEmptiesLogs() { + public void addSourceToCustomer_withUnExpiredCustomer_returnsAddedSourceAndEmptiesLogs() + throws CardException, APIException, InvalidRequestException, AuthenticationException, + APIConnectionException { CustomerEphemeralKey firstKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); assertNotNull(firstKey); @@ -447,46 +443,41 @@ public void addSourceToCustomer_withUnExpiredCustomer_returnsAddedSourceAndEmpti assertTrue(proxyCalendar.getTimeInMillis() > 0); mEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - proxyCalendar); - CustomerSession session = CustomerSession.getInstance(); - session.addProductUsageTokenIfValid("AddSourceActivity"); - session.addProductUsageTokenIfValid("PaymentMethodsActivity"); - - long firstCustomerCacheTime = session.getCustomerCacheTime(); + final CustomerSession customerSession = createCustomerSession(proxyCalendar); + customerSession.addProductUsageTokenIfValid("AddSourceActivity"); + customerSession.addProductUsageTokenIfValid("PaymentMethodsActivity"); + + long firstCustomerCacheTime = customerSession.getCustomerCacheTime(); long shortIntervalInMilliseconds = 10L; - CustomerSession.getInstance().addProductUsageTokenIfValid("AddSourceActivity"); + customerSession.addProductUsageTokenIfValid("AddSourceActivity"); proxyCalendar.setTimeInMillis(firstCustomerCacheTime + shortIntervalInMilliseconds); assertEquals(firstCustomerCacheTime + shortIntervalInMilliseconds, proxyCalendar.getTimeInMillis()); CustomerSession.SourceRetrievalListener mockListener = mock(CustomerSession.SourceRetrievalListener.class); - session.addCustomerSource(mContext, + customerSession.addCustomerSource(mContext, "abc123", Source.CARD, mockListener); - assertTrue(CustomerSession.getInstance().getProductUsageTokens().isEmpty()); - try { - verify(mStripeApiProxy).addCustomerSourceWithKey( - eq(mContext), - eq(mFirstCustomer.getId()), - eq("pk_test_abc123"), - mListArgumentCaptor.capture(), - eq("abc123"), - eq(Source.CARD), - eq(firstKey.getSecret())); - List productUsage = mListArgumentCaptor.getValue(); - assertEquals(2, productUsage.size()); - assertTrue(productUsage.contains("AddSourceActivity")); - assertTrue(productUsage.contains("PaymentMethodsActivity")); - } catch (StripeException unexpected) { - fail(unexpected.getMessage()); - } + assertTrue(customerSession.getProductUsageTokens().isEmpty()); + assertNotNull(FIRST_CUSTOMER); + assertNotNull(FIRST_CUSTOMER.getId()); + verify(mApiHandler).addCustomerSource( + eq(mContext), + eq(FIRST_CUSTOMER.getId()), + eq("pk_test_abc123"), + mListArgumentCaptor.capture(), + eq("abc123"), + eq(Source.CARD), + eq(firstKey.getSecret()), + ArgumentMatchers.isNull()); + final List productUsage = mListArgumentCaptor.getValue(); + assertEquals(2, productUsage.size()); + assertTrue(productUsage.contains("AddSourceActivity")); + assertTrue(productUsage.contains("PaymentMethodsActivity")); verify(mockListener).onSourceRetrieved(mSourceArgumentCaptor.capture()); final Source capturedSource = mSourceArgumentCaptor.getValue(); @@ -496,7 +487,8 @@ public void addSourceToCustomer_withUnExpiredCustomer_returnsAddedSourceAndEmpti @Test public void addSourceToCustomer_whenApiThrowsError_tellsListenerBroadcastsAndEmptiesLogs() - throws APIException, APIConnectionException, InvalidRequestException { + throws APIException, APIConnectionException, InvalidRequestException, + AuthenticationException, CardException { CustomerEphemeralKey firstKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); assertNotNull(firstKey); @@ -509,16 +501,12 @@ public void addSourceToCustomer_whenApiThrowsError_tellsListenerBroadcastsAndEmp assertTrue(proxyCalendar.getTimeInMillis() > 0); mEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - proxyCalendar); - CustomerSession session = CustomerSession.getInstance(); - session.addProductUsageTokenIfValid("AddSourceActivity"); - session.addProductUsageTokenIfValid("PaymentMethodsActivity"); - assertFalse(session.getProductUsageTokens().isEmpty()); - - long firstCustomerCacheTime = session.getCustomerCacheTime(); + final CustomerSession customerSession = createCustomerSession(proxyCalendar); + customerSession.addProductUsageTokenIfValid("AddSourceActivity"); + customerSession.addProductUsageTokenIfValid("PaymentMethodsActivity"); + assertFalse(customerSession.getProductUsageTokens().isEmpty()); + + long firstCustomerCacheTime = customerSession.getCustomerCacheTime(); long shortIntervalInMilliseconds = 10L; proxyCalendar.setTimeInMillis(firstCustomerCacheTime + shortIntervalInMilliseconds); @@ -528,7 +516,7 @@ public void addSourceToCustomer_whenApiThrowsError_tellsListenerBroadcastsAndEmp mock(CustomerSession.SourceRetrievalListener.class); setupErrorProxy(); - session.addCustomerSource(mContext, + customerSession.addCustomerSource(mContext, "abc123", Source.CARD, mockListener); @@ -542,12 +530,15 @@ public void addSourceToCustomer_whenApiThrowsError_tellsListenerBroadcastsAndEmp captured.getSerializableExtra(CustomerSession.EXTRA_EXCEPTION); assertNotNull(ex); - verify(mockListener).onError(404, "The card is invalid", null); - assertTrue(session.getProductUsageTokens().isEmpty()); + verify(mockListener) + .onError(404, "The card is invalid", null); + assertTrue(customerSession.getProductUsageTokens().isEmpty()); } @Test - public void removeSourceFromCustomer_withUnExpiredCustomer_returnsRemovedSourceAndEmptiesLogs() { + public void removeSourceFromCustomer_withUnExpiredCustomer_returnsRemovedSourceAndEmptiesLogs() + throws CardException, APIException, InvalidRequestException, AuthenticationException, + APIConnectionException { CustomerEphemeralKey firstKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); assertNotNull(firstKey); @@ -560,44 +551,39 @@ public void removeSourceFromCustomer_withUnExpiredCustomer_returnsRemovedSourceA assertTrue(proxyCalendar.getTimeInMillis() > 0); mEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - proxyCalendar); - CustomerSession session = CustomerSession.getInstance(); - session.addProductUsageTokenIfValid("AddSourceActivity"); - session.addProductUsageTokenIfValid("PaymentMethodsActivity"); - - long firstCustomerCacheTime = session.getCustomerCacheTime(); + final CustomerSession customerSession = createCustomerSession(proxyCalendar); + customerSession.addProductUsageTokenIfValid("AddSourceActivity"); + customerSession.addProductUsageTokenIfValid("PaymentMethodsActivity"); + + long firstCustomerCacheTime = customerSession.getCustomerCacheTime(); long shortIntervalInMilliseconds = 10L; - CustomerSession.getInstance().addProductUsageTokenIfValid("AddSourceActivity"); + customerSession.addProductUsageTokenIfValid("AddSourceActivity"); proxyCalendar.setTimeInMillis(firstCustomerCacheTime + shortIntervalInMilliseconds); assertEquals(firstCustomerCacheTime + shortIntervalInMilliseconds, proxyCalendar.getTimeInMillis()); CustomerSession.SourceRetrievalListener mockListener = mock(CustomerSession.SourceRetrievalListener.class); - session.deleteCustomerSource(mContext, + customerSession.deleteCustomerSource(mContext, "abc123", mockListener); - assertTrue(CustomerSession.getInstance().getProductUsageTokens().isEmpty()); - try { - verify(mStripeApiProxy).deleteCustomerSourceWithKey( - eq(mContext), - eq(mFirstCustomer.getId()), - eq("pk_test_abc123"), - mListArgumentCaptor.capture(), - eq("abc123"), - eq(firstKey.getSecret())); - List productUsage = mListArgumentCaptor.getValue(); - assertEquals(2, productUsage.size()); - assertTrue(productUsage.contains("AddSourceActivity")); - assertTrue(productUsage.contains("PaymentMethodsActivity")); - } catch (StripeException unexpected) { - fail(unexpected.getMessage()); - } + assertTrue(customerSession.getProductUsageTokens().isEmpty()); + assertNotNull(FIRST_CUSTOMER); + assertNotNull(FIRST_CUSTOMER.getId()); + verify(mApiHandler).deleteCustomerSource( + eq(mContext), + eq(FIRST_CUSTOMER.getId()), + eq("pk_test_abc123"), + mListArgumentCaptor.capture(), + eq("abc123"), + eq(firstKey.getSecret()), + ArgumentMatchers.isNull()); + final List productUsage = mListArgumentCaptor.getValue(); + assertEquals(2, productUsage.size()); + assertTrue(productUsage.contains("AddSourceActivity")); + assertTrue(productUsage.contains("PaymentMethodsActivity")); verify(mockListener).onSourceRetrieved(mSourceArgumentCaptor.capture()); final Source capturedSource = mSourceArgumentCaptor.getValue(); @@ -607,7 +593,8 @@ public void removeSourceFromCustomer_withUnExpiredCustomer_returnsRemovedSourceA @Test public void removeSourceFromCustomer_whenApiThrowsError_tellsListenerBroadcastsAndEmptiesLogs() - throws APIException, APIConnectionException, InvalidRequestException { + throws APIException, APIConnectionException, InvalidRequestException, + AuthenticationException, CardException { CustomerEphemeralKey firstKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); assertNotNull(firstKey); @@ -620,16 +607,12 @@ public void removeSourceFromCustomer_whenApiThrowsError_tellsListenerBroadcastsA assertTrue(proxyCalendar.getTimeInMillis() > 0); mEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - proxyCalendar); - CustomerSession session = CustomerSession.getInstance(); - session.addProductUsageTokenIfValid("AddSourceActivity"); - session.addProductUsageTokenIfValid("PaymentMethodsActivity"); - assertFalse(session.getProductUsageTokens().isEmpty()); - - long firstCustomerCacheTime = session.getCustomerCacheTime(); + final CustomerSession customerSession = createCustomerSession(proxyCalendar); + customerSession.addProductUsageTokenIfValid("AddSourceActivity"); + customerSession.addProductUsageTokenIfValid("PaymentMethodsActivity"); + assertFalse(customerSession.getProductUsageTokens().isEmpty()); + + long firstCustomerCacheTime = customerSession.getCustomerCacheTime(); long shortIntervalInMilliseconds = 10L; proxyCalendar.setTimeInMillis(firstCustomerCacheTime + shortIntervalInMilliseconds); @@ -639,7 +622,7 @@ public void removeSourceFromCustomer_whenApiThrowsError_tellsListenerBroadcastsA mock(CustomerSession.SourceRetrievalListener.class); setupErrorProxy(); - session.deleteCustomerSource(mContext, "abc123", mockListener); + customerSession.deleteCustomerSource(mContext, "abc123", mockListener); verify(mBroadcastReceiver).onReceive(any(Context.class), mIntentArgumentCaptor.capture()); @@ -650,12 +633,15 @@ public void removeSourceFromCustomer_whenApiThrowsError_tellsListenerBroadcastsA captured.getSerializableExtra(CustomerSession.EXTRA_EXCEPTION); assertNotNull(ex); - verify(mockListener).onError(404, "The card does not exist", null); - assertTrue(session.getProductUsageTokens().isEmpty()); + verify(mockListener) + .onError(404,"The card does not exist", null); + assertTrue(customerSession.getProductUsageTokens().isEmpty()); } @Test - public void setDefaultSourceForCustomer_withUnExpiredCustomer_returnsCustomerAndClearsLog() { + public void setDefaultSourceForCustomer_withUnExpiredCustomer_returnsCustomerAndClearsLog() + throws CardException, APIException, InvalidRequestException, AuthenticationException, + APIConnectionException { CustomerEphemeralKey firstKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); assertNotNull(firstKey); @@ -668,15 +654,11 @@ public void setDefaultSourceForCustomer_withUnExpiredCustomer_returnsCustomerAnd assertTrue(proxyCalendar.getTimeInMillis() > 0); mEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - proxyCalendar); - CustomerSession session = CustomerSession.getInstance(); - session.addProductUsageTokenIfValid("PaymentMethodsActivity"); - assertFalse(session.getProductUsageTokens().isEmpty()); - - long firstCustomerCacheTime = session.getCustomerCacheTime(); + final CustomerSession customerSession = createCustomerSession(proxyCalendar); + customerSession.addProductUsageTokenIfValid("PaymentMethodsActivity"); + assertFalse(customerSession.getProductUsageTokens().isEmpty()); + + long firstCustomerCacheTime = customerSession.getCustomerCacheTime(); long shortIntervalInMilliseconds = 10L; proxyCalendar.setTimeInMillis(firstCustomerCacheTime + shortIntervalInMilliseconds); @@ -685,37 +667,39 @@ public void setDefaultSourceForCustomer_withUnExpiredCustomer_returnsCustomerAnd CustomerSession.CustomerRetrievalListener mockListener = mock(CustomerSession.CustomerRetrievalListener.class); - session.setCustomerDefaultSource(mContext, + customerSession.setCustomerDefaultSource(mContext, "abc123", Source.CARD, mockListener); - assertTrue(session.getProductUsageTokens().isEmpty()); - try { - verify(mStripeApiProxy).setDefaultCustomerSourceWithKey( - eq(mContext), - eq(mFirstCustomer.getId()), - eq("pk_test_abc123"), - mListArgumentCaptor.capture(), - eq("abc123"), - eq(Source.CARD), - eq(firstKey.getSecret())); - List productUsage = mListArgumentCaptor.getValue(); - assertEquals(1, productUsage.size()); - assertTrue(productUsage.contains("PaymentMethodsActivity")); - } catch (StripeException unexpected) { - fail(unexpected.getMessage()); - } + assertTrue(customerSession.getProductUsageTokens().isEmpty()); + assertNotNull(FIRST_CUSTOMER); + assertNotNull(FIRST_CUSTOMER.getId()); + verify(mApiHandler).setDefaultCustomerSource( + eq(mContext), + eq(FIRST_CUSTOMER.getId()), + eq("pk_test_abc123"), + mListArgumentCaptor.capture(), + eq("abc123"), + eq(Source.CARD), + eq(firstKey.getSecret()), + ArgumentMatchers.isNull()); + + final List productUsage = mListArgumentCaptor.getValue(); + assertEquals(1, productUsage.size()); + assertTrue(productUsage.contains("PaymentMethodsActivity")); verify(mockListener).onCustomerRetrieved(mCustomerArgumentCaptor.capture()); - Customer capturedCustomer = mCustomerArgumentCaptor.getValue(); + final Customer capturedCustomer = mCustomerArgumentCaptor.getValue(); assertNotNull(capturedCustomer); - assertEquals(mSecondCustomer.getId(), capturedCustomer.getId()); + assertNotNull(SECOND_CUSTOMER); + assertEquals(SECOND_CUSTOMER.getId(), capturedCustomer.getId()); } @Test public void setDefaultSourceForCustomer_whenApiThrows_tellsListenerBroadcastsAndClearsLogs() - throws APIException, APIConnectionException, InvalidRequestException { + throws APIException, APIConnectionException, InvalidRequestException, + AuthenticationException, CardException { CustomerEphemeralKey firstKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); assertNotNull(firstKey); @@ -728,14 +712,10 @@ public void setDefaultSourceForCustomer_whenApiThrows_tellsListenerBroadcastsAnd assertTrue(proxyCalendar.getTimeInMillis() > 0); mEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - proxyCalendar); - CustomerSession session = CustomerSession.getInstance(); - session.addProductUsageTokenIfValid("PaymentMethodsActivity"); - - long firstCustomerCacheTime = session.getCustomerCacheTime(); + final CustomerSession customerSession = createCustomerSession(proxyCalendar); + customerSession.addProductUsageTokenIfValid("PaymentMethodsActivity"); + + long firstCustomerCacheTime = customerSession.getCustomerCacheTime(); long shortIntervalInMilliseconds = 10L; proxyCalendar.setTimeInMillis(firstCustomerCacheTime + shortIntervalInMilliseconds); @@ -745,9 +725,10 @@ public void setDefaultSourceForCustomer_whenApiThrows_tellsListenerBroadcastsAnd mock(CustomerSession.CustomerRetrievalListener.class); setupErrorProxy(); - session.setCustomerDefaultSource(mContext, "abc123", Source.CARD, mockListener); + customerSession.setCustomerDefaultSource(mContext, "abc123", Source.CARD, + mockListener); - ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); + final ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mBroadcastReceiver).onReceive(any(Context.class), intentArgumentCaptor.capture()); @@ -758,77 +739,81 @@ public void setDefaultSourceForCustomer_whenApiThrows_tellsListenerBroadcastsAnd captured.getSerializableExtra(CustomerSession.EXTRA_EXCEPTION); assertNotNull(ex); verify(mockListener).onError(405, "auth error", null); - assertTrue(session.getProductUsageTokens().isEmpty()); + assertTrue(customerSession.getProductUsageTokens().isEmpty()); } @Test public void shippingInfoScreen_whenLaunched_logs() { - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - null); - Intent intent = new Intent(); - PaymentSessionConfig paymentSessionConfig = new PaymentSessionConfig.Builder() - .build(); - intent.putExtra(PAYMENT_SESSION_CONFIG, paymentSessionConfig); - intent.putExtra(PAYMENT_SESSION_DATA_KEY, new PaymentSessionData()); + final CustomerSession customerSession = createCustomerSession(null); + CustomerSession.setInstance(customerSession); + final Intent intent = new Intent() + .putExtra(PAYMENT_SESSION_CONFIG, new PaymentSessionConfig.Builder() + .build()) + .putExtra(PAYMENT_SESSION_DATA_KEY, new PaymentSessionData()); Robolectric.buildActivity(PaymentFlowActivity.class, intent) .create().start().resume().visible(); - List actualTokens = new ArrayList<>( - CustomerSession.getInstance().getProductUsageTokens()); + List actualTokens = new ArrayList<>(customerSession.getProductUsageTokens()); assertTrue(actualTokens.contains("ShippingInfoScreen")); } @Test public void shippingMethodScreen_whenLaunched_logs() { - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - null); - Intent intent = new Intent(); - PaymentSessionConfig paymentSessionConfig = new PaymentSessionConfig.Builder() - .setShippingInfoRequired(false) - .build(); - intent.putExtra(PAYMENT_SESSION_CONFIG, paymentSessionConfig); - intent.putExtra(PAYMENT_SESSION_DATA_KEY, new PaymentSessionData()); + final CustomerSession customerSession = createCustomerSession(null); + CustomerSession.setInstance(customerSession); + + final Intent intent = new Intent() + .putExtra(PAYMENT_SESSION_CONFIG, new PaymentSessionConfig.Builder() + .setShippingInfoRequired(false) + .build()) + .putExtra(PAYMENT_SESSION_DATA_KEY, new PaymentSessionData()); + Robolectric.buildActivity(PaymentFlowActivity.class, intent) .create().start().resume().visible(); - List actualTokens = new ArrayList<>( - CustomerSession.getInstance().getProductUsageTokens()); - assertTrue(actualTokens.contains("ShippingMethodScreen")); + assertTrue(new ArrayList<>(customerSession.getProductUsageTokens()) + .contains("ShippingMethodScreen")); } private void setupErrorProxy() - throws APIException, APIConnectionException, InvalidRequestException { - when(mStripeApiProxy.addCustomerSourceWithKey( + throws APIException, APIConnectionException, InvalidRequestException, + AuthenticationException, CardException { + when(mApiHandler.addCustomerSource( eq(mContext), anyString(), anyString(), ArgumentMatchers.anyList(), anyString(), anyString(), - anyString())) + anyString(), + ArgumentMatchers.isNull())) .thenThrow(new APIException("The card is invalid", "request_123", 404, null, null)); - when(mStripeApiProxy.deleteCustomerSourceWithKey( + when(mApiHandler.deleteCustomerSource( eq(mContext), anyString(), anyString(), ArgumentMatchers.anyList(), anyString(), - anyString())) + anyString(), + ArgumentMatchers.isNull())) .thenThrow(new APIException("The card does not exist", "request_123", 404, null, null)); - when(mStripeApiProxy.setDefaultCustomerSourceWithKey( + when(mApiHandler.setDefaultCustomerSource( eq(mContext), anyString(), anyString(), ArgumentMatchers.anyList(), anyString(), anyString(), - anyString())) + anyString(), + ArgumentMatchers.isNull())) .thenThrow(new APIException("auth error", "reqId", 405, null, null)); } + + @NonNull + private CustomerSession createCustomerSession(@Nullable Calendar calendar) { + return new CustomerSession(mEphemeralKeyProvider, calendar, mThreadPoolExecutor, + mApiHandler); + } } diff --git a/stripe/src/test/java/com/stripe/android/PaymentSessionTest.java b/stripe/src/test/java/com/stripe/android/PaymentSessionTest.java index 54506a18ca9..57c8a38ba31 100644 --- a/stripe/src/test/java/com/stripe/android/PaymentSessionTest.java +++ b/stripe/src/test/java/com/stripe/android/PaymentSessionTest.java @@ -7,27 +7,34 @@ import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; -import com.stripe.android.exception.StripeException; +import com.stripe.android.exception.APIConnectionException; +import com.stripe.android.exception.APIException; +import com.stripe.android.exception.AuthenticationException; +import com.stripe.android.exception.CardException; +import com.stripe.android.exception.InvalidRequestException; import com.stripe.android.model.Customer; import com.stripe.android.model.Source; import com.stripe.android.testharness.TestEphemeralKeyProvider; import com.stripe.android.view.CardInputTestActivity; import com.stripe.android.view.PaymentMethodsActivity; -import org.json.JSONException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.Shadows; import org.robolectric.shadows.ShadowActivity; import java.util.Set; +import java.util.concurrent.ThreadPoolExecutor; import static android.app.Activity.RESULT_OK; import static com.stripe.android.CustomerSessionTest.FIRST_SAMPLE_KEY_RAW; @@ -38,10 +45,10 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; @@ -54,171 +61,140 @@ public class PaymentSessionTest { private TestEphemeralKeyProvider mEphemeralKeyProvider; - private Activity mActivity; - @Mock private CustomerSession.StripeApiProxy mStripeApiProxy; - @NonNull - private CustomerEphemeralKey getCustomerEphemeralKey(String key) { - try { - return CustomerEphemeralKey.fromString(key); - } catch (JSONException e) { - throw new RuntimeException(e); - } - } - + @Mock private StripeApiHandler mApiHandler; + @Mock private ThreadPoolExecutor mThreadPoolExecutor; + @Mock private PaymentSession.PaymentSessionListener mPaymentSessionListener; + + @Captor private ArgumentCaptor mPaymentSessionDataArgumentCaptor; + @Before - public void setup() { + public void setup() + throws CardException, APIException, InvalidRequestException, AuthenticationException, + APIConnectionException { MockitoAnnotations.initMocks(this); PaymentConfiguration.init("pk_test_abc123"); mEphemeralKeyProvider = new TestEphemeralKeyProvider(); - CustomerSession.initCustomerSession(mEphemeralKeyProvider, mStripeApiProxy, null); mActivity = Robolectric.buildActivity(AppCompatActivity.class).create().start().get(); - Customer firstCustomer = Customer.fromString(FIRST_TEST_CUSTOMER_OBJECT); + final Customer firstCustomer = Customer.fromString(FIRST_TEST_CUSTOMER_OBJECT); assertNotNull(firstCustomer); - Customer secondCustomer = Customer.fromString(SECOND_TEST_CUSTOMER_OBJECT); + final Customer secondCustomer = Customer.fromString(SECOND_TEST_CUSTOMER_OBJECT); assertNotNull(secondCustomer); - mEphemeralKeyProvider = new TestEphemeralKeyProvider(); - - Source addedSource = Source.fromString(CardInputTestActivity.EXAMPLE_JSON_CARD_SOURCE); + final Source addedSource = + Source.fromString(CardInputTestActivity.EXAMPLE_JSON_CARD_SOURCE); assertNotNull(addedSource); - try { - when(mStripeApiProxy.retrieveCustomerWithKey(anyString(), anyString())) - .thenReturn(firstCustomer, secondCustomer); - when(mStripeApiProxy.addCustomerSourceWithKey( - any(Context.class), - anyString(), - anyString(), - ArgumentMatchers.anyList(), - anyString(), - anyString(), - anyString())) - .thenReturn(addedSource); - when(mStripeApiProxy.setDefaultCustomerSourceWithKey( - any(Context.class), - anyString(), - anyString(), - ArgumentMatchers.anyList(), - anyString(), - anyString(), - anyString())) - .thenReturn(secondCustomer); - } catch (StripeException exception) { - fail("Exception when accessing mock api proxy: " + exception.getMessage()); - } + when(mApiHandler.retrieveCustomer(anyString(), anyString())) + .thenReturn(firstCustomer, secondCustomer); + when(mApiHandler.addCustomerSource( + any(Context.class), + anyString(), + anyString(), + ArgumentMatchers.anyList(), + anyString(), + anyString(), + anyString(), + ArgumentMatchers.isNull())) + .thenReturn(addedSource); + when(mApiHandler.setDefaultCustomerSource( + any(Context.class), + anyString(), + anyString(), + ArgumentMatchers.anyList(), + anyString(), + anyString(), + anyString(), + ArgumentMatchers.isNull())) + .thenReturn(secondCustomer); + + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + invocation.getArgument(0).run(); + return null; + } + }).when(mThreadPoolExecutor).execute(any(Runnable.class)); } @Test public void init_addsPaymentSessionToken_andFetchesCustomer() { - PaymentSession.PaymentSessionListener mockListener = - mock(PaymentSession.PaymentSessionListener.class); - PaymentSession paymentSession = new PaymentSession(mActivity); - paymentSession.init(mockListener, new PaymentSessionConfig.Builder().build()); + final CustomerSession customerSession = createCustomerSession(); + CustomerSession.setInstance(customerSession); + + final PaymentSession paymentSession = new PaymentSession(mActivity); + paymentSession.init(mPaymentSessionListener, new PaymentSessionConfig.Builder().build()); - Set tokenSet = CustomerSession.getInstance().getProductUsageTokens(); - assertTrue(tokenSet.contains(PaymentSession.TOKEN_PAYMENT_SESSION)); + assertTrue(customerSession.getProductUsageTokens() + .contains(PaymentSession.TOKEN_PAYMENT_SESSION)); - verify(mockListener).onCommunicatingStateChanged(eq(true)); + verify(mPaymentSessionListener).onCommunicatingStateChanged(eq(true)); } @Test public void init_whenEphemeralKeyProviderContinues_fetchesCustomerAndNotifiesListener() { - CustomerEphemeralKey firstKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); - assertNotNull(firstKey); - mEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - null); + CustomerSession.setInstance(createCustomerSession()); - PaymentSession.PaymentSessionListener mockListener = - mock(PaymentSession.PaymentSessionListener.class); - PaymentSession paymentSession = new PaymentSession(mActivity); - paymentSession.init(mockListener, new PaymentSessionConfig.Builder().build()); - verify(mockListener).onCommunicatingStateChanged(eq(true)); - verify(mockListener).onPaymentSessionDataChanged(any(PaymentSessionData.class)); - verify(mockListener).onCommunicatingStateChanged(eq(false)); + final PaymentSession paymentSession = new PaymentSession(mActivity); + paymentSession.init(mPaymentSessionListener, new PaymentSessionConfig.Builder().build()); + verify(mPaymentSessionListener).onCommunicatingStateChanged(eq(true)); + verify(mPaymentSessionListener).onPaymentSessionDataChanged(any(PaymentSessionData.class)); + verify(mPaymentSessionListener).onCommunicatingStateChanged(eq(false)); } @Test public void setCartTotal_setsExpectedValueAndNotifiesListener() { - CustomerEphemeralKey firstKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); - assertNotNull(firstKey); - mEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - null); - - PaymentSession.PaymentSessionListener mockListener = - mock(PaymentSession.PaymentSessionListener.class); - PaymentSession paymentSession = new PaymentSession(mActivity); - paymentSession.init(mockListener, new PaymentSessionConfig.Builder().build()); - - ArgumentCaptor dataArgumentCaptor = getDataCaptor(); + CustomerSession.setInstance(createCustomerSession()); + final PaymentSession paymentSession = new PaymentSession(mActivity); + paymentSession.init(mPaymentSessionListener, new PaymentSessionConfig.Builder().build()); paymentSession.setCartTotal(500L); - verify(mockListener).onPaymentSessionDataChanged(dataArgumentCaptor.capture()); - PaymentSessionData data = dataArgumentCaptor.getValue(); + verify(mPaymentSessionListener) + .onPaymentSessionDataChanged(mPaymentSessionDataArgumentCaptor.capture()); + final PaymentSessionData data = mPaymentSessionDataArgumentCaptor.getValue(); assertNotNull(data); assertEquals(500L, data.getCartTotal()); } @Test public void handlePaymentData_whenPaymentMethodRequest_notifiesListenerAndFetchesCustomer() { - CustomerEphemeralKey firstKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); - assertNotNull(firstKey); - mEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - null); + CustomerSession.setInstance(createCustomerSession()); - PaymentSession.PaymentSessionListener mockListener = - mock(PaymentSession.PaymentSessionListener.class); PaymentSession paymentSession = new PaymentSession(mActivity); - paymentSession.init(mockListener, new PaymentSessionConfig.Builder().build()); + paymentSession.init(mPaymentSessionListener, new PaymentSessionConfig.Builder().build()); // We have already tested the functionality up to here. - reset(mockListener); + reset(mPaymentSessionListener); boolean handled = paymentSession.handlePaymentData( PaymentSession.PAYMENT_METHOD_REQUEST, RESULT_OK, new Intent()); assertTrue(handled); - verify(mockListener).onPaymentSessionDataChanged(any(PaymentSessionData.class)); + verify(mPaymentSessionListener).onPaymentSessionDataChanged(any(PaymentSessionData.class)); } @Test public void selectPaymentMethod_launchesPaymentMethodsActivityWithLog() { - CustomerEphemeralKey firstKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); - assertNotNull(firstKey); - mEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - null); + CustomerSession.setInstance(createCustomerSession()); - PaymentSession.PaymentSessionListener mockListener = - mock(PaymentSession.PaymentSessionListener.class); PaymentSession paymentSession = new PaymentSession(mActivity); - paymentSession.init(mockListener, new PaymentSessionConfig.Builder().build()); + paymentSession.init(mPaymentSessionListener, new PaymentSessionConfig.Builder().build()); paymentSession.presentPaymentMethodSelection(); ShadowActivity.IntentForResult intentForResult = Shadows.shadowOf(mActivity).getNextStartedActivityForResult(); assertNotNull(intentForResult); + assertNotNull(intentForResult.intent.getComponent()); assertEquals(PaymentMethodsActivity.class.getName(), intentForResult.intent.getComponent().getClassName()); assertTrue(intentForResult.intent.hasExtra(EXTRA_PAYMENT_SESSION_ACTIVE)); @@ -226,25 +202,17 @@ public void selectPaymentMethod_launchesPaymentMethodsActivityWithLog() { @Test public void init_withoutSavedState_clearsLoggingTokensAndStartsWithPaymentSession() { - CustomerEphemeralKey firstKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); - assertNotNull(firstKey); - mEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - null); - - CustomerSession.getInstance().addProductUsageTokenIfValid("PaymentMethodsActivity"); - assertEquals(1, CustomerSession.getInstance().getProductUsageTokens().size()); + final CustomerSession customerSession = createCustomerSession(); + CustomerSession.setInstance(customerSession); + customerSession.addProductUsageTokenIfValid("PaymentMethodsActivity"); + assertEquals(1, customerSession.getProductUsageTokens().size()); - PaymentSession.PaymentSessionListener mockListener = - mock(PaymentSession.PaymentSessionListener.class); PaymentSession paymentSession = new PaymentSession(mActivity); - paymentSession.init(mockListener, new PaymentSessionConfig.Builder().build()); + paymentSession.init(mPaymentSessionListener, new PaymentSessionConfig.Builder().build()); // The init removes PaymentMethodsActivity, but then adds PaymentSession - Set loggingTokens = CustomerSession.getInstance().getProductUsageTokens(); + final Set loggingTokens = customerSession.getProductUsageTokens(); assertEquals(1, loggingTokens.size()); assertFalse(loggingTokens.contains("PaymentMethodsActivity")); assertTrue(loggingTokens.contains("PaymentSession")); @@ -252,25 +220,18 @@ public void init_withoutSavedState_clearsLoggingTokensAndStartsWithPaymentSessio @Test public void init_withSavedStateBundle_doesNotClearLoggingTokens() { - CustomerEphemeralKey firstKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); - assertNotNull(firstKey); - mEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - null); + final CustomerSession customerSession = createCustomerSession(); + CustomerSession.setInstance(customerSession); + customerSession.addProductUsageTokenIfValid("PaymentMethodsActivity"); + assertEquals(1, customerSession.getProductUsageTokens().size()); - CustomerSession.getInstance().addProductUsageTokenIfValid("PaymentMethodsActivity"); - assertEquals(1, CustomerSession.getInstance().getProductUsageTokens().size()); - - PaymentSession.PaymentSessionListener mockListener = - mock(PaymentSession.PaymentSessionListener.class); PaymentSession paymentSession = new PaymentSession(mActivity); // If it is given any saved state at all, the tokens are not cleared out. - paymentSession.init(mockListener, new PaymentSessionConfig.Builder().build(), new Bundle()); + paymentSession.init(mPaymentSessionListener, + new PaymentSessionConfig.Builder().build(), new Bundle()); - Set loggingTokens = CustomerSession.getInstance().getProductUsageTokens(); + final Set loggingTokens = customerSession.getProductUsageTokens(); assertEquals(2, loggingTokens.size()); assertTrue(loggingTokens.contains("PaymentMethodsActivity")); assertTrue(loggingTokens.contains("PaymentSession")); @@ -278,28 +239,21 @@ public void init_withSavedStateBundle_doesNotClearLoggingTokens() { @Test public void completePayment_withLoggedActions_clearsLoggingTokensAndSetsResult() { - CustomerEphemeralKey firstKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); - assertNotNull(firstKey); - mEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - null); + final CustomerSession customerSession = createCustomerSession(); + CustomerSession.setInstance(customerSession); + customerSession.addProductUsageTokenIfValid("PaymentMethodsActivity"); + assertEquals(1, customerSession.getProductUsageTokens().size()); - CustomerSession.getInstance().addProductUsageTokenIfValid("PaymentMethodsActivity"); - assertEquals(1, CustomerSession.getInstance().getProductUsageTokens().size()); - - PaymentSession.PaymentSessionListener mockListener = - mock(PaymentSession.PaymentSessionListener.class); PaymentSession paymentSession = new PaymentSession(mActivity); // If it is given any saved state at all, the tokens are not cleared out. - paymentSession.init(mockListener, new PaymentSessionConfig.Builder().build(), new Bundle()); + paymentSession.init(mPaymentSessionListener, + new PaymentSessionConfig.Builder().build(), new Bundle()); - Set loggingTokens = CustomerSession.getInstance().getProductUsageTokens(); + final Set loggingTokens = customerSession.getProductUsageTokens(); assertEquals(2, loggingTokens.size()); - reset(mockListener); + reset(mPaymentSessionListener); paymentSession.completePayment(new PaymentCompletionProvider() { @Override public void completePayment(@NonNull PaymentSessionData data, @@ -310,51 +264,47 @@ public void completePayment(@NonNull PaymentSessionData data, ArgumentCaptor dataArgumentCaptor = ArgumentCaptor.forClass(PaymentSessionData.class); - verify(mockListener).onPaymentSessionDataChanged(dataArgumentCaptor.capture()); + verify(mPaymentSessionListener).onPaymentSessionDataChanged(dataArgumentCaptor.capture()); PaymentSessionData capturedData = dataArgumentCaptor.getValue(); assertNotNull(capturedData); assertEquals(PaymentResultListener.SUCCESS, capturedData.getPaymentResult()); - assertTrue(CustomerSession.getInstance().getProductUsageTokens().isEmpty()); + assertTrue(customerSession.getProductUsageTokens().isEmpty()); } @Test public void init_withSavedState_setsPaymentSessionData() { - CustomerEphemeralKey firstKey = getCustomerEphemeralKey(FIRST_SAMPLE_KEY_RAW); - assertNotNull(firstKey); - mEphemeralKeyProvider.setNextRawEphemeralKey(FIRST_SAMPLE_KEY_RAW); - CustomerSession.initCustomerSession( - mEphemeralKeyProvider, - mStripeApiProxy, - null); + CustomerSession.setInstance(createCustomerSession()); - PaymentSession.PaymentSessionListener mockListener = - mock(PaymentSession.PaymentSessionListener.class); PaymentSession paymentSession = new PaymentSession(mActivity); - paymentSession.init(mockListener, new PaymentSessionConfig.Builder().build()); + paymentSession.init(mPaymentSessionListener, new PaymentSessionConfig.Builder().build()); - ArgumentCaptor paySessionDataCaptor = getDataCaptor(); paymentSession.setCartTotal(300L); - verify(mockListener).onPaymentSessionDataChanged(paySessionDataCaptor.capture()); - Bundle bundle = new Bundle(); + verify(mPaymentSessionListener) + .onPaymentSessionDataChanged(mPaymentSessionDataArgumentCaptor.capture()); + final Bundle bundle = new Bundle(); paymentSession.savePaymentSessionInstanceState(bundle); + PaymentSessionData firstPaymentSessionData = mPaymentSessionDataArgumentCaptor.getValue(); PaymentSession.PaymentSessionListener secondListener = mock(PaymentSession.PaymentSessionListener.class); - ArgumentCaptor secondSessionDataCaptor = getDataCaptor(); paymentSession.init(secondListener, new PaymentSessionConfig.Builder().build(), bundle); - verify(secondListener).onPaymentSessionDataChanged(secondSessionDataCaptor.capture()); - - PaymentSessionData firstData = paySessionDataCaptor.getValue(); - PaymentSessionData secondData = secondSessionDataCaptor.getValue(); - assertEquals(firstData.getCartTotal(), secondData.getCartTotal()); - assertEquals(firstData.getSelectedPaymentMethodId(), - secondData.getSelectedPaymentMethodId()); + verify(secondListener) + .onPaymentSessionDataChanged(mPaymentSessionDataArgumentCaptor.capture()); + + final PaymentSessionData secondPaymentSessionData = + mPaymentSessionDataArgumentCaptor.getValue(); + assertEquals(firstPaymentSessionData.getCartTotal(), + secondPaymentSessionData.getCartTotal()); + assertEquals(firstPaymentSessionData.getSelectedPaymentMethodId(), + secondPaymentSessionData.getSelectedPaymentMethodId()); } - private ArgumentCaptor getDataCaptor() { - return ArgumentCaptor.forClass(PaymentSessionData.class); + @NonNull + private CustomerSession createCustomerSession() { + return new CustomerSession(mEphemeralKeyProvider, null, mThreadPoolExecutor, + mApiHandler); } }