diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java index 025421f8749..5cb9e0b9e10 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java @@ -17,6 +17,7 @@ package org.springframework.test.web.servlet.request; import java.io.UnsupportedEncodingException; +import java.net.URI; import java.security.Principal; import java.util.ArrayList; import java.util.Arrays; @@ -114,6 +115,7 @@ public class MockHttpServletRequestBuilder implements RequestBuilder, Mergeable *

Although this class cannot be extended, additional ways to initialize * the {@code MockHttpServletRequest} can be plugged in via * {@link #with(RequestPostProcessor)}. + * @param httpMethod the HTTP method (GET, POST, etc) * @param urlTemplate a URL template; the resulting URL will be encoded * @param urlVariables zero or more URL variables */ @@ -124,6 +126,23 @@ public class MockHttpServletRequestBuilder implements RequestBuilder, Mergeable this.uriComponents = UriComponentsBuilder.fromUriString(urlTemplate).buildAndExpand(urlVariables).encode(); } + /** + * Package private constructor. To get an instance, use static factory + * methods in {@link MockMvcRequestBuilders}. + *

Although this class cannot be extended, additional ways to initialize + * the {@code MockHttpServletRequest} can be plugged in via + * {@link #with(RequestPostProcessor)}. + * @param httpMethod the HTTP method (GET, POST, etc) + * @param url the URL + * @since 4.0.3 + */ + MockHttpServletRequestBuilder(HttpMethod httpMethod, URI url) { + Assert.notNull(httpMethod, "httpMethod is required"); + Assert.notNull(url, "url is required"); + this.method = httpMethod; + this.uriComponents = UriComponentsBuilder.fromUri(url).build(); + } + /** * Add a request parameter to the {@link MockHttpServletRequest}. * If called more than once, the new values are added. diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMultipartHttpServletRequestBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMultipartHttpServletRequestBuilder.java index ab0e25de3b5..3e92a7e0829 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMultipartHttpServletRequestBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMultipartHttpServletRequestBuilder.java @@ -16,6 +16,7 @@ package org.springframework.test.web.servlet.request; +import java.net.URI; import java.util.ArrayList; import java.util.List; import javax.servlet.ServletContext; @@ -52,6 +53,20 @@ public class MockMultipartHttpServletRequestBuilder extends MockHttpServletReque super.contentType(MediaType.MULTIPART_FORM_DATA); } + /** + * Package-private constructor. Use static factory methods in + * {@link MockMvcRequestBuilders}. + *

For other ways to initialize a {@code MockMultipartHttpServletRequest}, + * see {@link #with(RequestPostProcessor)} and the + * {@link RequestPostProcessor} extension point. + * @param url the URL + * @since 4.0.3 + */ + MockMultipartHttpServletRequestBuilder(URI url) { + super(HttpMethod.POST, url); + super.contentType(MediaType.MULTIPART_FORM_DATA); + } + /** * Create a new MockMultipartFile with the given content. diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java index 34102bdb21e..0d680d2d529 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -22,6 +22,8 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.RequestBuilder; +import java.net.URI; + /** * Static factory methods for {@link RequestBuilder}s. * @@ -31,6 +33,7 @@ import org.springframework.test.web.servlet.RequestBuilder; * @author Arjen Poutsma * @author Rossen Stoyanchev * @author Greg Turnquist + * @author Sebastien Deleuze * @since 3.2 */ public abstract class MockMvcRequestBuilders { @@ -109,6 +112,80 @@ public abstract class MockMvcRequestBuilders { return new MockMultipartHttpServletRequestBuilder(urlTemplate, urlVariables); } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a GET request. + * @param url the URL + * @since 4.0.3 + */ + public static MockHttpServletRequestBuilder get(URI url) { + return new MockHttpServletRequestBuilder(HttpMethod.GET, url); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a POST request. + * @param url the URL + * @since 4.0.3 + */ + public static MockHttpServletRequestBuilder post(URI url) { + return new MockHttpServletRequestBuilder(HttpMethod.POST, url); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a PUT request. + * @param url the URL + * @since 4.0.3 + */ + public static MockHttpServletRequestBuilder put(URI url) { + return new MockHttpServletRequestBuilder(HttpMethod.PUT, url); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a PATCH request. + * @param url the URL + * @since 4.0.3 + */ + public static MockHttpServletRequestBuilder patch(URI url) { + return new MockHttpServletRequestBuilder(HttpMethod.PATCH, url); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a DELETE request. + * @param url the URL + * @since 4.0.3 + */ + public static MockHttpServletRequestBuilder delete(URI url) { + return new MockHttpServletRequestBuilder(HttpMethod.DELETE, url); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for an OPTIONS request. + * @param url the URL + * @since 4.0.3 + */ + public static MockHttpServletRequestBuilder options(URI url) { + return new MockHttpServletRequestBuilder(HttpMethod.OPTIONS, url); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a request with the given HTTP method. + * @param httpMethod the HTTP method (GET, POST, etc) + * @param url the URL + * @since 4.0.3 + */ + public static MockHttpServletRequestBuilder request(HttpMethod httpMethod, URI url) { + return new MockHttpServletRequestBuilder(httpMethod, url); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a multipart request. + * @param url the URL + * @since 4.0.3 + */ + public static MockMultipartHttpServletRequestBuilder fileUpload(URI url) { + return new MockMultipartHttpServletRequestBuilder(url); + } + /** * Create a {@link RequestBuilder} for an async dispatch from the * {@link MvcResult} of the request that started async processing. diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/Spr11441Tests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/Spr11441Tests.java new file mode 100644 index 00000000000..f9d674f1a03 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/Spr11441Tests.java @@ -0,0 +1,131 @@ +/* + * Copyright 2002-2014 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.test.web.servlet; + + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.PriorityOrdered; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Controller; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import org.springframework.web.util.UriComponentsBuilder; + +import java.net.URI; + +import static org.hamcrest.core.Is.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; + +/** + * Tests for SPR-11441 (MockMvc needs to accept prepared URI with encoded URI path variables). + * + * @author Sebastien Deleuze + */ +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +@ContextConfiguration +public class Spr11441Tests { + + @Autowired + private WebApplicationContext wac; + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = webAppContextSetup(this.wac).build(); + } + + @Test + public void test() throws Exception { + String id = "a/b"; + URI url = UriComponentsBuilder.fromUriString("/circuit").pathSegment(id).build().encode().toUri(); + ResultActions result = mockMvc.perform(get(url)); + result.andExpect(status().isOk()).andExpect(model().attribute("receivedId", is(id))); + } + + + @Configuration + @EnableWebMvc + static class WebConfig extends WebMvcConfigurerAdapter { + + @Bean + public MyController myController() { + return new MyController(); + } + + @Bean + public HandlerMappingConfigurer myHandlerMappingConfigurer() { + return new HandlerMappingConfigurer(); + } + } + + @Controller + private static class MyController { + + @RequestMapping(value = "/circuit/{id}", method = RequestMethod.GET) + public String getCircuit(@PathVariable String id, Model model) { + model.addAttribute("receivedId", id); + return "result"; + } + } + + @Component + private static class HandlerMappingConfigurer implements BeanPostProcessor, PriorityOrdered { + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof RequestMappingHandlerMapping) { + RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) bean; + + // URL decode after request mapping, not before. + requestMappingHandlerMapping.setUrlDecode(false); + + } + + return bean; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + public int getOrder() { + return PriorityOrdered.HIGHEST_PRECEDENCE; + } + } + +}