001/*
002 * Copyright 2002-2019 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.net.URI;
020import java.util.List;
021import java.util.Map;
022
023import javax.xml.xpath.XPathExpressionException;
024
025import org.hamcrest.Matcher;
026
027import org.springframework.http.HttpMethod;
028import org.springframework.http.client.ClientHttpRequest;
029import org.springframework.test.web.client.MockRestServiceServer;
030import org.springframework.test.web.client.RequestMatcher;
031import org.springframework.util.Assert;
032import org.springframework.util.MultiValueMap;
033import org.springframework.web.util.UriComponentsBuilder;
034
035import static org.hamcrest.MatcherAssert.assertThat;
036import static org.springframework.test.util.AssertionErrors.assertEquals;
037import static org.springframework.test.util.AssertionErrors.fail;
038
039/**
040 * Static factory methods for {@link RequestMatcher} classes. Typically used to
041 * provide input for {@link MockRestServiceServer#expect(RequestMatcher)}.
042 *
043 * <h3>Eclipse Users</h3>
044 * <p>Consider adding this class as a Java editor favorite. To navigate to
045 * this setting, open the Preferences and type "favorites".
046 *
047 * @author Craig Walls
048 * @author Rossen Stoyanchev
049 * @author Sam Brannen
050 * @since 3.2
051 */
052public abstract class MockRestRequestMatchers {
053
054        /**
055         * Match to any request.
056         */
057        public static RequestMatcher anything() {
058                return request -> {};
059        }
060
061        /**
062         * Assert the {@link HttpMethod} of the request.
063         * @param method the HTTP method
064         * @return the request matcher
065         */
066        public static RequestMatcher method(HttpMethod method) {
067                Assert.notNull(method, "'method' must not be null");
068                return request -> assertEquals("Unexpected HttpMethod", method, request.getMethod());
069        }
070
071        /**
072         * Assert the request URI string with the given Hamcrest matcher.
073         * @param matcher the String matcher for the expected URI
074         * @return the request matcher
075         */
076        public static RequestMatcher requestTo(Matcher<String> matcher) {
077                Assert.notNull(matcher, "'matcher' must not be null");
078                return request -> assertThat("Request URI", request.getURI().toString(), matcher);
079        }
080
081        /**
082         * Assert the request URI matches the given string.
083         * @param expectedUri the expected URI
084         * @return the request matcher
085         */
086        public static RequestMatcher requestTo(String expectedUri) {
087                Assert.notNull(expectedUri, "'uri' must not be null");
088                return request -> assertEquals("Request URI", expectedUri, request.getURI().toString());
089        }
090
091        /**
092         * Variant of {@link #requestTo(URI)} that prepares the URI from a URI
093         * template plus optional variables via {@link UriComponentsBuilder}
094         * including encoding.
095         * @param expectedUri the expected URI template
096         * @param uriVars zero or more URI variables to populate the expected URI
097         * @return the request matcher
098         */
099        public static RequestMatcher requestToUriTemplate(String expectedUri, Object... uriVars) {
100                Assert.notNull(expectedUri, "'uri' must not be null");
101                URI uri = UriComponentsBuilder.fromUriString(expectedUri).buildAndExpand(uriVars).encode().toUri();
102                return requestTo(uri);
103        }
104
105        /**
106         * Expect a request to the given URI.
107         * @param uri the expected URI
108         * @return the request matcher
109         */
110        public static RequestMatcher requestTo(URI uri) {
111                Assert.notNull(uri, "'uri' must not be null");
112                return request -> assertEquals("Unexpected request", uri, request.getURI());
113        }
114
115        /**
116         * Assert request query parameter values with the given Hamcrest matcher(s).
117         */
118        @SafeVarargs
119        public static RequestMatcher queryParam(String name, Matcher<? super String>... matchers) {
120                return request -> {
121                        MultiValueMap<String, String> params = getQueryParams(request);
122                        assertValueCount("query param", name, params, matchers.length);
123                        for (int i = 0 ; i < matchers.length; i++) {
124                                assertThat("Query param", params.get(name).get(i), matchers[i]);
125                        }
126                };
127        }
128
129        /**
130         * Assert request query parameter values.
131         */
132        public static RequestMatcher queryParam(String name, String... expectedValues) {
133                return request -> {
134                        MultiValueMap<String, String> params = getQueryParams(request);
135                        assertValueCount("query param", name, params, expectedValues.length);
136                        for (int i = 0 ; i < expectedValues.length; i++) {
137                                assertEquals("Query param [" + name + "]", expectedValues[i], params.get(name).get(i));
138                        }
139                };
140        }
141
142        private static MultiValueMap<String, String> getQueryParams(ClientHttpRequest request) {
143                return UriComponentsBuilder.fromUri(request.getURI()).build().getQueryParams();
144        }
145
146        private static void assertValueCount(
147                        String valueType, String name, MultiValueMap<String, String> map, int count) {
148
149                List<String> values = map.get(name);
150                String message = "Expected " + valueType + " <" + name + ">";
151                if (values == null) {
152                        fail(message + " to exist but was null");
153                }
154                if (count > values.size()) {
155                        fail(message + " to have at least <" + count + "> values but found " + values);
156                }
157        }
158
159        /**
160         * Assert request header values with the given Hamcrest matcher(s).
161         */
162        @SafeVarargs
163        public static RequestMatcher header(String name, Matcher<? super String>... matchers) {
164                return request -> {
165                        assertValueCount("header", name, request.getHeaders(), matchers.length);
166                        List<String> headerValues = request.getHeaders().get(name);
167                        Assert.state(headerValues != null, "No header values");
168                        for (int i = 0; i < matchers.length; i++) {
169                                assertThat("Request header [" + name + "]", headerValues.get(i), matchers[i]);
170                        }
171                };
172        }
173
174        /**
175         * Assert request header values.
176         */
177        public static RequestMatcher header(String name, String... expectedValues) {
178                return request -> {
179                        assertValueCount("header", name, request.getHeaders(), expectedValues.length);
180                        List<String> headerValues = request.getHeaders().get(name);
181                        Assert.state(headerValues != null, "No header values");
182                        for (int i = 0; i < expectedValues.length; i++) {
183                                assertEquals("Request header [" + name + "]", expectedValues[i], headerValues.get(i));
184                        }
185                };
186        }
187
188        /**
189         * Assert that the given request header does not exist.
190         * @since 5.2
191         */
192        public static RequestMatcher headerDoesNotExist(String name) {
193                return request -> {
194                        List<String> headerValues = request.getHeaders().get(name);
195                        if (headerValues != null) {
196                                fail("Expected header <" + name + "> not to exist, but it exists with values: " +
197                                                headerValues);
198                        }
199                };
200        }
201
202        /**
203         * Access to request body matchers.
204         */
205        public static ContentRequestMatchers content() {
206                return new ContentRequestMatchers();
207        }
208
209        /**
210         * Access to request body matchers using a
211         * <a href="https://github.com/jayway/JsonPath">JsonPath</a> expression to
212         * inspect a specific subset of the body. The JSON path expression can be a
213         * parameterized string using formatting specifiers as defined in
214         * {@link String#format(String, Object...)}.
215         * @param expression the JSON path optionally parameterized with arguments
216         * @param args arguments to parameterize the JSON path expression with
217         */
218        public static JsonPathRequestMatchers jsonPath(String expression, Object... args) {
219                return new JsonPathRequestMatchers(expression, args);
220        }
221
222        /**
223         * Access to request body matchers using a
224         * <a href="https://github.com/jayway/JsonPath">JsonPath</a> expression to
225         * inspect a specific subset of the body and a Hamcrest match for asserting
226         * the value found at the JSON path.
227         * @param expression the JSON path expression
228         * @param matcher a matcher for the value expected at the JSON path
229         */
230        public static <T> RequestMatcher jsonPath(String expression, Matcher<T> matcher) {
231                return new JsonPathRequestMatchers(expression).value(matcher);
232        }
233
234        /**
235         * Access to request body matchers using an XPath to inspect a specific
236         * subset of the body. The XPath expression can be a parameterized string
237         * using formatting specifiers as defined in
238         * {@link String#format(String, Object...)}.
239         * @param expression the XPath optionally parameterized with arguments
240         * @param args arguments to parameterize the XPath expression with
241         */
242        public static XpathRequestMatchers xpath(String expression, Object... args) throws XPathExpressionException {
243                return new XpathRequestMatchers(expression, null, args);
244        }
245
246        /**
247         * Access to response body matchers using an XPath to inspect a specific
248         * subset of the body. The XPath expression can be a parameterized string
249         * using formatting specifiers as defined in
250         * {@link String#format(String, Object...)}.
251         * @param expression the XPath optionally parameterized with arguments
252         * @param namespaces the namespaces referenced in the XPath expression
253         * @param args arguments to parameterize the XPath expression with
254         */
255        public static XpathRequestMatchers xpath(String expression, Map<String, String> namespaces, Object... args)
256                        throws XPathExpressionException {
257
258                return new XpathRequestMatchers(expression, namespaces, args);
259        }
260
261}