/* * Copyright (c) 2014. Escalon System-Entwicklung, Dietrich Schulten * * 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 de.escalon.hypermedia.spring.hydra; import de.escalon.hypermedia.spring.HypermediaTypes; import de.escalon.hypermedia.spring.sample.test.DummyEventControllerExposed; import de.escalon.hypermedia.spring.sample.test.ReviewController; 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.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.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; 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 org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; /** * Tests Spring mvc message converter for hydra. Created by dschulten on 11.09.2014. */ @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration(loader = AnnotationConfigWebContextLoader.class) public class HydraMessageConverterTest { public static final Logger LOG = LoggerFactory.getLogger(HydraMessageConverterTest.class); @Configuration @EnableWebMvc static class WebConfig extends WebMvcConfigurerAdapter { @Bean public ReviewController reviewController() { return new ReviewController(); } @Bean public DummyEventControllerExposed eventController() { return new DummyEventControllerExposed(); } @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { super.configureMessageConverters(converters); converters.add(new HydraMessageConverter()); } @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(); } @Test public void convertsResource() throws Exception { final MvcResult result = this.mockMvc.perform(MockMvcRequestBuilders.get("/events/1") .accept(HypermediaTypes.APPLICATION_JSONLD)) .andExpect(MockMvcResultMatchers.status() .isOk()) .andExpect(content().contentType("application/ld+json")) .andExpect(jsonPath("$.@type").value("Event")) .andExpect(jsonPath("$.performer").value("Cornelia Bielefeldt")) .andExpect(jsonPath("$.review.@id").value("http://localhost/reviews")) .andReturn(); LOG.debug(result.getResponse() .getContentAsString()); } @Test public void convertsQueryParamToObject() throws Exception { final MvcResult result = this.mockMvc.perform(MockMvcRequestBuilders.get ("/events/query?foo1=1&foo2=2&offset=5&size=20&bar.baz=4&strings=a&strings=b&bars[0].baz=42&bars[1]" + ".baz=8") .accept(HypermediaTypes.APPLICATION_JSONLD)) .andExpect(MockMvcResultMatchers.status() .isOk()) // .andExpect(jsonPath("$['hydra:member'][0].pageable.offset").value(5)) // .andExpect(jsonPath("$['hydra:member'][0].pageable.size").value(20)) // .andExpect(jsonPath("$.['hydra:member'][0]['hydra:collection'][0]['hydra:search']['hydra:template // ']").value // ("http://localhost/events/query{?offset,size,foo1,foo2}")) .andReturn(); LOG.debug(result.getResponse() .getContentAsString()); } @Test public void convertsResourceSupport() throws Exception { final MvcResult result = this.mockMvc.perform(MockMvcRequestBuilders.get("/events/resourcesupport/1") .accept(HypermediaTypes.APPLICATION_JSONLD)) .andExpect(MockMvcResultMatchers.status() .isOk()) .andExpect(content().contentType("application/ld+json")) .andExpect(jsonPath("$.@type").value("Event")) .andExpect(jsonPath("$.performer").value("Cornelia Bielefeldt")) .andExpect(jsonPath("$.review.@id").value("http://localhost/reviews")) .andReturn(); LOG.debug(result.getResponse() .getContentAsString()); } @Test public void convertsListOfResourceOfEvent() throws Exception { final MvcResult result = this.mockMvc.perform(MockMvcRequestBuilders.get("/events/list") .accept(HypermediaTypes.APPLICATION_JSONLD)) .andExpect(MockMvcResultMatchers.status() .isOk()) .andExpect(jsonPath("$.[0].@type").value("Event")) .andExpect(jsonPath("$.[0].performer").value("Walk off the Earth")) .andExpect(jsonPath("$.[0].['hydra:collection'][0].@id").value("http://localhost/reviews/events/1")) .andReturn(); LOG.debug(result.getResponse() .getContentAsString()); } @Test public void convertsResources() throws Exception { MvcResult result = null; result = this.mockMvc.perform(MockMvcRequestBuilders.get("/events") .accept(HypermediaTypes.APPLICATION_JSONLD)) .andExpect(MockMvcResultMatchers.status() .isOk()) .andExpect(content().contentType("application/ld+json")) .andExpect(jsonPath("$.@type").value("hydra:Collection")) .andExpect(jsonPath("$.['hydra:member'][0].@type").value("Event")) .andExpect(jsonPath("$.['hydra:member'][0].@id").value("http://localhost/events/1")) .andExpect(jsonPath("$.['hydra:member'][0].performer").value("Walk off the Earth")) .andExpect(jsonPath("$.['hydra:member'][0].workPerformed.['hydra:collection'][0].@id").value ("http://localhost/reviews/events/1")) .andExpect(jsonPath("$.['hydra:member'][1].@type").value("Event")) .andExpect(jsonPath("$.['hydra:member'][1].@id").value("http://localhost/events/2")) .andExpect(jsonPath("$.['hydra:member'][1].performer").value("Cornelia Bielefeldt")) .andExpect(jsonPath("$.['hydra:member'][1].workPerformed.['hydra:collection'][0].@id").value ("http://localhost/reviews/events/2")) .andReturn(); System.out.println(result.getResponse() .getContentAsString()); LOG.debug(result.getResponse() .getContentAsString()); } @Test public void convertsLinkToPost() throws Exception { MvcResult result = this.mockMvc.perform(MockMvcRequestBuilders.get("/events") .accept(HypermediaTypes.APPLICATION_JSONLD)) .andExpect(MockMvcResultMatchers.status() .isOk()) .andExpect(content().contentType("application/ld+json")) .andExpect(jsonPath("$.['hydra:member'][0].workPerformed.['hydra:collection'][0].@id") .value("http://localhost/reviews/events/1")) .andExpect(jsonPath("$.['hydra:member'][0].workPerformed.['hydra:collection'][0].['hydra:operation']" + ".[0]" + ".['hydra:method']").value("POST")) .andExpect(jsonPath("$.['hydra:member'][0].workPerformed.['hydra:collection'][0].['hydra:operation']" + ".[0]" + ".['hydra:expects'].['hydra:supportedProperty'].[?(@.['hydra:property']=='reviewBody')]") .exists()) .andExpect(jsonPath("$.['hydra:member'][0].workPerformed.['hydra:collection'][0].['hydra:operation']" + ".[0]" + ".['hydra:expects'].['hydra:supportedProperty'].[?(@.['hydra:property']=='reviewBody')]" + ".['valuePattern']") .value(".{10,}")) .andExpect(jsonPath("$.['hydra:member'][0].workPerformed.['hydra:collection'][0].['hydra:operation']" + ".[0]" + ".['hydra:expects'].['hydra:supportedProperty'].[?(@.['hydra:property']=='reviewRating')]") .exists()) .andExpect(jsonPath("$.['hydra:member'][0].workPerformed.['hydra:collection'][0].['hydra:operation']" + ".[0]" + ".['hydra:expects'].['hydra:supportedProperty'].[?(@.['hydra:property']=='reviewRating')]" + ".['rangeIncludes']" + ".['hydra:supportedProperty'][0].['hydra:property']").value("ratingValue")) .andExpect(jsonPath("$.['hydra:member'][0].workPerformed.['hydra:collection'][0].['hydra:operation']" + ".[0]" + ".['hydra:expects'].['hydra:supportedProperty'].[?(@.['hydra:property']=='reviewRating')]" + ".['rangeIncludes']" + ".['hydra:supportedProperty'][0].['minValue']").value(1)) .andExpect(jsonPath("$.['hydra:member'][0].workPerformed.['hydra:collection'][0].['hydra:operation']" + ".[0]" + ".['hydra:expects'].['hydra:supportedProperty'].[?(@.['hydra:property']=='reviewRating')]" + ".['rangeIncludes']" + ".['hydra:supportedProperty'][0].['maxValue']").value(5)) .andExpect(jsonPath("$.['hydra:member'][0].workPerformed.['hydra:collection'][0].['hydra:operation']" + ".[0]" + ".['hydra:expects'].['hydra:supportedProperty'].[?(@.['hydra:property']=='reviewRating')]" + ".['rangeIncludes']" + ".['hydra:supportedProperty'][0].['stepValue']").value(1)) .andExpect(jsonPath("$.['hydra:member'][0].workPerformed.['hydra:collection'][0].['hydra:operation']" + ".[0]" + ".['hydra:expects'].['hydra:supportedProperty'].[?(@.['hydra:property']=='reviewRating')]" + ".['rangeIncludes']" + ".['hydra:supportedProperty'][0].['defaultValue']").value(3)) .andReturn(); System.out.println(result.getResponse() .getContentAsString()); LOG.debug(result.getResponse() .getContentAsString()); } @Test public void deserializesRequestBody() throws Exception { String reviewJson = "{\"reviewBody\": \"meh.\", \"reviewRating\": {\"ratingValue\": 3}}"; MvcResult result = this.mockMvc.perform(MockMvcRequestBuilders.post("/reviews/events/1") .content(reviewJson) .contentType(HypermediaTypes.APPLICATION_JSONLD) .accept(HypermediaTypes.APPLICATION_JSONLD)) .andExpect(MockMvcResultMatchers.status() .isCreated()) .andReturn(); LOG.debug(result.getResponse() .getContentAsString()); } @Test public void convertsTemplatedLinkToSampleInvocationAsIriTemplate() throws Exception { // TODO make sure a collection resource is rendered as collection with hydra:search property // TODO if its link has unsatisfied optional or required variables // TODO if the url has unsatisfied required variables, the collection has no @id, only a hydra:search // TODO distinguish if affordance: // is still templated after satisfying all variables from call values // if remaining are all optional and may be stripped when used as Uri // if a variable has a default value but might use other values which should be described in the IriTemplate // other cases? final MvcResult result = this.mockMvc.perform(MockMvcRequestBuilders.get("/reviews/events/1") .accept(HypermediaTypes.APPLICATION_JSONLD)) .andExpect(content().contentType("application/ld+json")) .andExpect(jsonPath("$.['hydra:search'].@type").value("hydra:IriTemplate")) .andExpect(jsonPath("$.['hydra:search'].['hydra:template']").value("http://localhost/events/{eventId}")) .andExpect(jsonPath("$.['hydra:search'].['hydra:mapping'][0].['hydra:variable']").value("eventId")) .andExpect(jsonPath("$.['hydra:search'].['hydra:mapping'][0].['hydra:property']").value("eventId")) .andReturn(); LOG.debug(result.getResponse() .getContentAsString()); } // @Test // public void usesInputInclude() { // // throw new UnsupportedOperationException(); // } @Test public void convertsTemplatedLinkToMethodAsIriTemplate() throws Exception { final MvcResult result = this.mockMvc.perform(MockMvcRequestBuilders.get("/events") .accept(HypermediaTypes.APPLICATION_JSONLD)) .andExpect(MockMvcResultMatchers.status() .isOk()) .andExpect(content().contentType("application/ld+json")) .andExpect(jsonPath("$.['hydra:search'].@type").value("hydra:IriTemplate")) .andExpect(jsonPath("$.['hydra:search'].['hydra:template']").value ("http://localhost/events{?eventName}")) .andExpect(jsonPath("$.['hydra:search'].['hydra:mapping'][0].['hydra:variable']").value("eventName")) .andExpect(jsonPath("$.['hydra:search'].['hydra:mapping'][0].['hydra:property']").value ("http://schema.org/name")) .andReturn(); LOG.debug(result.getResponse() .getContentAsString()); } @Test public void convertsTemplatedLinkWithRegexAsIriTemplate() throws Exception { final MvcResult result = this.mockMvc.perform(MockMvcRequestBuilders.get("/events") .accept(HypermediaTypes.APPLICATION_JSONLD)) .andExpect(MockMvcResultMatchers.status() .isOk()) .andExpect(content().contentType("application/ld+json")) .andExpect(jsonPath("$.['ex:regex'].@type").value("hydra:IriTemplate")) .andExpect(jsonPath("$.['ex:regex'].['hydra:template']").value ("http://localhost/events/regex/{eventId}")) .andExpect(jsonPath("$.['ex:regex'].['hydra:mapping'][0].['hydra:variable']").value("eventId")) .andExpect(jsonPath("$.['ex:regex'].['hydra:mapping'][0].['hydra:property']").value ("ex:eventId")) .andReturn(); LOG.debug(result.getResponse() .getContentAsString()); } @Test public void convertsAffordanceWithRequestBody() throws Exception { final MvcResult result = this.mockMvc.perform(MockMvcRequestBuilders.get("/events") .accept(HypermediaTypes.APPLICATION_JSONLD)) .andExpect(MockMvcResultMatchers.status() .isOk()) .andExpect(content().contentType("application/ld+json")) .andExpect(jsonPath("$.['hydra:member'][0]['hydra:operation'].[0]['hydra:method']") .value("PUT")) .andExpect(jsonPath("$.['hydra:member'][0]['hydra:operation'].[0]['hydra:expects']" + ".['@type']") .value("Event")) .andExpect(jsonPath("$.['hydra:member'][0]['hydra:operation'].[1]['hydra:method']") .value("DELETE")) // .andExpect(jsonPath("$.['hydra:member'][0]['hydra:operation'].['hydra:expects'] //.['hydra:supportedProperty'][0].@type") // .value(Matchers.containsInAnyOrder("hydra:SupportedProperty", "PropertyValueSpecification"))) .andReturn(); LOG.debug(result.getResponse() .getContentAsString()); } }