package com.stripe.android.net;
import com.stripe.android.exception.APIConnectionException;
import com.stripe.android.exception.StripeException;
import com.stripe.android.model.Source;
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.annotation.Config;
import org.robolectric.shadows.ShadowLooper;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
/**
* Test class for {@link PollingNetworkHandler}.
*/
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 23)
public class PollingNetworkHandlerTest {
private static final String DUMMY_SOURCE_ID = "sourceId";
private static final String DUMMY_CLIENT_SECRET = "clientSecret";
private static final String DUMMY_PUBLISHABLE_KEY = "pubKey";
@Mock Source mCancelledSource;
@Mock Source mChargeableSource;
@Mock Source mConsumedSource;
@Mock Source mFailedSource;
@Mock Source mPendingSource;
@Mock SourceRetriever mSourceRetriever;
@Mock PollingResponseHandler mPollingResponseHandler;
private PollingNetworkHandler mPollingNetworkHandler;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mPollingNetworkHandler = initializeHandler(
mPollingResponseHandler,
3000,
mSourceRetriever);
when(mConsumedSource.getStatus()).thenReturn(Source.CONSUMED);
when(mChargeableSource.getStatus()).thenReturn(Source.CHARGEABLE);
when(mPendingSource.getStatus()).thenReturn(Source.PENDING);
when(mCancelledSource.getStatus()).thenReturn(Source.CANCELED);
when(mFailedSource.getStatus()).thenReturn(Source.FAILED);
}
@Test
public void createHandler_withNullTimeout_usesDefault() {
PollingNetworkHandler handler = initializeHandler(
mPollingResponseHandler,
null,
mSourceRetriever);
assertEquals(10000L, handler.getTimeoutMs());
}
@Test
public void createHandler_withNonNullTimeoutBelowMax_usesSetValue() {
PollingNetworkHandler handler = initializeHandler(
mPollingResponseHandler,
12345,
mSourceRetriever);
assertEquals(12345L, handler.getTimeoutMs());
}
@Test
public void createHandler_withTimeoutGreaterThanMax_usesMax() {
long fiveMinutesInMillis = 5L * 60L * 1000L;
Integer tenMinutesInMillis = 10 * 60 *1000;
PollingNetworkHandler handler = initializeHandler(
mPollingResponseHandler,
tenMinutesInMillis,
mSourceRetriever);
assertEquals(fiveMinutesInMillis, handler.getTimeoutMs());
}
@Test
public void startPolling_whenNeverUpdates_expires() {
setSourceResponse(mSourceRetriever, mPendingSource);
mPollingNetworkHandler.start();
advanceMainLooperBy(2999);
// This is simulating normal 200-type responses
assertEquals(0, mPollingNetworkHandler.getRetryCount());
advanceMainLooperBy(1);
ArgumentCaptor<PollingResponse> pollingResponseCaptor =
ArgumentCaptor.forClass(PollingResponse.class);
verify(mPollingResponseHandler).onPollingResponse(pollingResponseCaptor.capture());
PollingResponse response = pollingResponseCaptor.getValue();
assertTrue(response.isExpired());
assertFalse(response.isSuccess());
assertEquals(mPendingSource, response.getSource());
}
@Test
public void startPolling_whenChargeable_sendsSuccess() {
setSourceResponse(mSourceRetriever, mPendingSource);
mPollingNetworkHandler.start();
setSourceResponse(mSourceRetriever, mChargeableSource);
advanceMainLooperBy(1000);
ArgumentCaptor<PollingResponse> pollingResponseCaptor =
ArgumentCaptor.forClass(PollingResponse.class);
verify(mPollingResponseHandler).onPollingResponse(pollingResponseCaptor.capture());
PollingResponse response = pollingResponseCaptor.getValue();
assertTrue(response.isSuccess());
assertFalse(response.isExpired());
// They're mocks, but they should theoretically be equal.
assertEquals(mChargeableSource, response.getSource());
// Get to the timeout
advanceMainLooperBy(2000);
verifyNoMoreInteractions(mPollingResponseHandler);
}
@Test
public void startPolling_whenConsumed_sendsSuccess() {
setSourceResponse(mSourceRetriever, mPendingSource);
mPollingNetworkHandler.start();
setSourceResponse(mSourceRetriever, mConsumedSource);
advanceMainLooperBy(1000);
ArgumentCaptor<PollingResponse> pollingResponseCaptor =
ArgumentCaptor.forClass(PollingResponse.class);
verify(mPollingResponseHandler).onPollingResponse(pollingResponseCaptor.capture());
PollingResponse response = pollingResponseCaptor.getValue();
assertTrue(response.isSuccess());
assertFalse(response.isExpired());
// They're mocks, but they should theoretically be equal.
assertEquals(mConsumedSource, response.getSource());
}
@Test
public void startPolling_whenCancelled_sendsFailureResponse() {
setSourceResponse(mSourceRetriever, mPendingSource);
mPollingNetworkHandler.start();
setSourceResponse(mSourceRetriever, mCancelledSource);
advanceMainLooperBy(1000);
ArgumentCaptor<PollingResponse> pollingResponseCaptor =
ArgumentCaptor.forClass(PollingResponse.class);
verify(mPollingResponseHandler).onPollingResponse(pollingResponseCaptor.capture());
PollingResponse response = pollingResponseCaptor.getValue();
assertFalse(response.isSuccess());
assertFalse(response.isExpired());
// They're mocks, but they should theoretically be equal.
assertEquals(mCancelledSource, response.getSource());
}
@Test
public void startPolling_whenFailed_sendsFailureResponse() {
setSourceResponse(mSourceRetriever, mPendingSource);
mPollingNetworkHandler.start();
setSourceResponse(mSourceRetriever, mFailedSource);
advanceMainLooperBy(1000);
ArgumentCaptor<PollingResponse> pollingResponseCaptor =
ArgumentCaptor.forClass(PollingResponse.class);
verify(mPollingResponseHandler).onPollingResponse(pollingResponseCaptor.capture());
PollingResponse response = pollingResponseCaptor.getValue();
assertFalse(response.isSuccess());
assertFalse(response.isExpired());
// They're mocks, but they should theoretically be equal.
assertEquals(mFailedSource, response.getSource());
}
@Test
public void startPolling_whenExceptionThrownInApi_retriesAfterDecayingDelay() {
setSourceResponse(mSourceRetriever, mPendingSource);
mPollingNetworkHandler.start();
setSourceException(mSourceRetriever, new APIConnectionException("expected error"));
advanceMainLooperBy(1000);
assertEquals(1, mPollingNetworkHandler.getRetryCount());
}
@Test
public void startPolling_whenExceptionThrownInApiFollowedByPending_setsRetryCountToZero() {
PollingNetworkHandler pollingNetworkHandler = initializeHandler(
mPollingResponseHandler,
30000,
mSourceRetriever);
setSourceResponse(mSourceRetriever, mPendingSource);
pollingNetworkHandler.start();
SourceRetriever exceptionRetriever = mock(SourceRetriever.class);
setSourceException(exceptionRetriever, new APIConnectionException("expected error"));
pollingNetworkHandler.setSourceRetriever(exceptionRetriever);
advanceMainLooperBy(1000);
assertEquals(1, pollingNetworkHandler.getRetryCount());
pollingNetworkHandler.setSourceRetriever(mSourceRetriever);
advanceMainLooperBy(2000);
assertEquals(0, pollingNetworkHandler.getRetryCount());
}
@Test
public void startPolling_whenExceptionThrownTooManyTimes_sendsErrorResponse() {
PollingNetworkHandler pollingNetworkHandler = initializeHandler(
mPollingResponseHandler,
50000,
mSourceRetriever);
setSourceException(mSourceRetriever, new APIConnectionException("expected error"));
pollingNetworkHandler.start();
assertEquals(1, pollingNetworkHandler.getRetryCount());
advanceMainLooperBy(2000);
assertEquals(2, pollingNetworkHandler.getRetryCount());
advanceMainLooperBy(4000);
assertEquals(3, pollingNetworkHandler.getRetryCount());
advanceMainLooperBy(8000);
assertEquals(4, pollingNetworkHandler.getRetryCount());
ArgumentCaptor<PollingResponse> pollingResponseCaptor =
ArgumentCaptor.forClass(PollingResponse.class);
advanceMainLooperBy(15000);
assertEquals(5, pollingNetworkHandler.getRetryCount());
verify(mPollingResponseHandler).onPollingResponse(pollingResponseCaptor.capture());
PollingResponse response = pollingResponseCaptor.getValue();
assertNotNull(response.getStripeException());
assertEquals("expected error", response.getStripeException().getMessage());
assertFalse(response.isSuccess());
assertFalse(response.isExpired());
// This value is null because we never got a valid source back.
assertNull(response.getSource());
}
@Test
public void startPolling_whenTooManyErrorsAfterPendingOnce_sendsErrorResponseWithSource() {
PollingNetworkHandler pollingNetworkHandler = initializeHandler(
mPollingResponseHandler,
50000,
mSourceRetriever);
setSourceResponse(mSourceRetriever, mPendingSource);
pollingNetworkHandler.start();
SourceRetriever exceptionRetriever = mock(SourceRetriever.class);
setSourceException(exceptionRetriever, new APIConnectionException("expected error"));
pollingNetworkHandler.setSourceRetriever(exceptionRetriever);
advanceMainLooperBy(1000);
advanceMainLooperBy(2000);
advanceMainLooperBy(4000);
advanceMainLooperBy(8000);
SourceRetriever otherExceptionRetriever = mock(SourceRetriever.class);
setSourceException(otherExceptionRetriever, new APIConnectionException("next error"));
pollingNetworkHandler.setSourceRetriever(otherExceptionRetriever);
ArgumentCaptor<PollingResponse> pollingResponseCaptor =
ArgumentCaptor.forClass(PollingResponse.class);
advanceMainLooperBy(15000);
verify(mPollingResponseHandler).onPollingResponse(pollingResponseCaptor.capture());
PollingResponse response = pollingResponseCaptor.getValue();
assertNotNull(response.getStripeException());
assertEquals("next error", response.getStripeException().getMessage());
assertFalse(response.isSuccess());
assertFalse(response.isExpired());
assertEquals(mPendingSource, response.getSource());
}
private static PollingNetworkHandler initializeHandler(
PollingResponseHandler pollingResponseHandler,
Integer timeout,
SourceRetriever sourceRetriever) {
return new PollingNetworkHandler(
DUMMY_SOURCE_ID,
DUMMY_CLIENT_SECRET,
DUMMY_PUBLISHABLE_KEY,
pollingResponseHandler,
timeout,
sourceRetriever,
PollingParameters.generateDefaultParameters());
}
private static void advanceMainLooperBy(int millis) {
ShadowLooper.pauseMainLooper();
Robolectric.getForegroundThreadScheduler().advanceBy(millis, TimeUnit.MILLISECONDS);
// Let the time advance
ShadowLooper.unPauseMainLooper();
// Now pause again
ShadowLooper.pauseMainLooper();
}
private static void setSourceResponse(SourceRetriever sourceRetriever, Source source) {
try {
when(sourceRetriever.retrieveSource(
DUMMY_SOURCE_ID,
DUMMY_CLIENT_SECRET,
DUMMY_PUBLISHABLE_KEY)).thenReturn(source);
} catch (StripeException stripeEx) {
fail("Unexpected error: " + stripeEx);
}
}
private static void setSourceException(SourceRetriever sourceRetriever, StripeException ex) {
try {
when(sourceRetriever.retrieveSource(
DUMMY_SOURCE_ID,
DUMMY_CLIENT_SECRET,
DUMMY_PUBLISHABLE_KEY)).thenThrow(ex);
} catch (StripeException stripeEx) {
fail("Unexpected error: " + stripeEx);
}
}
}