Add cookie handling to WebTestClient request and response converters
Closes gh-447
This commit is contained in:
@@ -16,6 +16,7 @@ allprojects {
|
||||
group = 'org.springframework.restdocs'
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url 'https://repo.spring.io/libs-snapshot' }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +35,7 @@ sonarqube {
|
||||
}
|
||||
|
||||
ext {
|
||||
springVersion = '5.0.1.RELEASE'
|
||||
springVersion = '5.0.2.BUILD-SNAPSHOT'
|
||||
javadocLinks = [
|
||||
'http://docs.oracle.com/javase/8/docs/api/',
|
||||
"http://docs.spring.io/spring-framework/docs/$springVersion/javadoc-api/",
|
||||
|
||||
@@ -69,10 +69,10 @@ class WebTestClientRequestConverter implements RequestConverter<ExchangeResult>
|
||||
|
||||
@Override
|
||||
public OperationRequest convert(ExchangeResult result) {
|
||||
HttpHeaders headers = extractRequestHeaders(result);
|
||||
return new OperationRequestFactory().create(result.getUrl(), result.getMethod(),
|
||||
result.getRequestBodyContent(), extractRequestHeaders(result),
|
||||
extractParameters(result), extractRequestParts(result),
|
||||
extractCookies(result));
|
||||
result.getRequestBodyContent(), headers, extractParameters(result),
|
||||
extractRequestParts(result), extractCookies(headers));
|
||||
}
|
||||
|
||||
private HttpHeaders extractRequestHeaders(ExchangeResult result) {
|
||||
@@ -123,9 +123,19 @@ class WebTestClientRequestConverter implements RequestConverter<ExchangeResult>
|
||||
return contentStream;
|
||||
}
|
||||
|
||||
private Collection<RequestCookie> extractCookies(ExchangeResult result) {
|
||||
// Cookies are not available. See https://jira.spring.io/browse/SPR-16124.
|
||||
return Collections.emptyList();
|
||||
private Collection<RequestCookie> extractCookies(HttpHeaders headers) {
|
||||
List<String> cookieHeaders = headers.get(HttpHeaders.COOKIE);
|
||||
if (cookieHeaders == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
headers.remove(HttpHeaders.COOKIE);
|
||||
return cookieHeaders.stream().map(this::createRequestCookie)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private RequestCookie createRequestCookie(String header) {
|
||||
String[] components = header.split("=");
|
||||
return new RequestCookie(components[0], components[1]);
|
||||
}
|
||||
|
||||
private final class ExchangeResultReactiveHttpInputMessage
|
||||
|
||||
@@ -16,10 +16,16 @@
|
||||
|
||||
package org.springframework.restdocs.webtestclient;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.restdocs.operation.OperationResponse;
|
||||
import org.springframework.restdocs.operation.OperationResponseFactory;
|
||||
import org.springframework.restdocs.operation.ResponseConverter;
|
||||
import org.springframework.test.web.reactive.server.ExchangeResult;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link ResponseConverter} for creating an {@link OperationResponse} derived from an
|
||||
@@ -31,8 +37,56 @@ class WebTestClientResponseConverter implements ResponseConverter<ExchangeResult
|
||||
|
||||
@Override
|
||||
public OperationResponse convert(ExchangeResult result) {
|
||||
MultiValueMap<String, ResponseCookie> responseCookies = result
|
||||
.getResponseCookies();
|
||||
return new OperationResponseFactory().create(result.getStatus(),
|
||||
result.getResponseHeaders(), result.getResponseBodyContent());
|
||||
extractHeaders(result), result.getResponseBodyContent());
|
||||
}
|
||||
|
||||
private HttpHeaders extractHeaders(ExchangeResult result) {
|
||||
HttpHeaders headers = result.getResponseHeaders();
|
||||
if (result.getResponseCookies().isEmpty()
|
||||
|| headers.containsKey(HttpHeaders.SET_COOKIE)) {
|
||||
return headers;
|
||||
}
|
||||
result.getResponseCookies().values().stream().flatMap(Collection::stream)
|
||||
.forEach((cookie) -> headers.add(HttpHeaders.SET_COOKIE,
|
||||
generateSetCookieHeader(cookie)));
|
||||
return headers;
|
||||
}
|
||||
|
||||
private String generateSetCookieHeader(ResponseCookie cookie) {
|
||||
StringBuilder header = new StringBuilder();
|
||||
header.append(cookie.getName());
|
||||
header.append('=');
|
||||
appendIfAvailable(header, cookie.getValue());
|
||||
long maxAge = cookie.getMaxAge().getSeconds();
|
||||
if (maxAge > -1) {
|
||||
header.append("; Max-Age=");
|
||||
header.append(maxAge);
|
||||
}
|
||||
appendIfAvailable(header, "; Domain=", cookie.getDomain());
|
||||
appendIfAvailable(header, "; Path=", cookie.getPath());
|
||||
if (cookie.isSecure()) {
|
||||
header.append("; Secure");
|
||||
}
|
||||
if (cookie.isHttpOnly()) {
|
||||
header.append("; HttpOnly");
|
||||
}
|
||||
return header.toString();
|
||||
}
|
||||
|
||||
private void appendIfAvailable(StringBuilder header, String value) {
|
||||
if (StringUtils.hasText(value)) {
|
||||
header.append(value);
|
||||
}
|
||||
}
|
||||
|
||||
private void appendIfAvailable(StringBuilder header, String name, String value) {
|
||||
if (StringUtils.hasText(value)) {
|
||||
header.append(name);
|
||||
header.append(value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,15 +18,18 @@ package org.springframework.restdocs.webtestclient;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.http.ContentDisposition;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.restdocs.operation.OperationRequest;
|
||||
import org.springframework.restdocs.operation.OperationRequestPart;
|
||||
import org.springframework.restdocs.operation.RequestCookie;
|
||||
import org.springframework.test.web.reactive.server.ExchangeResult;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
@@ -37,6 +40,7 @@ import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.hamcrest.Matchers.hasEntry;
|
||||
import static org.junit.Assert.assertThat;
|
||||
@@ -205,4 +209,27 @@ public class WebTestClientRequestConverterTests {
|
||||
assertThat(part.getContent(), is(equalTo(new byte[] { 1, 2, 3, 4 })));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWithCookies() throws Exception {
|
||||
ExchangeResult result = WebTestClient
|
||||
.bindToRouterFunction(RouterFunctions.route(GET("/foo"), (req) -> null))
|
||||
.configureClient().baseUrl("http://localhost").build().get().uri("/foo")
|
||||
.cookie("cookieName1", "cookieVal1").cookie("cookieName2", "cookieVal2")
|
||||
.exchange().expectBody().returnResult();
|
||||
assertThat(result.getRequestHeaders().get(HttpHeaders.COOKIE),
|
||||
is(notNullValue()));
|
||||
OperationRequest request = this.converter.convert(result);
|
||||
assertThat(request.getUri(), is(URI.create("http://localhost/foo")));
|
||||
assertThat(request.getMethod(), is(HttpMethod.GET));
|
||||
assertThat(request.getCookies().size(), is(equalTo(2)));
|
||||
assertThat(request.getHeaders().get(HttpHeaders.COOKIE), is(nullValue()));
|
||||
Iterator<RequestCookie> cookieIterator = request.getCookies().iterator();
|
||||
RequestCookie cookie1 = cookieIterator.next();
|
||||
assertThat(cookie1.getName(), is(equalTo("cookieName1")));
|
||||
assertThat(cookie1.getValue(), is(equalTo("cookieVal1")));
|
||||
RequestCookie cookie2 = cookieIterator.next();
|
||||
assertThat(cookie2.getName(), is(equalTo("cookieName2")));
|
||||
assertThat(cookie2.getValue(), is(equalTo("cookieVal2")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,10 +16,14 @@
|
||||
|
||||
package org.springframework.restdocs.webtestclient;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.restdocs.operation.OperationResponse;
|
||||
import org.springframework.test.web.reactive.server.ExchangeResult;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
@@ -29,6 +33,7 @@ import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
|
||||
|
||||
/**
|
||||
@@ -55,4 +60,21 @@ public class WebTestClientResponseConverterTests {
|
||||
assertThat(response.getHeaders().getContentLength(), is(13L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void responseWithCookie() {
|
||||
ExchangeResult result = WebTestClient
|
||||
.bindToRouterFunction(RouterFunctions.route(GET("/foo"),
|
||||
(req) -> ServerResponse.ok()
|
||||
.cookie(ResponseCookie.from("name", "value")
|
||||
.domain("localhost").httpOnly(true).build())
|
||||
.build()))
|
||||
.configureClient().baseUrl("http://localhost").build().get().uri("/foo")
|
||||
.exchange().expectBody().returnResult();
|
||||
OperationResponse response = this.converter.convert(result);
|
||||
assertThat(response.getHeaders().size(), is(1));
|
||||
assertTrue(response.getHeaders().containsKey(HttpHeaders.SET_COOKIE));
|
||||
assertThat(response.getHeaders().get(HttpHeaders.SET_COOKIE), equalTo(
|
||||
Collections.singletonList("name=value; Domain=localhost; HttpOnly")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,7 +28,10 @@ import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.restdocs.JUnitRestDocumentation;
|
||||
import org.springframework.restdocs.templates.TemplateFormats;
|
||||
import org.springframework.test.web.reactive.server.EntityExchangeResult;
|
||||
@@ -52,6 +55,8 @@ import static org.springframework.restdocs.request.RequestDocumentation.pathPara
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
|
||||
import static org.springframework.restdocs.request.RequestDocumentation.requestParts;
|
||||
import static org.springframework.restdocs.templates.TemplateFormats.asciidoctor;
|
||||
import static org.springframework.restdocs.test.SnippetMatchers.codeBlock;
|
||||
import static org.springframework.restdocs.test.SnippetMatchers.httpResponse;
|
||||
import static org.springframework.restdocs.test.SnippetMatchers.snippet;
|
||||
import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader;
|
||||
import static org.springframework.restdocs.test.SnippetMatchers.tableWithTitleAndHeader;
|
||||
@@ -84,7 +89,11 @@ public class WebTestClientRestDocumentationIntegrationTests {
|
||||
return request.body(BodyExtractors.toMultipartData()).map((parts) -> {
|
||||
return ServerResponse.status(HttpStatus.OK).build().block();
|
||||
});
|
||||
});
|
||||
}).andRoute(RequestPredicates.GET("/set-cookie"),
|
||||
(request) -> ServerResponse.ok()
|
||||
.cookie(ResponseCookie.from("name", "value")
|
||||
.domain("localhost").httpOnly(true).build())
|
||||
.build());
|
||||
this.webTestClient = WebTestClient.bindToRouterFunction(route).configureClient()
|
||||
.baseUrl("https://api.example.com")
|
||||
.filter(documentationConfiguration(this.restDocumentation)).build();
|
||||
@@ -151,6 +160,46 @@ public class WebTestClientRestDocumentationIntegrationTests {
|
||||
"Part b"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void responseWithSetCookie() throws Exception {
|
||||
this.webTestClient.get().uri("/set-cookie").exchange().expectStatus().isOk()
|
||||
.expectBody().consumeWith(document("set-cookie"));
|
||||
assertThat(new File("build/generated-snippets/set-cookie/http-response.adoc"),
|
||||
is(snippet(asciidoctor())
|
||||
.withContents(httpResponse(asciidoctor(), HttpStatus.OK).header(
|
||||
HttpHeaders.SET_COOKIE,
|
||||
"name=value; Domain=localhost; HttpOnly"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void curlSnippetWithCookies() throws Exception {
|
||||
this.webTestClient.get().uri("/").cookie("cookieName", "cookieVal")
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk()
|
||||
.expectBody().consumeWith(document("curl-snippet-with-cookies"));
|
||||
assertThat(
|
||||
new File(
|
||||
"build/generated-snippets/curl-snippet-with-cookies/curl-request.adoc"),
|
||||
is(snippet(asciidoctor()).withContents(codeBlock(asciidoctor(), "bash")
|
||||
.content(String.format("$ curl 'https://api.example.com/' -i \\%n"
|
||||
+ " -H 'Accept: application/json' \\%n"
|
||||
+ " --cookie 'cookieName=cookieVal'")))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpieSnippetWithCookies() throws Exception {
|
||||
this.webTestClient.get().uri("/").cookie("cookieName", "cookieVal")
|
||||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk()
|
||||
.expectBody().consumeWith(document("httpie-snippet-with-cookies"));
|
||||
assertThat(
|
||||
new File(
|
||||
"build/generated-snippets/httpie-snippet-with-cookies/httpie-request.adoc"),
|
||||
is(snippet(asciidoctor())
|
||||
.withContents(codeBlock(asciidoctor(), "bash").content(
|
||||
String.format("$ http GET 'https://api.example.com/' \\%n"
|
||||
+ " 'Accept:application/json' \\%n"
|
||||
+ " 'Cookie:cookieName=cookieVal'")))));
|
||||
}
|
||||
|
||||
private void assertExpectedSnippetFilesExist(File directory, String... snippets) {
|
||||
Set<File> actual = new HashSet<>(Arrays.asList(directory.listFiles()));
|
||||
Set<File> expected = Stream.of(snippets)
|
||||
|
||||
Reference in New Issue
Block a user