package org.zalando.riptide; import com.google.common.io.ByteStreams; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.core.io.ClassPathResource; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpResponse; import org.springframework.test.web.client.MockRestServiceServer; import org.zalando.problem.ThrowableProblem; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.concurrent.CompletionException; import java.util.concurrent.atomic.AtomicReference; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasToString; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hobsoft.hamcrest.compose.ComposeMatchers.hasFeature; import static org.junit.Assert.assertThat; import static org.springframework.http.HttpStatus.CONFLICT; import static org.springframework.http.HttpStatus.NO_CONTENT; import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; import static org.zalando.riptide.Bindings.anyStatus; import static org.zalando.riptide.Bindings.on; import static org.zalando.riptide.Navigators.status; import static org.zalando.riptide.Route.headers; import static org.zalando.riptide.Route.location; import static org.zalando.riptide.Route.noRoute; import static org.zalando.riptide.Route.pass; import static org.zalando.riptide.Route.propagate; import static org.zalando.riptide.Route.to; public final class RouteTest { @Rule public final ExpectedException exception = ExpectedException.none(); private final URI url = URI.create("https://api.example.com/accounts/123"); private final Rest unit; private final MockRestServiceServer server; public RouteTest() { final MockSetup setup = new MockSetup(); this.unit = setup.getRest(); this.server = setup.getServer(); } @Test public void shouldPass() { server.expect(requestTo(url)).andRespond( withSuccess()); unit.get(url) .dispatch(status(), on(OK).call(pass()), anyStatus().call(this::fail)); } @Test public void shouldMapHeaders() { server.expect(requestTo(url)).andRespond( withSuccess() .body(new ClassPathResource("account.json")) .contentType(APPLICATION_JSON)); final AtomicReference<HttpHeaders> capture = new AtomicReference<>(); unit.head(url) .dispatch(status(), on(OK).call(to(headers()).andThen(capture::set)), anyStatus().call(this::fail)) .join(); final HttpHeaders headers = capture.get(); assertThat(headers.toSingleValueMap(), hasEntry("Content-Type", APPLICATION_JSON_VALUE)); } @Test public void shouldMapLocation() { final HttpHeaders headers = new HttpHeaders(); headers.setLocation(URI.create("http://example.org")); server.expect(requestTo(url)).andRespond( withSuccess() .headers(headers)); final AtomicReference<URI> capture = new AtomicReference<>(); unit.head(url) .dispatch(status(), on(OK).call(to(location()).andThen(capture::set)), anyStatus().call(this::fail)) .join(); final URI uri = capture.get(); assertThat(uri, hasToString("http://example.org")); } @Test public void shouldThrowNoRouteExceptionWithContent() { server.expect(requestTo(url)).andRespond( withStatus(CONFLICT) .body("verbose body content") .contentType(MediaType.TEXT_PLAIN)); exception.expect(CompletionException.class); exception.expectCause(instanceOf(NoRouteException.class)); exception.expectCause( hasFeature(Throwable::getMessage, containsString("Content-Type=[" + MediaType.TEXT_PLAIN + "]"))); exception.expectCause(hasFeature(Throwable::getMessage, containsString("verbose body content"))); unit.get(url) .dispatch(status(), anyStatus().call(noRoute())) .join(); } @Test public void shouldThrowNoRouteExceptionWithoutContent() { server.expect(requestTo(url)).andRespond(withStatus(NO_CONTENT)); exception.expect(CompletionException.class); exception.expectCause(instanceOf(NoRouteException.class)); unit.get(url) .dispatch(status(), anyStatus().call(noRoute())) .join(); } @Test public void shouldWrapAndPropagateException() throws IOException { exception.expect(IOException.class); exception.expectCause(instanceOf(URISyntaxException.class)); propagate().tryAccept(new URISyntaxException("foo", "bar")); } @Test public void shouldPropagateRuntimeExceptionAsIs() { server.expect(requestTo(url)).andRespond( withStatus(UNPROCESSABLE_ENTITY) .body(new ClassPathResource("problem.json")) .contentType(APPLICATION_JSON)); exception.expect(CompletionException.class); exception.expectCause(instanceOf(ThrowableProblem.class)); unit.get(url) .dispatch(status(), on(UNPROCESSABLE_ENTITY).call(ThrowableProblem.class, propagate()), anyStatus().call(this::fail)) .join(); } @Test public void shouldPropagateIOExceptionAsIs() { server.expect(requestTo(url)).andRespond( withStatus(UNPROCESSABLE_ENTITY) .body("{}") .contentType(APPLICATION_JSON)); exception.expect(CompletionException.class); exception.expectCause(instanceOf(IOException.class)); unit.get(url) .dispatch(status(), on(UNPROCESSABLE_ENTITY).call(IOException.class, propagate()), anyStatus().call(this::fail)) .join(); } @Test public void shouldAllowToCaptureBody() throws Exception { server.expect(requestTo(url)) .andRespond(withSuccess() .body("{}") .contentType(APPLICATION_JSON)); final AtomicReference<InputStream> body = new AtomicReference<>(); unit.get(url).call(((response, reader) -> body.set(response.getBody()))); // read response outside of consumer/callback // to make sure the stream is still available final byte[] bytes = ByteStreams.toByteArray(body.get()); assertThat(bytes.length, is(greaterThan(0))); } private void fail(final ClientHttpResponse response) throws IOException { throw new AssertionError(response.getRawStatusCode()); } }