/* * Copyright 2002-2018 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 * * http://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.web.reactive.result.method.annotation; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Collections; import java.util.List; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import org.junit.Before; import org.junit.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.support.DataBufferTestUtils; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.client.MultipartBodyBuilder; import org.springframework.http.codec.ClientCodecConfigurer; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.codec.multipart.MultipartHttpMessageWriter; import org.springframework.http.codec.multipart.Part; import org.springframework.mock.http.client.reactive.test.MockClientHttpRequest; import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; import org.springframework.mock.web.test.server.MockServerWebExchange; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.method.ResolvableMethod; import org.springframework.web.reactive.BindingContext; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebInputException; import static org.junit.Assert.*; import static org.springframework.core.ResolvableType.*; import static org.springframework.web.method.MvcAnnotationPredicates.*; /** * Unit tests for {@link RequestPartMethodArgumentResolver}. * @author Rossen Stoyanchev */ public class RequestPartMethodArgumentResolverTests { private RequestPartMethodArgumentResolver resolver; private ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build(); private MultipartHttpMessageWriter writer; @Before public void setup() throws Exception { List<HttpMessageReader<?>> readers = ServerCodecConfigurer.create().getReaders(); ReactiveAdapterRegistry registry = ReactiveAdapterRegistry.getSharedInstance(); this.resolver = new RequestPartMethodArgumentResolver(readers, registry); List<HttpMessageWriter<?>> writers = ClientCodecConfigurer.create().getWriters(); this.writer = new MultipartHttpMessageWriter(writers); } @Test public void supportsParameter() { MethodParameter param; param = this.testMethod.annot(requestPart()).arg(Person.class); assertTrue(this.resolver.supportsParameter(param)); param = this.testMethod.annot(requestPart()).arg(Mono.class, Person.class); assertTrue(this.resolver.supportsParameter(param)); param = this.testMethod.annot(requestPart()).arg(Flux.class, Person.class); assertTrue(this.resolver.supportsParameter(param)); param = this.testMethod.annot(requestPart()).arg(Part.class); assertTrue(this.resolver.supportsParameter(param)); param = this.testMethod.annot(requestPart()).arg(Mono.class, Part.class); assertTrue(this.resolver.supportsParameter(param)); param = this.testMethod.annot(requestPart()).arg(Flux.class, Part.class); assertTrue(this.resolver.supportsParameter(param)); param = this.testMethod.annotNotPresent(RequestPart.class).arg(Person.class); assertFalse(this.resolver.supportsParameter(param)); } @Test public void person() { MethodParameter param = this.testMethod.annot(requestPart()).arg(Person.class); MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder(); bodyBuilder.part("name", new Person("Jones")); Person actual = resolveArgument(param, bodyBuilder); assertEquals("Jones", actual.getName()); } @Test public void listPerson() { MethodParameter param = this.testMethod.annot(requestPart()).arg(List.class, Person.class); MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder(); bodyBuilder.part("name", new Person("Jones")); bodyBuilder.part("name", new Person("James")); List<Person> actual = resolveArgument(param, bodyBuilder); assertEquals("Jones", actual.get(0).getName()); assertEquals("James", actual.get(1).getName()); } @Test public void monoPerson() { MethodParameter param = this.testMethod.annot(requestPart()).arg(Mono.class, Person.class); MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder(); bodyBuilder.part("name", new Person("Jones")); Mono<Person> actual = resolveArgument(param, bodyBuilder); assertEquals("Jones", actual.block().getName()); } @Test public void fluxPerson() { MethodParameter param = this.testMethod.annot(requestPart()).arg(Flux.class, Person.class); MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder(); bodyBuilder.part("name", new Person("Jones")); bodyBuilder.part("name", new Person("James")); Flux<Person> actual = resolveArgument(param, bodyBuilder); List<Person> persons = actual.collectList().block(); assertEquals("Jones", persons.get(0).getName()); assertEquals("James", persons.get(1).getName()); } @Test public void part() { MethodParameter param = this.testMethod.annot(requestPart()).arg(Part.class); MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder(); bodyBuilder.part("name", new Person("Jones")); Part actual = resolveArgument(param, bodyBuilder); DataBuffer buffer = DataBufferUtils.join(actual.content()).block(); assertEquals("{\"name\":\"Jones\"}", DataBufferTestUtils.dumpString(buffer, StandardCharsets.UTF_8)); } @Test public void listPart() { MethodParameter param = this.testMethod.annot(requestPart()).arg(List.class, Part.class); MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder(); bodyBuilder.part("name", new Person("Jones")); bodyBuilder.part("name", new Person("James")); List<Part> actual = resolveArgument(param, bodyBuilder); assertEquals("{\"name\":\"Jones\"}", partToUtf8String(actual.get(0))); assertEquals("{\"name\":\"James\"}", partToUtf8String(actual.get(1))); } @Test public void monoPart() { MethodParameter param = this.testMethod.annot(requestPart()).arg(Mono.class, Part.class); MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder(); bodyBuilder.part("name", new Person("Jones")); Mono<Part> actual = resolveArgument(param, bodyBuilder); Part part = actual.block(); assertEquals("{\"name\":\"Jones\"}", partToUtf8String(part)); } @Test public void fluxPart() { MethodParameter param = this.testMethod.annot(requestPart()).arg(Flux.class, Part.class); MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder(); bodyBuilder.part("name", new Person("Jones")); bodyBuilder.part("name", new Person("James")); Flux<Part> actual = resolveArgument(param, bodyBuilder); List<Part> parts = actual.collectList().block(); assertEquals("{\"name\":\"Jones\"}", partToUtf8String(parts.get(0))); assertEquals("{\"name\":\"James\"}", partToUtf8String(parts.get(1))); } @Test public void personRequired() { MethodParameter param = this.testMethod.annot(requestPart()).arg(Person.class); ServerWebExchange exchange = createExchange(new MultipartBodyBuilder()); Mono<Object> result = this.resolver.resolveArgument(param, new BindingContext(), exchange); StepVerifier.create(result).expectError(ServerWebInputException.class).verify(); } @Test public void personNotRequired() { MethodParameter param = this.testMethod.annot(requestPart().notRequired()).arg(Person.class); ServerWebExchange exchange = createExchange(new MultipartBodyBuilder()); Mono<Object> result = this.resolver.resolveArgument(param, new BindingContext(), exchange); StepVerifier.create(result).verifyComplete(); } @Test public void partRequired() { MethodParameter param = this.testMethod.annot(requestPart()).arg(Part.class); ServerWebExchange exchange = createExchange(new MultipartBodyBuilder()); Mono<Object> result = this.resolver.resolveArgument(param, new BindingContext(), exchange); StepVerifier.create(result).expectError(ServerWebInputException.class).verify(); } @Test public void partNotRequired() { MethodParameter param = this.testMethod.annot(requestPart().notRequired()).arg(Part.class); ServerWebExchange exchange = createExchange(new MultipartBodyBuilder()); Mono<Object> result = this.resolver.resolveArgument(param, new BindingContext(), exchange); StepVerifier.create(result).verifyComplete(); } @SuppressWarnings("unchecked") private <T> T resolveArgument(MethodParameter param, MultipartBodyBuilder builder) { ServerWebExchange exchange = createExchange(builder); Mono<Object> result = this.resolver.resolveArgument(param, new BindingContext(), exchange); Object value = result.block(Duration.ofSeconds(5)); assertNotNull(value); assertTrue(param.getParameterType().isAssignableFrom(value.getClass())); return (T) value; } private ServerWebExchange createExchange(MultipartBodyBuilder builder) { MockClientHttpRequest clientRequest = new MockClientHttpRequest(HttpMethod.POST, "/"); this.writer.write(Mono.just(builder.build()), forClass(MultiValueMap.class), MediaType.MULTIPART_FORM_DATA, clientRequest, Collections.emptyMap()).block(); MockServerHttpRequest serverRequest = MockServerHttpRequest.post("/") .contentType(clientRequest.getHeaders().getContentType()) .body(clientRequest.getBody()); return MockServerWebExchange.from(serverRequest); } private String partToUtf8String(Part part) { DataBuffer buffer = DataBufferUtils.join(part.content()).block(); return DataBufferTestUtils.dumpString(buffer, StandardCharsets.UTF_8); } @SuppressWarnings("unused") void handle( @RequestPart("name") Person person, @RequestPart("name") Mono<Person> personMono, @RequestPart("name") Flux<Person> personFlux, @RequestPart("name") List<Person> personList, @RequestPart("name") Part part, @RequestPart("name") Mono<Part> partMono, @RequestPart("name") Flux<Part> partFlux, @RequestPart("name") List<Part> partList, @RequestPart(name = "anotherPart", required = false) Person anotherPerson, @RequestPart(name = "anotherPart", required = false) Part anotherPart, Person notAnnotated) {} private static class Person { private String name; @JsonCreator public Person(@JsonProperty("name") String name) { this.name = name; } public String getName() { return name; } } }