HttpClientRetryer extends HttpClientDecorator { @Override public ServiceCall callAsync(String url, String method, Map<String, String> headers, CallTemplate callTemplate, ServiceCallback serviceCallback) { RetryableCall retryableCall = new RetryableCall(mDecoratedApi, url, method, headers, callTemplate, serviceCallback); retryableCall.run(); return retryableCall; } HttpClientRetryer(HttpClient decoratedApi); @VisibleForTesting  HttpClientRetryer(HttpClient decoratedApi, Handler handler); @Override ServiceCall callAsync(String url, String method, Map<String, String> headers, CallTemplate callTemplate, ServiceCallback serviceCallback);  }
@Test public void success() { final ServiceCall call = mock(ServiceCall.class); final ServiceCallback callback = mock(ServiceCallback.class); HttpClient httpClient = mock(HttpClient.class); doAnswer(new Answer<ServiceCall>() { @Override public ServiceCall answer(InvocationOnMock invocationOnMock) { ((ServiceCallback) invocationOnMock.getArguments()[4]).onCallSucceeded(new HttpResponse(200, "mockSuccessPayload")); return call; } }).when(httpClient).callAsync(anyString(), anyString(), anyMapOf(String.class, String.class), any(HttpClient.CallTemplate.class), any(ServiceCallback.class)); HttpClientRetryer retryer = new HttpClientRetryer(httpClient); retryer.callAsync(null, null, null, null, callback); verify(callback).onCallSucceeded(eq(new HttpResponse(200, "mockSuccessPayload"))); verifyNoMoreInteractions(callback); verifyNoMoreInteractions(call); }
@Test public void successAfterOneRetry() { final ServiceCallback callback = mock(ServiceCallback.class); HttpClient httpClient = mock(HttpClient.class); doAnswer(new Answer<ServiceCall>() { @Override public ServiceCall answer(InvocationOnMock invocationOnMock) { ((ServiceCallback) invocationOnMock.getArguments()[4]).onCallFailed(new SocketException()); return mock(ServiceCall.class); } }).doAnswer(new Answer<ServiceCall>() { @Override public ServiceCall answer(InvocationOnMock invocationOnMock) { ((ServiceCallback) invocationOnMock.getArguments()[4]).onCallSucceeded(new HttpResponse(200, "mockSuccessPayload")); return mock(ServiceCall.class); } }).when(httpClient).callAsync(anyString(), anyString(), anyMapOf(String.class, String.class), any(HttpClient.CallTemplate.class), any(ServiceCallback.class)); Handler handler = mock(Handler.class); HttpClient retryer = new HttpClientRetryer(httpClient, handler); simulateRetryAfterDelay(handler); retryer.callAsync(null, null, null, null, callback); verifyDelay(handler, 0); verifyNoMoreInteractions(handler); verify(callback).onCallSucceeded(eq(new HttpResponse(200, "mockSuccessPayload"))); verifyNoMoreInteractions(callback); }
@Test public void retryOnceThenFail() { final HttpException expectedException = new HttpException(new HttpResponse(403)); final ServiceCallback callback = mock(ServiceCallback.class); HttpClient httpClient = mock(HttpClient.class); doAnswer(new Answer<ServiceCall>() { @Override public ServiceCall answer(InvocationOnMock invocationOnMock) { ((ServiceCallback) invocationOnMock.getArguments()[4]).onCallFailed(new UnknownHostException()); return mock(ServiceCall.class); } }).doAnswer(new Answer<ServiceCall>() { @Override public ServiceCall answer(InvocationOnMock invocationOnMock) { ((ServiceCallback) invocationOnMock.getArguments()[4]).onCallFailed(expectedException); return mock(ServiceCall.class); } }).when(httpClient).callAsync(anyString(), anyString(), anyMapOf(String.class, String.class), any(HttpClient.CallTemplate.class), any(ServiceCallback.class)); Handler handler = mock(Handler.class); HttpClient retryer = new HttpClientRetryer(httpClient, handler); simulateRetryAfterDelay(handler); retryer.callAsync(null, null, null, null, callback); verifyDelay(handler, 0); verifyNoMoreInteractions(handler); verify(callback).onCallFailed(any(Exception.class)); verify(callback).onCallFailed(expectedException); verifyNoMoreInteractions(callback); }
@Test public void exhaustRetries() { final ServiceCall call = mock(ServiceCall.class); ServiceCallback callback = mock(ServiceCallback.class); HttpClient httpClient = mock(HttpClient.class); doAnswer(new Answer<ServiceCall>() { @Override public ServiceCall answer(InvocationOnMock invocationOnMock) { ((ServiceCallback) invocationOnMock.getArguments()[4]).onCallFailed(new HttpException(new HttpResponse(408))); return call; } }).when(httpClient).callAsync(anyString(), anyString(), anyMapOf(String.class, String.class), any(HttpClient.CallTemplate.class), any(ServiceCallback.class)); Handler handler = mock(Handler.class); HttpClient retryer = new HttpClientRetryer(httpClient, handler); simulateRetryAfterDelay(handler); retryer.callAsync(null, null, null, null, callback); verifyDelay(handler, 0); verifyDelay(handler, 1); verifyDelay(handler, 2); verifyNoMoreInteractions(handler); verify(callback).onCallFailed(new HttpException(new HttpResponse(408))); verifyNoMoreInteractions(callback); verifyNoMoreInteractions(call); }
@Test public void delayUsingRetryHeader() { long retryAfterMS = 1234; Map<String, String> responseHeader = new HashMap<>(); responseHeader.put(CONTENT_TYPE_KEY, CONTENT_TYPE_VALUE); responseHeader.put(X_MS_RETRY_AFTER_MS_HEADER, Long.toString(retryAfterMS)); final HttpException expectedException = new HttpException(new HttpResponse(429, "call hit the retry limit", responseHeader)); final ServiceCallback callback = mock(ServiceCallback.class); HttpClient httpClient = mock(HttpClient.class); doAnswer(new Answer<ServiceCall>() { @Override public ServiceCall answer(InvocationOnMock invocationOnMock) { ((ServiceCallback) invocationOnMock.getArguments()[4]).onCallFailed(expectedException); return mock(ServiceCall.class); } }).doAnswer(new Answer<ServiceCall>() { @Override public ServiceCall answer(InvocationOnMock invocationOnMock) { ((ServiceCallback) invocationOnMock.getArguments()[4]).onCallSucceeded(new HttpResponse(200, "mockSuccessPayload")); return mock(ServiceCall.class); } }).when(httpClient).callAsync(anyString(), anyString(), anyMapOf(String.class, String.class), any(HttpClient.CallTemplate.class), any(ServiceCallback.class)); Handler handler = mock(Handler.class); HttpClient retryer = new HttpClientRetryer(httpClient, handler); simulateRetryAfterDelay(handler); retryer.callAsync(null, null, null, null, callback); verifyDelayFromHeader(handler, retryAfterMS); verifyNoMoreInteractions(handler); verify(callback).onCallSucceeded(eq(new HttpResponse(200, "mockSuccessPayload"))); verifyNoMoreInteractions(callback); }
@Test public void cancel() throws InterruptedException { final ServiceCall call = mock(ServiceCall.class); ServiceCallback callback = mock(ServiceCallback.class); HttpClient httpClient = mock(HttpClient.class); doAnswer(new Answer<ServiceCall>() { @Override public ServiceCall answer(InvocationOnMock invocationOnMock) { ((ServiceCallback) invocationOnMock.getArguments()[4]).onCallFailed(new HttpException(new HttpResponse(503))); return call; } }).when(httpClient).callAsync(anyString(), anyString(), anyMapOf(String.class, String.class), any(HttpClient.CallTemplate.class), any(ServiceCallback.class)); Handler handler = mock(Handler.class); HttpClient retryer = new HttpClientRetryer(httpClient, handler); retryer.callAsync(null, null, null, null, callback).cancel(); Thread.sleep(500); verifyNoMoreInteractions(callback); verify(call).cancel(); }