package org.springframework.data.rest.tck.jpa; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; import java.util.Locale; import com.jayway.jsonpath.JsonPath; import org.hamcrest.Matchers; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.rest.tck.AbstractTckTest; import org.springframework.hateoas.Link; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletResponse; /** * Tests that check the REST API of JPA entities that are exported through Spring Data REST. * * @author Jon Brisbin */ public class JpaTckTests extends AbstractTckTest { static final Logger LOG = LoggerFactory.getLogger(JpaTckTests.class); static final String SELF_REL = "self"; static final String CUSTOMERS_REL = "customer"; static final String CUSTOMER_REL = "customer.customer"; static final String CUSTOMER_ADDRESSES_REL = "customer.customer.addresses"; static final String CUSTOMER_ADDRESS_REL = "customer.customer.addresses.address"; static final String PRODUCTS_REL = "product"; static final String PRODUCT_REL = "product.product"; static final String ORDERS_REL = "order"; static final String ORDER_REL = "order.order"; static final String ORDER_LINEITEMS_REL = "order.order.lineItems"; static final String ORDER_LINEITEM_REL = "order.order.lineItems.lineItem"; static final String LINEITEMS_REL = "lineItem"; static final String LINEITEM_REL = "lineItem.lineItem"; @Autowired protected TestDataLoader dataLoader; @Override protected void loadData() { dataLoader.loadData(); } @Override protected void deleteData() { } /** * Test whether {@link org.springframework.data.repository.CrudRepository}s exported are discoverable. * * @throws Exception */ @Test public void testDiscoverability() throws Exception { Link customer = customersLink(); assertNotNull("Exposes a Link to manage Customers", customer); assertThat("Customer Link looks correct", customer.getHref(), allOf(startsWith("http://"), endsWith("/customer"))); } /** * Test whether entities are exposed inline. * * @throws Exception */ @Test public void testListsEntities() throws Exception { Link customer = customersLink(); MockHttpServletResponse response = request(customer.getHref()); String jsonBody = response.getContentAsString(); assertThat("Entity data is exposed inline", JsonPath.read(jsonBody, "$content[0].lastname").toString(), is("Doe")); } /** * Tests whether entities are exposed as links using the {@literal application/x-spring-data-compact+json} JSON type. * * @throws Exception */ @Test public void testLinksToEntities() throws Exception { List<Link> customers = linksToCustomers(); assertThat("Entity is referenced as a Link", customers, Matchers.<Link>iterableWithSize(2)); } /** * Test whether the {@link org.springframework.data.rest.tck.jpa.repository.CustomerRepository} exposes a CREATE * feature. * * @throws Exception */ @Test public void testCreate() throws Exception { for(Link l : linksToCustomers()) { MockHttpServletResponse response = request(l.getHref()); String jsonBody = response.getContentAsString(); assertThat("Customer is a Doe", JsonPath.read(jsonBody, "lastname").toString(), is("Doe")); assertThat("Entity contains self Link", links.findLinkWithRel(SELF_REL, jsonBody), notNullValue()); assertThat("Entity maintains addresses as Links", links.findLinkWithRel(CUSTOMER_ADDRESSES_REL, jsonBody), notNullValue()); } } @Test public void testWontCreateInvalidEntity() throws Exception { Link customers = customersLink(); String jsonBody = mockMvc .perform(post(customers.getHref()) .locale(Locale.GERMAN) .contentType(MediaType.APPLICATION_JSON) .content("{}")) .andExpect(status().isBadRequest()) .andReturn().getResponse().getContentAsString(); System.out.println("jsonBody: " + jsonBody); assertThat("Error messages were sent back", JsonPath.read(jsonBody, "$errors[0].message"), notNullValue()); assertThat("Error messages were localized correctly", JsonPath.read(jsonBody, "$errors[0].message").toString(), is("firstname darf nicht leer sein")); } /** * Test whether linked properties expose the entity inline. * * @throws Exception */ @Test public void testExposesAccessToLinkedEntities() throws Exception { for(Link l : linksToCustomers()) { List<Link> addresses = discover(l, CUSTOMER_ADDRESSES_REL); assertThat("Has linked Addresses", addresses, Matchers.<Link>iterableWithSize(1)); Link addressLink = addresses.iterator().next(); MockHttpServletResponse addrResponse = request(addressLink.getHref()); String addrJsonBody = addrResponse.getContentAsString(); assertThat("Has valid street", String.format("%s", JsonPath.read(addrJsonBody, "$content[0].street")), is("123 W 1st Street")); } } /** * Test that a PUT will do a partial update of the entity. * * @throws Exception */ @Test public void testExposesUpdate() throws Exception { Link customer = linksToCustomers().iterator().next(); byte[] bytes = Files.readAllBytes(Paths.get("src/test/resources/customer-update.txt")); mockMvc .perform(put(customer.getHref()) .contentType(MediaType.APPLICATION_JSON) .content(bytes)) .andExpect(status().isNoContent()); MockHttpServletResponse response2 = request(customer.getHref()); assertThat("Firstname field was updated correctly", JsonPath.read(response2.getContentAsString(), "firstname").toString(), is("Ralph")); } /** * Test that a DELETE really removes an entity from the datastore. * * @throws Exception */ @Test public void testExposesDelete() throws Exception { List<Link> customers = linksToCustomers(); Link customer = customers.get(customers.size() - 1); mockMvc .perform(delete(customer.getHref())) .andExpect(status().isNoContent()); mockMvc .perform(get(customer.getHref())) .andExpect(status().isNotFound()); } /** * Test that entities can be created with linked entities. * * @throws Exception */ @Test public void testCreatesEntityWithLinkedProperties() throws Exception { List<Link> customers = linksToCustomers(); List<Link> products = linksToProducts(); Link customer = customers.get(0); Link addrSelf = follow(discover(customer, CUSTOMER_ADDRESSES_REL).get(0), CUSTOMER_ADDRESS_REL, "self").get(0); Link product = products.get(0); String jsonBody = new String(Files.readAllBytes(Paths.get("src/test/resources/new-order-1.txt"))); jsonBody = jsonBody.replaceAll("%CUSTOMER_HREF%", customer.getHref()); jsonBody = jsonBody.replaceAll("%ADDR_HREF%", addrSelf.getHref()); jsonBody = jsonBody.replaceAll("%PRODUCT_HREF%", product.getHref()); Link orders = ordersLink(); String loc = mockMvc .perform(post(orders.getHref()) .contentType(MediaType.APPLICATION_JSON) .content(jsonBody)) .andExpect(status().isCreated()) .andReturn().getResponse().getHeader("Location"); assertNotNull("Location header is not null", loc); MockHttpServletResponse checkResp = request(loc); String checkJsonBody = checkResp.getContentAsString(); Link lineItemsLink = follow(new Link(loc), ORDER_LINEITEMS_REL, ORDER_LINEITEM_REL).get(0); assertNotNull("LineItems links is not null", lineItemsLink); mockMvc .perform(get(lineItemsLink.getHref()).accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("price", is(100.0))); Link custLink = links.findLinkWithRel("order.order.customer", checkJsonBody); assertNotNull("Customer link is not null", custLink); mockMvc .perform(get(custLink.getHref()).accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("content.lastname", is("Doe"))); } @Test public void testAddNewLinkedEntity() throws Exception { testCreatesEntityWithLinkedProperties(); List<Link> customers = linksToCustomers(); List<Link> products = linksToProducts(); Link lineItemsLink = lineItemsLink(); String jsonBody = new String(Files.readAllBytes(Paths.get("src/test/resources/lineitem-json.txt"))); jsonBody = jsonBody.replaceAll("%PRODUCT_HREF%", products.get(products.size() - 1).getHref()); mockMvc .perform(post(lineItemsLink.getHref()) .contentType(MediaType.APPLICATION_JSON) .content(jsonBody)) .andExpect(status().isCreated()); } @SuppressWarnings({"unchecked"}) @Test public void testUpdateExistingLinkedEntity() throws Exception { List<Link> products = linksToProducts(); Link lineItemsLink = lineItemsLink(); Link orderLink = linksToOrders().get(0); Link orderLineItemsLink = discover(orderLink, ORDER_LINEITEMS_REL).get(0); String jsonBody = new String(Files.readAllBytes(Paths.get("src/test/resources/lineitem-json.txt"))); jsonBody = jsonBody.replaceAll("%PRODUCT_HREF%", products.get(products.size() - 1).getHref()); String lineItemLoc = mockMvc .perform(post(lineItemsLink.getHref()) .contentType(MediaType.APPLICATION_JSON) .content(jsonBody.getBytes())) .andExpect(status().isCreated()) .andReturn().getResponse().getHeader("Location"); jsonBody = new String(Files.readAllBytes(Paths.get("src/test/resources/new-lineitem-json.txt"))); jsonBody = jsonBody.replaceAll("%LINEITEM_HREF%", lineItemLoc); mockMvc .perform(post(orderLineItemsLink.getHref()) .contentType(MediaType.APPLICATION_JSON) .content(jsonBody.getBytes())) .andExpect(status().isCreated()); assertThat("Order now contains additional LineItem", discover(orderLineItemsLink, ORDER_LINEITEM_REL), Matchers.<Link>iterableWithSize(2)); } @Test public void testReturnsNotFound() throws Exception { mockMvc .perform(get("/badlink")) .andExpect(status().isNotFound()); } @Test public void canAccessRelationsForEntitiesWithoutCommonSuperclass() throws Exception { dataLoader.createUsersAndAccounts(); mockMvc.perform(get("/accounts/1/user")). // andExpect(status().isOk()); } @Test public void cannotAccessNotAccessibleRepository() throws Exception { mockMvc .perform(get("/notAccessible")) .andExpect(status().isNotFound()); } private Link customersLink() throws Exception { return discoverRootLink(CUSTOMERS_REL); } private Link productsLink() throws Exception { return discoverRootLink(PRODUCTS_REL); } private Link ordersLink() throws Exception { return discoverRootLink(ORDERS_REL); } private Link lineItemsLink() throws Exception { return discoverRootLink(LINEITEMS_REL); } private List<Link> linksToCustomers() throws Exception { return discover(customersLink(), CUSTOMER_REL); } private List<Link> linksToProducts() throws Exception { return discover(productsLink(), PRODUCT_REL); } private List<Link> linksToOrders() throws Exception { return discover(ordersLink(), ORDER_REL); } }