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