001/*
002 * Copyright 2002-2016 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.test.web.client.match;
018
019import java.io.ByteArrayInputStream;
020import java.io.IOException;
021import java.io.InputStream;
022import javax.xml.transform.Source;
023import javax.xml.transform.dom.DOMSource;
024
025import org.hamcrest.Matcher;
026import org.w3c.dom.Node;
027
028import org.springframework.http.HttpHeaders;
029import org.springframework.http.HttpInputMessage;
030import org.springframework.http.MediaType;
031import org.springframework.http.client.ClientHttpRequest;
032import org.springframework.http.converter.FormHttpMessageConverter;
033import org.springframework.mock.http.client.MockClientHttpRequest;
034import org.springframework.test.util.XmlExpectationsHelper;
035import org.springframework.test.web.client.RequestMatcher;
036import org.springframework.util.MultiValueMap;
037
038import static org.hamcrest.MatcherAssert.*;
039import static org.springframework.test.util.AssertionErrors.*;
040
041/**
042 * Factory for request content {@code RequestMatcher}'s. An instance of this
043 * class is typically accessed via {@link MockRestRequestMatchers#content()}.
044 *
045 * @author Rossen Stoyanchev
046 * @since 3.2
047 */
048public class ContentRequestMatchers {
049
050        private final XmlExpectationsHelper xmlHelper;
051
052
053        /**
054         * Class constructor, not for direct instantiation.
055         * Use {@link MockRestRequestMatchers#content()}.
056         */
057        protected ContentRequestMatchers() {
058                this.xmlHelper = new XmlExpectationsHelper();
059        }
060
061
062        /**
063         * Assert the request content type as a String.
064         */
065        public RequestMatcher contentType(String expectedContentType) {
066                return contentType(MediaType.parseMediaType(expectedContentType));
067        }
068
069        /**
070         * Assert the request content type as a {@link MediaType}.
071         */
072        public RequestMatcher contentType(final MediaType expectedContentType) {
073                return new RequestMatcher() {
074                        @Override
075                        public void match(ClientHttpRequest request) throws IOException, AssertionError {
076                                MediaType actualContentType = request.getHeaders().getContentType();
077                                assertTrue("Content type not set", actualContentType != null);
078                                assertEquals("Content type", expectedContentType, actualContentType);
079                        }
080                };
081        }
082
083        /**
084         * Assert the request content type is compatible with the given
085         * content type as defined by {@link MediaType#isCompatibleWith(MediaType)}.
086         */
087        public RequestMatcher contentTypeCompatibleWith(String contentType) {
088                return contentTypeCompatibleWith(MediaType.parseMediaType(contentType));
089        }
090
091        /**
092         * Assert the request content type is compatible with the given
093         * content type as defined by {@link MediaType#isCompatibleWith(MediaType)}.
094         */
095        public RequestMatcher contentTypeCompatibleWith(final MediaType contentType) {
096                return new RequestMatcher() {
097                        @Override
098                        public void match(ClientHttpRequest request) throws IOException, AssertionError {
099                                MediaType actualContentType = request.getHeaders().getContentType();
100                                assertTrue("Content type not set", actualContentType != null);
101                                assertTrue("Content type [" + actualContentType + "] is not compatible with [" + contentType + "]",
102                                                actualContentType.isCompatibleWith(contentType));
103                        }
104                };
105        }
106
107        /**
108         * Get the body of the request as a UTF-8 string and appply the given {@link Matcher}.
109         */
110        public RequestMatcher string(final Matcher<? super String> matcher) {
111                return new RequestMatcher() {
112                        @Override
113                        public void match(ClientHttpRequest request) throws IOException, AssertionError {
114                                MockClientHttpRequest mockRequest = (MockClientHttpRequest) request;
115                                assertThat("Request content", mockRequest.getBodyAsString(), matcher);
116                        }
117                };
118        }
119
120        /**
121         * Get the body of the request as a UTF-8 string and compare it to the given String.
122         */
123        public RequestMatcher string(final String expectedContent) {
124                return new RequestMatcher() {
125                        @Override
126                        public void match(ClientHttpRequest request) throws IOException, AssertionError {
127                                MockClientHttpRequest mockRequest = (MockClientHttpRequest) request;
128                                assertEquals("Request content", expectedContent, mockRequest.getBodyAsString());
129                        }
130                };
131        }
132
133        /**
134         * Compare the body of the request to the given byte array.
135         */
136        public RequestMatcher bytes(final byte[] expectedContent) {
137                return new RequestMatcher() {
138                        @Override
139                        public void match(ClientHttpRequest request) throws IOException, AssertionError {
140                                MockClientHttpRequest mockRequest = (MockClientHttpRequest) request;
141                                assertEquals("Request content", expectedContent, mockRequest.getBodyAsBytes());
142                        }
143                };
144        }
145
146        /**
147         * Parse the body as form data and compare to the given {@code MultiValueMap}.
148         * @since 4.3
149         */
150        public RequestMatcher formData(final MultiValueMap<String, String> expectedContent) {
151                return new RequestMatcher() {
152                        @Override
153                        public void match(final ClientHttpRequest request) throws IOException, AssertionError {
154                                HttpInputMessage inputMessage = new HttpInputMessage() {
155                                        @Override
156                                        public InputStream getBody() throws IOException {
157                                                MockClientHttpRequest mockRequest = (MockClientHttpRequest) request;
158                                                return new ByteArrayInputStream(mockRequest.getBodyAsBytes());
159                                        }
160                                        @Override
161                                        public HttpHeaders getHeaders() {
162                                                return request.getHeaders();
163                                        }
164                                };
165                                FormHttpMessageConverter converter = new FormHttpMessageConverter();
166                                assertEquals("Request content", expectedContent, converter.read(null, inputMessage));
167                        }
168                };
169        }
170
171        /**
172         * Parse the request body and the given String as XML and assert that the
173         * two are "similar" - i.e. they contain the same elements and attributes
174         * regardless of order.
175         * <p>Use of this matcher assumes the
176         * <a href="http://xmlunit.sourceforge.net/">XMLUnit<a/> library is available.
177         * @param expectedXmlContent the expected XML content
178         */
179        public RequestMatcher xml(final String expectedXmlContent) {
180                return new AbstractXmlRequestMatcher() {
181                        @Override
182                        protected void matchInternal(MockClientHttpRequest request) throws Exception {
183                                xmlHelper.assertXmlEqual(expectedXmlContent, request.getBodyAsString());
184                        }
185                };
186        }
187
188        /**
189         * Parse the request content as {@link Node} and apply the given {@link Matcher}.
190         */
191        public RequestMatcher node(final Matcher<? super Node> matcher) {
192                return new AbstractXmlRequestMatcher() {
193                        @Override
194                        protected void matchInternal(MockClientHttpRequest request) throws Exception {
195                                xmlHelper.assertNode(request.getBodyAsString(), matcher);
196                        }
197                };
198        }
199
200        /**
201         * Parse the request content as {@link DOMSource} and apply the given {@link Matcher}.
202         * @see <a href="https://code.google.com/p/xml-matchers/">https://code.google.com/p/xml-matchers/</a>
203         */
204        public RequestMatcher source(final Matcher<? super Source> matcher) {
205                return new AbstractXmlRequestMatcher() {
206                        @Override
207                        protected void matchInternal(MockClientHttpRequest request) throws Exception {
208                                xmlHelper.assertSource(request.getBodyAsString(), matcher);
209                        }
210                };
211        }
212
213
214        /**
215         * Abstract base class for XML {@link RequestMatcher}'s.
216         */
217        private abstract static class AbstractXmlRequestMatcher implements RequestMatcher {
218
219                @Override
220                public final void match(ClientHttpRequest request) throws IOException, AssertionError {
221                        try {
222                                MockClientHttpRequest mockRequest = (MockClientHttpRequest) request;
223                                matchInternal(mockRequest);
224                        }
225                        catch (Exception ex) {
226                                throw new AssertionError("Failed to parse expected or actual XML request content: " + ex.getMessage());
227                        }
228                }
229
230                protected abstract void matchInternal(MockClientHttpRequest request) throws Exception;
231        }
232
233}
234