org.springframework.data.rest.webmvc.jpa.JpaWebTests.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.data.rest.webmvc.jpa.JpaWebTests.java

Source

/*
 * Copyright 2013-2016 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.data.rest.webmvc.jpa;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.rest.webmvc.util.TestUtils.*;
import static org.springframework.http.HttpHeaders.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import net.minidev.json.JSONArray;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.tests.CommonWebTests;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
import org.springframework.hateoas.RelProvider;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriTemplate;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.JsonPath;

/**
 * Web integration tests specific to JPA.
 * 
 * @author Oliver Gierke
 * @author Greg Turnquist
 */
@Transactional
@ContextConfiguration(classes = JpaRepositoryConfig.class)
public class JpaWebTests extends CommonWebTests {

    private static final MediaType TEXT_URI_LIST = MediaType.valueOf("text/uri-list");
    static final String LINK_TO_SIBLINGS_OF = "$._embedded..[?(@.firstName == '%s')]._links.siblings.href[0]";

    @Autowired
    TestDataPopulator loader;
    @Autowired
    ResourceMappings mappings;
    @Autowired
    RelProvider relProvider;

    ObjectMapper mapper = new ObjectMapper();

    /* 
     * (non-Javadoc)
     * @see org.springframework.data.rest.webmvc.AbstractWebIntegrationTests#setUp()
     */
    @Override
    @Before
    public void setUp() {
        loader.populateRepositories();
        super.setUp();
    }

    /* 
     * (non-Javadoc)
     * @see org.springframework.data.rest.webmvc.AbstractWebIntegrationTests#expectedRootLinkRels()
     */
    @Override
    protected Iterable<String> expectedRootLinkRels() {
        return Arrays.asList("people", "authors", "books");
    }

    /* 
     * (non-Javadoc)
     * @see org.springframework.data.rest.webmvc.AbstractWebIntegrationTests#getPayloadToPost()
     */
    @Override
    protected Map<String, String> getPayloadToPost() throws Exception {
        return Collections.singletonMap("people", readFileFromClasspath("person.json"));
    }

    /* 
     * (non-Javadoc)
     * @see org.springframework.data.rest.webmvc.AbstractWebIntegrationTests#getRootAndLinkedResources()
     */
    @Override
    protected MultiValueMap<String, String> getRootAndLinkedResources() {

        MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
        map.add("authors", "books");
        map.add("books", "authors");

        return map;
    }

    /**
     * @see DATAREST-99
     */
    @Test
    public void doesNotExposeCreditCardRepository() throws Exception {

        mvc.perform(get("/")). //
                andExpect(status().isOk()). //
                andExpect(doesNotHaveLinkWithRel(mappings.getMetadataFor(CreditCard.class).getRel()));
    }

    @Test
    public void accessPersons() throws Exception {

        MockHttpServletResponse response = client.request("/people?page=0&size=1");

        Link nextLink = client.assertHasLinkWithRel(Link.REL_NEXT, response);
        assertDoesNotHaveLinkWithRel(Link.REL_PREVIOUS, response);

        response = client.request(nextLink);
        client.assertHasLinkWithRel(Link.REL_PREVIOUS, response);
        nextLink = client.assertHasLinkWithRel(Link.REL_NEXT, response);

        response = client.request(nextLink);
        client.assertHasLinkWithRel(Link.REL_PREVIOUS, response);
        assertDoesNotHaveLinkWithRel(Link.REL_NEXT, response);
    }

    /**
     * @see DATAREST-169
     */
    @Test
    public void exposesLinkForRelatedResource() throws Exception {

        MockHttpServletResponse response = client.request("/");
        Link ordersLink = client.assertHasLinkWithRel("orders", response);

        MockHttpServletResponse orders = client.request(ordersLink);
        Link creatorLink = assertHasContentLinkWithRel("creator", orders);

        assertThat(client.request(creatorLink), is(notNullValue()));
    }

    /**
     * @see DATAREST-200
     */
    @Test
    public void exposesInlinedEntities() throws Exception {

        MockHttpServletResponse response = client.request("/");
        Link ordersLink = client.assertHasLinkWithRel("orders", response);

        MockHttpServletResponse orders = client.request(ordersLink);
        assertHasJsonPathValue("$..lineItems", orders);
    }

    /**
     * @see DATAREST-199
     */
    @Test
    public void createsOrderUsingPut() throws Exception {

        mvc.perform(//
                put("/orders/{id}", 4711).//
                        content(readFileFromClasspath("order.json")).contentType(MediaType.APPLICATION_JSON)//
        ).andExpect(status().isCreated());
    }

    /**
     * @see DATAREST-117
     */
    @Test
    public void createPersonThenVerifyIgnoredAttributesDontExist() throws Exception {

        Link peopleLink = client.discoverUnique("people");
        ObjectMapper mapper = new ObjectMapper();
        Person frodo = new Person("Frodo", "Baggins");
        frodo.setAge(77);
        frodo.setHeight(42);
        frodo.setWeight(75);
        String frodoString = mapper.writeValueAsString(frodo);

        MockHttpServletResponse response = postAndGet(peopleLink, frodoString, MediaType.APPLICATION_JSON);

        assertJsonPathEquals("$.firstName", "Frodo", response);
        assertJsonPathEquals("$.lastName", "Baggins", response);
        assertJsonPathDoesntExist("$.age", response);
        assertJsonPathDoesntExist("$.height", response);
        assertJsonPathDoesntExist("$.weight", response);
    }

    /**
     * @see DATAREST-95
     */
    @Test
    public void createThenPatch() throws Exception {

        Link peopleLink = client.discoverUnique("people");

        MockHttpServletResponse bilbo = postAndGet(peopleLink,
                "{ \"firstName\" : \"Bilbo\", \"lastName\" : \"Baggins\" }", MediaType.APPLICATION_JSON);

        Link bilboLink = client.assertHasLinkWithRel("self", bilbo);

        assertThat((String) JsonPath.read(bilbo.getContentAsString(), "$.firstName"), is("Bilbo"));
        assertThat((String) JsonPath.read(bilbo.getContentAsString(), "$.lastName"), is("Baggins"));

        MockHttpServletResponse frodo = patchAndGet(bilboLink, "{ \"firstName\" : \"Frodo\" }",
                MediaType.APPLICATION_JSON);

        assertThat((String) JsonPath.read(frodo.getContentAsString(), "$.firstName"), is("Frodo"));
        assertThat((String) JsonPath.read(frodo.getContentAsString(), "$.lastName"), is("Baggins"));

        frodo = patchAndGet(bilboLink, "{ \"firstName\" : null }", MediaType.APPLICATION_JSON);

        assertThat((String) JsonPath.read(frodo.getContentAsString(), "$.firstName"), is(nullValue()));
        assertThat((String) JsonPath.read(frodo.getContentAsString(), "$.lastName"), is("Baggins"));
    }

    /**
     * @see DATAREST-150
     */
    @Test
    public void createThenPut() throws Exception {

        Link peopleLink = client.discoverUnique("people");

        MockHttpServletResponse bilbo = postAndGet(peopleLink, //
                "{ \"firstName\" : \"Bilbo\", \"lastName\" : \"Baggins\" }", //
                MediaType.APPLICATION_JSON);

        Link bilboLink = client.assertHasLinkWithRel("self", bilbo);

        assertThat((String) JsonPath.read(bilbo.getContentAsString(), "$.firstName"), equalTo("Bilbo"));
        assertThat((String) JsonPath.read(bilbo.getContentAsString(), "$.lastName"), equalTo("Baggins"));

        MockHttpServletResponse frodo = putAndGet(bilboLink, //
                "{ \"firstName\" : \"Frodo\" }", //
                MediaType.APPLICATION_JSON);

        assertThat((String) JsonPath.read(frodo.getContentAsString(), "$.firstName"), equalTo("Frodo"));
        assertNull(JsonPath.read(frodo.getContentAsString(), "$.lastName"));
    }

    @Test
    public void listsSiblingsWithContentCorrectly() throws Exception {
        assertPersonWithNameAndSiblingLink("John");
    }

    @Test
    public void listsEmptySiblingsCorrectly() throws Exception {
        assertPersonWithNameAndSiblingLink("Billy Bob");
    }

    /**
     * @see DATAREST-219
     */
    @Test
    public void manipulatePropertyCollectionRestfullyWithMultiplePosts() throws Exception {

        List<Link> links = preparePersonResources(new Person("Frodo", "Baggins"), //
                new Person("Bilbo", "Baggins"), //
                new Person("Merry", "Baggins"), //
                new Person("Pippin", "Baggins"));

        Link frodosSiblingLink = links.get(0);

        patchAndGet(frodosSiblingLink, links.get(1).getHref(), TEXT_URI_LIST);
        patchAndGet(frodosSiblingLink, links.get(2).getHref(), TEXT_URI_LIST);
        patchAndGet(frodosSiblingLink, links.get(3).getHref(), TEXT_URI_LIST);

        assertSiblingNames(frodosSiblingLink, "Bilbo", "Merry", "Pippin");
    }

    /**
     * @see DATAREST-219
     */
    @Test
    public void manipulatePropertyCollectionRestfullyWithSinglePost() throws Exception {

        List<Link> links = preparePersonResources(new Person("Frodo", "Baggins"), //
                new Person("Bilbo", "Baggins"), //
                new Person("Merry", "Baggins"), //
                new Person("Pippin", "Baggins"));

        Link frodosSiblingLink = links.get(0);

        patchAndGet(frodosSiblingLink, toUriList(links.get(1), links.get(2), links.get(3)), TEXT_URI_LIST);

        assertSiblingNames(frodosSiblingLink, "Bilbo", "Merry", "Pippin");
    }

    /**
     * @see DATAREST-219
     */
    @Test
    public void manipulatePropertyCollectionRestfullyWithMultiplePuts() throws Exception {

        List<Link> links = preparePersonResources(new Person("Frodo", "Baggins"), //
                new Person("Bilbo", "Baggins"), //
                new Person("Merry", "Baggins"), //
                new Person("Pippin", "Baggins"));

        Link frodosSiblingsLink = links.get(0);

        putAndGet(frodosSiblingsLink, links.get(1).expand().getHref(), TEXT_URI_LIST);
        putAndGet(frodosSiblingsLink, links.get(2).expand().getHref(), TEXT_URI_LIST);
        putAndGet(frodosSiblingsLink, links.get(3).expand().getHref(), TEXT_URI_LIST);
        assertSiblingNames(frodosSiblingsLink, "Pippin");

        patchAndGet(frodosSiblingsLink, links.get(2).getHref(), TEXT_URI_LIST);
        assertSiblingNames(frodosSiblingsLink, "Merry", "Pippin");
    }

    /**
     * @see DATAREST-219
     */
    @Test
    public void manipulatePropertyCollectionRestfullyWithSinglePut() throws Exception {

        List<Link> links = preparePersonResources(new Person("Frodo", "Baggins"), //
                new Person("Bilbo", "Baggins"), //
                new Person("Merry", "Baggins"), //
                new Person("Pippin", "Baggins"));

        Link frodoSiblingLink = links.get(0);

        putAndGet(frodoSiblingLink, toUriList(links.get(1), links.get(2), links.get(3)), TEXT_URI_LIST);
        assertSiblingNames(frodoSiblingLink, "Bilbo", "Merry", "Pippin");

        putAndGet(frodoSiblingLink, toUriList(links.get(3)), TEXT_URI_LIST);
        assertSiblingNames(frodoSiblingLink, "Pippin");

        patchAndGet(frodoSiblingLink, toUriList(links.get(2)), TEXT_URI_LIST);
        assertSiblingNames(frodoSiblingLink, "Merry", "Pippin");
    }

    /**
     * @see DATAREST-219
     */
    @Test
    public void manipulatePropertyCollectionRestfullyWithDelete() throws Exception {

        List<Link> links = preparePersonResources(new Person("Frodo", "Baggins"), //
                new Person("Bilbo", "Baggins"), //
                new Person("Merry", "Baggins"), //
                new Person("Pippin", "Baggins"));

        Link frodosSiblingsLink = links.get(0);

        patchAndGet(frodosSiblingsLink, links.get(1).getHref(), TEXT_URI_LIST);
        patchAndGet(frodosSiblingsLink, links.get(2).getHref(), TEXT_URI_LIST);
        patchAndGet(frodosSiblingsLink, links.get(3).getHref(), TEXT_URI_LIST);

        String pippinId = new UriTemplate("/people/{id}").match(links.get(3).getHref()).get("id");
        deleteAndVerify(new Link(frodosSiblingsLink.getHref() + "/" + pippinId));

        assertSiblingNames(frodosSiblingsLink, "Bilbo", "Merry");
    }

    /**
     * @see DATAREST-50
     */
    @Test
    public void propertiesCanHaveNulls() throws Exception {

        Link peopleLink = client.discoverUnique("people");

        Person frodo = new Person();
        frodo.setFirstName("Frodo");
        frodo.setLastName(null);

        MockHttpServletResponse response = postAndGet(peopleLink, mapper.writeValueAsString(frodo),
                MediaType.APPLICATION_JSON);
        String responseBody = response.getContentAsString();

        assertEquals(JsonPath.read(responseBody, "$.firstName"), "Frodo");
        assertNull(JsonPath.read(responseBody, "$.lastName"));
    }

    /**
     * @see DATAREST-238
     */
    @Test
    public void putShouldWorkDespiteExistingLinks() throws Exception {

        Link peopleLink = client.discoverUnique("people");

        Person frodo = new Person("Frodo", "Baggins");
        String frodoString = mapper.writeValueAsString(frodo);

        MockHttpServletResponse createdPerson = postAndGet(peopleLink, frodoString, MediaType.APPLICATION_JSON);

        Link frodoLink = client.assertHasLinkWithRel("self", createdPerson);
        assertJsonPathEquals("$.firstName", "Frodo", createdPerson);

        String bilboWithFrodosLinks = createdPerson.getContentAsString().replace("Frodo", "Bilbo");

        MockHttpServletResponse overwrittenResponse = putAndGet(frodoLink, bilboWithFrodosLinks,
                MediaType.APPLICATION_JSON);

        client.assertHasLinkWithRel("self", overwrittenResponse);
        assertJsonPathEquals("$.firstName", "Bilbo", overwrittenResponse);
    }

    /**
     * @see DATAREST-217
     */
    @Test
    public void doesNotAllowGetToCollectionResourceIfFindAllIsNotExported() throws Exception {

        Link link = client.discoverUnique("addresses");

        mvc.perform(get(link.getHref())).//
                andExpect(status().isMethodNotAllowed());
    }

    /**
     * @see DATAREST-217
     */
    @Test
    public void doesNotAllowPostToCollectionResourceIfSaveIsNotExported() throws Exception {

        Link link = client.discoverUnique("addresses");

        mvc.perform(post(link.getHref()).content("{}").contentType(MediaType.APPLICATION_JSON)).//
                andExpect(status().isMethodNotAllowed());
    }

    /**
     * Checks, that the server only returns the properties contained in the projection requested.
     * 
     * @see OrderSummary
     * @see DATAREST-221
     */
    @Test
    public void returnsProjectionIfRequested() throws Exception {

        Link orders = client.discoverUnique("orders");

        MockHttpServletResponse response = client.request(orders);
        Link orderLink = assertContentLinkWithRel("self", response, true).expand();

        UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(orderLink.getHref());
        String uri = builder.queryParam("projection", "summary").build().toUriString();

        response = mvc.perform(get(uri)). //
                andExpect(status().isOk()). //
                andExpect(jsonPath("$.price", is(2.5))).//
                andReturn().getResponse();

        assertJsonPathDoesntExist("$.lineItems", response);
    }

    /**
     * @see DATAREST-261
     */
    @Test
    public void relProviderDetectsCustomizedMapping() {
        assertThat(relProvider.getCollectionResourceRelFor(Person.class), is("people"));
    }

    /**
     * @see DATAREST-311
     */
    @Test
    public void onlyLinksShouldAppearWhenExecuteSearchCompact() throws Exception {

        Link peopleLink = client.discoverUnique("people");
        Person daenerys = new Person("Daenerys", "Targaryen");
        String daenerysString = mapper.writeValueAsString(daenerys);

        MockHttpServletResponse createdPerson = postAndGet(peopleLink, daenerysString, MediaType.APPLICATION_JSON);
        Link daenerysLink = client.assertHasLinkWithRel("self", createdPerson);
        assertJsonPathEquals("$.firstName", "Daenerys", createdPerson);

        Link searchLink = client.discoverUnique(peopleLink, "search");
        Link byFirstNameLink = client.discoverUnique(searchLink, "findFirstPersonByFirstName");

        MockHttpServletResponse response = client.request(byFirstNameLink.expand("Daenerys"),
                MediaType.parseMediaType("application/x-spring-data-compact+json"));

        String responseBody = response.getContentAsString();

        JSONArray personLinks = JsonPath.<JSONArray>read(responseBody, "$.links[?(@.rel=='person')].href");

        assertThat(personLinks, hasSize(1));
        assertThat(personLinks.get(0), is((Object) daenerysLink.getHref()));
        assertThat(JsonPath.<JSONArray>read(responseBody, "$.content"), hasSize(0));
    }

    /**
     * @see DATAREST-317
     */
    @Test
    public void rendersExcerptProjectionsCorrectly() throws Exception {

        Link authorsLink = client.discoverUnique("authors");

        MockHttpServletResponse response = client.request(authorsLink);
        String firstAuthorPath = "$._embedded.authors[0]";

        // Has main content
        assertHasJsonPathValue(firstAuthorPath.concat(".name"), response);

        // Embeddes content of related entity, self link and keeps relation link
        assertHasJsonPathValue(firstAuthorPath.concat("._embedded.books[0].title"), response);
        assertHasJsonPathValue(firstAuthorPath.concat("._embedded.books[0]._links.self"), response);
        assertHasJsonPathValue(firstAuthorPath.concat("._links.books"), response);

        // Access item resource and expect link to related resource present
        String content = response.getContentAsString();
        String href = JsonPath.read(content, firstAuthorPath.concat("._links.self.href"));

        client.follow(new Link(href)).andExpect(client.hasLinkWithRel("books"));
    }

    /**
     * @see DATAREST-353
     */
    @Test
    public void returns404WhenTryingToDeleteANonExistingResource() throws Exception {

        Link receiptsLink = client.discoverUnique("receipts");

        mvc.perform(delete(receiptsLink.getHref().concat("/{id}"), 4711)).//
                andExpect(status().isNotFound());
    }

    /**
     * @see DATAREST-384
     */
    @Test
    public void execturesSearchThatTakesASort() throws Exception {

        Link booksLink = client.discoverUnique("books");
        Link searchLink = client.discoverUnique(booksLink, "search");
        Link findBySortedLink = client.discoverUnique(searchLink, "find-by-sorted");

        // Assert sort options advertised
        assertThat(findBySortedLink.isTemplated(), is(true));
        assertThat(findBySortedLink.getVariableNames(), hasItems("sort", "projection"));

        // Assert results returned as specified
        client.follow(findBySortedLink.expand("title,desc")).//
                andExpect(jsonPath("$._embedded.books[0].title").value("Spring Data (Second Edition)")).//
                andExpect(jsonPath("$._embedded.books[1].title").value("Spring Data")).//
                andExpect(client.hasLinkWithRel("self"));

        client.follow(findBySortedLink.expand("title,asc")).//
                andExpect(jsonPath("$._embedded.books[0].title").value("Spring Data")).//
                andExpect(jsonPath("$._embedded.books[1].title").value("Spring Data (Second Edition)")).//
                andExpect(client.hasLinkWithRel("self"));
    }

    /**
     * @see DATAREST-160
     */
    @Test
    public void returnConflictWhenConcurrentlyEditingVersionedEntity() throws Exception {

        Link receiptLink = client.discoverUnique("receipts");

        Receipt receipt = new Receipt();
        receipt.setAmount(new BigDecimal(50));
        receipt.setSaleItem("Springy Tacos");

        String stringReceipt = mapper.writeValueAsString(receipt);

        MockHttpServletResponse createdReceipt = postAndGet(receiptLink, stringReceipt, MediaType.APPLICATION_JSON);
        Link tacosLink = client.assertHasLinkWithRel("self", createdReceipt);
        assertJsonPathEquals("$.saleItem", "Springy Tacos", createdReceipt);

        UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(tacosLink.getHref());
        String concurrencyTag = createdReceipt.getHeader("ETag");

        mvc.perform(patch(builder.build().toUriString()).content("{ \"saleItem\" : \"SpringyBurritos\" }")
                .contentType(MediaType.APPLICATION_JSON).header(IF_MATCH, concurrencyTag))
                .andExpect(status().is2xxSuccessful());

        mvc.perform(patch(builder.build().toUriString()).content("{ \"saleItem\" : \"SpringyTequila\" }")
                .contentType(MediaType.APPLICATION_JSON).header(IF_MATCH, "\"falseETag\""))
                .andExpect(status().isPreconditionFailed());
    }

    /**
     * @see DATAREST-423
     */
    @Test
    public void invokesCustomControllerAndBindsDomainObjectCorrectly() throws Exception {

        MockHttpServletResponse authorsResponse = client.request(client.discoverUnique("authors"));

        String authorUri = JsonPath.read(authorsResponse.getContentAsString(),
                "$._embedded.authors[0]._links.self.href");

        mvc.perform(delete(authorUri)).//
                andExpect(status().isIAmATeapot());
    }

    /**
     * @see DATAREST-523
     */
    @Test
    public void augmentsCollectionAssociationUsingPost() throws Exception {

        List<Link> links = preparePersonResources(new Person("Frodo", "Baggins"), //
                new Person("Bilbo", "Baggins"));

        Link frodosSiblingsLink = links.get(0).expand();
        Link bilboLink = links.get(1);

        for (int i = 1; i <= 2; i++) {

            mvc.perform(post(frodosSiblingsLink.getHref()).//
                    content(bilboLink.getHref()).//
                    contentType(TEXT_URI_LIST)).//
                    andExpect(status().isNoContent());

            mvc.perform(get(frodosSiblingsLink.getHref())).//
                    andExpect(jsonPath("$._embedded.people", hasSize(i)));
        }
    }

    /**
     * @see DATAREST-658
     */
    @Test
    public void returnsLinkHeadersForHeadRequestToItemResource() throws Exception {

        MockHttpServletResponse response = client.request(client.discoverUnique("people"));
        String personHref = JsonPath.read(response.getContentAsString(), "$._embedded.people[0]._links.self.href");

        response = mvc.perform(head(personHref))//
                .andExpect(status().isNoContent())//
                .andReturn().getResponse();

        Links links = Links.valueOf(response.getHeader("Link"));
        assertThat(links.hasLink("self"), is(true));
        assertThat(links.hasLink("person"), is(true));
    }

    private List<Link> preparePersonResources(Person primary, Person... persons) throws Exception {

        Link peopleLink = client.discoverUnique("people");
        List<Link> links = new ArrayList<Link>();

        MockHttpServletResponse primaryResponse = postAndGet(peopleLink, mapper.writeValueAsString(primary),
                MediaType.APPLICATION_JSON);
        links.add(client.assertHasLinkWithRel("siblings", primaryResponse));

        for (Person person : persons) {

            String payload = mapper.writeValueAsString(person);
            MockHttpServletResponse response = postAndGet(peopleLink, payload, MediaType.APPLICATION_JSON);

            links.add(client.assertHasLinkWithRel(Link.REL_SELF, response));
        }

        return links;
    }

    /**
     * Asserts the {@link Person} resource the given link points to contains siblings with the given names.
     * 
     * @param link
     * @param siblingNames
     * @throws Exception
     */
    private void assertSiblingNames(Link link, String... siblingNames) throws Exception {

        String responseBody = client.request(link).getContentAsString();
        List<String> persons = JsonPath.read(responseBody, "$._embedded.people[*].firstName");

        assertThat(persons, hasSize(siblingNames.length));
        assertThat(persons, hasItems(siblingNames));
    }

    private void assertPersonWithNameAndSiblingLink(String name) throws Exception {

        MockHttpServletResponse response = client.request(client.discoverUnique("people"));

        String jsonPath = String.format("$._embedded.people[?(@.firstName == '%s')]", name);

        // Assert content inlined
        Object john = JsonPath.<JSONArray>read(response.getContentAsString(), jsonPath).get(0);
        assertThat(john, is(notNullValue()));
        assertThat(JsonPath.read(john, "$.firstName"), is(notNullValue()));

        // Assert sibling link exposed in resource pointed to
        Link selfLink = new Link(JsonPath.<String>read(john, "$._links.self.href"));
        client.follow(selfLink).//
                andExpect(status().isOk()).//
                andExpect(jsonPath("$._links.siblings", is(notNullValue())));
    }

    private static String toUriList(Link... links) {

        List<String> uris = new ArrayList<String>(links.length);

        for (Link link : links) {
            uris.add(link.expand().getHref());
        }

        return StringUtils.collectionToDelimitedString(uris, "\n");
    }
}