Update multipart RequestMatcher

See gh-23772
This commit is contained in:
Rossen Stoyanchev 2020-06-17 22:57:55 +01:00
parent e01160db31
commit bc33ae3f8b
6 changed files with 299 additions and 564 deletions

View File

@ -41,6 +41,7 @@ dependencies {
optional("org.xmlunit:xmlunit-matchers")
optional("org.skyscreamer:jsonassert")
optional("com.jayway.jsonpath:json-path")
optional("commons-fileupload:commons-fileupload")
optional("org.jetbrains.kotlin:kotlin-reflect")
optional("org.jetbrains.kotlin:kotlin-stdlib")
optional("io.projectreactor:reactor-test")
@ -74,7 +75,6 @@ dependencies {
testCompile("org.hsqldb:hsqldb")
testCompile("org.apache.httpcomponents:httpclient")
testCompile("io.projectreactor.netty:reactor-netty")
testCompile("commons-fileupload:commons-fileupload")
testCompile("de.bechte.junit:junit-hierarchicalcontextrunner")
testRuntime("org.junit.vintage:junit-vintage-engine") {
exclude group: "junit", module: "junit"

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@ -19,6 +19,8 @@ package org.springframework.test.web.client.match;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
@ -26,16 +28,23 @@ import javax.xml.transform.dom.DOMSource;
import org.hamcrest.Matcher;
import org.w3c.dom.Node;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.mock.http.client.MockClientHttpRequest;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.util.JsonExpectationsHelper;
import org.springframework.test.util.XmlExpectationsHelper;
import org.springframework.test.web.client.RequestMatcher;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StreamUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.springframework.test.util.AssertionErrors.assertEquals;
@ -159,10 +168,69 @@ public class ContentRequestMatchers {
}
/**
* Access to request body matchers. Matches content type {@link MediaType#MULTIPART_FORM_DATA}
* Parse the body as multipart data and assert it contains exactly the
* values from the given {@code MultiValueMap}. Values may be of type:
* <ul>
* <li>{@code String} - form field
* <li>{@link Resource} - content from a file
* <li>{@code byte[]} - other raw content
* </ul>
* <p><strong>Note:</strong> This method uses the Apache Commons File Upload
* library to parse the multipart data and it must be on the test classpath.
* @param expectedMap the expected multipart values
* @since 5.3
*/
public MultipartFormDataRequestMatchers multipart() {
return new MultipartFormDataRequestMatchers();
public RequestMatcher multipartData(MultiValueMap<String, ?> expectedMap) {
return multipartData(expectedMap, true);
}
/**
* Variant of {@link #multipartData(MultiValueMap)} that does the same but
* only for a subset of the actual values.
* @param expectedMap the expected multipart values
* @since 5.3
*/
public RequestMatcher multipartDataContains(Map<String, ?> expectedMap) {
MultiValueMap<String, Object> map = new LinkedMultiValueMap<>(expectedMap.size());
expectedMap.forEach(map::add);
return multipartData(map, false);
}
@SuppressWarnings("ConstantConditions")
private RequestMatcher multipartData(MultiValueMap<String, ?> expectedMap, boolean assertSize) {
return request -> {
MultiValueMap<String, ?> actualMap = MultipartHelper.parse(request);
if (assertSize) {
assertEquals("Multipart request content: " + actualMap, expectedMap.size(), actualMap.size());
}
for (Map.Entry<String, ? extends List<?>> entry : expectedMap.entrySet()) {
String name = entry.getKey();
List<?> values = entry.getValue();
assertTrue("No Multipart '" + name + "'", actualMap.get(name) != null);
assertTrue("Multipart value count " + values.size(), assertSize ?
values.size() == actualMap.get(name).size() :
values.size() <= actualMap.get(name).size());
for (int i = 0; i < values.size(); i++) {
Object expected = values.get(i);
Object actual = actualMap.get(name).get(i);
if (expected instanceof Resource) {
expected = StreamUtils.copyToByteArray(((Resource) expected).getInputStream());
}
if (expected instanceof byte[]) {
assertTrue("Multipart is not a file", actual instanceof MultipartFile);
assertEquals("Multipart content", expected, ((MultipartFile) actual).getBytes());
}
else if (expected instanceof String) {
assertTrue("Multipart is not a String", actual instanceof String);
assertEquals("Multipart content", expected, (String) actual);
}
else {
throw new IllegalArgumentException(
"Unexpected multipart value type: " + expected.getClass());
}
}
}
};
}
/**
@ -268,4 +336,32 @@ public class ContentRequestMatchers {
protected abstract void matchInternal(MockClientHttpRequest request) throws Exception;
}
private static class MultipartHelper {
public static MultiValueMap<String, ?> parse(ClientHttpRequest request) {
MultipartHttpServletRequest servletRequest = adaptToMultipartRequest(request);
MultiValueMap<String, Object> result = new LinkedMultiValueMap<>();
for (Map.Entry<String, List<MultipartFile>> entry : servletRequest.getMultiFileMap().entrySet()) {
for (MultipartFile value : entry.getValue()) {
result.add(entry.getKey(), value);
}
}
for (Map.Entry<String, String[]> entry : servletRequest.getParameterMap().entrySet()) {
for (String value : entry.getValue()) {
result.add(entry.getKey(), value);
}
}
return result;
}
private static MultipartHttpServletRequest adaptToMultipartRequest(ClientHttpRequest request) {
MockClientHttpRequest source = (MockClientHttpRequest) request;
MockHttpServletRequest target = new MockHttpServletRequest();
target.setContent(source.getBodyAsBytes());
source.getHeaders().forEach((name, values) -> values.forEach(v -> target.addHeader(name, v)));
return new CommonsMultipartResolver().resolveMultipart(target);
}
}
}

View File

@ -1,238 +0,0 @@
/*
* Copyright 2002-2019 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
*
* https://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.client.match;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.io.IOUtils;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.mock.http.client.MockClientHttpRequest;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.web.client.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.springframework.test.util.AssertionErrors.assertEquals;
import static org.springframework.test.util.AssertionErrors.fail;
/**
* Factory for assertions on the multipart form data parameters. Handles only {@link MediaType#MULTIPART_FORM_DATA}
*
* <p>An instance of this class is typically accessed via {@link ContentRequestMatchers#multipart()}
*
* @author Valentin Spac
* @since 5.3
*/
public class MultipartFormDataRequestMatchers {
public RequestMatcher param(String parameter, String... expectedValues) {
List<Matcher<? super String>> matcherList = Arrays.stream(expectedValues)
.map(Matchers::equalTo)
.collect(Collectors.toList());
return this.param(parameter, matcherList);
}
@SafeVarargs
@SuppressWarnings("varargs")
public final RequestMatcher param(String parameter, Matcher<? super String>... matchers) {
return this.param(parameter, Arrays.stream(matchers).collect(Collectors.toList()));
}
public RequestMatcher param(String parameter, Matcher<Iterable<? extends String>> matchers) {
return request -> {
Map<String, String[]> requestParams = MultipartRequestParser.parameterMap(request);
assertValueCount(parameter, requestParams, 1);
String[] values = requestParams.get(parameter);
assertThat("Request parameter [" + parameter + "]", Arrays.asList(values), matchers);
};
}
private RequestMatcher param(String parameter, List<Matcher<? super String>> matchers) {
return request -> {
Map<String, String[]> requestParams = MultipartRequestParser.parameterMap(request);
assertValueCount(parameter, requestParams, matchers.size());
String[] values = requestParams.get(parameter);
Assert.state(values != null, "No values for request parameter " + parameter);
for (int i = 0; i < matchers.size(); i++) {
assertThat("Request parameter [" + parameter + "]", values[i], matchers.get(i));
}
};
}
public RequestMatcher params(MultiValueMap<String, String> expectedParameters) {
return request -> {
Map<String, String[]> requestParams = MultipartRequestParser.parameterMap(request);
expectedParameters.forEach((param, values) -> {
String[] actualValues = requestParams.get(param);
Assert.state(actualValues != null, "No values for request parameter " + param);
assertValueCount(param, requestParams, values.size());
assertEquals("Parameter " + param, values, Arrays.asList(actualValues));
});
};
}
public RequestMatcher file(String parameter, byte[]... resources) {
return request -> {
MultiValueMap<String, MultipartFile> files = MultipartRequestParser.multiFileMap(request);
assertValueCount(parameter, files, resources.length);
assertByteArrayMatch(parameter, Arrays.asList(resources), files.get(parameter));
};
}
@SafeVarargs
@SuppressWarnings("varargs")
public final RequestMatcher file(String parameter, Matcher<? super Resource>... matchers) {
return request -> {
MultiValueMap<String, MultipartFile> files = MultipartRequestParser.multiFileMap(request);
assertValueCount(parameter, files, matchers.length);
List<MultipartFile> parts = files.get(parameter);
for (int i = 0; i < matchers.length; i++) {
assertThat("File [" + parameter + "]", parts.get(i).getResource(), matchers[i]);
}
};
}
public RequestMatcher file(String parameter, Resource... resources) {
return request -> {
MultiValueMap<String, MultipartFile> files = MultipartRequestParser.multiFileMap(request);
assertValueCount(parameter, files, resources.length);
assertResourceMatch(parameter, Arrays.asList(resources), files.get(parameter));
};
}
public RequestMatcher files(MultiValueMap<String, Resource> expectedFiles) {
return request -> {
MultiValueMap<String, MultipartFile> actualFiles = MultipartRequestParser.multiFileMap(request);
expectedFiles.forEach((param, parts) -> {
assertValueCount(param, actualFiles, parts.size());
assertResourceMatch(param, parts, actualFiles.get(param));
});
};
}
private void assertByteArrayMatch(String parameterName, List<byte[]> expectedFiles,
List<MultipartFile> actualFiles) {
for (int index = 0; index < actualFiles.size(); index++) {
MultipartFile multiPartFile = actualFiles.get(index);
byte[] expectedContent = expectedFiles.get(index);
try {
assertEquals("Content mismatch for file " + parameterName, expectedContent,
multiPartFile.getBytes());
}
catch (IOException ex) {
throw new AssertionError("Could not get bytes from actual multipart files", ex);
}
}
}
private void assertResourceMatch(String parameterName, List<Resource> expectedFiles,
List<MultipartFile> actualFiles) {
for (int index = 0; index < actualFiles.size(); index++) {
MultipartFile multiPartFile = actualFiles.get(index);
Resource expectedResource = expectedFiles.get(index);
try {
byte[] fileContent = IOUtils.toByteArray(expectedResource.getInputStream());
assertEquals("Content mismatch for file " + parameterName, fileContent,
multiPartFile.getBytes());
assertEquals("Filename ", expectedResource.getFilename(), multiPartFile.getOriginalFilename());
}
catch (IOException ex) {
throw new AssertionError("Could not get bytes from actual multipart files", ex);
}
}
}
private static void assertValueCount(String parameter, Map<String, String[]> map, int count) {
String[] values = map.get(parameter);
if (values == null) {
fail("Expected <" + parameter + "> to exist but was null");
}
assertValueCount(parameter, count, Arrays.asList(values));
}
private static void assertValueCount(String parameter, MultiValueMap<String, ?> map, int count) {
List<?> values = map.get(parameter);
assertValueCount(parameter, count, values);
}
private static void assertValueCount(String parameter, int count, List<?> values) {
String message = "Expected multipart file <" + parameter + ">";
if (count > values.size()) {
fail(message + " to have at least <" + count + "> values but found " + values.size());
}
}
private static class MultipartRequestParser {
private static MultipartHttpServletRequest extract(ClientHttpRequest request) {
MockClientHttpRequest mockRequest = (MockClientHttpRequest) request;
final MockHttpServletRequest mockHttpServletRequest = toMockHttpServletRequest(mockRequest);
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
return multipartResolver.resolveMultipart(mockHttpServletRequest);
}
@NotNull
private static MockHttpServletRequest toMockHttpServletRequest(MockClientHttpRequest mockRequest) {
final MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest();
mockHttpServletRequest.setContent(mockRequest.getBodyAsBytes());
// copy headers
mockRequest.getHeaders()
.forEach((headerName, headerValue) ->
headerValue.forEach(value -> mockHttpServletRequest.addHeader(headerName, value)));
return mockHttpServletRequest;
}
private static Map<String, String[]> parameterMap(ClientHttpRequest request) {
return extract(request).getParameterMap();
}
private static MultiValueMap<String, MultipartFile> multiFileMap(ClientHttpRequest request) {
return extract(request).getMultiFileMap();
}
}
}

View File

@ -1,320 +0,0 @@
/*
* Copyright 2002-2019 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
*
* https://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.client.match;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.mock.http.client.MockClientHttpRequest;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
/**
* Unit tests for {@link MultipartFormDataRequestMatchers}.
*
* @author Valentin Spac
*/
public class MultipartFormDatRequestMatchersTests {
private MockClientHttpRequest request = new MockClientHttpRequest();
private MultipartFormDataRequestMatchers multipartRequestMatchers = MockRestRequestMatchers.content().multipart();
@BeforeEach
public void setUp() {
this.request.getHeaders().setContentType(MediaType.MULTIPART_FORM_DATA);
}
@Test
public void testContains() throws Exception {
MultiValueMap<String, String> payload = new LinkedMultiValueMap<>();
payload.add("foo", "bar");
payload.add("foo", "baz");
payload.add("lorem", "ipsum");
writeForm(payload);
multipartRequestMatchers.param("foo", containsInAnyOrder("bar", "baz")).match(request);
}
@Test
public void testNoContains() throws Exception {
MultiValueMap<String, String> payload = new LinkedMultiValueMap<>();
payload.add("foo", "bar");
payload.add("foo", "baz");
payload.add("lorem", "ipsum");
writeForm(payload);
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
multipartRequestMatchers.param("foo", containsInAnyOrder("wrongValue")).match(request));
}
@Test
public void testEqualMatcher() throws Exception {
MultiValueMap<String, String> payload = new LinkedMultiValueMap<>();
payload.add("foo", "bar");
payload.add("baz", "foobar");
writeForm(payload);
multipartRequestMatchers.param("foo", equalTo("bar")).match(request);
}
@Test
public void testNoEqualMatcher() throws Exception {
MultiValueMap<String, String> payload = new LinkedMultiValueMap<>();
payload.add("foo", "bar");
payload.add("baz", "foobar");
writeForm(payload);
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
multipartRequestMatchers.param("foo", equalTo("wrongValue")).match(request));
}
@Test
public void testParamMatch() throws Exception {
MultiValueMap<String, String> payload = new LinkedMultiValueMap<>();
payload.add("foo", "bar");
payload.add("baz", "foobar");
writeForm(payload);
multipartRequestMatchers.param("foo", "bar").match(request);
}
@Test
public void testParamNoMatch() throws Exception {
MultiValueMap<String, String> payload = new LinkedMultiValueMap<>();
payload.add("foo", "bar");
payload.add("baz", "foobar");
writeForm(payload);
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
multipartRequestMatchers.param("foo", "wrongValue").match(request));
}
@Test
public void testParamsMultimapMatch() throws Exception {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("foo", "value 1");
map.add("bar", "value A");
map.add("baz", "value B");
writeForm(map);
multipartRequestMatchers.params(map).match(this.request);
}
@Test
public void testParamsMultimapNoMatch() throws Exception {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("foo", "foo value");
map.add("bar", "bar value");
map.add("baz", "baz value");
map.add("baz", "second baz value");
writeForm(map);
map.set("baz", "wrong baz value");
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
multipartRequestMatchers.params(map).match(this.request));
}
@Test
public void testResourceMatch() throws Exception {
MockMultipartFile foo = new MockMultipartFile("fooFile", "foo.txt", "text/plain", "Foo Lorem ipsum".getBytes());
MockMultipartFile bar = new MockMultipartFile("fooFile", "bar.txt", "text/plain", "Bar Lorem ipsum".getBytes());
MockMultipartFile foobar = new MockMultipartFile("foobarFile", "foobar.txt", "text/plain", "Foobar Lorem ipsum".getBytes());
MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("fooParam", "foo value");
map.add("barParam", "bar value");
map.add(foo.getName(), foo.getResource());
map.add(bar.getName(), bar.getResource());
map.add(foobar.getName(), foobar.getResource());
writeForm(map);
multipartRequestMatchers.file(foo.getName(), foo.getResource(), bar.getResource()).match(this.request);
}
@Test
public void testResourceNoMatch() throws Exception {
MockMultipartFile foo = new MockMultipartFile("fooFile", "foo.txt", "text/plain", "Foo Lorem ipsum".getBytes());
MockMultipartFile bar = new MockMultipartFile("barFile", "bar.txt", "text/plain", "Bar Lorem ipsum".getBytes());
MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("fooParam", "foo value");
map.add("barParam", "bar value");
map.add(foo.getName(), foo.getResource());
map.add(bar.getName(), bar.getResource());
writeForm(map);
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
multipartRequestMatchers.file(foo.getName(), foo.getResource(), bar.getResource()).match(this.request));
}
@Test
public void testByteArrayMatch() throws Exception {
MockMultipartFile foo = new MockMultipartFile("fooFile", "foo.txt", "text/plain", "Foo Lorem ipsum".getBytes());
MockMultipartFile bar = new MockMultipartFile("fooFile", "bar.txt", "text/plain", "Bar Lorem ipsum".getBytes());
MockMultipartFile foobar = new MockMultipartFile("foobarFile", "foobar.txt", "text/plain", "Foobar Lorem ipsum".getBytes());
MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("fooParam", "foo value");
map.add("barParam", "bar value");
map.add(foo.getName(), foo.getResource());
map.add(bar.getName(), bar.getResource());
map.add(foobar.getName(), foobar.getResource());
writeForm(map);
multipartRequestMatchers.file(foo.getName(), foo.getBytes(), bar.getBytes()).match(this.request);
}
@Test
public void testByteArrayNoMatch() throws Exception {
MockMultipartFile foo = new MockMultipartFile("fooFile", "foo.txt", "text/plain", "Foo Lorem ipsum".getBytes());
MockMultipartFile bar = new MockMultipartFile("barFile", "bar.txt", "text/plain", "Bar Lorem ipsum".getBytes());
MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("fooParam", "foo value");
map.add("barParam", "bar value");
map.add(foo.getName(), foo.getResource());
map.add(bar.getName(), bar.getResource());
writeForm(map);
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
multipartRequestMatchers.file(foo.getName(), bar.getBytes()).match(this.request));
}
@Test
public void testResourceMatcher() throws Exception {
MockMultipartFile foo = new MockMultipartFile("fooFile", "foo.txt", "text/plain", "Foo Lorem ipsum".getBytes());
MockMultipartFile bar = new MockMultipartFile("barFile", "bar.txt", "text/plain", "Bar Lorem ipsum".getBytes());
MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("fooParam", "foo value");
map.add("barParam", "bar value");
map.add(foo.getName(), foo.getResource());
map.add(bar.getName(), bar.getResource());
writeForm(map);
multipartRequestMatchers.file(foo.getName(), resourceMatcher(foo.getResource())).match(this.request);
}
@Test
public void testResourceMatcherNoMatch() throws Exception {
MockMultipartFile foo = new MockMultipartFile("fooFile", "foo.txt", "text/plain", "Foo Lorem ipsum".getBytes());
MockMultipartFile bar = new MockMultipartFile("barFile", "bar.txt", "text/plain", "Bar Lorem ipsum".getBytes());
MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("fooParam", "foo value");
map.add("barParam", "bar value");
map.add(foo.getName(), foo.getResource());
map.add(bar.getName(), bar.getResource());
writeForm(map);
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
multipartRequestMatchers.file(foo.getName(), resourceMatcher(bar.getResource())).match(this.request));
}
@NotNull
private Matcher<Resource> resourceMatcher(Resource expectedResource) {
return new TypeSafeMatcher<Resource>() {
@Override
public void describeTo(Description description) {
description.appendValue(expectedResource.getDescription());
}
@Override
protected boolean matchesSafely(Resource resource) {
try {
byte[] actual = IOUtils.toByteArray(resource.getInputStream());
byte[] expected = IOUtils.toByteArray(expectedResource.getInputStream());
return StringUtils.equals(expectedResource.getFilename(), resource.getFilename())
&& Arrays.equals(expected, actual);
}
catch (IOException e) {
throw new RuntimeException("Could not read resource content");
}
}
};
}
@Test
public void testResourceMultimapMatch() throws Exception {
MockMultipartFile foo = new MockMultipartFile("fooFile", "foo.txt", "text/plain", "Foo Lorem ipsum".getBytes());
MockMultipartFile bar = new MockMultipartFile("barFile", "bar.txt", "text/plain", "Bar Lorem ipsum".getBytes());
MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("fooParam", "foo value");
map.add("barParam", "bar value");
map.add(foo.getName(), foo.getResource());
map.add(bar.getName(), bar.getResource());
writeForm(map);
MultiValueMap<String, Resource> files = new LinkedMultiValueMap<>();
files.add(foo.getName(), foo.getResource());
files.add(bar.getName(), bar.getResource());
multipartRequestMatchers.files(files).match(this.request);
}
private void writeForm(MultiValueMap<String, ?> payload) throws IOException {
FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
formHttpMessageConverter.write(payload, MediaType.MULTIPART_FORM_DATA, new HttpOutputMessage() {
@Override
public OutputStream getBody() throws IOException {
return MultipartFormDatRequestMatchersTests.this.request.getBody();
}
@Override
public HttpHeaders getHeaders() {
return MultipartFormDatRequestMatchersTests.this.request.getHeaders();
}
});
}
}

View File

@ -0,0 +1,190 @@
/*
* Copyright 2002-2020 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
*
* https://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.client.match;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.mock.http.client.MockClientHttpRequest;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Unit tests for
* {@link ContentRequestMatchers#multipartData(MultiValueMap)} and.
* {@link ContentRequestMatchers#multipartDataContains(Map)}.
*
* @author Valentin Spac
* @author Rossen Stoyanchev
*/
public class MultipartRequestMatchersTests {
private final MockClientHttpRequest request = new MockClientHttpRequest();
private final MultiValueMap<String, Object> input = new LinkedMultiValueMap<>();
private final MultiValueMap<String, Object> expected = new LinkedMultiValueMap<>();
@BeforeEach
public void setUp() {
this.request.getHeaders().setContentType(MediaType.MULTIPART_FORM_DATA);
}
@Test
public void testContains() throws Exception {
this.input.add("foo", "bar");
this.input.add("foo", "baz");
this.input.add("lorem", "ipsum");
this.expected.add("foo", "bar");
writeAndAssertContains();
}
@Test
public void testDoesNotContain() {
this.input.add("foo", "bar");
this.input.add("foo", "baz");
this.input.add("lorem", "ipsum");
this.expected.add("foo", "wrongValue");
assertThatExceptionOfType(AssertionError.class).isThrownBy(this::writeAndAssert);
}
@Test
public void testParamsMatch() throws Exception {
this.input.add("foo", "value 1");
this.input.add("bar", "value A");
this.input.add("baz", "value B");
this.expected.addAll(this.input);
writeAndAssert();
}
@Test
public void testResourceMatch() throws Exception {
MultipartFile f1 = new MockMultipartFile("f1", "foo.txt", "text/plain", "Foo Lorem ipsum".getBytes());
MultipartFile f2 = new MockMultipartFile("f2", "bar.txt", "text/plain", "Bar Lorem ipsum".getBytes());
MultipartFile f3 = new MockMultipartFile("f3", "foobar.txt", "text/plain", "Foobar Lorem ipsum".getBytes());
this.input.add("fooParam", "foo value");
this.input.add("barParam", "bar value");
this.input.add(f1.getName(), f1.getResource());
this.input.add(f2.getName(), f2.getResource());
this.input.add(f3.getName(), f3.getResource());
this.expected.addAll(this.input);
writeAndAssert();
}
@Test
public void testResourceNoMatch() {
MockMultipartFile foo = new MockMultipartFile("f1", "foo.txt", "text/plain", "Foo Lorem ipsum".getBytes());
MockMultipartFile bar = new MockMultipartFile("f2", "bar.txt", "text/plain", "Bar Lorem ipsum".getBytes());
this.input.add("fooParam", "foo value");
this.input.add("barParam", "bar value");
this.input.add(foo.getName(), foo.getResource());
this.input.add(bar.getName(), bar.getResource());
this.expected.addAll(this.input);
this.expected.set(foo.getName(), bar.getResource());
assertThatExceptionOfType(AssertionError.class).isThrownBy(this::writeAndAssert);
}
@Test
public void testByteArrayMatch() throws Exception {
MultipartFile f1 = new MockMultipartFile("f1", "foo.txt", "text/plain", "Foo Lorem ipsum".getBytes());
MultipartFile f2 = new MockMultipartFile("f2", "bar.txt", "text/plain", "Bar Lorem ipsum".getBytes());
MultipartFile f3 = new MockMultipartFile("f3", "foobar.txt", "text/plain", "Foobar Lorem ipsum".getBytes());
this.input.add("fooParam", "foo value");
this.input.add("barParam", "bar value");
this.input.add(f1.getName(), f1.getResource());
this.input.add(f2.getName(), f2.getResource());
this.input.add(f3.getName(), f3.getResource());
this.expected.addAll(this.input);
this.expected.set(f1.getName(), f1.getBytes());
this.expected.set(f2.getName(), f2.getBytes());
this.expected.set(f3.getName(), f3.getBytes());
writeAndAssert();
}
@Test
public void testByteArrayNoMatch() throws Exception {
MultipartFile f1 = new MockMultipartFile("f1", "foo.txt", "text/plain", "Foo Lorem ipsum".getBytes());
MultipartFile f2 = new MockMultipartFile("f2", "bar.txt", "text/plain", "Bar Lorem ipsum".getBytes());
this.input.add("fooParam", "foo value");
this.input.add("barParam", "bar value");
this.input.add(f1.getName(), f1.getResource());
this.input.add(f2.getName(), f2.getResource());
this.expected.addAll(this.input);
this.expected.set(f1.getName(), f2.getBytes());
assertThatExceptionOfType(AssertionError.class).isThrownBy(this::writeAndAssert);
}
private void writeAndAssert() throws IOException {
writeForm();
new ContentRequestMatchers().multipartData(this.expected).match(request);
}
private void writeAndAssertContains() throws IOException {
writeForm();
Map<String, Object> expectedMap = this.expected.toSingleValueMap();
new ContentRequestMatchers().multipartDataContains(expectedMap).match(request);
}
private void writeForm() throws IOException {
new FormHttpMessageConverter().write(this.input, MediaType.MULTIPART_FORM_DATA,
new HttpOutputMessage() {
@Override
public OutputStream getBody() throws IOException {
return MultipartRequestMatchersTests.this.request.getBody();
}
@Override
public HttpHeaders getHeaders() {
return MultipartRequestMatchersTests.this.request.getHeaders();
}
});
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@ -232,4 +232,11 @@ public class CommonsMultipartFile implements MultipartFile, Serializable {
}
}
@Override
public String toString() {
return "MultipartFile[field=\"" + this.fileItem.getFieldName() + "\"" +
(this.fileItem.getName() != null ? ", filename=" + this.fileItem.getName() : "" ) +
(this.fileItem.getContentType() != null ? ", contentType=" + this.fileItem.getContentType() : "") +
", size=" + this.fileItem.getSize() + "]";
}
}