Skip to content

Commit

Permalink
Add support for documenting request and response cookies
Browse files Browse the repository at this point in the history
See gh-592
  • Loading branch information
clydebarrow authored and wilkinsona committed Jul 18, 2022
1 parent 15980d7 commit f72a9f1
Show file tree
Hide file tree
Showing 38 changed files with 1,572 additions and 17 deletions.
3 changes: 2 additions & 1 deletion config/checkstyle/checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
<property name="file" value="${config_loc}/checkstyle-suppressions.xml"/>
</module>
<module name="io.spring.javaformat.checkstyle.SpringChecks">
<property name="avoidStaticImportExcludes" value=" org.springframework.restdocs.cli.CliDocumentation.*"/>
<property name="avoidStaticImportExcludes" value="org.springframework.restdocs.cli.CliDocumentation.*,
org.springframework.restdocs.cookies.CookieDocumentation.*"/>
</module>
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
<module name="com.puppycrawl.tools.checkstyle.checks.imports.IllegalImportCheck">
Expand Down
1 change: 1 addition & 0 deletions docs/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ dependencies {
testImplementation(project(":spring-restdocs-mockmvc"))
testImplementation(project(":spring-restdocs-restassured"))
testImplementation(project(":spring-restdocs-webtestclient"))
testImplementation("jakarta.servlet:jakarta.servlet-api")
testImplementation("jakarta.validation:jakarta.validation-api")
testImplementation("junit:junit")
testImplementation("org.testng:testng:6.9.10")
Expand Down
55 changes: 55 additions & 0 deletions docs/src/docs/asciidoc/documenting-your-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,61 @@ 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:

====
[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`.
[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.
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`.
[source,java,indent=0,role="secondary"]
.REST Assured
----
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`.
====

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
Expand Down
46 changes: 46 additions & 0 deletions docs/src/test/java/com/example/mockmvc/HttpCookies.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.mockmvc;

import jakarta.servlet.http.Cookie;

import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName;
import static org.springframework.restdocs.cookies.CookieDocumentation.requestCookies;
import static org.springframework.restdocs.cookies.CookieDocumentation.responseCookies;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

public class HttpCookies {

private MockMvc mockMvc;

public void cookies() throws Exception {
// tag::cookies[]
this.mockMvc.perform(get("/").cookie(new Cookie("JSESSIONID", "ACBCDFD0FF93D5BB"))) // <1>
.andExpect(status().isOk()).andDo(document("cookies", requestCookies(// <2>
cookieWithName("JSESSIONID").description("Session token")), // <3>
responseCookies(// <4>
cookieWithName("JSESSIONID").description("Updated session token"),
cookieWithName("logged_in")
.description("Set to true if the user is currently logged in"))));
// end::cookies[]
}

}
44 changes: 44 additions & 0 deletions docs/src/test/java/com/example/restassured/HttpCookies.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.restassured;

import io.restassured.RestAssured;
import io.restassured.specification.RequestSpecification;

import static org.hamcrest.CoreMatchers.is;
import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName;
import static org.springframework.restdocs.cookies.CookieDocumentation.requestCookies;
import static org.springframework.restdocs.cookies.CookieDocumentation.responseCookies;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;

public class HttpCookies {

private RequestSpecification spec;

public void cookies() throws Exception {
// tag::cookies[]
RestAssured.given(this.spec).filter(document("cookies", requestCookies(// <1>
cookieWithName("JSESSIONID").description("Saved session token")), // <2>
responseCookies(// <3>
cookieWithName("logged_in").description("If user is logged in"),
cookieWithName("JSESSIONID").description("Updated session token"))))
.cookie("JSESSIONID", "ACBCDFD0FF93D5BB") // <4>
.when().get("/people").then().assertThat().statusCode(is(200));
// end::cookies[]
}

}
47 changes: 47 additions & 0 deletions docs/src/test/java/com/example/webtestclient/HttpCookies.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.webtestclient;

import org.springframework.test.web.reactive.server.WebTestClient;

import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName;
import static org.springframework.restdocs.cookies.CookieDocumentation.requestCookies;
import static org.springframework.restdocs.cookies.CookieDocumentation.responseCookies;
import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;

public class HttpCookies {

// @formatter:off

private WebTestClient webTestClient;

public void cookies() throws Exception {
// 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"))));
// end::cookies[]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.restdocs.cookies;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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;

/**
* 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
*/
public abstract class AbstractCookiesSnippet extends TemplatedSnippet {

private List<CookieDescriptor> cookieDescriptors;

protected final boolean ignoreUndocumentedCookies;

private String type;

/**
* Creates a new {@code AbstractCookiesSnippet} that will produce a snippet named
* {@code <type>-cookies}. The cookies will be documented using the given
* {@code descriptors} and the given {@code attributes} will be included in the model
* during template rendering.
* @param type the type of the cookies
* @param descriptors the cookie descriptors
* @param attributes the additional attributes
* @param ignoreUndocumentedCookies whether undocumented cookies should be ignored
*/
protected AbstractCookiesSnippet(String type, List<CookieDescriptor> descriptors, Map<String, Object> attributes,
boolean ignoreUndocumentedCookies) {
super(type + "-cookies", attributes);
for (CookieDescriptor descriptor : descriptors) {
Assert.notNull(descriptor.getName(), "The name of the cookie must not be null");
if (!descriptor.isIgnored()) {
Assert.notNull(descriptor.getDescription(), "The description of the cookie must not be null");
}
}
this.cookieDescriptors = descriptors;
this.type = type;
this.ignoreUndocumentedCookies = ignoreUndocumentedCookies;
}

@Override
protected Map<String, Object> createModel(Operation operation) {
validateCookieDocumentation(operation);

Map<String, Object> model = new HashMap<>();
List<Map<String, Object>> cookies = new ArrayList<>();
model.put("cookies", cookies);
for (CookieDescriptor descriptor : this.cookieDescriptors) {
cookies.add(createModelForDescriptor(descriptor));
}
return model;
}

private void validateCookieDocumentation(Operation operation) {
List<CookieDescriptor> missingCookies = findMissingCookies(operation);
if (!missingCookies.isEmpty()) {
List<String> 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<CookieDescriptor> findMissingCookies(Operation operation) {
List<CookieDescriptor> missingCookies = new ArrayList<>();
Set<String> actualCookies = extractActualCookies(operation);
for (CookieDescriptor cookieDescriptor : this.cookieDescriptors) {
if (!cookieDescriptor.isOptional() && !actualCookies.contains(cookieDescriptor.getName())) {
missingCookies.add(cookieDescriptor);
}
}

return missingCookies;
}

/**
* Extracts the names of the cookies from the request or response of the given
* {@code operation}.
* @param operation the operation
* @return the cookie names
*/
protected abstract Set<String> extractActualCookies(Operation operation);

/**
* Returns the list of {@link CookieDescriptor CookieDescriptors} that will be used to
* generate the documentation.
* @return the cookie descriptors
*/
protected final List<CookieDescriptor> getCookieDescriptors() {
return this.cookieDescriptors;
}

/**
* Returns a model for the given {@code descriptor}.
* @param descriptor the descriptor
* @return the model
*/
protected Map<String, Object> createModelForDescriptor(CookieDescriptor descriptor) {
Map<String, Object> model = new HashMap<>();
model.put("name", descriptor.getName());
model.put("description", descriptor.getDescription());
model.put("optional", descriptor.isOptional());
model.putAll(descriptor.getAttributes());
return model;
}

}
Loading

0 comments on commit f72a9f1

Please sign in to comment.