View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.web.servlet.mvc.method.annotation;
18  
19  import java.lang.reflect.Method;
20  import java.net.URI;
21  import java.util.Arrays;
22  import java.util.Collections;
23  
24  import org.junit.Before;
25  import org.junit.Test;
26  import org.mockito.ArgumentCaptor;
27  
28  import org.springframework.core.MethodParameter;
29  import org.springframework.http.HttpEntity;
30  import org.springframework.http.HttpHeaders;
31  import org.springframework.http.HttpInputMessage;
32  import org.springframework.http.HttpMethod;
33  import org.springframework.http.HttpOutputMessage;
34  import org.springframework.http.HttpStatus;
35  import org.springframework.http.MediaType;
36  import org.springframework.http.RequestEntity;
37  import org.springframework.http.ResponseEntity;
38  import org.springframework.http.converter.HttpMessageConverter;
39  import org.springframework.mock.web.test.MockHttpServletRequest;
40  import org.springframework.mock.web.test.MockHttpServletResponse;
41  import org.springframework.web.HttpMediaTypeNotAcceptableException;
42  import org.springframework.web.HttpMediaTypeNotSupportedException;
43  import org.springframework.web.bind.annotation.RequestMapping;
44  import org.springframework.web.context.request.ServletWebRequest;
45  import org.springframework.web.method.support.ModelAndViewContainer;
46  
47  import static org.junit.Assert.*;
48  import static org.mockito.BDDMockito.any;
49  import static org.mockito.BDDMockito.*;
50  import static org.mockito.BDDMockito.isA;
51  import static org.mockito.Matchers.eq;
52  import static org.springframework.web.servlet.HandlerMapping.*;
53  
54  /**
55   * Test fixture for {@link HttpEntityMethodProcessor} delegating to a mock
56   * {@link HttpMessageConverter}.
57   *
58   * <p>Also see {@link HttpEntityMethodProcessorTests}.
59   *
60   * @author Arjen Poutsma
61   * @author Rossen Stoyanchev
62   */
63  public class HttpEntityMethodProcessorMockTests {
64  
65  	private HttpEntityMethodProcessor processor;
66  
67  	private HttpMessageConverter<String> messageConverter;
68  
69  	private MethodParameter paramHttpEntity;
70  	private MethodParameter paramRequestEntity;
71  	private MethodParameter paramResponseEntity;
72  	private MethodParameter paramInt;
73  	private MethodParameter returnTypeResponseEntity;
74  	private MethodParameter returnTypeResponseEntityProduces;
75  	private MethodParameter returnTypeHttpEntity;
76  	private MethodParameter returnTypeHttpEntitySubclass;
77  	private MethodParameter returnTypeInt;
78  
79  	private ModelAndViewContainer mavContainer;
80  
81  	private MockHttpServletRequest servletRequest;
82  
83  	private MockHttpServletResponse servletResponse;
84  
85  	private ServletWebRequest webRequest;
86  
87  
88  	@SuppressWarnings("unchecked")
89  	@Before
90  	public void setUp() throws Exception {
91  		messageConverter = mock(HttpMessageConverter.class);
92  		given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
93  
94  		processor = new HttpEntityMethodProcessor(Collections.<HttpMessageConverter<?>>singletonList(messageConverter));
95  		reset(messageConverter);
96  
97  		Method handle1 = getClass().getMethod("handle1", HttpEntity.class, ResponseEntity.class, Integer.TYPE, RequestEntity.class);
98  		paramHttpEntity = new MethodParameter(handle1, 0);
99  		paramRequestEntity = new MethodParameter(handle1, 3);
100 		paramResponseEntity = new MethodParameter(handle1, 1);
101 		paramInt = new MethodParameter(handle1, 2);
102 		returnTypeResponseEntity = new MethodParameter(handle1, -1);
103 		returnTypeResponseEntityProduces = new MethodParameter(getClass().getMethod("handle4"), -1);
104 		returnTypeHttpEntity = new MethodParameter(getClass().getMethod("handle2", HttpEntity.class), -1);
105 		returnTypeHttpEntitySubclass = new MethodParameter(getClass().getMethod("handle2x", HttpEntity.class), -1);
106 		returnTypeInt = new MethodParameter(getClass().getMethod("handle3"), -1);
107 
108 		mavContainer = new ModelAndViewContainer();
109 		servletRequest = new MockHttpServletRequest();
110 		servletResponse = new MockHttpServletResponse();
111 		webRequest = new ServletWebRequest(servletRequest, servletResponse);
112 	}
113 
114 
115 	@Test
116 	public void supportsParameter() {
117 		assertTrue("HttpEntity parameter not supported", processor.supportsParameter(paramHttpEntity));
118 		assertTrue("RequestEntity parameter not supported", processor.supportsParameter(paramRequestEntity));
119 		assertFalse("ResponseEntity parameter supported", processor.supportsParameter(paramResponseEntity));
120 		assertFalse("non-entity parameter supported", processor.supportsParameter(paramInt));
121 	}
122 
123 	@Test
124 	public void supportsReturnType() {
125 		assertTrue("ResponseEntity return type not supported", processor.supportsReturnType(returnTypeResponseEntity));
126 		assertTrue("HttpEntity return type not supported", processor.supportsReturnType(returnTypeHttpEntity));
127 		assertTrue("Custom HttpEntity subclass not supported", processor.supportsReturnType(returnTypeHttpEntitySubclass));
128 		assertFalse("RequestEntity parameter supported",
129 				processor.supportsReturnType(paramRequestEntity));
130 		assertFalse("non-ResponseBody return type supported", processor.supportsReturnType(returnTypeInt));
131 	}
132 
133 	@Test
134 	public void resolveArgument() throws Exception {
135 		MediaType contentType = MediaType.TEXT_PLAIN;
136 		servletRequest.addHeader("Content-Type", contentType.toString());
137 
138 		String body = "Foo";
139 		given(messageConverter.canRead(String.class, contentType)).willReturn(true);
140 		given(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
141 
142 		Object result = processor.resolveArgument(paramHttpEntity, mavContainer, webRequest, null);
143 
144 		assertTrue(result instanceof HttpEntity);
145 		assertFalse("The requestHandled flag shouldn't change", mavContainer.isRequestHandled());
146 		assertEquals("Invalid argument", body, ((HttpEntity<?>) result).getBody());
147 	}
148 
149 	@Test
150 	public void resolveArgumentRequestEntity() throws Exception {
151 		MediaType contentType = MediaType.TEXT_PLAIN;
152 		servletRequest.addHeader("Content-Type", contentType.toString());
153 		servletRequest.setMethod("GET");
154 		servletRequest.setServerName("www.example.com");
155 		servletRequest.setServerPort(80);
156 		servletRequest.setRequestURI("/path");
157 
158 		String body = "Foo";
159 		given(messageConverter.canRead(String.class, contentType)).willReturn(true);
160 		given(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
161 
162 		Object result = processor.resolveArgument(paramRequestEntity, mavContainer, webRequest, null);
163 
164 		assertTrue(result instanceof RequestEntity);
165 		assertFalse("The requestHandled flag shouldn't change", mavContainer.isRequestHandled());
166 		RequestEntity<?> requestEntity = (RequestEntity<?>) result;
167 		assertEquals("Invalid method", HttpMethod.GET, requestEntity.getMethod());
168 		assertEquals("Invalid url", new URI("http", null, "www.example.com", 80, "/path", null, null), requestEntity.getUrl());
169 		assertEquals("Invalid argument", body, requestEntity.getBody());
170 	}
171 
172 	@Test(expected = HttpMediaTypeNotSupportedException.class)
173 	public void resolveArgumentNotReadable() throws Exception {
174 		MediaType contentType = MediaType.TEXT_PLAIN;
175 		servletRequest.addHeader("Content-Type", contentType.toString());
176 
177 		given(messageConverter.getSupportedMediaTypes()).willReturn(Arrays.asList(contentType));
178 		given(messageConverter.canRead(String.class, contentType)).willReturn(false);
179 
180 		processor.resolveArgument(paramHttpEntity, mavContainer, webRequest, null);
181 
182 		fail("Expected exception");
183 	}
184 
185 	@Test(expected = HttpMediaTypeNotSupportedException.class)
186 	public void resolveArgumentNoContentType() throws Exception {
187 		processor.resolveArgument(paramHttpEntity, mavContainer, webRequest, null);
188 		fail("Expected exception");
189 	}
190 
191 	@Test
192 	public void handleReturnValue() throws Exception {
193 		String body = "Foo";
194 		ResponseEntity<String> returnValue = new ResponseEntity<String>(body, HttpStatus.OK);
195 
196 		MediaType accepted = MediaType.TEXT_PLAIN;
197 		servletRequest.addHeader("Accept", accepted.toString());
198 
199 		given(messageConverter.canWrite(String.class, null)).willReturn(true);
200 		given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
201 		given(messageConverter.canWrite(String.class, accepted)).willReturn(true);
202 
203 		processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
204 
205 		assertTrue(mavContainer.isRequestHandled());
206 		verify(messageConverter).write(eq(body), eq(accepted), isA(HttpOutputMessage.class));
207 	}
208 
209 	@Test
210 	public void handleReturnValueProduces() throws Exception {
211 		String body = "Foo";
212 		ResponseEntity<String> returnValue = new ResponseEntity<String>(body, HttpStatus.OK);
213 
214 		servletRequest.addHeader("Accept", "text/*");
215 		servletRequest.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton(MediaType.TEXT_HTML));
216 
217 		given(messageConverter.canWrite(String.class, MediaType.TEXT_HTML)).willReturn(true);
218 
219 		processor.handleReturnValue(returnValue, returnTypeResponseEntityProduces, mavContainer, webRequest);
220 
221 		assertTrue(mavContainer.isRequestHandled());
222 		verify(messageConverter).write(eq(body), eq(MediaType.TEXT_HTML), isA(HttpOutputMessage.class));
223 	}
224 
225 	@Test
226 	public void handleReturnValueWithResponseBodyAdvice() throws Exception {
227 		ResponseEntity<String> returnValue = new ResponseEntity<>(HttpStatus.OK);
228 
229 		servletRequest.addHeader("Accept", "text/*");
230 		servletRequest.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton(MediaType.TEXT_HTML));
231 
232 		ResponseBodyAdvice<String> advice = mock(ResponseBodyAdvice.class);
233 		given(advice.supports(any(), any())).willReturn(true);
234 		given(advice.beforeBodyWrite(any(), any(), any(), any(), any(), any())).willReturn("Foo");
235 
236 		HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
237 				Collections.singletonList(messageConverter), null, Collections.singletonList(advice));
238 
239 		reset(messageConverter);
240 		given(messageConverter.canWrite(String.class, MediaType.TEXT_HTML)).willReturn(true);
241 
242 		processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
243 
244 		assertTrue(mavContainer.isRequestHandled());
245 		verify(messageConverter).write(eq("Foo"), eq(MediaType.TEXT_HTML), isA(HttpOutputMessage.class));
246 	}
247 
248 	@Test(expected = HttpMediaTypeNotAcceptableException.class)
249 	public void handleReturnValueNotAcceptable() throws Exception {
250 		String body = "Foo";
251 		ResponseEntity<String> returnValue = new ResponseEntity<String>(body, HttpStatus.OK);
252 
253 		MediaType accepted = MediaType.APPLICATION_ATOM_XML;
254 		servletRequest.addHeader("Accept", accepted.toString());
255 
256 		given(messageConverter.canWrite(String.class, null)).willReturn(true);
257 		given(messageConverter.getSupportedMediaTypes()).willReturn(Arrays.asList(MediaType.TEXT_PLAIN));
258 		given(messageConverter.canWrite(String.class, accepted)).willReturn(false);
259 
260 		processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
261 
262 		fail("Expected exception");
263 	}
264 
265 	@Test(expected = HttpMediaTypeNotAcceptableException.class)
266 	public void handleReturnValueNotAcceptableProduces() throws Exception {
267 		String body = "Foo";
268 		ResponseEntity<String> returnValue = new ResponseEntity<String>(body, HttpStatus.OK);
269 
270 		MediaType accepted = MediaType.TEXT_PLAIN;
271 		servletRequest.addHeader("Accept", accepted.toString());
272 
273 		given(messageConverter.canWrite(String.class, null)).willReturn(true);
274 		given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
275 		given(messageConverter.canWrite(String.class, accepted)).willReturn(false);
276 
277 		processor.handleReturnValue(returnValue, returnTypeResponseEntityProduces, mavContainer, webRequest);
278 
279 		fail("Expected exception");
280 	}
281 
282 	// SPR-9142
283 
284 	@Test(expected=HttpMediaTypeNotAcceptableException.class)
285 	public void handleReturnValueNotAcceptableParseError() throws Exception {
286 		ResponseEntity<String> returnValue = new ResponseEntity<String>("Body", HttpStatus.ACCEPTED);
287 		servletRequest.addHeader("Accept", "01");
288 
289 		processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
290 		fail("Expected exception");
291 	}
292 
293 	@Test
294 	public void responseHeaderNoBody() throws Exception {
295 		HttpHeaders headers = new HttpHeaders();
296 		headers.set("headerName", "headerValue");
297 		ResponseEntity<String> returnValue = new ResponseEntity<String>(headers, HttpStatus.ACCEPTED);
298 
299 		processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
300 
301 		assertTrue(mavContainer.isRequestHandled());
302 		assertEquals("headerValue", servletResponse.getHeader("headerName"));
303 	}
304 
305 	@Test
306 	public void responseHeaderAndBody() throws Exception {
307 		HttpHeaders responseHeaders = new HttpHeaders();
308 		responseHeaders.set("header", "headerValue");
309 		ResponseEntity<String> returnValue = new ResponseEntity<String>("body", responseHeaders, HttpStatus.ACCEPTED);
310 
311 		given(messageConverter.canWrite(String.class, null)).willReturn(true);
312 		given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
313 		given(messageConverter.canWrite(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
314 
315 		processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
316 
317 		ArgumentCaptor<HttpOutputMessage> outputMessage = ArgumentCaptor.forClass(HttpOutputMessage.class);
318 		verify(messageConverter).write(eq("body"), eq(MediaType.TEXT_PLAIN),  outputMessage.capture());
319 		assertTrue(mavContainer.isRequestHandled());
320 		assertEquals("headerValue", outputMessage.getValue().getHeaders().get("header").get(0));
321 	}
322 
323 	public ResponseEntity<String> handle1(HttpEntity<String> httpEntity, ResponseEntity<String> responseEntity, int i, RequestEntity<String> requestEntity) {
324 		return responseEntity;
325 	}
326 
327 	public HttpEntity<?> handle2(HttpEntity<?> entity) {
328 		return entity;
329 	}
330 
331 	public CustomHttpEntity handle2x(HttpEntity<?> entity) {
332 		return new CustomHttpEntity();
333 	}
334 
335 	public int handle3() {
336 		return 42;
337 	}
338 
339 	@RequestMapping(produces = {"text/html", "application/xhtml+xml"})
340 	public ResponseEntity<String> handle4() {
341 		return null;
342 	}
343 
344 
345 	public static class CustomHttpEntity extends HttpEntity<Object> {
346 	}
347 
348 }