/* * Copyright 2013-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.hateoas.client; import static net.jadler.Jadler.*; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.springframework.hateoas.client.Hop.*; import java.io.IOException; import java.net.URI; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.core.ParameterizedTypeReference; import org.springframework.hateoas.Link; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.Resource; import org.springframework.hateoas.client.Traverson.TraversalBuilder; import org.springframework.hateoas.core.JsonPathLinkDiscoverer; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpRequest; import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.client.RestTemplate; /** * Integration tests for {@link Traverson}. * * @author Oliver Gierke * @author Greg Turnquist * @since 0.11 */ public class TraversonTest { URI baseUri; Server server; Traverson traverson; @Before public void setUp() { this.server = new Server(); this.baseUri = URI.create(server.rootResource()); this.traverson = new Traverson(baseUri, MediaTypes.HAL_JSON); setUpActors(); } @After public void tearDown() throws IOException { if (server != null) { server.close(); } } /** * @see #131 */ @Test(expected = IllegalArgumentException.class) public void rejectsNullBaseUri() { new Traverson(null, MediaTypes.HAL_JSON); } /** * @see #131 */ @Test(expected = IllegalArgumentException.class) public void rejectsEmptyMediaTypes() { new Traverson(baseUri, new MediaType[0]); } /** * @see #131 */ @Test public void sendsConfiguredMediaTypesInAcceptHeader() { traverson.follow().toObject(String.class); verifyThatRequest(). // havingPathEqualTo("/"). // havingHeader("Accept", hasItem("application/hal+json")); } /** * @see #131 */ @Test public void readsTraversalIntoJsonPathExpression() { assertThat(traverson.follow("movies", "movie", "actor").<String> toObject("$.name"), is("Keanu Reaves")); } /** * @see #131 */ @Test public void readsJsonPathTraversalIntoJsonPathExpression() { assertThat(traverson.follow(// "$._links.movies.href", // "$._links.movie.href", // "$._links.actor.href").<String> toObject("$.name"), is("Keanu Reaves")); } /** * @see #131 */ @Test public void readsTraversalIntoResourceInstance() { ParameterizedTypeReference<Resource<Actor>> typeReference = new ParameterizedTypeReference<Resource<Actor>>() {}; Resource<Actor> result = traverson.follow("movies", "movie", "actor").toObject(typeReference); assertThat(result.getContent().name, is("Keanu Reaves")); } /** * @see #187 */ @Test public void sendsConfiguredHeadersForJsonPathExpression() { String expectedHeader = "<http://www.example.com>;rel=\"home\""; HttpHeaders headers = new HttpHeaders(); headers.add("Link", expectedHeader); assertThat(traverson.follow("movies", "movie", "actor").// withHeaders(headers).<String> toObject("$.name"), is("Keanu Reaves")); verifyThatRequest(). // havingPathEqualTo("/actors/d95dbf62-f900-4dfa-9de8-0fc71e02ffa4"). // havingHeader("Link", hasItem(expectedHeader)); } /** * @see #187 */ @Test public void sendsConfiguredHeadersForToEntity() { String expectedHeader = "<http://www.example.com>;rel=\"home\""; HttpHeaders headers = new HttpHeaders(); headers.add("Link", expectedHeader); traverson.follow("movies", "movie", "actor").// withHeaders(headers).toEntity(Actor.class); verifyThatRequest(). // havingPathEqualTo("/actors/d95dbf62-f900-4dfa-9de8-0fc71e02ffa4"). // havingHeader("Link", hasItem(expectedHeader)); } /** * @see #201, #203 */ @Test public void allowsCustomizingRestTemplate() { CountingInterceptor interceptor = new CountingInterceptor(); RestTemplate restTemplate = new RestTemplate(); restTemplate.setInterceptors(Arrays.<ClientHttpRequestInterceptor> asList(interceptor)); this.traverson = new Traverson(baseUri, MediaTypes.HAL_JSON); this.traverson.setRestOperations(restTemplate); traverson.follow("movies", "movie", "actor").<String> toObject("$.name"); assertThat(interceptor.intercepted, is(4)); } /** * @see #185 */ @Test public void usesCustomLinkDiscoverer() { this.traverson = new Traverson(URI.create(server.rootResource() + "/github"), MediaType.APPLICATION_JSON); this.traverson.setLinkDiscoverers(Arrays.asList(new GitHubLinkDiscoverer())); String value = this.traverson.follow("foo").toObject("$.key"); assertThat(value, is("value")); } /** * @see #212 */ @Test public void shouldReturnLastLinkFound() { Link result = traverson.follow("movies").asLink(); assertThat(result.getHref(), endsWith("/movies")); assertThat(result.getRel(), is("movies")); } /** * @see #307 */ @Test public void returnsTemplatedLinkIfRequested() { TraversalBuilder follow = new Traverson(URI.create(server.rootResource().concat("/link")), MediaTypes.HAL_JSON) .follow("self"); Link link = follow.asTemplatedLink(); assertThat(link.isTemplated(), is(true)); assertThat(link.getVariableNames(), hasItem("template")); link = follow.asLink(); assertThat(link.isTemplated(), is(false)); } /** * @see #258 */ @Test public void returnsDefaultMessageConvertersForHal() { List<HttpMessageConverter<?>> converters = Traverson.getDefaultMessageConverters(MediaTypes.HAL_JSON); assertThat(converters, hasSize(2)); assertThat(converters.get(0), is(instanceOf(StringHttpMessageConverter.class))); assertThat(converters.get(1), is(instanceOf(MappingJackson2HttpMessageConverter.class))); } /** * @see #258 */ @Test public void returnsDefaultMessageConverters() { List<HttpMessageConverter<?>> converters = Traverson .getDefaultMessageConverters(Collections.<MediaType> emptyList()); assertThat(converters, hasSize(1)); assertThat(converters.get(0), is(instanceOf(StringHttpMessageConverter.class))); } /** * @see #346 */ @Test public void chainMultipleFollowOperations() { ParameterizedTypeReference<Resource<Actor>> typeReference = new ParameterizedTypeReference<Resource<Actor>>() {}; Resource<Actor> result = traverson.follow("movies").follow("movie").follow("actor").toObject(typeReference); assertThat(result.getContent().name, is("Keanu Reaves")); } /** * @see #346 */ @Test public void allowAlteringTheDetailsOfASingleHop() { this.traverson = new Traverson(URI.create(server.rootResource() + "/springagram"), MediaTypes.HAL_JSON); // tag::hop-with-param[] ParameterizedTypeReference<Resource<Item>> resourceParameterizedTypeReference = new ParameterizedTypeReference<Resource<Item>>() {}; Resource<Item> itemResource = traverson.// follow(rel("items").withParameter("projection", "noImages")).// follow("$._embedded.items[0]._links.self.href").// toObject(resourceParameterizedTypeReference); // end::hop-with-param[] assertThat(itemResource.hasLink("self"), is(true)); assertThat(itemResource.getLink("self").expand().getHref(), equalTo(server.rootResource() + "/springagram/items/1")); final Item item = itemResource.getContent(); assertThat(item.image, equalTo(server.rootResource() + "/springagram/file/cat")); assertThat(item.description, equalTo("cat")); } /** * @see #346 */ @Test public void allowAlteringTheDetailsOfASingleHopByMapOperations() { this.traverson = new Traverson(URI.create(server.rootResource() + "/springagram"), MediaTypes.HAL_JSON); // tag::hop-put[] ParameterizedTypeReference<Resource<Item>> resourceParameterizedTypeReference = new ParameterizedTypeReference<Resource<Item>>() {}; Map<String, String> params = new HashMap<String, String>(); params.put("projection", "noImages"); Resource<Item> itemResource = traverson.// follow(rel("items").withParameters(params)).// follow("$._embedded.items[0]._links.self.href").// toObject(resourceParameterizedTypeReference); // end::hop-put[] assertThat(itemResource.hasLink("self"), is(true)); assertThat(itemResource.getLink("self").expand().getHref(), equalTo(server.rootResource() + "/springagram/items/1")); final Item item = itemResource.getContent(); assertThat(item.image, equalTo(server.rootResource() + "/springagram/file/cat")); assertThat(item.description, equalTo("cat")); } /** * @see #346 */ @Test public void allowGlobalsToImpactSingleHops() { this.traverson = new Traverson(URI.create(server.rootResource() + "/springagram"), MediaTypes.HAL_JSON); Map<String, Object> params = new HashMap<String, Object>(); params.put("projection", "thisShouldGetOverwrittenByLocalHop"); ParameterizedTypeReference<Resource<Item>> resourceParameterizedTypeReference = new ParameterizedTypeReference<Resource<Item>>() {}; Resource<Item> itemResource = traverson.follow(rel("items").withParameter("projection", "noImages")) .follow("$._embedded.items[0]._links.self.href") // retrieve first Item in the collection .withTemplateParameters(params).toObject(resourceParameterizedTypeReference); assertThat(itemResource.hasLink("self"), is(true)); assertThat(itemResource.getLink("self").expand().getHref(), equalTo(server.rootResource() + "/springagram/items/1")); final Item item = itemResource.getContent(); assertThat(item.image, equalTo(server.rootResource() + "/springagram/file/cat")); assertThat(item.description, equalTo("cat")); } /** * @see #337 */ @Test public void doesNotDoubleEncodeURI() { this.traverson = new Traverson(URI.create(server.rootResource() + "/springagram"), MediaTypes.HAL_JSON); Resource<?> itemResource = traverson.// follow(rel("items").withParameters(Collections.singletonMap("projection", "no images"))).// toObject(Resource.class); assertThat(itemResource.hasLink("self"), is(true)); assertThat(itemResource.getLink("self").expand().getHref(), equalTo(server.rootResource() + "/springagram/items")); } private void setUpActors() { Resource<Actor> actor = new Resource<Actor>(new Actor("Keanu Reaves")); String actorUri = server.mockResourceFor(actor); Movie movie = new Movie("The Matrix"); Resource<Movie> resource = new Resource<Movie>(movie); resource.add(new Link(actorUri, "actor")); server.mockResourceFor(resource); server.finishMocking(); } static class CountingInterceptor implements ClientHttpRequestInterceptor { int intercepted; @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { this.intercepted++; return execution.execute(request, body); } }; static class GitHubLinkDiscoverer extends JsonPathLinkDiscoverer { public GitHubLinkDiscoverer() { super("$.%s_url", MediaType.APPLICATION_JSON); } } }