package org.zalando.riptide.exceptions;
import com.github.restdriver.clientdriver.ClientDriverRule;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
import org.zalando.riptide.Plugin;
import org.zalando.riptide.Rest;
import org.zalando.riptide.httpclient.RestAsyncClientHttpRequestFactory;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.TimeUnit;
import static com.github.restdriver.clientdriver.RestClientDriver.giveEmptyResponse;
import static com.github.restdriver.clientdriver.RestClientDriver.onRequestTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hobsoft.hamcrest.compose.ComposeMatchers.hasFeature;
import static org.springframework.http.HttpStatus.Series.SUCCESSFUL;
import static org.zalando.riptide.Bindings.on;
import static org.zalando.riptide.Navigators.series;
import static org.zalando.riptide.Route.pass;
import static org.zalando.riptide.exceptions.ExceptionClassifier.create;
public final class TemporaryExceptionPluginTest {
private static final int SOCKET_TIMEOUT = 1000;
private static final int DELAY = 2000;
@Rule
public final ExpectedException exception = ExpectedException.none();
@Rule
public final ClientDriverRule driver = new ClientDriverRule();
private final CloseableHttpClient client = HttpClientBuilder.create()
.setDefaultRequestConfig(RequestConfig.custom()
.setSocketTimeout(SOCKET_TIMEOUT)
.build())
.build();
private final ConcurrentTaskExecutor executor = new ConcurrentTaskExecutor();
private final RestAsyncClientHttpRequestFactory factory = new RestAsyncClientHttpRequestFactory(client, executor);
@After
public void tearDown() throws IOException {
client.close();
}
@Test
public void shouldClassifyAsTemporary() {
final Rest unit = newUnit(new TemporaryExceptionPlugin());
driver.addExpectation(onRequestTo("/"),
giveEmptyResponse().after(DELAY, TimeUnit.MILLISECONDS));
exception.expect(CompletionException.class);
exception.expectCause(instanceOf(TemporaryException.class));
// not second level of CompletionException!
exception.expectCause(hasFeature(Throwable::getCause, is(instanceOf(SocketTimeoutException.class))));
execute(unit);
}
@Test
public void shouldNotClassifyAsTemporaryIfNotMatching() {
final Rest unit = newUnit(new TemporaryExceptionPlugin(create()));
driver.addExpectation(onRequestTo("/"),
giveEmptyResponse().after(DELAY, TimeUnit.MILLISECONDS));
exception.expect(CompletionException.class);
exception.expectCause(instanceOf(SocketTimeoutException.class));
execute(unit);
}
@Test
public void shouldClassifyExceptionAsTemporaryAsIs() {
final Rest unit = newUnit((arguments, execution) -> () -> {
final CompletableFuture<ClientHttpResponse> future = new CompletableFuture<>();
future.completeExceptionally(new IllegalArgumentException());
return future;
}, new TemporaryExceptionPlugin(create(IllegalArgumentException.class::isInstance)));
exception.expect(CompletionException.class);
exception.expectCause(instanceOf(TemporaryException.class));
exception.expectCause(hasFeature(Throwable::getCause, is(instanceOf(IllegalArgumentException.class))));
request(unit).join();
}
private void execute(final Rest unit) {
request(unit)
.join();
}
private CompletableFuture<Void> request(final Rest unit) {
return unit.get("/")
.dispatch(series(),
on(SUCCESSFUL).call(pass()));
}
@Test
public void shouldClassifyAsPermanent() {
final Rest unit = newUnit(new TemporaryExceptionPlugin());
driver.addExpectation(onRequestTo("/"),
giveEmptyResponse());
exception.expect(CompletionException.class);
exception.expectCause(instanceOf(MalformedURLException.class));
unit.get("/")
.dispatch(series(),
on(SUCCESSFUL).call(() -> {throw new MalformedURLException();}))
.join();
}
private Rest newUnit(final Plugin... plugins) {
return Rest.builder()
.baseUrl(driver.getBaseUrl())
.requestFactory(factory)
.plugins(Arrays.asList(plugins))
.build();
}
}