package de.escalon.hypermedia.spring.siren; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.hateoas.Link; import org.springframework.hateoas.Resource; import org.springframework.hateoas.ResourceSupport; import org.springframework.hateoas.Resources; import org.springframework.hateoas.core.Relation; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.AnnotationConfigWebContextLoader; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; import java.util.List; import static com.jayway.jsonassert.JsonAssert.with; import static de.escalon.hypermedia.spring.AffordanceBuilder.linkTo; import static de.escalon.hypermedia.spring.AffordanceBuilder.methodOn; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; /** * Created by Dietrich on 18.04.2016. */ @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration(loader = AnnotationConfigWebContextLoader.class) public class SirenMessageConverterTest { public static final Logger LOG = LoggerFactory.getLogger(SirenMessageConverterTest.class); private ObjectMapper objectMapper = new ObjectMapper(); SirenUtils sirenUtils = new SirenUtils(); @Relation("customer") class Customer { private final String customerId = "pj123"; private final String name = "Peter Joseph"; public String getCustomerId() { return customerId; } public String getName() { return name; } } @RequestMapping("/customers") static class DummyCustomersController { @RequestMapping("/{customerId}") public ResponseEntity<Resource<Customer>> getCustomer(@PathVariable String customerId) { return null; } } public static class OrderItem { private int orderNumber; private String productCode; private Integer quantity; @JsonCreator public OrderItem(@JsonProperty("orderNumber") int orderNumber, @JsonProperty("productCode") String productCode, @JsonProperty("quantity") Integer quantity) { this.orderNumber = orderNumber; this.productCode = productCode; this.quantity = quantity; } public int getOrderNumber() { return orderNumber; } public String getProductCode() { return productCode; } public Integer getQuantity() { return quantity; } } class Order extends ResourceSupport { private final int orderNumber = 42; private final int itemCount = 3; private final String status = "pending"; private final Resource<Customer> customer = new Resource<Customer>(new Customer()); public Order() { customer.add(linkTo(methodOn(DummyCustomersController.class) .getCustomer("pj123")) .withSelfRel()); } public int getOrderNumber() { return orderNumber; } public int getItemCount() { return itemCount; } public String getStatus() { return status; } public Resource<Customer> getCustomer() { return customer; } } @RequestMapping("/orders") static class DummyOrderController { @RequestMapping("/{orderNumber}") public ResponseEntity<Resource<Order>> getOrder(@PathVariable int orderNumber) { return null; } @RequestMapping("/{orderNumber}/items") public ResponseEntity<Resource<OrderItem>> getOrderItems(@PathVariable int orderNumber) { return null; } @RequestMapping(value = "/{orderNumber}/items", method = RequestMethod.POST) public ResponseEntity<Void> addOrderItems(@PathVariable int orderNumber, @RequestBody OrderItem orderItem) { return null; } @RequestMapping public ResponseEntity<Resources<Order>> getOrders(@RequestParam List<String> attr) { return null; } } @Configuration @EnableWebMvc static class WebConfig extends WebMvcConfigurerAdapter { @Bean public DummyOrderController orderController() { return new DummyOrderController(); } @Bean public DummyCustomersController customersController() { return new DummyCustomersController(); } @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { super.configureMessageConverters(converters); converters.add(new SirenMessageConverter()); } @Override public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) { final ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver(); resolver.setWarnLogCategory(resolver.getClass() .getName()); exceptionResolvers.add(resolver); } } @Autowired private WebApplicationContext wac; private MockMvc mockMvc; @Before public void setUp() { this.mockMvc = webAppContextSetup(this.wac).build(); objectMapper.enable(SerializationFeature.INDENT_OUTPUT); } @Test public void testActions() throws JsonProcessingException { Order order = new Order(); order.add(linkTo(methodOn(DummyOrderController.class) .addOrderItems(42, new OrderItem(42, null, null))) .withRel("order-items")); order.add(linkTo(methodOn(DummyOrderController.class) .getOrder(42)) .withSelfRel()); order.add(linkTo(methodOn(DummyOrderController.class) .getOrder(43)) .withRel("next")); order.add(linkTo(methodOn(DummyOrderController.class) .getOrder(41)) .withRel("previous")); order.add(linkTo(methodOn(DummyOrderController.class) .getOrders(null)).withRel("orders")); // no support for non-query link templates order.add(new Link("http://example.com/{foo}", "foo")); order.add(new Link("http://example.com/{foo}{?bar}", "foo-query")); // support for query link templates order.add(new Link("http://example.com{?bar}", "bar")); SirenEntity entity = new SirenEntity(); sirenUtils.toSirenEntity(entity, order); String json = objectMapper.valueToTree(entity) .toString(); with(json).assertThat("$.actions", hasSize(3)); with(json).assertThat("$.actions[0].fields", hasSize(3)); with(json).assertThat("$.actions[0].fields[0].name", equalTo("orderNumber")); with(json).assertThat("$.actions[0].fields[0].type", equalTo("number")); with(json).assertThat("$.actions[0].fields[0].value", equalTo("42")); with(json).assertThat("$.actions[0].method", equalTo("POST")); // TODO list query parameter: do something smarter with(json).assertThat("$.actions[1].fields[0].name", equalTo("attr"), "missing action for orders uri template"); with(json).assertThat("$.actions[1].fields[0].type", equalTo("text")); // non-query variables are not supported with(json).assertNotDefined("$.entities[?(@.rel[0]=='foo')][0]"); with(json).assertNotDefined("$.entities[?(@.rel[0]=='foo-query')][0]"); with(json).assertThat("$.actions[2].fields[0].name", equalTo("bar"), "missing action for foo-query uri template"); with(json).assertThat("$.actions[2].fields[0].type", equalTo("text")); } }