Add cookie handling to WebTestClient request and response converters

Closes gh-447
This commit is contained in:
Andy Wilkinson
2017-11-12 16:48:19 +00:00
parent b7bf8cb992
commit 0f0769245d
6 changed files with 172 additions and 9 deletions

View File

@@ -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/",

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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")));
}
}

View File

@@ -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")));
}
}

View File

@@ -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)