Provide simple way to create ClientResponse

This commit introduces ClientResponse.Builder, an easier way to create a
ClientResponse from an existing response, or from scratch.

Issue: SPR-16553
This commit is contained in:
Arjen Poutsma 2018-03-08 15:47:48 +01:00
parent f8588e364a
commit 04c2a2990d
10 changed files with 879 additions and 9 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* 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.
@ -16,20 +16,26 @@
package org.springframework.web.reactive.function.client;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.function.Consumer;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyExtractor;
@ -68,6 +74,11 @@ public interface ClientResponse {
*/
MultiValueMap<String, ResponseCookie> cookies();
/**
* Return the strategies used to convert the body of this response.
*/
ExchangeStrategies strategies();
/**
* Extract the body with the given {@code BodyExtractor}.
* @param extractor the {@code BodyExtractor} that reads from the response
@ -141,6 +152,66 @@ public interface ClientResponse {
<T> Mono<ResponseEntity<List<T>>> toEntityList(ParameterizedTypeReference<T> typeReference);
// Static builder methods
/**
* Create a builder with the status, headers, and cookies of the given response.
* @param other the response to copy the status, headers, and cookies from
* @return the created builder
*/
static Builder from(ClientResponse other) {
Assert.notNull(other, "'other' must not be null");
return new DefaultClientResponseBuilder(other);
}
/**
* Create a response builder with the given status code and using default strategies for reading
* the body.
* @param statusCode the status code
* @return the created builder
*/
static Builder create(HttpStatus statusCode) {
return create(statusCode, ExchangeStrategies.withDefaults());
}
/**
* Create a response builder with the given status code and strategies for reading the body.
* @param statusCode the status code
* @param strategies the strategies
* @return the created builder
*/
static Builder create(HttpStatus statusCode, ExchangeStrategies strategies) {
Assert.notNull(statusCode, "'statusCode' must not be null");
Assert.notNull(strategies, "'strategies' must not be null");
return new DefaultClientResponseBuilder(strategies)
.statusCode(statusCode);
}
/**
* Create a response builder with the given status code and message body readers.
* @param statusCode the status code
* @param messageReaders the message readers
* @return the created builder
*/
static Builder create(HttpStatus statusCode, List<HttpMessageReader<?>> messageReaders) {
Assert.notNull(statusCode, "'statusCode' must not be null");
Assert.notNull(messageReaders, "'messageReaders' must not be null");
return create(statusCode, new ExchangeStrategies() {
@Override
public List<HttpMessageReader<?>> messageReaders() {
return messageReaders;
}
@Override
public List<HttpMessageWriter<?>> messageWriters() {
// not used in the response
return Collections.emptyList();
}
});
}
/**
* Represents the headers of the HTTP response.
* @see ClientResponse#headers()
@ -172,4 +243,80 @@ public interface ClientResponse {
HttpHeaders asHttpHeaders();
}
/**
* Defines a builder for a response.
*/
interface Builder {
/**
* Set the status code of the response.
* @param statusCode the new status code.
* @return this builder
*/
Builder statusCode(HttpStatus statusCode);
/**
* Add the given header value(s) under the given name.
* @param headerName the header name
* @param headerValues the header value(s)
* @return this builder
* @see HttpHeaders#add(String, String)
*/
Builder header(String headerName, String... headerValues);
/**
* Manipulate this response's headers with the given consumer. The
* headers provided to the consumer are "live", so that the consumer can be used to
* {@linkplain HttpHeaders#set(String, String) overwrite} existing header values,
* {@linkplain HttpHeaders#remove(Object) remove} values, or use any of the other
* {@link HttpHeaders} methods.
* @param headersConsumer a function that consumes the {@code HttpHeaders}
* @return this builder
*/
Builder headers(Consumer<HttpHeaders> headersConsumer);
/**
* Add a cookie with the given name and value(s).
* @param name the cookie name
* @param values the cookie value(s)
* @return this builder
*/
Builder cookie(String name, String... values);
/**
* Manipulate this response's cookies with the given consumer. The
* map provided to the consumer is "live", so that the consumer can be used to
* {@linkplain MultiValueMap#set(Object, Object) overwrite} existing header values,
* {@linkplain MultiValueMap#remove(Object) remove} values, or use any of the other
* {@link MultiValueMap} methods.
* @param cookiesConsumer a function that consumes the cookies map
* @return this builder
*/
Builder cookies(Consumer<MultiValueMap<String, ResponseCookie>> cookiesConsumer);
/**
* Sets the body of the response. Calling this methods will
* {@linkplain org.springframework.core.io.buffer.DataBufferUtils#release(DataBuffer) release}
* the existing body of the builder.
* @param body the new body.
* @return this builder
*/
Builder body(Flux<DataBuffer> body);
/**
* Sets the body of the response to the UTF-8 encoded bytes of the given string.
* Calling this methods will
* {@linkplain org.springframework.core.io.buffer.DataBufferUtils#release(DataBuffer) release}
* the existing body of the builder.
* @param body the new body.
* @return this builder
*/
Builder body(String body);
/**
* Builds the response.
* @return the response
*/
ClientResponse build();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* 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.
@ -61,6 +61,10 @@ class DefaultClientResponse implements ClientResponse {
this.headers = new DefaultHeaders();
}
@Override
public ExchangeStrategies strategies() {
return this.strategies;
}
@Override
public HttpStatus statusCode() {

View File

@ -0,0 +1,189 @@
/*
* 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.function.client;
import java.nio.charset.StandardCharsets;
import java.util.function.Consumer;
import reactor.core.publisher.Flux;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Default implementation of {@link ClientResponse.Builder}.
*
* @author Arjen Poutsma
* @since 5.0.5
*/
class DefaultClientResponseBuilder implements ClientResponse.Builder {
private final HttpHeaders headers = new HttpHeaders();
private final MultiValueMap<String, ResponseCookie> cookies = new LinkedMultiValueMap<>();
private HttpStatus statusCode = HttpStatus.OK;
private Flux<DataBuffer> body = Flux.empty();
private ExchangeStrategies strategies;
public DefaultClientResponseBuilder(ExchangeStrategies strategies) {
Assert.notNull(strategies, "'strategies' must not be null");
this.strategies = strategies;
}
public DefaultClientResponseBuilder(ClientResponse other) {
this(other.strategies());
statusCode(other.statusCode());
headers(headers -> headers.addAll(other.headers().asHttpHeaders()));
cookies(cookies -> cookies.addAll(other.cookies()));
}
@Override
public DefaultClientResponseBuilder statusCode(HttpStatus statusCode) {
Assert.notNull(statusCode, "'statusCode' must not be null");
this.statusCode = statusCode;
return this;
}
@Override
public ClientResponse.Builder header(String headerName, String... headerValues) {
for (String headerValue : headerValues) {
this.headers.add(headerName, headerValue);
}
return this;
}
@Override
public ClientResponse.Builder headers(Consumer<HttpHeaders> headersConsumer) {
Assert.notNull(headersConsumer, "'headersConsumer' must not be null");
headersConsumer.accept(this.headers);
return this;
}
@Override
public DefaultClientResponseBuilder cookie(String name, String... values) {
for (String value : values) {
this.cookies.add(name, ResponseCookie.from(name, value).build());
}
return this;
}
@Override
public ClientResponse.Builder cookies(
Consumer<MultiValueMap<String, ResponseCookie>> cookiesConsumer) {
Assert.notNull(cookiesConsumer, "'cookiesConsumer' must not be null");
cookiesConsumer.accept(this.cookies);
return this;
}
@Override
public ClientResponse.Builder body(Flux<DataBuffer> body) {
Assert.notNull(body, "'body' must not be null");
releaseBody();
this.body = body;
return this;
}
@Override
public ClientResponse.Builder body(String body) {
Assert.notNull(body, "'body' must not be null");
releaseBody();
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
this.body = Flux.just(body).
map(s -> {
byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
return dataBufferFactory.wrap(bytes);
});
return this;
}
private void releaseBody() {
this.body.subscribe(DataBufferUtils.releaseConsumer());
}
@Override
public ClientResponse build() {
ClientHttpResponse clientHttpResponse = new BuiltClientHttpResponse(this.statusCode,
this.headers, this.cookies, this.body);
return new DefaultClientResponse(clientHttpResponse, this.strategies);
}
private static class BuiltClientHttpResponse implements ClientHttpResponse {
private final HttpStatus statusCode;
private final HttpHeaders headers;
private final MultiValueMap<String, ResponseCookie> cookies;
private final Flux<DataBuffer> body;
public BuiltClientHttpResponse(HttpStatus statusCode, HttpHeaders headers,
MultiValueMap<String, ResponseCookie> cookies,
Flux<DataBuffer> body) {
this.statusCode = statusCode;
this.headers = HttpHeaders.readOnlyHttpHeaders(headers);
this.cookies = unmodifiableCopy(cookies);
this.body = body;
}
private static @Nullable <K, V> MultiValueMap<K, V> unmodifiableCopy(@Nullable MultiValueMap<K, V> original) {
if (original != null) {
return CollectionUtils.unmodifiableMultiValueMap(new LinkedMultiValueMap<>(original));
}
else {
return null;
}
}
@Override
public HttpStatus getStatusCode() {
return this.statusCode;
}
@Override
public HttpHeaders getHeaders() {
return this.headers;
}
@Override
public MultiValueMap<String, ResponseCookie> getCookies() {
return this.cookies;
}
@Override
public Flux<DataBuffer> getBody() {
return this.body;
}
}
}

View File

@ -0,0 +1,176 @@
/*
* 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.function.client.support;
import java.util.List;
import java.util.Optional;
import java.util.OptionalLong;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyExtractor;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
/**
* Implementation of the {@link ClientResponse} interface that can be subclassed
* to adapt the request in a
* {@link org.springframework.web.reactive.function.client.ExchangeFilterFunction exchange filter function}.
* All methods default to calling through to the wrapped request.
*
* @author Arjen Poutsma
* @since 5.0.5
*/
public class ClientResponseWrapper implements ClientResponse {
private final ClientResponse delegate;
/**
* Create a new {@code ClientResponseWrapper} that wraps the given response.
* @param delegate the response to wrap
*/
public ClientResponseWrapper(ClientResponse delegate) {
Assert.notNull(delegate, "'delegate' must not be null");
this.delegate = delegate;
}
/**
* Return the wrapped request.
*/
public ClientResponse response() {
return this.delegate;
}
@Override
public ExchangeStrategies strategies() {
return this.delegate.strategies();
}
@Override
public HttpStatus statusCode() {
return this.delegate.statusCode();
}
@Override
public Headers headers() {
return this.delegate.headers();
}
@Override
public MultiValueMap<String, ResponseCookie> cookies() {
return this.delegate.cookies();
}
@Override
public <T> T body(BodyExtractor<T, ? super ClientHttpResponse> extractor) {
return this.delegate.body(extractor);
}
@Override
public <T> Mono<T> bodyToMono(Class<? extends T> elementClass) {
return this.delegate.bodyToMono(elementClass);
}
@Override
public <T> Mono<T> bodyToMono(ParameterizedTypeReference<T> typeReference) {
return this.delegate.bodyToMono(typeReference);
}
@Override
public <T> Flux<T> bodyToFlux(Class<? extends T> elementClass) {
return this.delegate.bodyToFlux(elementClass);
}
@Override
public <T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> typeReference) {
return this.delegate.bodyToFlux(typeReference);
}
@Override
public <T> Mono<ResponseEntity<T>> toEntity(Class<T> bodyType) {
return this.delegate.toEntity(bodyType);
}
@Override
public <T> Mono<ResponseEntity<T>> toEntity(ParameterizedTypeReference<T> typeReference) {
return this.delegate.toEntity(typeReference);
}
@Override
public <T> Mono<ResponseEntity<List<T>>> toEntityList(Class<T> elementType) {
return this.delegate.toEntityList(elementType);
}
@Override
public <T> Mono<ResponseEntity<List<T>>> toEntityList(ParameterizedTypeReference<T> typeReference) {
return this.delegate.toEntityList(typeReference);
}
/**
* Implementation of the {@code Headers} interface that can be subclassed
* to adapt the headers in a
* {@link org.springframework.web.reactive.function.client.ExchangeFilterFunction exchange filter function}.
* All methods default to calling through to the wrapped request.
*/
public static class HeadersWrapper implements ClientResponse.Headers {
private final Headers headers;
/**
* Create a new {@code HeadersWrapper} that wraps the given request.
* @param headers the headers to wrap
*/
public HeadersWrapper(Headers headers) {
this.headers = headers;
}
@Override
public OptionalLong contentLength() {
return this.headers.contentLength();
}
@Override
public Optional<MediaType> contentType() {
return this.headers.contentType();
}
@Override
public List<String> header(String headerName) {
return this.headers.header(headerName);
}
@Override
public HttpHeaders asHttpHeaders() {
return this.headers.asHttpHeaders();
}
}
}

View File

@ -0,0 +1,26 @@
/*
* 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.
*/
/**
* Classes supporting the {@code org.springframework.web.reactive.function.client} package.
* Contains a {@code ClientResponse} wrapper to adapt a request.
*/
@NonNullApi
@NonNullFields
package org.springframework.web.reactive.function.client.support;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* 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.
@ -40,14 +40,14 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyExtractor;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.WebSession;
import org.springframework.web.util.UriBuilder;
/**
* Implementation of the {@link ServerRequest} interface that can be subclassed
* to adapt the request to a {@link HandlerFunction handler function}.
* to adapt the request in a
* {@link org.springframework.web.reactive.function.server.HandlerFilterFunction handler filter function}.
* All methods default to calling through to the wrapped request.
*
* @author Arjen Poutsma
@ -59,7 +59,7 @@ public class ServerRequestWrapper implements ServerRequest {
/**
* Create a new {@code RequestWrapper} that wraps the given request.
* Create a new {@code ServerRequestWrapper} that wraps the given request.
* @param delegate the request to wrap
*/
public ServerRequestWrapper(ServerRequest delegate) {
@ -187,13 +187,15 @@ public class ServerRequestWrapper implements ServerRequest {
/**
* Implementation of the {@code Headers} interface that can be subclassed
* to adapt the headers to a {@link HandlerFunction handler function}.
* to adapt the headers in a
* {@link org.springframework.web.reactive.function.server.HandlerFilterFunction handler filter function}.
* All methods default to calling through to the wrapped headers.
*/
public static class HeadersWrapper implements ServerRequest.Headers {
private final Headers headers;
/**
* Create a new {@code HeadersWrapper} that wraps the given request.
* @param headers the headers to wrap
@ -203,6 +205,7 @@ public class ServerRequestWrapper implements ServerRequest {
this.headers = headers;
}
@Override
public List<MediaType> accept() {
return this.headers.accept();

View File

@ -1,5 +1,5 @@
/**
* Classes supporting the {@code org.springframework.web.reactive.function} package.
* Classes supporting the {@code org.springframework.web.reactive.function.server} package.
* Contains a {@code HandlerAdapter} that supports {@code HandlerFunction}s,
* a {@code HandlerResultHandler} that supports {@code ServerResponse}s, and
* a {@code ServerRequest} wrapper to adapt a request.

View File

@ -0,0 +1,104 @@
/*
* 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.function.client;
import java.nio.charset.StandardCharsets;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import static org.junit.Assert.*;
/**
* @author Arjen Poutsma
*/
public class DefaultClientResponseBuilderTests {
private DataBufferFactory dataBufferFactory;
@Before
public void createBufferFactory() {
this.dataBufferFactory = new DefaultDataBufferFactory();
}
@Test
public void normal() {
Flux<DataBuffer> body = Flux.just("baz")
.map(s -> s.getBytes(StandardCharsets.UTF_8))
.map(dataBufferFactory::wrap);
ClientResponse response = ClientResponse.create(HttpStatus.BAD_GATEWAY, ExchangeStrategies.withDefaults())
.header("foo", "bar")
.cookie("baz", "qux")
.body(body)
.build();
assertEquals(HttpStatus.BAD_GATEWAY, response.statusCode());
HttpHeaders responseHeaders = response.headers().asHttpHeaders();
assertEquals("bar", responseHeaders.getFirst("foo"));
assertNotNull("qux", response.cookies().getFirst("baz"));
assertEquals("qux", response.cookies().getFirst("baz").getValue());
StepVerifier.create(response.bodyToFlux(String.class))
.expectNext("baz")
.verifyComplete();
}
@Test
public void from() throws Exception {
Flux<DataBuffer> otherBody = Flux.just("foo", "bar")
.map(s -> s.getBytes(StandardCharsets.UTF_8))
.map(dataBufferFactory::wrap);
ClientResponse other = ClientResponse.create(HttpStatus.BAD_REQUEST, ExchangeStrategies.withDefaults())
.header("foo", "bar")
.cookie("baz", "qux")
.body(otherBody)
.build();
Flux<DataBuffer> body = Flux.just("baz")
.map(s -> s.getBytes(StandardCharsets.UTF_8))
.map(dataBufferFactory::wrap);
ClientResponse result = ClientResponse.from(other)
.headers(httpHeaders -> httpHeaders.set("foo", "baar"))
.cookies(cookies -> cookies.set("baz", ResponseCookie.from("baz", "quux").build()))
.body(body)
.build();
assertEquals(HttpStatus.BAD_REQUEST, result.statusCode());
assertEquals(1, result.headers().asHttpHeaders().size());
assertEquals("baar", result.headers().asHttpHeaders().getFirst("foo"));
assertEquals(1, result.cookies().size());
assertEquals("quux", result.cookies().getFirst("baz").getValue());
StepVerifier.create(result.bodyToFlux(String.class))
.expectNext("baz")
.verifyComplete();
}
}

View File

@ -0,0 +1,163 @@
/*
* 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.function.client.support;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpStatus;
import org.springframework.http.ReactiveHttpInputMessage;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyExtractor;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.client.ClientResponse;
import static java.util.Collections.singletonList;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* @author Arjen Poutsma
*/
public class ClientResponseWrapperTests {
private ClientResponse mockResponse;
private ClientResponseWrapper wrapper;
@Before
public void createWrapper() {
this.mockResponse = mock(ClientResponse.class);
this.wrapper = new ClientResponseWrapper(mockResponse);
}
@Test
public void response() throws Exception {
assertSame(mockResponse, wrapper.response());
}
@Test
public void statusCode() throws Exception {
HttpStatus status = HttpStatus.BAD_REQUEST;
when(mockResponse.statusCode()).thenReturn(status);
assertSame(status, wrapper.statusCode());
}
@Test
public void headers() throws Exception {
ClientResponse.Headers headers = mock(ClientResponse.Headers.class);
when(mockResponse.headers()).thenReturn(headers);
assertSame(headers, wrapper.headers());
}
@Test
public void cookies() throws Exception {
MultiValueMap<String, ResponseCookie> cookies = mock(MultiValueMap.class);
when(mockResponse.cookies()).thenReturn(cookies);
assertSame(cookies, wrapper.cookies());
}
@Test
public void bodyExtractor() throws Exception {
Mono<String> result = Mono.just("foo");
BodyExtractor<Mono<String>, ReactiveHttpInputMessage> extractor = BodyExtractors.toMono(String.class);
when(mockResponse.body(extractor)).thenReturn(result);
assertSame(result, wrapper.body(extractor));
}
@Test
public void bodyToMonoClass() throws Exception {
Mono<String> result = Mono.just("foo");
when(mockResponse.bodyToMono(String.class)).thenReturn(result);
assertSame(result, wrapper.bodyToMono(String.class));
}
@Test
public void bodyToMonoParameterizedTypeReference() throws Exception {
Mono<String> result = Mono.just("foo");
ParameterizedTypeReference<String> reference = new ParameterizedTypeReference<String>() {};
when(mockResponse.bodyToMono(reference)).thenReturn(result);
assertSame(result, wrapper.bodyToMono(reference));
}
@Test
public void bodyToFluxClass() throws Exception {
Flux<String> result = Flux.just("foo");
when(mockResponse.bodyToFlux(String.class)).thenReturn(result);
assertSame(result, wrapper.bodyToFlux(String.class));
}
@Test
public void bodyToFluxParameterizedTypeReference() throws Exception {
Flux<String> result = Flux.just("foo");
ParameterizedTypeReference<String> reference = new ParameterizedTypeReference<String>() {};
when(mockResponse.bodyToFlux(reference)).thenReturn(result);
assertSame(result, wrapper.bodyToFlux(reference));
}
@Test
public void toEntityClass() throws Exception {
Mono<ResponseEntity<String>> result = Mono.just(new ResponseEntity<>("foo", HttpStatus.OK));
when(mockResponse.toEntity(String.class)).thenReturn(result);
assertSame(result, wrapper.toEntity(String.class));
}
@Test
public void toEntityParameterizedTypeReference() throws Exception {
Mono<ResponseEntity<String>> result = Mono.just(new ResponseEntity<>("foo", HttpStatus.OK));
ParameterizedTypeReference<String> reference = new ParameterizedTypeReference<String>() {};
when(mockResponse.toEntity(reference)).thenReturn(result);
assertSame(result, wrapper.toEntity(reference));
}
@Test
public void toEntityListClass() throws Exception {
Mono<ResponseEntity<List<String>>> result = Mono.just(new ResponseEntity<>(singletonList("foo"), HttpStatus.OK));
when(mockResponse.toEntityList(String.class)).thenReturn(result);
assertSame(result, wrapper.toEntityList(String.class));
}
@Test
public void toEntityListParameterizedTypeReference() throws Exception {
Mono<ResponseEntity<List<String>>> result = Mono.just(new ResponseEntity<>(singletonList("foo"), HttpStatus.OK));
ParameterizedTypeReference<String> reference = new ParameterizedTypeReference<String>() {};
when(mockResponse.toEntityList(reference)).thenReturn(result);
assertSame(result, wrapper.toEntityList(reference));
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* 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.
@ -23,10 +23,17 @@ import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpMethod;
import org.springframework.http.ReactiveHttpInputMessage;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyExtractor;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.server.ServerRequest;
import static org.junit.Assert.*;
@ -128,4 +135,55 @@ public class ServerRequestWrapperTests {
assertSame(pathVariables, wrapper.pathVariables());
}
@Test
public void cookies() throws Exception {
MultiValueMap<String, HttpCookie> cookies = mock(MultiValueMap.class);
when(mockRequest.cookies()).thenReturn(cookies);
assertSame(cookies, wrapper.cookies());
}
@Test
public void bodyExtractor() throws Exception {
Mono<String> result = Mono.just("foo");
BodyExtractor<Mono<String>, ReactiveHttpInputMessage> extractor = BodyExtractors.toMono(String.class);
when(mockRequest.body(extractor)).thenReturn(result);
assertSame(result, wrapper.body(extractor));
}
@Test
public void bodyToMonoClass() throws Exception {
Mono<String> result = Mono.just("foo");
when(mockRequest.bodyToMono(String.class)).thenReturn(result);
assertSame(result, wrapper.bodyToMono(String.class));
}
@Test
public void bodyToMonoParameterizedTypeReference() throws Exception {
Mono<String> result = Mono.just("foo");
ParameterizedTypeReference<String> reference = new ParameterizedTypeReference<String>() {};
when(mockRequest.bodyToMono(reference)).thenReturn(result);
assertSame(result, wrapper.bodyToMono(reference));
}
@Test
public void bodyToFluxClass() throws Exception {
Flux<String> result = Flux.just("foo");
when(mockRequest.bodyToFlux(String.class)).thenReturn(result);
assertSame(result, wrapper.bodyToFlux(String.class));
}
@Test
public void bodyToFluxParameterizedTypeReference() throws Exception {
Flux<String> result = Flux.just("foo");
ParameterizedTypeReference<String> reference = new ParameterizedTypeReference<String>() {};
when(mockRequest.bodyToFlux(reference)).thenReturn(result);
assertSame(result, wrapper.bodyToFlux(reference));
}
}