diff --git a/docs/src/docs/asciidoc/documenting-your-api.adoc b/docs/src/docs/asciidoc/documenting-your-api.adoc index 0f267476..9adae2ca 100644 --- a/docs/src/docs/asciidoc/documenting-your-api.adoc +++ b/docs/src/docs/asciidoc/documenting-your-api.adoc @@ -988,40 +988,38 @@ Each contains a table describing the headers. When documenting HTTP Headers, the test fails if a documented header is not found in the request or response. + [[documenting-your-api-http-cookies]] === HTTP Cookies -You can document the cookies in a request or response by using `requestCookies` and -`responseCookies`, respectively. The following examples show how to do so: +You can document the cookies in a request or response by using `requestCookies` and `responseCookies`, respectively. +The following examples show how to do so: -==== [source,java,indent=0,role="primary"] .MockMvc ---- include::{examples-dir}/com/example/mockmvc/HttpCookies.java[tags=cookies] ---- -<1> Configure Spring REST Docs to produce a snippet describing the request's cookies. - Uses the static `requestCookies` method on - `org.springframework.restdocs.cookies.CookieDocumentation`. -<2> Document the `JSESSIONID` cookie. Uses the static `cookieWithName` method on - `org.springframework.restdocs.cookies.CookieDocumentation. -<3> Produce a snippet describing the response's cookies. Uses the static `responseCookies` - method on `org.springframework.restdocs.cookies.CookieDocumentation`. -<4> Configure the request with an `JSESSIONID` and an additional cookie `logged_in`. +<1> Make a GET request with a `JSESSIONID` cookie. +<2> Configure Spring REST Docs to produce a snippet describing the request's cookies. + Uses the static `requestCookies` method on `org.springframework.restdocs.cookies.CookieDocumentation`. +<3> Document the `JSESSIONID` cookie. Uses the static `cookieWithName` method on `org.springframework.restdocs.cookies.CookieDocumentation`. +<4> Produce a snippet describing the response's cookies. + Uses the static `responseCookies` method on `org.springframework.restdocs.cookies.CookieDocumentation`. [source,java,indent=0,role="secondary"] .WebTestClient ---- include::{examples-dir}/com/example/webtestclient/HttpCookies.java[tags=cookies] ---- -<1> Configure Spring REST Docs to produce a snippet describing the request's cookies. +<1> Make a GET request with a `JSESSIONID` cookie. +<2> Configure Spring REST Docs to produce a snippet describing the request's cookies. Uses the static `requestCookies` method on `org.springframework.restdocs.cookies.CookieDocumentation`. -<2> Document the `JSESSIONID` cookie. Uses the static `cookieWithName` method on - `org.springframework.restdocs.cookies.CookieDocumentation. -<3> Produce a snippet describing the response's cookies. Uses the static `responseCookies` - method on `org.springframework.restdocs.cookies.CookieDocumentation`. -<4> Configure the request with an `JSESSIONID` and an additional cookie `logged_in`. +<3> Document the `JSESSIONID` cookie. + Uses the static `cookieWithName` method on `org.springframework.restdocs.cookies.CookieDocumentation`. +<4> Produce a snippet describing the response's cookies. + Uses the static `responseCookies` method on `org.springframework.restdocs.cookies.CookieDocumentation`. [source,java,indent=0,role="secondary"] .REST Assured @@ -1029,20 +1027,19 @@ include::{examples-dir}/com/example/webtestclient/HttpCookies.java[tags=cookies] include::{examples-dir}/com/example/restassured/HttpCookies.java[tags=cookies] ---- <1> Configure Spring REST Docs to produce a snippet describing the request's cookies. - Uses the static `requestCookies` method on - `org.springframework.restdocs.cookies.CookieDocumentation`. -<2> Document the `JSESSIONID` cookie. Uses the static `cookieWithName` method on - `org.springframework.restdocs.cookies.CookieDocumentation. -<3> Produce a snippet describing the response's cookies. Uses the static `responseCookies` - method on `org.springframework.restdocs.cookies.CookieDocumentation`. -<4> Configure the request with an `JSESSIONID` and an additional cookie `logged_in`. -==== + Uses the static `requestCookies` method on `org.springframework.restdocs.cookies.CookieDocumentation`. +<2> Document the `JSESSIONID` cookie. + Uses the static `cookieWithName` method on `org.springframework.restdocs.cookies.CookieDocumentation`. +<3> Produce a snippet describing the response's cookies. + Uses the static `responseCookies` method on `org.springframework.restdocs.cookies.CookieDocumentation`. +<4> Send a `JSESSIONID` cookie with the request. + +The result is a snippet named `request-cookies.adoc` and a snippet named `response-cookies.adoc`. +Each contains a table describing the cookies. + +When documenting HTTP Cookies, the test fails if a documented cookie is not found in the request or response. -The result is a snippet named `request-cookies.adoc` and a snippet named -`response-cookies.adoc`. Each contains a table describing the cookies. -When documenting HTTP Cookies, the test fails if a documented cookie is not found in -the request or response. [[documenting-your-api-reusing-snippets]] === Reusing Snippets diff --git a/docs/src/test/java/com/example/mockmvc/HttpCookies.java b/docs/src/test/java/com/example/mockmvc/HttpCookies.java index aab39db7..7b1be451 100644 --- a/docs/src/test/java/com/example/mockmvc/HttpCookies.java +++ b/docs/src/test/java/com/example/mockmvc/HttpCookies.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2022 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. diff --git a/docs/src/test/java/com/example/restassured/HttpCookies.java b/docs/src/test/java/com/example/restassured/HttpCookies.java index f6b76293..9171a8fb 100644 --- a/docs/src/test/java/com/example/restassured/HttpCookies.java +++ b/docs/src/test/java/com/example/restassured/HttpCookies.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2017 the original author or authors. + * Copyright 2014-2022 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. @@ -29,7 +29,7 @@ public class HttpCookies { private RequestSpecification spec; - public void cookies() throws Exception { + public void cookies() { // tag::cookies[] RestAssured.given(this.spec).filter(document("cookies", requestCookies(// <1> cookieWithName("JSESSIONID").description("Saved session token")), // <2> diff --git a/docs/src/test/java/com/example/webtestclient/HttpCookies.java b/docs/src/test/java/com/example/webtestclient/HttpCookies.java index 8979520d..7d9bf36d 100644 --- a/docs/src/test/java/com/example/webtestclient/HttpCookies.java +++ b/docs/src/test/java/com/example/webtestclient/HttpCookies.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2017 the original author or authors. + * Copyright 2014-2022 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. @@ -25,23 +25,17 @@ import static org.springframework.restdocs.webtestclient.WebTestClientRestDocume public class HttpCookies { - // @formatter:off - private WebTestClient webTestClient; - public void cookies() throws Exception { + public void cookies() { // tag::cookies[] - this.webTestClient - .get().uri("/people").cookie("JSESSIONID", "ACBCDFD0FF93D5BB=") // <1> - .exchange().expectStatus().isOk().expectBody() - .consumeWith(document("cookies", - requestCookies(// <2> - cookieWithName("JSESSIONID").description("Session token")), // <3> - responseCookies(// <4> - cookieWithName("JSESSIONID") - .description("Updated session token"), - cookieWithName("logged_in") - .description("User is logged in")))); + this.webTestClient.get().uri("/people").cookie("JSESSIONID", "ACBCDFD0FF93D5BB=") // <1> + .exchange().expectStatus().isOk().expectBody().consumeWith(document("cookies", requestCookies(// <2> + cookieWithName("JSESSIONID").description("Session token")), // <3> + responseCookies(// <4> + cookieWithName("JSESSIONID").description("Updated session token"), + cookieWithName("logged_in").description("User is logged in")))); // end::cookies[] } + } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/AbstractCookiesSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/AbstractCookiesSnippet.java index 865ea397..19201586 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/AbstractCookiesSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/AbstractCookiesSnippet.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2017 the original author or authors. + * Copyright 2014-2022 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. @@ -17,13 +17,16 @@ package org.springframework.restdocs.cookies; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import org.springframework.restdocs.operation.Operation; -import org.springframework.restdocs.snippet.SnippetException; import org.springframework.restdocs.snippet.TemplatedSnippet; import org.springframework.util.Assert; @@ -31,17 +34,15 @@ import org.springframework.util.Assert; * Abstract {@link TemplatedSnippet} subclass that provides a base for snippets that * document a RESTful resource's request or response cookies. * - * @author Andreas Evers * @author Clyde Stubbs - * @since 2.1 + * @author Andy Wilkinson + * @since 3.0 */ public abstract class AbstractCookiesSnippet extends TemplatedSnippet { - private List cookieDescriptors; + private final Map descriptorsByName = new LinkedHashMap<>(); - protected final boolean ignoreUndocumentedCookies; - - private String type; + private final boolean ignoreUndocumentedCookies; /** * Creates a new {@code AbstractCookiesSnippet} that will produce a snippet named @@ -57,58 +58,53 @@ public abstract class AbstractCookiesSnippet extends TemplatedSnippet { boolean ignoreUndocumentedCookies) { super(type + "-cookies", attributes); for (CookieDescriptor descriptor : descriptors) { - Assert.notNull(descriptor.getName(), "The name of the cookie must not be null"); + Assert.notNull(descriptor.getName(), "Cookie descriptors must have a name"); if (!descriptor.isIgnored()) { - Assert.notNull(descriptor.getDescription(), "The description of the cookie must not be null"); + Assert.notNull(descriptor.getDescription(), "The descriptor for cookie '" + descriptor.getName() + + "' must either have a description or be marked as ignored"); } + this.descriptorsByName.put(descriptor.getName(), descriptor); } - this.cookieDescriptors = descriptors; - this.type = type; this.ignoreUndocumentedCookies = ignoreUndocumentedCookies; } @Override protected Map createModel(Operation operation) { - validateCookieDocumentation(operation); + verifyCookieDescriptors(operation); Map model = new HashMap<>(); List> cookies = new ArrayList<>(); - model.put("cookies", cookies); - for (CookieDescriptor descriptor : this.cookieDescriptors) { - cookies.add(createModelForDescriptor(descriptor)); + for (CookieDescriptor descriptor : this.descriptorsByName.values()) { + if (!descriptor.isIgnored()) { + cookies.add(createModelForDescriptor(descriptor)); + } } + model.put("cookies", cookies); return model; } - private void validateCookieDocumentation(Operation operation) { - List missingCookies = findMissingCookies(operation); - if (!missingCookies.isEmpty()) { - List names = new ArrayList<>(); - for (CookieDescriptor cookieDescriptor : missingCookies) { - names.add(cookieDescriptor.getName()); - } - throw new SnippetException( - "Cookies with the following names were not found" + " in the " + this.type + ": " + names); - } - } - - /** - * Finds the cookies that are missing from the operation. A cookie is missing if it is - * described by one of the {@code cookieDescriptors} but is not present in the - * operation. - * @param operation the operation - * @return descriptors for the cookies that are missing from the operation - */ - protected List findMissingCookies(Operation operation) { - List missingCookies = new ArrayList<>(); + private void verifyCookieDescriptors(Operation operation) { Set actualCookies = extractActualCookies(operation); - for (CookieDescriptor cookieDescriptor : this.cookieDescriptors) { - if (!cookieDescriptor.isOptional() && !actualCookies.contains(cookieDescriptor.getName())) { - missingCookies.add(cookieDescriptor); + Set expectedCookies = new HashSet<>(); + for (Entry entry : this.descriptorsByName.entrySet()) { + if (!entry.getValue().isOptional()) { + expectedCookies.add(entry.getKey()); } } + Set undocumentedCookies; + if (this.ignoreUndocumentedCookies) { + undocumentedCookies = Collections.emptySet(); + } + else { + undocumentedCookies = new HashSet<>(actualCookies); + undocumentedCookies.removeAll(this.descriptorsByName.keySet()); + } + Set missingCookies = new HashSet<>(expectedCookies); + missingCookies.removeAll(actualCookies); - return missingCookies; + if (!undocumentedCookies.isEmpty() || !missingCookies.isEmpty()) { + verificationFailed(undocumentedCookies, missingCookies); + } } /** @@ -119,13 +115,30 @@ public abstract class AbstractCookiesSnippet extends TemplatedSnippet { */ protected abstract Set extractActualCookies(Operation operation); + /** + * Called when the documented cookies do not match the actual cookies. + * @param undocumentedCookies the cookies that were found in the operation but were + * not documented + * @param missingCookies the cookies that were documented but were not found in the + * operation + */ + protected abstract void verificationFailed(Set undocumentedCookies, Set missingCookies); + /** * Returns the list of {@link CookieDescriptor CookieDescriptors} that will be used to * generate the documentation. * @return the cookie descriptors */ - protected final List getCookieDescriptors() { - return this.cookieDescriptors; + protected final Map getCookieDescriptors() { + return this.descriptorsByName; + } + + /** + * Returns whether or not this snippet ignores undocumented cookies. + * @return {@code true} if undocumented cookies are ignored, otherwise {@code false} + */ + protected final boolean isIgnoreUndocumentedCookies() { + return this.ignoreUndocumentedCookies; } /** diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/CookieDescriptor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/CookieDescriptor.java index 51a4787a..97ee508c 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/CookieDescriptor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/CookieDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2022 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. @@ -21,9 +21,9 @@ import org.springframework.restdocs.snippet.IgnorableDescriptor; /** * A description of a cookie found in a request or response. * - * @author Andreas Evers * @author Clyde Stubbs - * @since 2.1 + * @author Andy Wilkinson + * @since 3.0 * @see CookieDocumentation#cookieWithName(String) */ public class CookieDescriptor extends IgnorableDescriptor { diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/CookieDocumentation.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/CookieDocumentation.java index 0a88d13a..20c20f43 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/CookieDocumentation.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/CookieDocumentation.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2022 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. @@ -25,11 +25,9 @@ import org.springframework.restdocs.snippet.Snippet; /** * Static factory methods for documenting a RESTful API's request and response cookies. * - * @author Andreas Evers - * @author Andy Wilkinson - * @author Marcel Overdijk * @author Clyde Stubbs - * @since 2.1 + * @author Andy Wilkinson + * @since 3.0 */ public abstract class CookieDocumentation { @@ -51,8 +49,10 @@ public abstract class CookieDocumentation { * Returns a new {@link Snippet} that will document the cookies of the API operation's * request. The cookies will be documented using the given {@code descriptors}. *

- * If a cookie is documented, is not marked as optional, and is not present in the - * request, a failure will occur. + * If a cookie is present in the request, but is not documented by one of the + * descriptors, a failure will occur when the snippet is invoked. Similarly, if a + * cookie is documented, is not marked as optional, and is not present in the request, + * a failure will also occur. * @param descriptors the descriptions of the request's cookies * @return the snippet that will document the request cookies * @see #cookieWithName(String) @@ -65,8 +65,10 @@ public abstract class CookieDocumentation { * Returns a new {@link Snippet} that will document the cookies of the API operation's * request. The cookies will be documented using the given {@code descriptors}. *

- * If a cookie is documented, is not marked as optional, and is not present in the - * request, a failure will occur. + * If a cookie is present in the request, but is not documented by one of the + * descriptors, a failure will occur when the snippet is invoked. Similarly, if a + * cookie is documented, is not marked as optional, and is not present in the request, + * a failure will also occur. * @param descriptors the descriptions of the request's cookies * @return the snippet that will document the request cookies * @see #cookieWithName(String) @@ -75,13 +77,43 @@ public abstract class CookieDocumentation { return new RequestCookiesSnippet(descriptors); } + /** + * Returns a new {@link Snippet} that will document the cookies of the API operation's + * request. The cookies will be documented using the given {@code descriptors}. + *

+ * If a cookie is documented, is not marked as optional, and is not present in the + * request, a failure will occur. Any undocumented cookies will be ignored. + * @param descriptors the descriptions of the request's cookies + * @return the snippet that will document the request cookies + * @see #cookieWithName(String) + */ + public static RequestCookiesSnippet relaxedRequestCookies(CookieDescriptor... descriptors) { + return relaxedRequestCookies(Arrays.asList(descriptors)); + } + + /** + * Returns a new {@link Snippet} that will document the cookies of the API operation's + * request. The cookies will be documented using the given {@code descriptors}. + *

+ * If a cookie is documented, is not marked as optional, and is not present in the + * request, a failure will occur. Any undocumented cookies will be ignored. + * @param descriptors the descriptions of the request's cookies + * @return the snippet that will document the request cookies + * @see #cookieWithName(String) + */ + public static RequestCookiesSnippet relaxedRequestCookies(List descriptors) { + return new RequestCookiesSnippet(descriptors, true); + } + /** * Returns a new {@link Snippet} that will document the cookies of the API * operations's request. The given {@code attributes} will be available during snippet * generation and the cookies will be documented using the given {@code descriptors}. *

- * If a cookie is documented, is not marked as optional, and is not present in the - * request, a failure will occur. + * If a cookie is present in the request, but is not documented by one of the + * descriptors, a failure will occur when the snippet is invoked. Similarly, if a + * cookie is documented, is not marked as optional, and is not present in the request, + * a failure will also occur. * @param attributes the attributes * @param descriptors the descriptions of the request's cookies * @return the snippet that will document the request cookies @@ -97,9 +129,10 @@ public abstract class CookieDocumentation { * operations's request. The given {@code attributes} will be available during snippet * generation and the cookies will be documented using the given {@code descriptors}. *

- * If a cookie is documented, is not marked as optional, and is not present in the - * request, a failure will occur. Any cookies present in the request that are not - * documented will result in an error. + * If a cookie is present in the request, but is not documented by one of the + * descriptors, a failure will occur when the snippet is invoked. Similarly, if a + * cookie is documented, is not marked as optional, and is not present in the request, + * a failure will also occur. * @param attributes the attributes * @param descriptors the descriptions of the request's cookies * @return the snippet that will document the request cookies @@ -107,7 +140,7 @@ public abstract class CookieDocumentation { */ public static RequestCookiesSnippet requestCookies(Map attributes, List descriptors) { - return new RequestCookiesSnippet(descriptors, attributes, false); + return new RequestCookiesSnippet(descriptors, attributes); } /** @@ -116,8 +149,24 @@ public abstract class CookieDocumentation { * generation and the cookies will be documented using the given {@code descriptors}. *

* If a cookie is documented, is not marked as optional, and is not present in the - * request, a failure will occur. An undocumented cookie in the request will not - * generate an error. + * request, a failure will occur. Any undocumented cookies will be ignored. + * @param attributes the attributes + * @param descriptors the descriptions of the request's cookies + * @return the snippet that will document the request cookies + * @see #cookieWithName(String) + */ + public static RequestCookiesSnippet relaxedRequestCookies(Map attributes, + CookieDescriptor... descriptors) { + return relaxedRequestCookies(attributes, Arrays.asList(descriptors)); + } + + /** + * Returns a new {@link Snippet} that will document the cookies of the API + * operations's request. The given {@code attributes} will be available during snippet + * generation and the cookies will be documented using the given {@code descriptors}. + *

+ * If a cookie is documented, is not marked as optional, and is not present in the + * request, a failure will occur. Any undocumented cookies will be ignored. * @param attributes the attributes * @param descriptors the descriptions of the request's cookies * @return the snippet that will document the request cookies @@ -132,8 +181,10 @@ public abstract class CookieDocumentation { * Returns a new {@link Snippet} that will document the cookies of the API operation's * response. The cookies will be documented using the given {@code descriptors}. *

- * If a cookie is documented, is not marked as optional or ignored, and is not present - * in the request, a failure will occur. + * If a cookie is present in the response, but is not documented by one of the + * descriptors, a failure will occur when the snippet is invoked. Similarly, if a + * cookie is documented, is not marked as optional, and is not present in the + * response, a failure will also occur. * @param descriptors the descriptions of the response's cookies * @return the snippet that will document the response cookies * @see #cookieWithName(String) @@ -146,9 +197,10 @@ public abstract class CookieDocumentation { * Returns a new {@link Snippet} that will document the cookies of the API operation's * response. The cookies will be documented using the given {@code descriptors}. *

- * If a cookie is documented, is not marked as optional or ignored, and is not present - * in the request, a failure will occur. If a cookie is present in the response but is - * undocumented a failure will occur. + * If a cookie is present in the response, but is not documented by one of the + * descriptors, a failure will occur when the snippet is invoked. Similarly, if a + * cookie is documented, is not marked as optional, and is not present in the + * response, a failure will also occur. * @param descriptors the descriptions of the response's cookies * @return the snippet that will document the response cookies * @see #cookieWithName(String) @@ -161,15 +213,28 @@ public abstract class CookieDocumentation { * Returns a new {@link Snippet} that will document the cookies of the API operation's * response. The cookies will be documented using the given {@code descriptors}. *

- * If a cookie is documented, is not marked as optional or ignored, and is not present - * in the request, a failure will occur. No failure will occur if a cookie is present - * but undocumented. + * If a cookie is documented, is not marked as optional, and is not present in the + * response, a failure will occur. Any undocumented cookies will be ignored. + * @param descriptors the descriptions of the response's cookies + * @return the snippet that will document the response cookies + * @see #cookieWithName(String) + */ + public static ResponseCookiesSnippet relaxedResponseCookies(CookieDescriptor... descriptors) { + return relaxedResponseCookies(Arrays.asList(descriptors)); + } + + /** + * Returns a new {@link Snippet} that will document the cookies of the API operation's + * response. The cookies will be documented using the given {@code descriptors}. + *

+ * If a cookie is documented, is not marked as optional, and is not present in the + * response, a failure will occur. Any undocumented cookies will be ignored. * @param descriptors the descriptions of the response's cookies * @return the snippet that will document the response cookies * @see #cookieWithName(String) */ public static ResponseCookiesSnippet relaxedResponseCookies(List descriptors) { - return new ResponseCookiesSnippet(descriptors, null, true); + return new ResponseCookiesSnippet(descriptors, true); } /** @@ -178,9 +243,10 @@ public abstract class CookieDocumentation { * snippet generation and the cookies will be documented using the given * {@code descriptors}. *

- * If a cookie is documented, is not marked as optional, and is not present in the - * response, a failure will occur. If a cookie is present in the response but is - * undocumented a failure will occur. + * If a cookie is present in the response, but is not documented by one of the + * descriptors, a failure will occur when the snippet is invoked. Similarly, if a + * cookie is documented, is not marked as optional, and is not present in the + * response, a failure will also occur. * @param attributes the attributes * @param descriptors the descriptions of the response's cookies * @return the snippet that will document the response cookies @@ -197,9 +263,10 @@ public abstract class CookieDocumentation { * snippet generation and the cookies will be documented using the given * {@code descriptors}. *

- * If a cookie is documented, is not marked as optional, and is not present in the - * response, a failure will occur. If a cookie is present in the response but is - * undocumented a failure will occur. + * If a cookie is present in the response, but is not documented by one of the + * descriptors, a failure will occur when the snippet is invoked. Similarly, if a + * cookie is documented, is not marked as optional, and is not present in the + * response, a failure will also occur. * @param attributes the attributes * @param descriptors the descriptions of the response's cookies * @return the snippet that will document the response cookies @@ -207,7 +274,7 @@ public abstract class CookieDocumentation { */ public static ResponseCookiesSnippet responseCookies(Map attributes, List descriptors) { - return new ResponseCookiesSnippet(descriptors, attributes, false); + return new ResponseCookiesSnippet(descriptors, attributes); } /** @@ -217,8 +284,25 @@ public abstract class CookieDocumentation { * {@code descriptors}. *

* If a cookie is documented, is not marked as optional, and is not present in the - * response, a failure will occur. No failure will occur if a cookie is present but - * undocumented. + * response, a failure will occur. Any undocumented cookies will be ignored. + * @param attributes the attributes + * @param descriptors the descriptions of the response's cookies + * @return the snippet that will document the response cookies + * @see #cookieWithName(String) + */ + public static ResponseCookiesSnippet relaxedResponseCookies(Map attributes, + CookieDescriptor... descriptors) { + return relaxedResponseCookies(attributes, Arrays.asList(descriptors)); + } + + /** + * Returns a new {@link Snippet} that will document the cookies of the API + * operations's response. The given {@code attributes} will be available during + * snippet generation and the cookies will be documented using the given + * {@code descriptors}. + *

+ * If a cookie is documented, is not marked as optional, and is not present in the + * response, a failure will occur. Any undocumented cookies will be ignored. * @param attributes the attributes * @param descriptors the descriptions of the response's cookies * @return the snippet that will document the response cookies diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/RequestCookiesSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/RequestCookiesSnippet.java index 1a8d7ccc..e5f4d5d2 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/RequestCookiesSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/RequestCookiesSnippet.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2022 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. @@ -26,14 +26,14 @@ import java.util.Set; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.operation.RequestCookie; import org.springframework.restdocs.snippet.Snippet; +import org.springframework.restdocs.snippet.SnippetException; /** * A {@link Snippet} that documents the cookies in a request. * - * @author Andreas Evers - * @author Andy Wilkinson * @author Clyde Stubbs - * @since 2.1 + * @author Andy Wilkinson + * @since 3.0 * @see CookieDocumentation#requestCookies(CookieDescriptor...) * @see CookieDocumentation#requestCookies(Map, CookieDescriptor...) */ @@ -50,15 +50,14 @@ public class RequestCookiesSnippet extends AbstractCookiesSnippet { /** * Creates a new {@code RequestCookiesSnippet} that will document the cookies in the - * request using the given {@code descriptors}. The given {@code attributes} will be - * included in the model during template rendering. + * request using the given {@code descriptors}. If {@code ignoreUndocumentedCookies} + * is {@code true}, undocumented cookies will be ignored and will not trigger a + * failure. * @param descriptors the descriptors - * @param attributes the additional attributes - * @param ignoreUndocumentedCookies if set undocumented cookies will be ignored + * @param ignoreUndocumentedCookies whether undocumented cookies should be ignored */ - protected RequestCookiesSnippet(List descriptors, Map attributes, - boolean ignoreUndocumentedCookies) { - super("request", descriptors, attributes, ignoreUndocumentedCookies); + protected RequestCookiesSnippet(List descriptors, boolean ignoreUndocumentedCookies) { + this(descriptors, null, ignoreUndocumentedCookies); } /** @@ -70,7 +69,20 @@ public class RequestCookiesSnippet extends AbstractCookiesSnippet { * @param attributes the additional attributes */ protected RequestCookiesSnippet(List descriptors, Map attributes) { - super("request", descriptors, attributes, false); + this(descriptors, attributes, false); + } + + /** + * Creates a new {@code RequestCookiesSnippet} that will document the cookies in the + * request using the given {@code descriptors}. The given {@code attributes} will be + * included in the model during template rendering. + * @param descriptors the descriptors + * @param attributes the additional attributes + * @param ignoreUndocumentedCookies whether undocumented cookies should be ignored + */ + protected RequestCookiesSnippet(List descriptors, Map attributes, + boolean ignoreUndocumentedCookies) { + super("request", descriptors, attributes, ignoreUndocumentedCookies); } @Override @@ -82,6 +94,21 @@ public class RequestCookiesSnippet extends AbstractCookiesSnippet { return actualCookies; } + @Override + protected void verificationFailed(Set undocumentedCookies, Set missingCookies) { + String message = ""; + if (!undocumentedCookies.isEmpty()) { + message += "Cookies with the following names were not documented: " + undocumentedCookies; + } + if (!missingCookies.isEmpty()) { + if (message.length() > 0) { + message += ". "; + } + message += "Cookies with the following names were not found in the request: " + missingCookies; + } + throw new SnippetException(message); + } + /** * Returns a new {@code RequestCookiesSnippet} configured with this snippet's * attributes and its descriptors combined with the given @@ -101,9 +128,9 @@ public class RequestCookiesSnippet extends AbstractCookiesSnippet { * @return the new snippet */ public final RequestCookiesSnippet and(List additionalDescriptors) { - List combinedDescriptors = new ArrayList<>(this.getCookieDescriptors()); + List combinedDescriptors = new ArrayList<>(this.getCookieDescriptors().values()); combinedDescriptors.addAll(additionalDescriptors); - return new RequestCookiesSnippet(combinedDescriptors, getAttributes(), false); + return new RequestCookiesSnippet(combinedDescriptors, getAttributes(), isIgnoreUndocumentedCookies()); } } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/ResponseCookiesSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/ResponseCookiesSnippet.java index 99076115..f6a5176f 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/ResponseCookiesSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/ResponseCookiesSnippet.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2022 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. @@ -26,14 +26,14 @@ import java.util.stream.Collectors; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.operation.ResponseCookie; import org.springframework.restdocs.snippet.Snippet; +import org.springframework.restdocs.snippet.SnippetException; /** * A {@link Snippet} that documents the cookies in a response. * - * @author Andreas Evers - * @author Andy Wilkinson * @author Clyde Stubbs - * @since 2.1 + * @author Andy Wilkinson + * @since 3.0 * @see CookieDocumentation#responseCookies(CookieDescriptor...) * @see CookieDocumentation#responseCookies(Map, CookieDescriptor...) */ @@ -50,14 +50,26 @@ public class ResponseCookiesSnippet extends AbstractCookiesSnippet { /** * Creates a new {@code ResponseCookiesSnippet} that will document the cookies in the - * response using the given {@code descriptors}. The given {@code attributes} will be - * included in the model during template rendering. Undocumented cookies will cause a + * response using the given {@code descriptors}. If {@code ignoreUndocumentedCookies} + * is {@code true}, undocumented cookies will be ignored and will not trigger a * failure. * @param descriptors the descriptors + * @param ignoreUndocumentedCookies whether undocumented cookies should be ignored + */ + protected ResponseCookiesSnippet(List descriptors, boolean ignoreUndocumentedCookies) { + this(descriptors, null, ignoreUndocumentedCookies); + } + + /** + * Creates a new {@code ResponseCookiesSnippet} that will document the cookies in the + * response using the given {@code descriptors}. The given {@code attributes} will be + * included in the model during template rendering. Undocumented cookies will not be + * ignored. + * @param descriptors the descriptors * @param attributes the additional attributes */ protected ResponseCookiesSnippet(List descriptors, Map attributes) { - super("response", descriptors, attributes, false); + this(descriptors, attributes, false); } /** @@ -66,7 +78,7 @@ public class ResponseCookiesSnippet extends AbstractCookiesSnippet { * included in the model during template rendering. * @param descriptors the descriptors * @param attributes the additional attributes - * @param ignoreUndocumentedCookies ignore any cookies that are undocumented + * @param ignoreUndocumentedCookies whether undocumented cookies should be ignored */ protected ResponseCookiesSnippet(List descriptors, Map attributes, boolean ignoreUndocumentedCookies) { @@ -78,6 +90,21 @@ public class ResponseCookiesSnippet extends AbstractCookiesSnippet { return operation.getResponse().getCookies().stream().map(ResponseCookie::getName).collect(Collectors.toSet()); } + @Override + protected void verificationFailed(Set undocumentedCookies, Set missingCookies) { + String message = ""; + if (!undocumentedCookies.isEmpty()) { + message += "Cookies with the following names were not documented: " + undocumentedCookies; + } + if (!missingCookies.isEmpty()) { + if (message.length() > 0) { + message += ". "; + } + message += "Cookies with the following names were not found in the response: " + missingCookies; + } + throw new SnippetException(message); + } + /** * Returns a new {@code ResponseCookiesSnippet} configured with this snippet's * attributes and its descriptors combined with the given @@ -97,9 +124,9 @@ public class ResponseCookiesSnippet extends AbstractCookiesSnippet { * @return the new snippet */ public final ResponseCookiesSnippet and(List additionalDescriptors) { - List combinedDescriptors = new ArrayList<>(this.getCookieDescriptors()); + List combinedDescriptors = new ArrayList<>(this.getCookieDescriptors().values()); combinedDescriptors.addAll(additionalDescriptors); - return new ResponseCookiesSnippet(combinedDescriptors, getAttributes(), this.ignoreUndocumentedCookies); + return new ResponseCookiesSnippet(combinedDescriptors, getAttributes(), isIgnoreUndocumentedCookies()); } } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/package-info.java index d59b9452..afc01d60 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/package-info.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2022 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. diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationResponse.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationResponse.java index 4778f84f..8490ade2 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationResponse.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-2022 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. @@ -71,7 +71,7 @@ public interface OperationResponse { * Returns the {@link ResponseCookie cookies} returned with the response. If no * cookies were returned an empty collection is returned. * @return the cookies, never {@code null} - * @since 2.1 + * @since 3.0 */ Collection getCookies(); diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/ResponseCookie.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/ResponseCookie.java index 69ed9271..9cf39302 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/ResponseCookie.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/ResponseCookie.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 the original author or authors. + * Copyright 2014-2022 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. @@ -20,7 +20,7 @@ package org.springframework.restdocs.operation; * A representation of a Cookie returned in a response. * * @author Clyde Stubbs - * @since 2.1 + * @since 3.0 */ public final class ResponseCookie { diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationResponse.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationResponse.java index 73676e6a..5d9045af 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationResponse.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-2022 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. diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/RequestCookiesSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/RequestCookiesSnippetFailureTests.java index c10ab68d..6498b2d5 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/RequestCookiesSnippetFailureTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/RequestCookiesSnippetFailureTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 the original author or authors. + * Copyright 2014-2022 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,7 +16,6 @@ package org.springframework.restdocs.cookies; -import java.io.IOException; import java.util.Collections; import org.junit.Rule; @@ -32,8 +31,8 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; * Tests for failures when rendering {@link RequestCookiesSnippet} due to missing or * undocumented cookies. * - * @author Andy Wilkinson * @author Clyde Stubbs + * @author Andy Wilkinson */ public class RequestCookiesSnippetFailureTests { @@ -41,20 +40,20 @@ public class RequestCookiesSnippetFailureTests { public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor()); @Test - public void missingRequestCookie() throws IOException { + public void missingRequestCookie() { assertThatExceptionOfType(SnippetException.class) .isThrownBy(() -> new RequestCookiesSnippet( - Collections.singletonList(CookieDocumentation.cookieWithName("Accept").description("one"))) + Collections.singletonList(CookieDocumentation.cookieWithName("JSESSIONID").description("one"))) .document(this.operationBuilder.request("http://localhost").build())) - .withMessage("Cookies with the following names were not found" + " in the request: [Accept]"); + .withMessage("Cookies with the following names were not found in the request: [JSESSIONID]"); } @Test - public void undocumentedRequestCookieAndMissingRequestHeader() throws IOException { - assertThatExceptionOfType(SnippetException.class).isThrownBy(() -> new RequestCookiesSnippet( - Collections.singletonList(CookieDocumentation.cookieWithName("Accept").description("one"))) - .document(this.operationBuilder.request("http://localhost").cookie("X-Test", "test").build())) - .withMessageEndingWith("Cookies with the following names were not found" + " in the request: [Accept]"); + public void undocumentedRequestCookie() { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new RequestCookiesSnippet(Collections.emptyList()).document(this.operationBuilder + .request("http://localhost").cookie("JSESSIONID", "1234abcd5678efgh").build())) + .withMessageEndingWith("Cookies with the following names were not documented: [JSESSIONID]"); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/RequestCookiesSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/RequestCookiesSnippetTests.java index bb2685f3..1fa41c0b 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/RequestCookiesSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/RequestCookiesSnippetTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 the original author or authors. + * Copyright 2014-2022 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. @@ -32,15 +32,15 @@ import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; /** * Tests for {@link RequestCookiesSnippet}. * - * @author Andreas Evers - * @author Andy Wilkinson * @author Clyde Stubbs + * @author Andy Wilkinson */ public class RequestCookiesSnippetTests extends AbstractSnippetTests { @@ -50,27 +50,42 @@ public class RequestCookiesSnippetTests extends AbstractSnippetTests { @Test public void requestWithCookies() throws IOException { - new RequestCookiesSnippet(Arrays.asList(CookieDocumentation.cookieWithName("Session").description("one"), - CookieDocumentation.cookieWithName("User").description("two"), - CookieDocumentation.cookieWithName("Timeout").description("three"), - CookieDocumentation.cookieWithName("Preference").description("four"), - CookieDocumentation.cookieWithName("Connection").description("five"))) - .document(this.operationBuilder.request("http://localhost").cookie("Session", "test") - .cookie("User", "nobody").cookie("Timeout", "3600").cookie("Preference", "inverse") - .cookie("Connection", "secure").build()); + new RequestCookiesSnippet( + Arrays.asList(cookieWithName("tz").description("one"), cookieWithName("logged_in").description("two"))) + .document(this.operationBuilder.request("http://localhost").cookie("tz", "Europe%2FLondon") + .cookie("logged_in", "true").build()); assertThat(this.generatedSnippets.requestCookies()) - .is(tableWithHeader("Name", "Description").row("`Session`", "one").row("`User`", "two") - .row("`Timeout`", "three").row("`Preference`", "four").row("`Connection`", "five")); + .is(tableWithHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two")); } @Test - public void undocumentedRequestCookie() throws IOException { + public void ignoredRequestCookie() throws IOException { new RequestCookiesSnippet( - Collections.singletonList(CookieDocumentation.cookieWithName("X-Test").description("one"))) - .document(this.operationBuilder.request("http://localhost").cookie("X-Test", "test") - .cookie("Second", "*/*").build()); + Arrays.asList(cookieWithName("tz").ignored(), cookieWithName("logged_in").description("two"))) + .document(this.operationBuilder.request("http://localhost").cookie("tz", "Europe%2FLondon") + .cookie("logged_in", "true").build()); assertThat(this.generatedSnippets.requestCookies()) - .is(tableWithHeader("Name", "Description").row("`X-Test`", "one")); + .is(tableWithHeader("Name", "Description").row("`logged_in`", "two")); + } + + @Test + public void allUndocumentedCookiesCanBeIgnored() throws IOException { + new RequestCookiesSnippet( + Arrays.asList(cookieWithName("tz").description("one"), cookieWithName("logged_in").description("two")), + true).document( + this.operationBuilder.request("http://localhost").cookie("tz", "Europe%2FLondon") + .cookie("logged_in", "true").cookie("user_session", "abcd1234efgh5678").build()); + assertThat(this.generatedSnippets.requestCookies()) + .is(tableWithHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two")); + } + + @Test + public void missingOptionalCookie() throws IOException { + new RequestCookiesSnippet(Arrays.asList(cookieWithName("tz").description("one").optional(), + cookieWithName("logged_in").description("two"))).document( + this.operationBuilder.request("http://localhost").cookie("logged_in", "true").build()); + assertThat(this.generatedSnippets.requestCookies()) + .is(tableWithHeader("Name", "Description").row("`tz`", "one").row("`logged_in`", "two")); } @Test @@ -78,12 +93,11 @@ public class RequestCookiesSnippetTests extends AbstractSnippetTests { TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); given(resolver.resolveTemplateResource("request-cookies")) .willReturn(snippetResource("request-cookies-with-title")); - new RequestCookiesSnippet( - Collections.singletonList(CookieDocumentation.cookieWithName("X-Test").description("one")), + new RequestCookiesSnippet(Collections.singletonList(cookieWithName("tz").description("one")), attributes(key("title").value("Custom title"))) .document(this.operationBuilder .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost").cookie("X-Test", "test").build()); + .request("http://localhost").cookie("tz", "Europe%2FLondon").build()); assertThat(this.generatedSnippets.requestCookies()).contains("Custom title"); } @@ -92,44 +106,45 @@ public class RequestCookiesSnippetTests extends AbstractSnippetTests { TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); given(resolver.resolveTemplateResource("request-cookies")) .willReturn(snippetResource("request-cookies-with-extra-column")); - new RequestCookiesSnippet(Arrays.asList( - CookieDocumentation.cookieWithName("X-Test").description("one").attributes(key("foo").value("alpha")), - CookieDocumentation.cookieWithName("Accept-Encoding").description("two") - .attributes(key("foo").value("bravo")), - CookieDocumentation.cookieWithName("Accept").description("three") - .attributes(key("foo").value("charlie")))) + new RequestCookiesSnippet( + Arrays.asList(cookieWithName("tz").description("one").attributes(key("foo").value("alpha")), + cookieWithName("logged_in").description("two").attributes(key("foo").value("bravo")))) .document(this.operationBuilder .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost").cookie("X-Test", "test") - .cookie("Accept-Encoding", "gzip, deflate").cookie("Accept", "*/*").build()); + .request("http://localhost").cookie("tz", "Europe%2FLondon") + .cookie("logged_in", "true").build()); assertThat(this.generatedSnippets.requestCookies()).is(// - tableWithHeader("Name", "Description", "Foo").row("X-Test", "one", "alpha") - .row("Accept-Encoding", "two", "bravo").row("Accept", "three", "charlie")); + tableWithHeader("Name", "Description", "Foo").row("tz", "one", "alpha").row("logged_in", "two", + "bravo")); } @Test public void additionalDescriptors() throws IOException { - CookieDocumentation - .requestCookies(CookieDocumentation.cookieWithName("X-Test").description("one"), - CookieDocumentation.cookieWithName("Accept").description("two"), - CookieDocumentation.cookieWithName("Accept-Encoding").description("three"), - CookieDocumentation.cookieWithName("Accept-Language").description("four")) - .and(CookieDocumentation.cookieWithName("Cache-Control").description("five"), - CookieDocumentation.cookieWithName("Connection").description("six")) - .document(this.operationBuilder.request("http://localhost").cookie("X-Test", "test") - .cookie("Accept", "*/*").cookie("Accept-Encoding", "gzip, deflate") - .cookie("Accept-Language", "en-US,en;q=0.5").cookie("Cache-Control", "max-age=0") - .cookie("Connection", "keep-alive").build()); - assertThat(this.generatedSnippets.requestCookies()).is(tableWithHeader("Name", "Description") - .row("`X-Test`", "one").row("`Accept`", "two").row("`Accept-Encoding`", "three") - .row("`Accept-Language`", "four").row("`Cache-Control`", "five").row("`Connection`", "six")); + new RequestCookiesSnippet( + Arrays.asList(cookieWithName("tz").description("one"), cookieWithName("logged_in").description("two"))) + .and(cookieWithName("user_session").description("three")) + .document(this.operationBuilder.request("http://localhost").cookie("tz", "Europe%2FLondon") + .cookie("logged_in", "true").cookie("user_session", "abcd1234efgh5678").build()); + assertThat(this.generatedSnippets.requestCookies()).is(tableWithHeader("Name", "Description").row("`tz`", "one") + .row("`logged_in`", "two").row("`user_session`", "three")); + } + + @Test + public void additionalDescriptorsWithRelaxedRequestCookies() throws IOException { + new RequestCookiesSnippet( + Arrays.asList(cookieWithName("tz").description("one"), cookieWithName("logged_in").description("two")), + true).and(cookieWithName("user_session").description("three")) + .document(this.operationBuilder.request("http://localhost").cookie("tz", "Europe%2FLondon") + .cookie("logged_in", "true").cookie("user_session", "abcd1234efgh5678") + .cookie("color_theme", "light").build()); + assertThat(this.generatedSnippets.requestCookies()).is(tableWithHeader("Name", "Description").row("`tz`", "one") + .row("`logged_in`", "two").row("`user_session`", "three")); } @Test public void tableCellContentIsEscapedWhenNecessary() throws IOException { - new RequestCookiesSnippet( - Collections.singletonList(CookieDocumentation.cookieWithName("Foo|Bar").description("one|two"))) - .document(this.operationBuilder.request("http://localhost").cookie("Foo|Bar", "baz").build()); + new RequestCookiesSnippet(Collections.singletonList(cookieWithName("Foo|Bar").description("one|two"))) + .document(this.operationBuilder.request("http://localhost").cookie("Foo|Bar", "baz").build()); assertThat(this.generatedSnippets.requestCookies()).is(tableWithHeader("Name", "Description") .row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/ResponseCookiesSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/ResponseCookiesSnippetFailureTests.java index 100edc90..93dfe5be 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/ResponseCookiesSnippetFailureTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/ResponseCookiesSnippetFailureTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 the original author or authors. + * Copyright 2014-2022 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,7 +16,6 @@ package org.springframework.restdocs.cookies; -import java.io.IOException; import java.util.Collections; import org.junit.Rule; @@ -27,13 +26,14 @@ import org.springframework.restdocs.templates.TemplateFormats; import org.springframework.restdocs.testfixtures.OperationBuilder; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName; /** * Tests for failures when rendering {@link ResponseCookiesSnippet} due to missing or * undocumented cookies. * - * @author Andy Wilkinson * @author Clyde Stubbs + * @author Andy Wilkinson */ public class ResponseCookiesSnippetFailureTests { @@ -41,22 +41,20 @@ public class ResponseCookiesSnippetFailureTests { public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor()); @Test - public void missingResponseCookie() throws IOException { + public void missingResponseCookie() { assertThatExceptionOfType(SnippetException.class) - .isThrownBy(() -> new ResponseCookiesSnippet(Collections - .singletonList(CookieDocumentation.cookieWithName("Content-Type").description("one"))) + .isThrownBy(() -> new ResponseCookiesSnippet( + Collections.singletonList(cookieWithName("JSESSIONID").description("one"))) .document(this.operationBuilder.response().build())) - .withMessage("Cookies with the following names were not found" + " in the response: [Content-Type]"); + .withMessage("Cookies with the following names were not found in the response: [JSESSIONID]"); } @Test - public void undocumentedResponseCookieAndMissingResponseHeader() throws IOException { + public void undocumentedResponseCookie() { assertThatExceptionOfType(SnippetException.class) - .isThrownBy(() -> new ResponseCookiesSnippet(Collections - .singletonList(CookieDocumentation.cookieWithName("Content-Type").description("one"))) - .document(this.operationBuilder.response().cookie("X-Test", "test").build())) - .withMessageEndingWith( - "Cookies with the following names were not found" + " in the response: [Content-Type]"); + .isThrownBy(() -> new ResponseCookiesSnippet(Collections.emptyList()) + .document(this.operationBuilder.response().cookie("JSESSIONID", "1234abcd5678efgh").build())) + .withMessageEndingWith("Cookies with the following names were not documented: [JSESSIONID]"); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/ResponseCookiesSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/ResponseCookiesSnippetTests.java index ec05d3d9..39a08328 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/ResponseCookiesSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cookies/ResponseCookiesSnippetTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 the original author or authors. + * Copyright 2014-2022 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. @@ -32,15 +32,15 @@ import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName; import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; /** * Tests for {@link ResponseCookiesSnippet}. * - * @author Andreas Evers - * @author Andy Wilkinson * @author Clyde Stubbs + * @author Andy Wilkinson */ public class ResponseCookiesSnippetTests extends AbstractSnippetTests { @@ -50,27 +50,41 @@ public class ResponseCookiesSnippetTests extends AbstractSnippetTests { @Test public void responseWithCookies() throws IOException { - new ResponseCookiesSnippet(Arrays.asList(CookieDocumentation.cookieWithName("X-Test").description("one"), - CookieDocumentation.cookieWithName("Content-Type").description("two"), - CookieDocumentation.cookieWithName("Etag").description("three"), - CookieDocumentation.cookieWithName("Cache-Control").description("five"), - CookieDocumentation.cookieWithName("Vary").description("six"))) - .document(this.operationBuilder.response().cookie("X-Test", "test") - .cookie("Content-Type", "application/json").cookie("Etag", "lskjadldj3ii32l2ij23") - .cookie("Cache-Control", "max-age=0").cookie("Vary", "User-Agent").build()); - assertThat(this.generatedSnippets.responseCookies()) - .is(tableWithHeader("Name", "Description").row("`X-Test`", "one").row("`Content-Type`", "two") - .row("`Etag`", "three").row("`Cache-Control`", "five").row("`Vary`", "six")); + new ResponseCookiesSnippet(Arrays.asList(cookieWithName("has_recent_activity").description("one"), + cookieWithName("user_session").description("two"))) + .document(this.operationBuilder.response().cookie("has_recent_activity", "true") + .cookie("user_session", "1234abcd5678efgh").build()); + assertThat(this.generatedSnippets.responseCookies()).is(tableWithHeader("Name", "Description") + .row("`has_recent_activity`", "one").row("`user_session`", "two")); } @Test - public void undocumentedResponseCookie() throws IOException { - new ResponseCookiesSnippet( - Collections.singletonList(CookieDocumentation.cookieWithName("X-Test").description("one"))) - .document(this.operationBuilder.response().cookie("X-Test", "test") - .cookie("Content-Type", "*/*").build()); + public void ignoredResponseCookie() throws IOException { + new ResponseCookiesSnippet(Arrays.asList(cookieWithName("has_recent_activity").ignored(), + cookieWithName("user_session").description("two"))) + .document(this.operationBuilder.response().cookie("has_recent_activity", "true") + .cookie("user_session", "1234abcd5678efgh").build()); assertThat(this.generatedSnippets.responseCookies()) - .is(tableWithHeader("Name", "Description").row("`X-Test`", "one")); + .is(tableWithHeader("Name", "Description").row("`user_session`", "two")); + } + + @Test + public void allUndocumentedResponseCookiesCanBeIgnored() throws IOException { + new ResponseCookiesSnippet(Arrays.asList(cookieWithName("has_recent_activity").description("one"), + cookieWithName("user_session").description("two")), true) + .document(this.operationBuilder.response().cookie("has_recent_activity", "true") + .cookie("user_session", "1234abcd5678efgh").cookie("some_cookie", "value").build()); + assertThat(this.generatedSnippets.responseCookies()).is(tableWithHeader("Name", "Description") + .row("`has_recent_activity`", "one").row("`user_session`", "two")); + } + + @Test + public void missingOptionalResponseCookie() throws IOException { + new ResponseCookiesSnippet(Arrays.asList(cookieWithName("has_recent_activity").description("one").optional(), + cookieWithName("user_session").description("two"))) + .document(this.operationBuilder.response().cookie("user_session", "1234abcd5678efgh").build()); + assertThat(this.generatedSnippets.responseCookies()).is(tableWithHeader("Name", "Description") + .row("`has_recent_activity`", "one").row("`user_session`", "two")); } @Test @@ -78,12 +92,11 @@ public class ResponseCookiesSnippetTests extends AbstractSnippetTests { TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); given(resolver.resolveTemplateResource("response-cookies")) .willReturn(snippetResource("response-cookies-with-title")); - new ResponseCookiesSnippet( - Collections.singletonList(CookieDocumentation.cookieWithName("X-Test").description("one")), + new ResponseCookiesSnippet(Collections.singletonList(cookieWithName("has_recent_activity").description("one")), attributes(key("title").value("Custom title"))) .document(this.operationBuilder .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .response().cookie("X-Test", "test").build()); + .response().cookie("has_recent_activity", "true").build()); assertThat(this.generatedSnippets.responseCookies()).contains("Custom title"); } @@ -93,40 +106,45 @@ public class ResponseCookiesSnippetTests extends AbstractSnippetTests { given(resolver.resolveTemplateResource("response-cookies")) .willReturn(snippetResource("response-cookies-with-extra-column")); new ResponseCookiesSnippet(Arrays.asList( - CookieDocumentation.cookieWithName("X-Test").description("one").attributes(key("foo").value("alpha")), - CookieDocumentation.cookieWithName("Content-Type").description("two") - .attributes(key("foo").value("bravo")), - CookieDocumentation.cookieWithName("Etag").description("three") - .attributes(key("foo").value("charlie")))) - .document(this.operationBuilder - .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .response().cookie("X-Test", "test").cookie("Content-Type", "application/json") - .cookie("Etag", "lskjadldj3ii32l2ij23").build()); - assertThat(this.generatedSnippets.responseCookies()).is(tableWithHeader("Name", "Description", "Foo") - .row("X-Test", "one", "alpha").row("Content-Type", "two", "bravo").row("Etag", "three", "charlie")); + cookieWithName("has_recent_activity").description("one").attributes(key("foo").value("alpha")), + cookieWithName("user_session").description("two").attributes(key("foo").value("bravo")), + cookieWithName("color_theme").description("three").attributes(key("foo").value("charlie")))) + .document(this.operationBuilder + .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) + .response().cookie("has_recent_activity", "true") + .cookie("user_session", "1234abcd5678efgh").cookie("color_theme", "high_contrast") + .build()); + assertThat(this.generatedSnippets.responseCookies()) + .is(tableWithHeader("Name", "Description", "Foo").row("has_recent_activity", "one", "alpha") + .row("user_session", "two", "bravo").row("color_theme", "three", "charlie")); } @Test public void additionalDescriptors() throws IOException { CookieDocumentation - .responseCookies(CookieDocumentation.cookieWithName("X-Test").description("one"), - CookieDocumentation.cookieWithName("Content-Type").description("two"), - CookieDocumentation.cookieWithName("Etag").description("three")) - .and(CookieDocumentation.cookieWithName("Cache-Control").description("five"), - CookieDocumentation.cookieWithName("Vary").description("six")) - .document(this.operationBuilder.response().cookie("X-Test", "test") - .cookie("Content-Type", "application/json").cookie("Etag", "lskjadldj3ii32l2ij23") - .cookie("Cache-Control", "max-age=0").cookie("Vary", "User-Agent").build()); - assertThat(this.generatedSnippets.responseCookies()) - .is(tableWithHeader("Name", "Description").row("`X-Test`", "one").row("`Content-Type`", "two") - .row("`Etag`", "three").row("`Cache-Control`", "five").row("`Vary`", "six")); + .responseCookies(cookieWithName("has_recent_activity").description("one"), + cookieWithName("user_session").description("two")) + .and(cookieWithName("color_theme").description("three")) + .document(this.operationBuilder.response().cookie("has_recent_activity", "true") + .cookie("user_session", "1234abcd5678efgh").cookie("color_theme", "light").build()); + assertThat(this.generatedSnippets.responseCookies()).is(tableWithHeader("Name", "Description") + .row("`has_recent_activity`", "one").row("`user_session`", "two").row("`color_theme`", "three")); + } + + @Test + public void additionalDescriptorsWithRelaxedResponseCookies() throws IOException { + CookieDocumentation.relaxedResponseCookies(cookieWithName("has_recent_activity").description("one")) + .and(cookieWithName("color_theme").description("two")) + .document(this.operationBuilder.response().cookie("has_recent_activity", "true") + .cookie("user_session", "1234abcd5678efgh").cookie("color_theme", "light").build()); + assertThat(this.generatedSnippets.responseCookies()).is( + tableWithHeader("Name", "Description").row("`has_recent_activity`", "one").row("`color_theme`", "two")); } @Test public void tableCellContentIsEscapedWhenNecessary() throws IOException { - new ResponseCookiesSnippet( - Collections.singletonList(CookieDocumentation.cookieWithName("Foo|Bar").description("one|two"))) - .document(this.operationBuilder.response().cookie("Foo|Bar", "baz").build()); + new ResponseCookiesSnippet(Collections.singletonList(cookieWithName("Foo|Bar").description("one|two"))) + .document(this.operationBuilder.response().cookie("Foo|Bar", "baz").build()); assertThat(this.generatedSnippets.responseCookies()).is(tableWithHeader("Name", "Description") .row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); } diff --git a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/GeneratedSnippets.java b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/GeneratedSnippets.java index 0071ce91..e619e02b 100644 --- a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/GeneratedSnippets.java +++ b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/GeneratedSnippets.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-2022 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. diff --git a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OperationBuilder.java b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OperationBuilder.java index de29ad39..a9299048 100644 --- a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OperationBuilder.java +++ b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OperationBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-2022 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. diff --git a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRequestConverter.java b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRequestConverter.java index 2ab8fcd8..053df890 100644 --- a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRequestConverter.java +++ b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRequestConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-2022 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. @@ -51,6 +51,7 @@ import org.springframework.web.multipart.MultipartFile; * {@link MockHttpServletRequest}. * * @author Andy Wilkinson + * @author Clyde Stubbs */ class MockMvcRequestConverter implements RequestConverter { diff --git a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcResponseConverter.java b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcResponseConverter.java index 2ffd33de..56d44f7c 100644 --- a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcResponseConverter.java +++ b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcResponseConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-2022 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. @@ -43,20 +43,19 @@ class MockMvcResponseConverter implements ResponseConverter cookies = extractCookies(mockResponse, headers); + Collection cookies = extractCookies(mockResponse); return new OperationResponseFactory().create(mockResponse.getStatus(), headers, mockResponse.getContentAsByteArray(), cookies); } - private Collection extractCookies(MockHttpServletResponse mockRequest, HttpHeaders headers) { - if (mockRequest.getCookies() == null || mockRequest.getCookies().length == 0) { + private Collection extractCookies(MockHttpServletResponse mockResponse) { + if (mockResponse.getCookies() == null || mockResponse.getCookies().length == 0) { return Collections.emptyList(); } List cookies = new ArrayList<>(); - for (Cookie servletCookie : mockRequest.getCookies()) { - cookies.add(new ResponseCookie(servletCookie.getName(), servletCookie.getValue())); + for (Cookie cookie : mockResponse.getCookies()) { + cookies.add(new ResponseCookie(cookie.getName(), cookie.getValue())); } - headers.remove(HttpHeaders.COOKIE); return cookies; } diff --git a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcResponseConverterTests.java b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcResponseConverterTests.java index c1a28b62..40338091 100644 --- a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcResponseConverterTests.java +++ b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcResponseConverterTests.java @@ -26,6 +26,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.restdocs.operation.OperationResponse; +import org.springframework.restdocs.operation.ResponseCookie; import static org.assertj.core.api.Assertions.assertThat; @@ -58,6 +59,9 @@ public class MockMvcResponseConverterTests { assertThat(operationResponse.getHeaders()).hasSize(1); assertThat(operationResponse.getHeaders()).containsEntry(HttpHeaders.SET_COOKIE, Collections.singletonList("name=value; Domain=localhost; HttpOnly")); + assertThat(operationResponse.getCookies()).hasSize(1); + assertThat(operationResponse.getCookies()).first().extracting(ResponseCookie::getName).isEqualTo("name"); + assertThat(operationResponse.getCookies()).first().extracting(ResponseCookie::getValue).isEqualTo("value"); } @Test diff --git a/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredRequestConverter.java b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredRequestConverter.java index 9d50090a..f4e00b2e 100644 --- a/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredRequestConverter.java +++ b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredRequestConverter.java @@ -48,6 +48,7 @@ import org.springframework.util.StreamUtils; * {@link FilterableRequestSpecification}. * * @author Andy Wilkinson + * @author Clyde Stubbs */ class RestAssuredRequestConverter implements RequestConverter { diff --git a/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredResponseConverter.java b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredResponseConverter.java index a1c1399b..09b95978 100644 --- a/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredResponseConverter.java +++ b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredResponseConverter.java @@ -53,10 +53,9 @@ class RestAssuredResponseConverter implements ResponseConverter { return Collections.emptyList(); } List cookies = new ArrayList<>(); - for (Map.Entry servletCookie : response.getCookies().entrySet()) { - cookies.add(new ResponseCookie(servletCookie.getKey(), servletCookie.getValue())); + for (Map.Entry cookie : response.getCookies().entrySet()) { + cookies.add(new ResponseCookie(cookie.getKey(), cookie.getValue())); } - headers.remove(HttpHeaders.COOKIE); return cookies; } diff --git a/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientResponseConverter.java b/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientResponseConverter.java index af5aeefc..fc7d4147 100644 --- a/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientResponseConverter.java +++ b/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientResponseConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-2022 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. @@ -33,12 +33,13 @@ import org.springframework.util.StringUtils; * {@link ExchangeResult}. * * @author Andy Wilkinson + * @author Clyde Stubbs */ class WebTestClientResponseConverter implements ResponseConverter { @Override public OperationResponse convert(ExchangeResult result) { - Collection cookies = extractCookies(result, result.getResponseHeaders()); + Collection cookies = extractCookies(result); return new OperationResponseFactory().create(result.getStatus().value(), extractHeaders(result), result.getResponseBodyContent(), cookies); } @@ -74,14 +75,9 @@ class WebTestClientResponseConverter implements ResponseConverter extractCookies(ExchangeResult result, HttpHeaders headers) { - List cookieHeaders = headers.get(HttpHeaders.COOKIE); - if (cookieHeaders == null) { - return result.getResponseCookies().values().stream().flatMap(List::stream).map(this::createResponseCookie) - .collect(Collectors.toSet()); - } - headers.remove(HttpHeaders.COOKIE); - return cookieHeaders.stream().map(this::createResponseCookie).collect(Collectors.toList()); + private Collection extractCookies(ExchangeResult result) { + return result.getResponseCookies().values().stream().flatMap(List::stream).map(this::createResponseCookie) + .collect(Collectors.toSet()); } private void appendIfAvailable(StringBuilder header, String value) { @@ -101,9 +97,4 @@ class WebTestClientResponseConverter implements ResponseConverter ServerResponse.ok() - .cookie(ResponseCookie.from("name", "value").domain("localhost").httpOnly(true).build()) + .cookie(org.springframework.http.ResponseCookie.from("name", "value") + .domain("localhost").httpOnly(true).build()) .build())) .configureClient().baseUrl("http://localhost").build().get().uri("/foo").exchange().expectBody() .returnResult(); @@ -70,6 +71,9 @@ public class WebTestClientResponseConverterTests { assertThat(response.getHeaders()).hasSize(1); assertThat(response.getHeaders()).containsEntry(HttpHeaders.SET_COOKIE, Collections.singletonList("name=value; Domain=localhost; HttpOnly")); + assertThat(response.getCookies()).hasSize(1); + assertThat(response.getCookies()).first().extracting(ResponseCookie::getName).isEqualTo("name"); + assertThat(response.getCookies()).first().extracting(ResponseCookie::getValue).isEqualTo("value"); } }