/*
* Copyright 2002-2017 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.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import rx.Observable;
import rx.RxReactiveStreams;
import rx.Single;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.StringDecoder;
import org.springframework.http.HttpEntity;
import org.springframework.http.RequestEntity;
import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;
import org.springframework.util.ObjectUtils;
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.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
import static org.springframework.http.MediaType.TEXT_PLAIN;
import static org.springframework.mock.http.server.reactive.test.MockServerHttpRequest.post;
/**
* Unit tests for {@link HttpEntityArgumentResolver}.When adding a test also
* consider whether the logic under test is in a parent class, then see:
* {@link MessageReaderArgumentResolverTests}.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
*/
public class HttpEntityArgumentResolverTests {
private HttpEntityArgumentResolver resolver = createResolver();
private final ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build();
private HttpEntityArgumentResolver createResolver() {
List<HttpMessageReader<?>> readers = new ArrayList<>();
readers.add(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true)));
return new HttpEntityArgumentResolver(readers, new ReactiveAdapterRegistry());
}
@Test
public void supports() throws Exception {
testSupports(this.testMethod.arg(httpEntityType(String.class)));
testSupports(this.testMethod.arg(httpEntityType(Mono.class, String.class)));
testSupports(this.testMethod.arg(httpEntityType(Single.class, String.class)));
testSupports(this.testMethod.arg(httpEntityType(io.reactivex.Single.class, String.class)));
testSupports(this.testMethod.arg(httpEntityType(Maybe.class, String.class)));
testSupports(this.testMethod.arg(httpEntityType(CompletableFuture.class, String.class)));
testSupports(this.testMethod.arg(httpEntityType(Flux.class, String.class)));
testSupports(this.testMethod.arg(httpEntityType(Observable.class, String.class)));
testSupports(this.testMethod.arg(httpEntityType(io.reactivex.Observable.class, String.class)));
testSupports(this.testMethod.arg(httpEntityType(Flowable.class, String.class)));
testSupports(this.testMethod.arg(forClassWithGenerics(RequestEntity.class, String.class)));
}
private void testSupports(MethodParameter parameter) {
assertTrue(this.resolver.supportsParameter(parameter));
}
@Test
public void doesNotSupport() throws Exception {
assertFalse(this.resolver.supportsParameter(this.testMethod.arg(Mono.class, String.class)));
assertFalse(this.resolver.supportsParameter(this.testMethod.arg(String.class)));
try {
this.resolver.supportsParameter(this.testMethod.arg(Mono.class, httpEntityType(String.class)));
fail();
}
catch (IllegalStateException ex) {
assertTrue("Unexpected error message:\n" + ex.getMessage(),
ex.getMessage().startsWith(
"HttpEntityArgumentResolver doesn't support reactive type wrapper"));
}
}
@Test
public void emptyBodyWithString() throws Exception {
ResolvableType type = httpEntityType(String.class);
HttpEntity<Object> entity = resolveValueWithEmptyBody(type);
assertNull(entity.getBody());
}
@Test
public void emptyBodyWithMono() throws Exception {
ResolvableType type = httpEntityType(Mono.class, String.class);
HttpEntity<Mono<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(entity.getBody()).expectNextCount(0).expectComplete().verify();
}
@Test
public void emptyBodyWithFlux() throws Exception {
ResolvableType type = httpEntityType(Flux.class, String.class);
HttpEntity<Flux<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(entity.getBody()).expectNextCount(0).expectComplete().verify();
}
@Test
public void emptyBodyWithSingle() throws Exception {
ResolvableType type = httpEntityType(Single.class, String.class);
HttpEntity<Single<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(RxReactiveStreams.toPublisher(entity.getBody()))
.expectNextCount(0)
.expectError(ServerWebInputException.class)
.verify();
}
@Test
public void emptyBodyWithRxJava2Single() throws Exception {
ResolvableType type = httpEntityType(io.reactivex.Single.class, String.class);
HttpEntity<io.reactivex.Single<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(entity.getBody().toFlowable())
.expectNextCount(0)
.expectError(ServerWebInputException.class)
.verify();
}
@Test
public void emptyBodyWithRxJava2Maybe() throws Exception {
ResolvableType type = httpEntityType(Maybe.class, String.class);
HttpEntity<Maybe<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(entity.getBody().toFlowable())
.expectNextCount(0)
.expectComplete()
.verify();
}
@Test
public void emptyBodyWithObservable() throws Exception {
ResolvableType type = httpEntityType(Observable.class, String.class);
HttpEntity<Observable<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(RxReactiveStreams.toPublisher(entity.getBody()))
.expectNextCount(0)
.expectComplete()
.verify();
}
@Test
public void emptyBodyWithRxJava2Observable() throws Exception {
ResolvableType type = httpEntityType(io.reactivex.Observable.class, String.class);
HttpEntity<io.reactivex.Observable<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(entity.getBody().toFlowable(BackpressureStrategy.BUFFER))
.expectNextCount(0)
.expectComplete()
.verify();
}
@Test
public void emptyBodyWithFlowable() throws Exception {
ResolvableType type = httpEntityType(Flowable.class, String.class);
HttpEntity<Flowable<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(entity.getBody())
.expectNextCount(0)
.expectComplete()
.verify();
}
@Test
public void emptyBodyWithCompletableFuture() throws Exception {
ResolvableType type = httpEntityType(CompletableFuture.class, String.class);
HttpEntity<CompletableFuture<String>> entity = resolveValueWithEmptyBody(type);
entity.getBody().whenComplete((body, ex) -> {
assertNull(body);
assertNull(ex);
});
}
@Test
public void httpEntityWithStringBody() throws Exception {
ServerWebExchange exchange = postExchange("line1");
ResolvableType type = httpEntityType(String.class);
HttpEntity<String> httpEntity = resolveValue(exchange, type);
assertEquals(exchange.getRequest().getHeaders(), httpEntity.getHeaders());
assertEquals("line1", httpEntity.getBody());
}
@Test
public void httpEntityWithMonoBody() throws Exception {
ServerWebExchange exchange = postExchange("line1");
ResolvableType type = httpEntityType(Mono.class, String.class);
HttpEntity<Mono<String>> httpEntity = resolveValue(exchange, type);
assertEquals(exchange.getRequest().getHeaders(), httpEntity.getHeaders());
assertEquals("line1", httpEntity.getBody().block());
}
@Test
public void httpEntityWithSingleBody() throws Exception {
ServerWebExchange exchange = postExchange("line1");
ResolvableType type = httpEntityType(Single.class, String.class);
HttpEntity<Single<String>> httpEntity = resolveValue(exchange, type);
assertEquals(exchange.getRequest().getHeaders(), httpEntity.getHeaders());
assertEquals("line1", httpEntity.getBody().toBlocking().value());
}
@Test
public void httpEntityWithRxJava2SingleBody() throws Exception {
ServerWebExchange exchange = postExchange("line1");
ResolvableType type = httpEntityType(io.reactivex.Single.class, String.class);
HttpEntity<io.reactivex.Single<String>> httpEntity = resolveValue(exchange, type);
assertEquals(exchange.getRequest().getHeaders(), httpEntity.getHeaders());
assertEquals("line1", httpEntity.getBody().blockingGet());
}
@Test
public void httpEntityWithRxJava2MaybeBody() throws Exception {
ServerWebExchange exchange = postExchange("line1");
ResolvableType type = httpEntityType(Maybe.class, String.class);
HttpEntity<Maybe<String>> httpEntity = resolveValue(exchange, type);
assertEquals(exchange.getRequest().getHeaders(), httpEntity.getHeaders());
assertEquals("line1", httpEntity.getBody().blockingGet());
}
@Test
public void httpEntityWithCompletableFutureBody() throws Exception {
ServerWebExchange exchange = postExchange("line1");
ResolvableType type = httpEntityType(CompletableFuture.class, String.class);
HttpEntity<CompletableFuture<String>> httpEntity = resolveValue(exchange, type);
assertEquals(exchange.getRequest().getHeaders(), httpEntity.getHeaders());
assertEquals("line1", httpEntity.getBody().get());
}
@Test
public void httpEntityWithFluxBody() throws Exception {
ServerWebExchange exchange = postExchange("line1\nline2\nline3\n");
ResolvableType type = httpEntityType(Flux.class, String.class);
HttpEntity<Flux<String>> httpEntity = resolveValue(exchange, type);
assertEquals(exchange.getRequest().getHeaders(), httpEntity.getHeaders());
StepVerifier.create(httpEntity.getBody())
.expectNext("line1\n")
.expectNext("line2\n")
.expectNext("line3\n")
.expectComplete()
.verify();
}
@Test
public void requestEntity() throws Exception {
ServerWebExchange exchange = postExchange("line1");
ResolvableType type = forClassWithGenerics(RequestEntity.class, String.class);
RequestEntity<String> requestEntity = resolveValue(exchange, type);
assertEquals(exchange.getRequest().getMethod(), requestEntity.getMethod());
assertEquals(exchange.getRequest().getURI(), requestEntity.getUrl());
assertEquals(exchange.getRequest().getHeaders(), requestEntity.getHeaders());
assertEquals("line1", requestEntity.getBody());
}
private MockServerWebExchange postExchange(String body) {
return post("/path").header("foo", "bar").contentType(TEXT_PLAIN).body(body).toExchange();
}
private ResolvableType httpEntityType(Class<?> bodyType, Class<?>... generics) {
return ResolvableType.forClassWithGenerics(HttpEntity.class,
ObjectUtils.isEmpty(generics) ?
ResolvableType.forClass(bodyType) :
ResolvableType.forClassWithGenerics(bodyType, generics));
}
@SuppressWarnings("unchecked")
private <T> T resolveValue(ServerWebExchange exchange, ResolvableType type) {
MethodParameter param = this.testMethod.arg(type);
Mono<Object> result = this.resolver.resolveArgument(param, new BindingContext(), exchange);
Object value = result.block(Duration.ofSeconds(5));
assertNotNull(value);
assertTrue("Unexpected return value type: " + value.getClass(),
param.getParameterType().isAssignableFrom(value.getClass()));
return (T) value;
}
@SuppressWarnings("unchecked")
private <T> HttpEntity<T> resolveValueWithEmptyBody(ResolvableType type) {
ServerWebExchange exchange = post("/path").toExchange();
MethodParameter param = this.testMethod.arg(type);
Mono<Object> result = this.resolver.resolveArgument(param, new BindingContext(), exchange);
HttpEntity<String> httpEntity = (HttpEntity<String>) result.block(Duration.ofSeconds(5));
assertEquals(exchange.getRequest().getHeaders(), httpEntity.getHeaders());
return (HttpEntity<T>) httpEntity;
}
@SuppressWarnings("unused")
void handle(
String string,
Mono<String> monoString,
HttpEntity<String> httpEntity,
HttpEntity<Mono<String>> monoBody,
HttpEntity<Flux<String>> fluxBody,
HttpEntity<Single<String>> singleBody,
HttpEntity<io.reactivex.Single<String>> rxJava2SingleBody,
HttpEntity<Maybe<String>> rxJava2MaybeBody,
HttpEntity<Observable<String>> observableBody,
HttpEntity<io.reactivex.Observable<String>> rxJava2ObservableBody,
HttpEntity<Flowable<String>> flowableBody,
HttpEntity<CompletableFuture<String>> completableFutureBody,
RequestEntity<String> requestEntity,
Mono<HttpEntity<String>> httpEntityMono) {}
}